## 阻断性缺陷 - list.vue 是 0 字节空文件、slist.vue 与 search/search.vue 从未存在, 而前者是 tabBar 首页、后者是 tabBar「搜索」页 —— 开屏即白屏。 按 .nvue 原型与详情页契约重建三页(CSS 渐变主视觉、分类筛选、搜索历史/热搜/联想)。 - parse-image-url.js 对空封面调 undefined.startsWith 直接抛错,列表页整页崩。 - 云函数目录缺 uni-cms-articles / uni-cms-categories / uni-cms-unlock-record schema 与 schema.ext.js,线上内容渲染与解锁逻辑无配置可用。 ## 越权与数据一致性 - uni-cms-articles:del/update/add 全部无鉴权,未登录即可删任意文章、 改他人文章作者与阅读量。补 login + 作者归属校验,作者与计数改为服务端取值。 - comments.likeComment/relikeComment:直接采信客户端传入的 user_id, 可冒名点赞刷计数。改为以令牌为准,并纳入事务。 - comments.updateComment:对数组取 .author_id,权限判断恒失败; 字段名 updateTime 与 ip_location 类型与 schema 不符。 - comments.deleteComment:`!root_id === 0` 优先级错误导致计数恒不减; 且误更新 uni-cms-articles、按不存在的 type 字段删点赞明细产生孤儿数据。 - cms-articles-like/collect:查重条件混入本次请求时间戳,防重永远失效, 可无限重复刷计数;补唯一索引并事务化。 - cms-vote:读-改-写票数导致并发丢票,记录与统计非原子;改为事务 + 原子自增。 - cms-articles-log:忽略传入 user_id 直接返回全表,泄露全站浏览记录。 - article_info:get() 使用未定义变量必崩;读接口全部无鉴权。 - user-info:公开资料接口可查任意用户 last_login_ip。 ## 资源与数据 - 全项目清空失效的签名外链(expire_at 均为 2025-03,必然 403), 改为本地生成资源:6 套文章模板、8 个编辑器图标、2 张文章配图。 - 新增分类 / 模板 / 礼物 / 热搜词种子数据,并在 db_init.json 登记, 同时补上点赞、收藏、投票、浏览日志的唯一索引。 ## 功能 - 草稿箱:预览页拆出「发布」与「存为草稿」,作品列表按状态筛选并显示徽标。 原实现有 4 个 tab 但只有 1 个有内容,且 article_status 在 UI 上无体现。 - 编辑中断恢复:接上原本空实现的「编辑草稿」回调,区分新建与编辑已有文章。 ## 工具 - tools/audit-project.js:编码 / 页面路由 / 云调用 / 云函数鉴权 / 敏感信息检查 - tools/check-vue.js:SFC 脚本语法(词法扫描处理 import·export 与条件编译) - tools/verify.js:一键验证;两个检查器各带自测,防止"永远通过" - tools/gen-*.py:模板与图标资源生成脚本
236 lines
5.4 KiB
JSON
236 lines
5.4 KiB
JSON
{
|
||
"bsonType": "object",
|
||
"required": [
|
||
"user_id",
|
||
"title",
|
||
"content"
|
||
],
|
||
"permission": {
|
||
"read": true,
|
||
"create": "auth.uid != null",
|
||
"update": "'admin' in auth.role || doc.user_id == auth.uid",
|
||
"delete": "'admin' in auth.role || doc.user_id == auth.uid"
|
||
},
|
||
"properties": {
|
||
"_id": {
|
||
"description": "存储文档 ID(用户 ID),系统自动生成"
|
||
},
|
||
"user_id": {
|
||
"bsonType": "string",
|
||
"description": "文章作者ID, 参考`uni-id-users` 表",
|
||
"foreignKey": "uni-id-users._id",
|
||
"defaultValue": {
|
||
"$env": "uid"
|
||
}
|
||
},
|
||
"edit_type": {
|
||
"bsonType": "string",
|
||
"description": "编辑器类型(pc/mobile)"
|
||
},
|
||
"title_delta": {
|
||
"bsonType": "object",
|
||
"description": "标题html片段"
|
||
},
|
||
"p_type": {
|
||
"bsonType": "string",
|
||
"description": "兵种选择"
|
||
},
|
||
"temp_id": {
|
||
"bsonType": "string",
|
||
"description": "模板ID, 参考`cms-temp` 表",
|
||
"foreignKey": "cms-temp._id"
|
||
},
|
||
"music_url_id": {
|
||
"bsonType": "object",
|
||
"description": "音乐文件"
|
||
},
|
||
"category_id": {
|
||
"bsonType": "string",
|
||
"title": "分类",
|
||
"description": "分类 id,参考`uni-news-categories`表",
|
||
"foreignKey": "uni-cms-categories._id",
|
||
"enum": {
|
||
"collection": "uni-cms-categories",
|
||
"field": "name as text, _id as value"
|
||
}
|
||
},
|
||
"title": {
|
||
"bsonType": "string",
|
||
"title": "标题",
|
||
"description": "标题",
|
||
"label": "标题",
|
||
"trim": "both"
|
||
},
|
||
"title_html": {
|
||
"bsonType": "string",
|
||
"title": "标题html片段",
|
||
"description": "标题html片段",
|
||
"label": "标题html片段",
|
||
"trim": "both"
|
||
},
|
||
"cmsLst": {
|
||
"bsonType": "array",
|
||
"title": "文章内容列表"
|
||
},
|
||
"content": {
|
||
"bsonType": "object",
|
||
"title": "文章内容",
|
||
"description": "文章内容; 格式为Quill编辑器的Delta格式",
|
||
"label": "文章内容"
|
||
},
|
||
"excerpt": {
|
||
"bsonType": "string",
|
||
"title": "文章摘录",
|
||
"description": "文章摘录",
|
||
"label": "摘要",
|
||
"trim": "both"
|
||
},
|
||
"article_status": {
|
||
"bsonType": "int",
|
||
"title": "文章状态",
|
||
"description": "文章状态:0 草稿箱 1 已发布",
|
||
"defaultValue": 0,
|
||
"enum": [
|
||
{
|
||
"value": 0,
|
||
"text": "草稿箱"
|
||
},
|
||
{
|
||
"value": 1,
|
||
"text": "已发布"
|
||
}
|
||
]
|
||
},
|
||
"reply_count": {
|
||
"bsonType": "int",
|
||
"title": "回复数量",
|
||
"description": "回复数量",
|
||
"defaultValue": 0
|
||
},
|
||
"like_count": {
|
||
"bsonType": "int",
|
||
"title": "点赞数",
|
||
"description": "喜欢数、点赞数",
|
||
"defaultValue": 0,
|
||
"permission": {
|
||
"write": false
|
||
}
|
||
},
|
||
"collect_count": {
|
||
"bsonType": "int",
|
||
"title": "收藏数量",
|
||
"description": "收藏数量",
|
||
"defaultValue": 0,
|
||
"permission": {
|
||
"write": false
|
||
}
|
||
},
|
||
"gift_count": {
|
||
"bsonType": "int",
|
||
"title": "送礼数量",
|
||
"description": "送礼数量",
|
||
"defaultValue": 0,
|
||
"permission": {
|
||
"write": false
|
||
}
|
||
},
|
||
"view_count": {
|
||
"bsonType": "int",
|
||
"title": "阅读数量",
|
||
"description": "阅读数量",
|
||
"defaultValue": 0,
|
||
"permission": {
|
||
"write": false
|
||
}
|
||
},
|
||
"is_sticky": {
|
||
"bsonType": "bool",
|
||
"title": "是否置顶",
|
||
"description": "是否置顶",
|
||
"permission": {
|
||
"write": false
|
||
}
|
||
},
|
||
"is_essence": {
|
||
"bsonType": "bool",
|
||
"title": "阅读加精",
|
||
"description": "阅读加精",
|
||
"permission": {
|
||
"write": false
|
||
}
|
||
},
|
||
"comment_status": {
|
||
"bsonType": "int",
|
||
"title": "开放评论",
|
||
"description": "评论状态:0 关闭 1 开放",
|
||
"enum": [
|
||
{
|
||
"value": 0,
|
||
"text": "关闭"
|
||
},
|
||
{
|
||
"value": 1,
|
||
"text": "开放"
|
||
}
|
||
]
|
||
},
|
||
"comment_count": {
|
||
"bsonType": "int",
|
||
"description": "评论数量",
|
||
"permission": {
|
||
"write": false
|
||
}
|
||
},
|
||
"last_comment_user_id": {
|
||
"bsonType": "string",
|
||
"description": "最后回复用户 id,参考`uni-id-users` 表",
|
||
"foreignKey": "uni-id-users._id"
|
||
},
|
||
"thumbnail": {
|
||
"bsonType": "array",
|
||
"title": "封面大图",
|
||
"description": "缩略图地址",
|
||
"label": "封面大图",
|
||
"defaultValue": []
|
||
},
|
||
"publish_date": {
|
||
"bsonType": "timestamp",
|
||
"title": "发表时间",
|
||
"description": "发表时间",
|
||
"defaultValue": {
|
||
"$env": "now"
|
||
}
|
||
},
|
||
"publish_ip": {
|
||
"bsonType": "string",
|
||
"title": "发布文章时IP地址",
|
||
"description": "发表时 IP 地址",
|
||
"forceDefaultValue": {
|
||
"$env": "clientIP"
|
||
}
|
||
},
|
||
"last_modify_date": {
|
||
"bsonType": "timestamp",
|
||
"title": "最后修改时间",
|
||
"description": "最后修改时间",
|
||
"defaultValue": {
|
||
"$env": "now"
|
||
}
|
||
},
|
||
"last_modify_ip": {
|
||
"bsonType": "string",
|
||
"description": "最后修改时 IP 地址",
|
||
"forceDefaultValue": {
|
||
"$env": "clientIP"
|
||
}
|
||
},
|
||
"preview_secret": {
|
||
"bsonType": "string",
|
||
"description": "文章预览密钥"
|
||
},
|
||
"preview_expired": {
|
||
"bsonType": "timestamp",
|
||
"description": "文章预览过期时间"
|
||
}
|
||
}
|
||
} |