fix: 修复首页空白/失效外链/云函数越权,补齐缺失页面与种子数据
## 阻断性缺陷 - 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:模板与图标资源生成脚本
This commit is contained in:
@@ -1,174 +1,110 @@
|
||||
const { count } = require("console");
|
||||
const uniID = require('uni-id-common');
|
||||
|
||||
const db = uniCloud.database();
|
||||
const _ =db.command;
|
||||
const _ = db.command;
|
||||
|
||||
const VOTE_DB = 'cms-vote';
|
||||
const VOTE_INFO_DB = 'cms-vote-info';
|
||||
|
||||
module.exports = {
|
||||
_before: function () { // 通用预处理器
|
||||
this.uniID = uniID.createInstance({
|
||||
context: this.getClientInfo()
|
||||
});
|
||||
},
|
||||
|
||||
async getVoteInfo(query) {
|
||||
try {
|
||||
let {
|
||||
vote_id,
|
||||
user_id
|
||||
} = query;
|
||||
// 用户选择
|
||||
const user_res = await db.collection("cms-vote")
|
||||
.where({
|
||||
user_id: user_id,
|
||||
vote_id: vote_id
|
||||
})
|
||||
.get();
|
||||
// 获得总选择
|
||||
const all_res = await db.collection("cms-vote-info")
|
||||
.where({
|
||||
vote_id: vote_id
|
||||
})
|
||||
.get();
|
||||
|
||||
const { vote_id } = query || {};
|
||||
if (!vote_id) return { code: 400, msg: '缺少 vote_id' };
|
||||
|
||||
// 只能查自己的投票记录,避免通过传 user_id 探测他人选择
|
||||
const payload = await this.uniID.checkToken(this.getUniIdToken());
|
||||
const user_id = payload.errCode ? null : payload.uid;
|
||||
|
||||
const user_res = user_id
|
||||
? await db.collection(VOTE_DB).where({ user_id, vote_id }).get()
|
||||
: { data: [] };
|
||||
const all_res = await db.collection(VOTE_INFO_DB).where({ vote_id }).get();
|
||||
|
||||
// 返回可安全解构的默认值:页面模板会直接访问
|
||||
// all_data.selectValue[...] / all_data.count,null 会导致渲染报错
|
||||
return {
|
||||
code: 200,
|
||||
user_data: user_res.data[0],
|
||||
all_data: all_res.data[0]
|
||||
}
|
||||
|
||||
|
||||
} catch(e) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: e.message
|
||||
}
|
||||
user_data: user_res.data[0] || {},
|
||||
all_data: all_res.data[0] || { vote_id, selectValue: {}, count: 0 }
|
||||
};
|
||||
} catch (e) {
|
||||
return { code: 500, msg: e.message };
|
||||
}
|
||||
},
|
||||
|
||||
async add_vote(query) {
|
||||
const payload = await this.uniID.checkToken(this.getUniIdToken());
|
||||
if (payload.errCode) throw new Error('登录状态失效,请重新登录');
|
||||
const user_id = payload.uid; // 以服务端令牌为准,忽略客户端传入
|
||||
|
||||
const { vote_id, selectValue, type } = query || {};
|
||||
if (!vote_id || !selectValue) {
|
||||
return { code: 400, msg: '参数不完整' };
|
||||
}
|
||||
|
||||
// 用值的形态判断单选/多选,而不是比较文案。前端的类型名里混用了
|
||||
// 全角"("和半角")",按字符串比较容易漏判。
|
||||
const options = Array.isArray(selectValue) ? selectValue.slice() : [selectValue];
|
||||
if (!options.length) {
|
||||
return { code: 400, msg: '未选择任何选项' };
|
||||
}
|
||||
|
||||
const transaction = await db.startTransaction();
|
||||
try {
|
||||
const payload = await this.uniID.checkToken(this.getUniIdToken());
|
||||
if (payload.errCode) throw new Error('登录状态失效,请重新登录');
|
||||
let {
|
||||
// 投票记录与统计必须同事务,否则会出现"记录已投但票数没加"的状态
|
||||
const exist = await transaction.collection(VOTE_DB)
|
||||
.where({ user_id, vote_id })
|
||||
.count();
|
||||
if (exist.total > 0) {
|
||||
await transaction.rollback();
|
||||
return { code: 409, msg: '已投票!' };
|
||||
}
|
||||
|
||||
await transaction.collection(VOTE_DB).add({
|
||||
vote_id,
|
||||
selectValue,
|
||||
user_id,
|
||||
type
|
||||
} = query;
|
||||
user_id = payload.uid; // 以服务端令牌为准,忽略客户端传入
|
||||
|
||||
let t = await db.collection("cms-vote")
|
||||
.where({
|
||||
user_id: user_id,
|
||||
vote_id: vote_id
|
||||
})
|
||||
.count();
|
||||
|
||||
|
||||
|
||||
if (t.total > 0) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: "已投票!"
|
||||
}
|
||||
} else {
|
||||
await db.collection("cms-vote").add({
|
||||
vote_id: vote_id,
|
||||
selectValue: selectValue,
|
||||
user_id: user_id,
|
||||
type: type
|
||||
});
|
||||
const res = await db.collection("cms-vote-info")
|
||||
.where({
|
||||
vote_id: vote_id
|
||||
})
|
||||
.count();
|
||||
|
||||
if (res.total > 0) {
|
||||
if (type === "单选(默认)") {
|
||||
const voteInfo = await db.collection("cms-vote-info")
|
||||
.where({
|
||||
vote_id: vote_id
|
||||
})
|
||||
.get();
|
||||
console.log(voteInfo)
|
||||
let values = voteInfo.data[0].selectValue;
|
||||
values[selectValue] = (values[selectValue] || 0) + 1;
|
||||
|
||||
await db.collection("cms-vote-info")
|
||||
.where({
|
||||
vote_id: vote_id
|
||||
})
|
||||
.update({
|
||||
selectValue: values,
|
||||
count: voteInfo.data[0].count + 1
|
||||
});
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
const voteInfo = await db.collection("cms-vote-info")
|
||||
.where({
|
||||
vote_id: vote_id
|
||||
})
|
||||
.get();
|
||||
console.log(voteInfo)
|
||||
|
||||
let values = voteInfo.data[0].selectValue;
|
||||
|
||||
for (let i = 0; i < selectValue.length ; i ++ ) {
|
||||
values[selectValue[i]] = (values[selectValue[i]] || 0) + 1;
|
||||
console.log((values[selectValue] || 0) + 1, values);
|
||||
}
|
||||
console.log(values);
|
||||
|
||||
await db.collection("cms-vote-info")
|
||||
.where({
|
||||
vote_id: vote_id
|
||||
})
|
||||
.update({
|
||||
selectValue: values,
|
||||
count: voteInfo.data[0].count + 1
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (type === "单选(默认)") {
|
||||
await db.collection("cms-vote-info")
|
||||
.add({
|
||||
vote_id: vote_id,
|
||||
selectValue: {
|
||||
[selectValue]: 1
|
||||
},
|
||||
type: type,
|
||||
count: 1
|
||||
});
|
||||
} else {
|
||||
let values = {};
|
||||
|
||||
for (let i = 0 ; i < selectValue.length; i ++ ) {
|
||||
values[selectValue[i]] = 1;
|
||||
}
|
||||
console.log(values);
|
||||
await db.collection("cms-vote-info")
|
||||
.add({
|
||||
vote_id: vote_id,
|
||||
selectValue: values,
|
||||
type: type,
|
||||
count: 1
|
||||
});
|
||||
}
|
||||
type,
|
||||
create_time: Date.now()
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200
|
||||
}
|
||||
// 原子自增。原实现是"读出整个 selectValue → 改 → 写回",
|
||||
// 并发投票时会互相覆盖,导致票数丢失。
|
||||
const incData = { count: _.inc(1) };
|
||||
for (const option of options) {
|
||||
incData[`selectValue.${option}`] = _.inc(1);
|
||||
}
|
||||
|
||||
|
||||
} catch(e) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: e.message
|
||||
|
||||
const infoRes = await transaction.collection(VOTE_INFO_DB)
|
||||
.where({ vote_id })
|
||||
.update(incData);
|
||||
|
||||
// 首次投票时统计行还不存在,补建
|
||||
if (!infoRes.updated) {
|
||||
const initValue = {};
|
||||
for (const option of options) {
|
||||
initValue[option] = 1;
|
||||
}
|
||||
await transaction.collection(VOTE_INFO_DB).add({
|
||||
vote_id,
|
||||
selectValue: initValue,
|
||||
type,
|
||||
count: 1
|
||||
});
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
return { code: 200 };
|
||||
} catch (e) {
|
||||
await transaction.rollback();
|
||||
return { code: 500, msg: e.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user