明树Git Lab

Commit 92a3120b authored by zfp1's avatar zfp1

update

parent eb30e1dd
Pipeline #111440 passed with stage
in 4 seconds
...@@ -222,6 +222,22 @@ const Project = sequelize.define('Project', { ...@@ -222,6 +222,22 @@ const Project = sequelize.define('Project', {
// sfgjldjq: { type: DataTypes.INTEGER, allowNull: true, comment: "是否国家领导见签" }, // sfgjldjq: { type: DataTypes.INTEGER, allowNull: true, comment: "是否国家领导见签" },
// sfjntbjgl: { type: DataTypes.INTEGER, allowNull: true, comment: "是否境内特别监管类" }, // sfjntbjgl: { type: DataTypes.INTEGER, allowNull: true, comment: "是否境内特别监管类" },
// sfydylyxgj: { type: DataTypes.INTEGER, allowNull: true, comment: "是否一带一路沿线国家" }, // sfydylyxgj: { type: DataTypes.INTEGER, allowNull: true, comment: "是否一带一路沿线国家" },
sfythzxm: { type: DataTypes.STRING(10), allowNull: true, comment: "是否一体化子项目", _mark: 'juece', zjType: 'danxuan', zjKey: 'sf' }, //1是 2否
sflrgzwfmqd: { type: DataTypes.STRING(10), allowNull: true, comment: "是否列入国资委负面清单", _mark: 'juece', zjType: 'danxuan', zjKey: 'sf' }, //1是 2否
bjfl: { type: DataTypes.STRING(10), allowNull: true, comment: "2+9布局分类", _mark: 'juece', zjType: 'danxuan' }, //1是 2否
"jcpfshcs": {
"name": "决策批复审核处室",
"type": DataTypes.STRING,
},
jcpfwj: {
type: DataTypes.JSON,
comment: "决策批复文件",
},
tpbgjcsb: {
type: DataTypes.JSON,
comment: "投评报告及测算表",
},
//建设规模(实物量指标)---立项表格 //建设规模(实物量指标)---立项表格
gsgllc: { type: DataTypes.DECIMAL(20, 8), allowNull: true, comment: "高速公路里程", _mark: 'juece' }, gsgllc: { type: DataTypes.DECIMAL(20, 8), allowNull: true, comment: "高速公路里程", _mark: 'juece' },
qtsfgllc: { type: DataTypes.DECIMAL(20, 8), allowNull: true, comment: "其他收费公路里程", _mark: 'juece' }, qtsfgllc: { type: DataTypes.DECIMAL(20, 8), allowNull: true, comment: "其他收费公路里程", _mark: 'juece' },
...@@ -500,11 +516,11 @@ const Project = sequelize.define('Project', { ...@@ -500,11 +516,11 @@ const Project = sequelize.define('Project', {
}, },
"kybzdw": { "kybzdw": {
"name": "可研编制单位", "name": "可研编制单位",
"type": DataTypes.STRING, "type": DataTypes.STRING(50),
}, },
"kypsdw": { "kypsdw": {
"name": "可研评审单位", "name": "可研评审单位",
"type": DataTypes.STRING, "type": DataTypes.STRING(50),
}, },
"qtzcwnbsyl": { "qtzcwnbsyl": {
"name": "经济可行性-全投资财务内部收益率(税前)", "name": "经济可行性-全投资财务内部收益率(税前)",
...@@ -516,15 +532,15 @@ const Project = sequelize.define('Project', { ...@@ -516,15 +532,15 @@ const Project = sequelize.define('Project', {
}, },
"jxjlhznf": { "jxjlhznf": {
"name": "经济可行性-净现金流回正年份", "name": "经济可行性-净现金流回正年份",
"type": DataTypes.STRING, "type": DataTypes.STRING(20),
}, },
"jlrhznf": { "jlrhznf": {
"name": "经济可行性-净利润回正年份", "name": "经济可行性-净利润回正年份",
"type": DataTypes.STRING, "type": DataTypes.STRING(20),
}, },
"kfplrhznf": { "kfplrhznf": {
"name": "经济可行性-可分配利润回正年份", "name": "经济可行性-可分配利润回正年份",
"type": DataTypes.STRING, "type": DataTypes.STRING(20),
}, },
"cwjxz": { "cwjxz": {
"name": "经济可行性-财务净现值(税前)", "name": "经济可行性-财务净现值(税前)",
......
# 代码检查报告
检查时间:2026-05-15
范围:项目根目录下的 JS 代码、路由、控制器、模块、模型初始化和部分配置。
说明:本次只做检查并输出建议,未修改业务代码。
## 检查结果概览
- 全量 JS 语法检查通过:对 `rg --files -g "*.js"` 找到的所有 JS 文件执行 `node --check`,结果为 `ALL_JS_SYNTAX_OK`
- `npm run lint -- --quiet` 未能执行规则检查:当前项目使用 ESLint 9.4.0,但项目仍是 `.eslintrc.js` 配置,ESLint 9 默认要求 `eslint.config.js`
- 当前没有发现解析级语法错误,但存在多处会导致接口运行时报错、数据遗漏、权限边界不准或线上部署风险的问题。
## 高风险问题
### 1. 批量上传会运行时报错
位置:`controller/fileController.js:44`
```js
let data = await DB.File.insertMany(ret);
```
`DB.File` 是 Sequelize 模型,不是 Mongo/Mongoose 模型,Sequelize 没有 `insertMany` 方法。`/file/batch/upload` 会在保存文件记录时报 `DB.File.insertMany is not a function`
建议:
```js
let data = await DB.File.bulkCreate(ret);
```
并建议补充空文件判断:
```js
if (!req.files || !req.files.length) {
return res.sendError(errorMessage.paramsError);
}
```
### 2. 创建用户时岗位关系字段写错
位置:`controller/userController.js:182`
```js
let userPositions = body.positions.map(o => { return { userId: ret.id, roleId: o && o.id || o } });
```
`system_user_position` 模型字段是 `posiId`,不是 `roleId`。当前创建用户时岗位关系会写不进去或写成无效字段,导致用户岗位丢失。
建议:
```js
let userPositions = body.positions.map(o => {
return { userId: ret.id, posiId: o && o.id || o };
});
```
### 3. 更新用户岗位时新增岗位取错数组
位置:`module/userModule.js:85-88`
```js
for (let index = 0; index < positionIds.length; index++) {
const element = positions[index];
if (!dbIds.includes(element)) {
needAddIds.push(element);
}
}
```
这里应该遍历 `positionIds`,但实际取的是 `positions[index]`。结果是新增岗位时会把旧岗位对象放入 `needAddIds`,后续 `posiId` 可能变成对象或 `undefined`,导致岗位关系异常。
建议:
```js
const element = positionIds[index];
```
### 4. 更新用户岗位后删除了错误字段
位置:`controller/userController.js:278-282`
```js
if (body.positions && body.positions.length) {
const positionIds = body.positions.map(o => { return o && o.id || o });
await userModule.setUserPosition(user.id, positionIds, user.positions || []);
delete body.roles;
}
```
这里应该删除 `body.positions`,否则 `DB.User.update(body)` 仍会带着 `positions` 字段更新用户表,可能导致 Sequelize 忽略无效字段,也可能引入不可预期行为。同时误删 `body.roles` 会影响同一次请求中角色字段处理后的状态。
建议:
```js
delete body.positions;
```
### 5. 更新用户时空用户会先 `toJSON` 报错
位置:`controller/userController.js:238-262`
```js
let user = await DB.User.findOne(...);
user = user.toJSON();
if (!(user && user.id)) {
return res.sendError(errorMessage.resourceNotFound);
}
```
如果用户不存在,`user``null`,会先执行 `user.toJSON()` 并抛异常,无法返回预期的 `resourceNotFound`
建议:
```js
if (!(user && user.id)) {
return res.sendError(errorMessage.resourceNotFound);
}
user = user.toJSON();
```
### 6. 菜单详情接口查错模型
位置:`controller/menuController.js:124`
```js
const ret = await DB.Depart.findOne({ where: { id: req.body.id }, raw: true });
```
`getMenu` 应该查菜单表,但当前查的是部门表。菜单详情接口会返回部门数据或空数据。
建议:
```js
const ret = await DB.Menu.findOne({ where: { id: req.body.id }, raw: true });
```
### 7. 部门删除前检查子部门用户时漏逗号
位置:`controller/departController.js:105`
```js
let depts = await DB.Depart.findAll({
where: { parentIds: { [Op.startsWith]: curNode.parentIds + curNode.id } },
attributes: ['id'],
raw: true
});
```
`parentIds` 的生成规则是 `父 parentIds + "," + 父 id`。这里拼接少了逗号,比如 `40,86` + `100` 变成 `40,86100`,会查不到子部门。结果是:子部门下有用户时,父部门仍可能被删除。
建议复用后面的 `parStr`
```js
let parStr = curNode.parentIds ? `${curNode.parentIds},${curNode.id}` : String(curNode.id);
let depts = await DB.Depart.findAll({
where: {
[Op.or]: [
{ parentIds: parStr },
{ parentIds: { [Op.startsWith]: `${parStr},` } }
]
},
attributes: ['id'],
raw: true
});
```
### 8. 部门/菜单级联删除存在前缀误伤风险
位置:
- `controller/departController.js:119`
- `controller/menuController.js:108`
当前写法:
```js
{ parentIds: { [Op.startsWith]: parStr } }
```
如果节点路径是 `40,86,10`,另一个节点路径是 `40,86,100`,字符串上后者也以 `40,86,10` 开头,可能误删不属于当前节点的兄弟分支。
建议使用边界匹配:
```js
[Op.or]: [
{ parentIds: parStr },
{ parentIds: { [Op.startsWith]: `${parStr},` } },
{ id: curNode.id }
]
```
### 9. 请求鉴权中间件调用 `next(401)` 后没有 `return`
位置:`middleware/request.js:10-29`
当前代码在以下分支调用 `next(createError(401))` 后仍会继续执行后续日志和最终 `next()`
```js
next(createError(401));
```
这可能导致未授权请求继续进入后续流程,或触发 `next()` 多次,出现重复响应、错误日志异常等问题。
建议所有错误分支都改成:
```js
return next(createError(401));
```
同时 Redis 解析失败分支也应立即返回。
### 10. 项目资料更新中的 `Promise.all` 实际没有等待更新完成
位置示例:
- `controller/projectController.js:182`
- `controller/projectController.js:189`
- `controller/projectController.js:196`
- `controller/projectController.js:203`
- `controller/projectController.js:210`
- `controller/projectController.js:218`
- `controller/projectController.js:225`
- `controller/projectController.js:616-695` 同类问题
- `controller/projectThController.js:278`
- `controller/projectThController.js:316`
- `controller/projectThController.js:402`
示例:
```js
await Promise.all(pns1.map(item => { DB.ProjectJsgm.update(item, { where: { id: item.id } }) }));
```
箭头函数用了 `{}` 但没有 `return``map` 返回的是 `undefined` 数组,`Promise.all` 会立即完成,实际更新在后台执行,错误也可能丢失。接口可能提前返回,造成数据未更新完或异常无法回滚。
建议:
```js
await Promise.all(pns1.map(item => {
return DB.ProjectJsgm.update(item, { where: { id: item.id } });
}));
```
或简写:
```js
await Promise.all(pns1.map(item => DB.ProjectJsgm.update(item, { where: { id: item.id } })));
```
### 11. 项目公司列表权限会漏掉挂在公司根节点上的用户
位置:`controller/projectController.js:1331-1345`
当前逻辑查出公司下属部门后,只补了当前部门:
```js
departs.push(curentDepart);
let departIds = departs.map(o => { return o.id });
```
当当前用户在公司下属部门时,`pidStr` 能定位到公司根路径,但 `departIds` 没有加入公司根节点本身。如果有用户直接挂在公司根部门上,该用户创建的项目会被漏掉。
建议在计算 `pidStr` 时同时记录 `companyDepartId`,并加入 `departIds`
```js
let companyDepartId;
if (curentDepart.parentId == 86) {
companyDepartId = curentDepart.id;
pidStr = `${curentDepart.parentIds},${curentDepart.id}`;
} else {
const index = parentIds.indexOf('86');
const result = index !== -1 ? parentIds.slice(0, index + 2) : parentIds;
pidStr = result.join(",");
companyDepartId = Number(parentIds[index + 1]);
}
let departIds = departs.map(o => o.id);
if (companyDepartId) departIds.push(companyDepartId);
departIds = [...new Set(departIds)];
```
### 12. `getCompanyProjectApprover` 有同类公司根节点遗漏
位置:`module/userModule.js:153-196`
`getCompanyProjectApprover` 的部门范围计算和项目列表权限类似,也只把 `curentDepart` 加入 `departs`,没有在“当前用户位于公司下属部门”时加入公司根节点。若角色为 `xmssdwbmz` 的用户直接挂在项目公司根节点,该审批人会漏掉。
建议和项目列表权限使用同一个公共函数,例如:
```js
getProjectCompanyScopeDepartIds(userDepartId)
```
统一返回“公司根节点 + 全部子部门”的部门 id 列表,避免两处逻辑继续分叉。
## 中风险问题
### 13. 生产环境启动仍会自动 `sync({ alter: true })`
位置:
- `db/model/index.js:5-8`
- 多个模型文件,例如 `db/model/system/depart.js:55-58``db/model/jt/project.js:590-593``db/model/jt/rcCgqygl.js:580-583` 等。
当前 Sequelize 初始化没有按生产环境禁用模型同步,且大量模型文件在 `require` 时直接执行 `sync({ alter: true })`。这会带来几个风险:
- 生产启动时自动 DDL,可能改表、锁表或造成字段类型变化。
- MySQL prepared statement 元数据失效,容易出现 `Prepared statement needs to be re-prepared`
- 多模型并发 `alter` 时启动不稳定。
建议:
- 生产环境不要在模型文件中执行 `sync/alter`
- 使用迁移脚本管理表结构。
- 至少在 `db/model/index.js` 中按 `NODE_ENV === 'production'` 禁用模型级 `sync()`
### 14. 数据库连接配置没有读取 `port` 和 `logging`
位置:`db/model/index.js:5-8`
`production.json` 配了 `mysql.port``mysql.logging`,但 Sequelize 初始化没有使用:
```js
host: sysConfig.mysql.host || 'localhost',
dialect: 'mysql',
logging: false,
```
建议:
```js
port: sysConfig.mysql.port || 3306,
logging: sysConfig.mysql.logging || false,
```
### 15. `setRoleMenu` 的调试输出写法有误
位置:`module/userModule.js:123`
```js
console.log(roleId, "---", needAddIds.length, needDelIds), "============"
```
`"============"` 不会被打印,只是逗号表达式的右侧值,没有实际作用。
建议:
```js
console.log(roleId, "---", needAddIds.length, needDelIds, "============");
```
### 16. 登录接口打印手机号和明文密码
位置:`controller/userController.js:31`
```js
console.log(req.body.mobile, req.body.password, "================")
```
这会把用户手机号和明文密码写入日志,属于敏感信息泄露。
建议删除该日志,或只记录脱敏手机号和登录结果。
### 17. 登录 AES 密钥硬编码
位置:`controller/userController.js:27`
```js
CryptoJS.AES.decrypt(req.body.encryptLogStr, "GFG5w5AP0Ja2rNaa")
```
密钥硬编码在代码中,不利于环境隔离和密钥轮换。
建议放入环境变量或配置文件,并区分 dev/production。
### 18. 请求日志记录完整 headers,可能记录 token
位置:`middleware/request.js:45`
```js
headers: JSON.stringify(req.headers),
```
headers 中包含 `authorization`,会把登录 token 写入请求日志。
建议记录前先脱敏:
```js
const headers = { ...req.headers };
delete headers.authorization;
delete headers.Authorization;
```
### 19. 文件下载路径拼接不稳
位置:
- `controller/fileController.js:71`
- `controller/fileController.js:111`
```js
const filePath = Path.join(__dirname, '../../../', path);
```
上传时保存的 `req.file.path` 在当前配置下可能是绝对路径,如 `/data/uploadfiles/...``Path.join(__dirname, '../../../', absolutePath)` 在不同平台上语义不一致,Windows/Linux 都可能出现不可预期路径。
建议:
- 如果数据库保存绝对路径,下载时直接使用该绝对路径。
- 如果要保存相对路径,上传时就统一转成相对路径。
- 使用 `Path.isAbsolute(path)` 做兼容判断。
### 20. `utils.buildTree` 对根节点判断不一致
位置:`utils/index.js:37-49`
```js
const parentId = String(node.parentId);
if (parentId !== null && node.del == 0) {
...
}
...
return ...filter(node => node.parentId === null)
```
`String(null)``"null"`,所以 `parentId !== null` 永远为真。当前主要依赖 `nodeMap.has(parentId)` 避免出错,但逻辑表达不准确,遇到 `undefined``0` 或字符串 `"null"` 时容易出现树根判断异常。
建议统一根节点判断:
```js
const parentId = node.parentId == null ? null : String(node.parentId);
if (parentId && nodeMap.has(parentId) && node.del == 0) {
...
}
return _.orderBy(Array.from(nodeMap.values()).filter(node => node.parentId == null), 'order');
```
## 低风险/维护性问题
### 21. `npm run lint` 当前不可用
位置:`package.json:9-10``.eslintrc.js`
项目依赖 ESLint 9.4.0,但配置仍是旧版 `.eslintrc.js`。执行:
```bash
npm run lint -- --quiet
```
结果:
```txt
ESLint couldn't find an eslint.config.(js|mjs|cjs) file.
```
建议二选一:
- 迁移到 `eslint.config.js`
- 或将 ESLint 降级到兼容 `.eslintrc.js` 的 v8。
### 22. 大量 `console.log` 留在接口链路中
位置示例:
- `controller/projectController.js:1293`
- `controller/projectController.js:1330`
- `module/userModule.js:144`
- `middleware/parameter.js:18`
- `middleware/request.js:61`
这些日志会影响性能、污染生产日志,也可能输出业务数据。
建议统一接入日志工具,并按环境控制日志级别。
### 23. `server.js` 引入了未使用的中间件
位置:`server.js:10`
```js
const parameter = require('./middleware/parameter');
```
但后面没有 `app.use(parameter)`。如果这是遗留代码建议删除;如果本来用于参数校验,应明确挂载。
### 24. 配置文件包含生产账号密码
位置:`config/production.json`
生产 MySQL、Redis 密码直接保存在仓库配置文件里。虽然这不属于语法问题,但属于部署和安全风险。
建议改为环境变量注入,配置文件只保留变量名或默认值。
## 建议优先修复顺序
1. 修复会立即导致接口报错的问题:`DB.File.insertMany``getMenu` 查错模型、用户岗位字段错误。
2. 修复权限和部门树边界:项目公司权限漏公司根节点、审批人范围复用同一套部门范围函数、部门/菜单删除前缀误伤。
3. 修复异步更新未等待:所有 `Promise.all(map(... => { DB.xxx.update(...) }))` 改为返回 Promise。
4. 修复生产启动风险:移除或生产禁用模型文件里的 `sync({ alter: true })`
5. 恢复 lint 能力,并把敏感日志、token 日志、明文密码日志清掉。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment