const db = uniCloud.database(); const _ = db.command; const $ = db.command.aggregate; const { log, time } = require('console'); const uniID = require('uni-id-common'); let obj = { getDateTime() { return new Date(); }, async getDeletedCount(commentId) { const { deleted } = await db.collection('comment_like') .where({ comment_id: commentId }) .count(); return deleted; }, // 高并发安全的计数方法 async incrementReadCount(query) { const t = Date.now(); try { await this.checkRequestFrequency(query.article_id, context.CLIENTIP); const res = await db.collection('uni-cms-articles') .doc(query.article_id.toString()) .update({ view_count: db.command.inc(1), last_view_time: t }); return { code: 200, data: { view_count: res.updated ? res.data.view_count : 0 } }; } catch (e) { return this.handleCountError(e); } }, // 频率控制核心方法 async checkRequestFrequency(articleId, ip) { const key = `req:${articleId}:${ip}`; const now = Date.now(); const record = await db.collection('request_logs') .where({ key }) .orderBy('time', 'desc') .limit(1) .get(); if (record.data.length > 0 && (now - record.data[0].time) < 1000) { throw new Error('REQUEST_LIMIT'); } await db.collection('request_logs').add({ key, time: now, articleId, ip }); }, // 统一错误处理器 handleCountError(e) { const errorConfig = { 'REQUEST_LIMIT': { code: 429, message: '访问过于频繁' }, 'DATABASE_BUSY': { code: 503, message: '系统繁忙请重试' } }; return errorConfig[e.message] || { code: 500, message: '服务内部错误' }; }, async getUserInfoMap(userIds) { const res = await db.collection('uni-id-users') .where({ _id: db.command.in(userIds) }) .field({ _id: true, nickname: true, avatar_file: true }) .get(); return new Map(res.data.map(u => [u._id, u])); }, buildCommentTree(comments) { if (!Array.isArray(comments) || comments.length === 0) { console.error('无效的评论数据'); return []; } const map = new Map(); const roots = []; // 1. 数据预处理 comments.forEach(comment => { // 强制校验 _id 字段 if (!comment._id) { console.error('评论缺少 _id 字段:', comment); return; } const safeId = comment._id.toString(); const parentId = comment.parent_id; const rootId = comment.root_id; const node = { ...comment, id: safeId, replies: [], reply_count: Number(comment.reply_count) || 0, update_time: Number(comment.update_time) || Date.now(), create_time: new Date(comment.create_time).getTime() || Date.now() }; map.set(node.id, node); // 根节点判断(仅依赖 parent_id) const isRootNode = !parentId; if (isRootNode) { roots.push(node); } console.log('节点预处理:', { id: node.id, parentId, isRootNode }); }); // 2. 构建树结构 comments.forEach(comment => { const currentId = comment._id.toString(); const current = map.get(currentId); const parentId = current.parent_id; if (parentId) { const parent = map.get(parentId); if (parent) { parent.replies.push(current); parent.reply_count = parent.replies.length; } else { console.warn(`父节点 ${parentId} 不存在,将当前节点 ${currentId} 升级为根节点`); roots.push(current); } } }); // 3. 排序 return roots.sort((a, b) => { return b.update_time - a.update_time; }); }, } module.exports = { _before() { this.uniID = uniID.createInstance({ context: this.getClientInfo() }); if (!this.uniID) throw new Error('uniID 初始化失败'); }, async getReplyComment(query) { try { const clientInfo = this.getClientInfo(); const { uid } = await this.uniID.checkToken(clientInfo.uniIdToken) let user_id = uid; // 1. 优化数据库查询(仅获取根评论) const commentsRes = await db.collection('comment') .where({ root_id: query.root_id }) .field({ _id: true, content: true, author_id: true, parent_id: true, root_id: true, create_time: true, update_time: true, ip_location: true, like_count: true, reply_count: true }) .orderBy('reply_count', 'desc') // 新增按时间倒序[9](@ref) .get(); // 2. 数据预处理 const rawComments = Object.freeze(commentsRes.data); // 3. 批量获取用户信息 const userIds = [...new Set(rawComments.map(c => c.author_id))]; const userMap = await obj.getUserInfoMap(userIds); // 4. 批量查询点赞状态 const commentIds = rawComments.map(c => c._id); const likeRes = await db.collection('comment_like') .where({ comment_id: db.command.in(commentIds), user_id: user_id }) .get(); const likeSet = new Set(likeRes.data.map(l => l.comment_id)); // 5. 构建扁平化数据结构 const commentList = rawComments.map(comment => ({ id: comment._id, _id: comment._id, pl_user_id: comment.author_id, owner: comment.author_id === user_id, hasLike: likeSet.has(comment._id), likeNum: comment.like_count || 0, root_id: comment.root_id, parent_id: comment.parent_id, avatarUrl: userMap.get(comment.author_id)?.avatar_file || {}, nickName: userMap.get(comment.author_id)?.nickname || '已注销用户', content: comment.content, createTime: comment.create_time, ip_location: comment.ip_location, hasShowMore: false })); return { code: 200, data: { commentList: commentList } }; } catch (e) { console.error('获取评论失败:', e); return { code: 500, message: '查询服务异常' }; } }, async replyComment(query) { try { const clientInfo = this.getClientInfo(); const { uid } = await this.uniID.checkToken(clientInfo.uniIdToken); // 参数校验 if (!query.content) { return { code: 400, message: '评论内容不能为空' }; } console.log(query); let { article_id, content, parent_id, root_id } = query; console.log(root_id, parent_id); // 处理父评论逻辑 if (parent_id && parent_id !== 0) { const parentRecord = await db.collection('comment') .doc(parent_id) .get(); if (!parentRecord.data[0]) { return { code: 404, message: '父评论不存在' }; } } const parent_comment = await db.collection("comment") .doc(parent_id) .get(); console.log(parent_comment); // 构建评论数据 const commentData = { article_id, author_id: uid, content, parent_id, root_id, parent_author_id: parent_comment.data.length > 0 ? parent_comment.data[0].author_id : 0, parent_author_content: parent_comment.data.length > 0 ? parent_comment.data[0].content : "", create_time: obj.getDateTime(), update_time: obj.getDateTime(), ip_location: { clientIP: clientInfo.clientIP } || {}, children:[], like_count: 0, reply_count: 0 }; // 插入数据库 const { id } = await db.collection('comment').add(commentData); await db.collection('comment').doc(root_id) .update({ reply_count: _.inc(1) }); // 获取用户信息 const userMap = await obj.getUserInfoMap([uid]); const userInfo = userMap.get(uid) || {}; // 构建返回数据 return { code: 200, data: { id, _id: id, pl_user_id: uid, owner: true, hasLike: false, likeNum: 0, root_id: commentData.root_id, parent_id: commentData.parent_id, avatarUrl: userInfo.avatar_file || { url: '', name: '', extname: '' }, nickName: userInfo.nickname || '注销用户', content: commentData.content, createTime: commentData.create_time, ip_location: commentData.ip_location, replyCount: 0 } }; } catch (e) { console.error('添加评论失败:', e); return { code: 500, message: '添加评论失败' }; } }, async addComment(query) { try { const clientInfo = this.getClientInfo(); const { uid } = await this.uniID.checkToken(clientInfo.uniIdToken); // 参数校验 if (!query.content) { return { code: 400, message: '评论内容不能为空' }; } let { content, parent_id = 0, root_id = 0 } = query; // 处理父评论逻辑 if (parent_id && parent_id !== 0) { const parentRecord = await db.collection('comment') .doc(parent_id) .get(); if (!parentRecord.data[0]) { return { code: 404, message: '父评论不存在' }; } // 继承父评论的root_id(如果父评论是根评论则使用父评论ID) root_id = parentRecord.data[0].root_id === 0 ? parent_id : parentRecord.data[0].root_id; } // 构建评论数据 const commentData = { article_id: query.article_id, author_id: uid, content, parent_id, root_id, create_time: obj.getDateTime(), update_time: obj.getDateTime(), ip_location: { clientIP: clientInfo.clientIP } || {}, like_count: 0, reply_count: 0 }; console.log(commentData); // 插入数据库 const { id } = await db.collection('comment').add(commentData); // 更新父评论的回复计数 if (parent_id !== 0) { await db.collection('comment') .doc(parent_id) .update({ reply_count: db.command.inc(1) }); } // 获取用户信息 const userMap = await obj.getUserInfoMap([uid]); const userInfo = userMap.get(uid) || {}; // 构建返回数据 return { code: 200, data: { id, _id: id, pl_user_id: uid, owner: true, hasLike: false, likeNum: 0, root_id: commentData.root_id, parent_id: commentData.parent_id, avatarUrl: userInfo.avatar_file || { url: '', name: '', extname: '' }, children: [], nickName: userInfo.nickname || '注销用户', content: commentData.content, createTime: commentData.create_time, ip_location: commentData.ip_location, replyCount: 0 } }; } catch (e) { console.error('添加评论失败:', e); return { code: 500, message: '添加评论失败' }; } }, async deleteComment(id, root_id) { let transaction = null; try { if (!id || typeof id !== 'string' || id.length !== 24) { throw new Error('INVALID_ID: 无效的评论ID'); } const clientInfo = this.getClientInfo(); const { uid } = await this.uniID.checkToken(clientInfo.uniIdToken); if (!uid) throw new Error('PERMISSION_DENIED: 用户未登录'); const commentDoc = await db.collection('comment') .doc(id) .field({ author_id: 1, article_id: 1 }) .get(); if (!commentDoc.data) { throw new Error('NOT_FOUND: 评论不存在'); } console.log(commentDoc.data); if (commentDoc.data[0].author_id !== uid) { throw new Error('PERMISSION_DENIED: 无权限删除该评论'); } transaction = await db.startTransaction(); await transaction.collection('comment').doc(id).remove(); await transaction.collection('comment_like') .where({ comment_id: id, type: 'comment' // 添加类型过滤提高准确性 }) .remove(); if (!root_id === 0) { await transaction.collection('uni-cms-articles') .doc(root_id) .update({ reply_count: _.inc(-1) }); } await transaction.commit(); await db.collection('comment_operation_logs').add({ type: 'comment_delete', uid, comment_id: commentDoc.id, timestamp: obj.getDateTime() }); console.log(1); return { code: 200, message: '评论及关联数据删除成功', data: { deletedComment: 1, del_id: id } }; } catch (e) { // ================= 11. 事务回滚 ================= if (transaction) await transaction.rollback(); // ================= 12. 错误分类处理 ================= console.error(`[DELETE_COMMENT_ERROR] ${e.message}`, { comment_id: id, error: e.stack }); const errorMap = { 'INVALID_ID': 400, 'PERMISSION_DENIED': 403, 'NOT_FOUND': 404 }; return { code: errorMap[e.message.split(':')[0]] || 500, message: e.message.split(':')[1]?.trim() || '系统错误', errorType: e.message.split(':')[0] || 'UNKNOWN_ERROR' }; } }, // ================= 辅助方法 ================= async updateComment(id, comment) { try { // ==== 参数校验 ==== if (!id || typeof id !== 'string' || id.length !== 24) { throw new Error('INVALID_ID: 评论ID格式错误'); } if (!comment?.content?.trim() || comment.content.trim().length > 500) { throw new Error('INVALID_CONTENT: 评论内容不能为空且不超过500字符'); } // ==== 身份验证 ==== const clientInfo = this.getClientInfo(); const { uid } = await this.uniID.checkToken(clientInfo.uniIdToken); // ==== 权限验证 ==== const commentDoc = await db.collection('comment') .doc(id) .field({ author_id: 1 }) .get(); if (commentDoc.data.author_id !== uid) { throw new Error('PERMISSION_DENIED: 无权修改该评论'); } // ==== 数据处理 ==== const sanitizedContent = comment.content.trim(); const maskedIP = clientInfo.clientIP.replace(/\.\d+$/, '.*'); // ==== 构建更新数据 ==== const updateData = { updateTime: obj.getDateTime(), content: sanitizedContent, ip_location: maskedIP }; if (comment.title) { updateData.title = comment.title.substring(0, 50); } // ==== 执行更新 ==== const res = await db.collection('comment') .doc(id) .update(updateData); // ==== 记录操作日志 ==== await db.collection('comment_operation_logs').add({ type: 'comment_update', operator: uid, target_id: id, before_content: commentDoc.data.content, after_content: sanitizedContent, timestamp: obj.getDateTime() }); return { code: 200, message: '评论更新成功', data: { updatedId: id, newContent: sanitizedContent, timestamp: Date.now() } }; } catch (error) { console.error(`[UPDATE_COMMENT_ERROR] ${error.message}`, { id, user: this.getClientInfo().uid }); return { code: error.code || 500, message: error.message.split(':')[1]?.trim() || '系统错误', errorType: error.message.split(':')[0] }; } }, async getComments(query) { try { const clientInfo = this.getClientInfo(); const { uid } = await this.uniID.checkToken(clientInfo.uniIdToken) let user_id = uid; // 1. 优化数据库查询(仅获取根评论) console.log(query); const commentsRes = await db.collection('comment') .where({ root_id: 0, article_id: query.article_id }) .field({ _id: true, content: true, author_id: true, parent_id: true, root_id: true, create_time: true, update_time: true, ip_location: true, like_count: true, reply_count: true }) .orderBy('reply_count', 'desc') .get(); // 2. 数据预处理 const rawComments = Object.freeze(commentsRes.data); // 3. 批量获取用户信息 const userIds = [...new Set(rawComments.map(c => c.author_id))]; const userMap = await obj.getUserInfoMap(userIds); // 4. 批量查询点赞状态 const commentIds = rawComments.map(c => c._id); const likeRes = await db.collection('comment_like') .where({ comment_id: db.command.in(commentIds), user_id: user_id }) .get(); const likeSet = new Set(likeRes.data.map(l => l.comment_id)); // 5. 构建扁平化数据结构 const commentList = rawComments.map(comment => ({ id: comment._id, _id: comment._id, pl_user_id: comment.author_id, owner: comment.author_id === user_id, hasLike: likeSet.has(comment._id), likeNum: comment.like_count || 0, root_id: comment.root_id, parent_id: comment.parent_id, avatarUrl: userMap.get(comment.author_id)?.avatar_file || {}, nickName: userMap.get(comment.author_id)?.nickname || '已注销用户', content: comment.content, createTime: comment.create_time, ip_location: comment.ip_location, replyCount: comment.reply_count || 0, children: [], hasShowMore: false })); return { code: 200, data: { commentList: commentList // 直接返回无需构建树形结构[8](@ref) } }; } catch (e) { console.error('获取评论失败:', e); return { code: 500, message: '查询服务异常' }; } }, async getReadCount(query) { try { if (!query || !query.article_id) { throw new Error('INVALID_PARAM'); } const res = await db.collection('uni-cms-articles') .doc(query.article_id.toString()) .field({ view_count: 1, _id: 0 }) .get(); return { code: 200, data: { view_count: res.data[0]?.view_count || 0 } }; } catch (e) { const errorMap = { 'INVALID_PARAM': { code: 400, message: '参数异常' }, 'DATABASE_ERROR': { code: 503, message: '服务暂不可用' } }; return errorMap[e.message] || { code: 500, data: { view_count: 0 }, message: '统计服务异常' }; } }, async likeComment(user_id, comment_id) { let datatime = new Date().getTime(); const transaction = await db.startTransaction(); try { const exist = await transaction.collection('comment_like') .where({ user_id: user_id, comment_id: comment_id }).count(); if (exist.total > 0) throw new Error('已点赞'); await transaction.collection('comment_like').add({ user_id: user_id, comment_id: comment_id, create_time: datatime }); await transaction.collection('comment').doc(comment_id) .update({ like_count: _.inc(1) }); await transaction.commit(); return { code: 200 }; } catch (e) { await transaction.rollback(); return { code: 500, msg: e.message }; } }, async relikeComment(user_id, comment_id) { try { const likeRes = await db.collection('comment_like') .where({ user_id: user_id, comment_id: comment_id }).remove(); if (likeRes.deleted === 0) throw new Error('未找到点赞记录'); await db.collection('comment').doc(comment_id) .update({ like_count: _.inc(-1) }); return { code: 200 }; } catch (e) { return { code: 500, msg: e.message }; } }, }