'use strict'; const Service = require('egg').Service; /** * 新闻服务 - NewsService * * 封装新闻相关的数据库查询操作。 * 支持按组别过滤、按置顶过滤、分页查询。 */ class NewsService extends Service { /** * 查询新闻列表(支持分页和过滤) * * 过滤参数: * - group: 组别筛选(如 zhongzhi, gaozhi, benke, jiaoshi),同时包含 group='all' 的通用新闻 * - pinned: 是否仅查询置顶新闻(true/false) * * 排序规则:pinned DESC, publish_time DESC * * @param {Object} params - 查询参数 * @param {string} [params.group] - 目标组别 * @param {string|boolean} [params.pinned] - 是否只查置顶 * @param {number} [params.page=1] - 页码 * @param {number} [params.pageSize=20] - 每页数量 * @returns {Promise<{list: Array, total: number, page: number, pageSize: number}>} 分页结果 */ async list(params = {}) { const { ctx } = this; const { Sequelize } = ctx.app; const { group, pinned, page, pageSize } = params; // 分页参数,默认第 1 页,每页 20 条 const currentPage = parseInt(page) || 1; const currentPageSize = parseInt(pageSize) || 20; const offset = (currentPage - 1) * currentPageSize; // 构建查询条件 const where = { // 🔴 R176:草稿只允许后台可见。成绩发布时会自动生成一条「成绩公示」预览新闻, // 人工编辑确认前绝不能出现在前台列表(否则等于提前公示未定稿成绩)。 is_draft: 0, }; // 组别过滤:同时包含指定组别和通用(all)新闻 if (group) { where.group = { [Sequelize.Op.in]: [group, 'all'], }; } // 置顶过滤 if (pinned !== undefined && pinned !== null && pinned !== '') { where.pinned = pinned === 'true' || pinned === true; } // 构建查询 const queryOptions = { where, order: [ ['pinned', 'DESC'], ['sort_order', 'ASC'], ['publish_time', 'DESC'], ], attributes: ['id', 'title', 'content', 'publish_time', 'pinned', 'group', 'tags', 'sort_order', 'created_at', 'updated_at'], offset, limit: currentPageSize, }; // 查询新闻列表和总数 const [list, total] = await Promise.all([ ctx.model.News.findAll(queryOptions), ctx.model.News.count({ where }), ]); return { list, total, page: currentPage, pageSize: currentPageSize, }; } /** * 获取各分组新闻计数(用于首页 Tab 显示数量) * @returns {Promise} { all: number, zhongzhi: number, gaozhi: number, benke: number, jiaoshi: number } */ async getGroupCounts() { const { ctx } = this; const { Sequelize } = ctx.app; const groups = ['all', 'zhongzhi', 'gaozhi', 'benke', 'jiaoshi']; const counts = { all: 0, zhongzhi: 0, gaozhi: 0, benke: 0, jiaoshi: 0 }; // 并行查询各分组计数 await Promise.all(groups.map(async group => { // 🔴 R176:计数口径必须与 list 一致,否则首页 Tab 显示「3 条」点进去只有 2 条 const where = { is_draft: 0 }; // 对于具体组别,同时包含该组别和 all 通用新闻 if (group !== 'all') { where.group = { [Sequelize.Op.in]: [group, 'all'] }; } counts[group] = await ctx.model.News.count({ where }); })); return counts; } /** * 根据 ID 查询新闻详情 * * @param {number} id - 新闻 ID * @returns {Promise} 新闻模型对象或 null */ async findById(id) { const { ctx } = this; // 🔴 R176:草稿不可通过 id 直接访问(前台详情页按 id 拉取 ⇒ 只过滤列表等于没防住) return await ctx.model.News.findOne({ where: { id, is_draft: 0 }, attributes: ['id', 'title', 'content', 'publish_time', 'pinned', 'group', 'tags', 'sort_order', 'attachment_url', 'attachment_name', 'created_at', 'updated_at'], }); } /** * 批量更新新闻排序 * @param {Array<{id: number, sort_order: number}>} orders - 排序信息数组 * @returns {Promise} */ async updateSortOrder(orders) { const { ctx } = this; await Promise.all(orders.map(async ({ id, sort_order }) => { await ctx.model.News.update({ sort_order }, { where: { id } }); })); } } module.exports = NewsService;