Files
guoju0808/_self/manager_projects.md
T
郭庆泰 25cec1e404 docs(projects): 删除误标的 P0 #1 #2 + 相关错误标注
- §1.1 L47 删除公告: 简化 5 个 URL + is_published='0' → 3 个 URL
- §5.3 L164 开通单条: 删 "⚠️ 字段写错了" + "但实现不对" 标记
- §5.3 L165 删除公告: 简化 5 个 URL + is_published → 3 个 URL
- §9.1 L273-277: 删除 5 行死字段表 (noticeUrl/scheduleUrl/isPublished/publishTime/announcementType)
- §9.1 L283: 删除"修订标记"段落 + "后果"说明
- §9.1 L369: 删除"🔴 严重冗余"bullet
- §10 L430-431: 删除待修复 #1 + #2
2026-08-20 22:50:36 +08:00

33 KiB
Raw Blame History

manager/projects — 端到端技术审查

审查时间: 2026-08-18 审查范围: ry-vue3/src/views/manager/Projects.vue (主列表) + 后端 BizProject 全链路 + DB 实测 对比原型: proto/html/components/project-manage.html


修复记录

日期 章节 修复内容
2026-08-18 §6.3 / §9.1 BizProject.projectId String → Long (9 文件: domain / IBizProjectService / BizProjectServiceImpl / BizProjectMapper.java+xml / BizProjectController / BizExecutionIntentController / BizPublicityIntentController / BizPublicController)。SnowflakeId.injectIfEmpty 反射依赖 setter(String), Long 后会抛 NoSuchMethodException 被吞掉 (SnowflakeId.java:30-31), 安全 — 但显式删掉更干净 (biz_project 用 AUTO_INCREMENT)。
2026-08-18 §6.3 / §9.1 BizProject.startTime / endTime String → Date, 加 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") (跟 BizMeeting 一致)。Mapper XML 8 处 <if test="... != null and ... != ''"> 改为 <if test="... != null"> (Date 无 isEmpty, OGNL 会报错)。前端 el-date-picker value-format "YYYY-MM-DD HH:mm:ss" 双向兼容, 零前端改动。
2026-08-18 §10 #7 batchScoreRows 死代码删除 (Projects.vue:501)。注释说"保留兼容旧引用", 但 grep 全文 0 个引用, 纯死变量。
2026-08-18 §10 #5 生产代码残留 console.info/error 4 处删除 (Projects.vue:299, 302, 306, 874)。load() 进/出/失败各一处 + 路由 watch reload 一处, 共 4 行, 全部删除。
2026-08-18 §10 #9 日期选择器 value-format 对齐 type (Projects.vue:17, 19)。原 type="date" + value-format="YYYY-MM-DD HH:mm:ss" 矛盾 (date picker 给不出 HH:mm:ss), 改 value-format 为 YYYY-MM-DD, MySQL 对 datetime 列隐式转换。
2026-08-18 §7.1 / §10 #12 DB: biz_project 加索引 idx_sponsor_admin_user_id。ALTER TABLE biz_project ADD INDEX idx_sponsor_admin_user_id (sponsor_admin_user_id)。EXPLAIN 验证: type=ref, Extra=Using index。
2026-08-18 §7.1 / §10 #13 DB: biz_project 加复合索引 idx_start_end_time (start_time, end_time)。比 2 个单列索引更适合 WHERE start_time >= ? AND end_time <= ? 的范围查询。EXPLAIN 验证: type=range, Extra=Using where; Using index。
2026-08-18 §7.2 / §10 #14 DB: biz_project_assign 加 UNIQUE uk_project_unit (project_id, execution_unit_id)。先查 11 行 0 重复, 安全。防重复分配。注意: biz_project_assign项目→executor (执行方) 分配表, 不是 sponsor; sponsor 走独立的 biz_project_sponsor_assign
2026-08-20 §2 / §8.1 / §10 #3 P0 修复: selectList 补 managerScore 过滤 (88df386)。前端评价得分筛选透传到后端但 mapper 没读, 形同虚设。加 <if test="managerScore != null">and manager_score = #{managerScore}</if>, 跟 INSERT/UPDATE 同款 BigDecimal null check。EXPLAIN 验证 12 行数据走 PRIMARY Backward scan, 命中 1 行 manager_score=5。

0. 4 跳定位 (铁律: 不按名字猜)

文件:行 结果
① 角色菜单 ry-vue3/src/layout/AdminLayout.vue:98 path: '/manager/projects', title: '项目管理'
② 路由 ry-vue3/src/router/index.js:65 component: () => import('@/views/manager/Projects.vue')
③ 组件 ry-vue3/src/views/manager/Projects.vue 881 行,本审查对象
④ 原型 proto/html/manager.html:206 <a data-page="components/project-manage.html">

1. 主要功能 + 可见性

1.1 主要功能

功能 前端入口 后端接口 涉及表
项目列表(分页+筛选) manager/Projects.vue 表头筛选 + load() GET /business/project/listBizProjectController.listBizProjectService.selectList biz_project
单条/批量结题 onToggleStatus (批量走 openBatch('close')) PUT /business/projectbizProjectService.updateByPrimaryKey (只改 is_finished='1') biz_project.is_finished
单条/批量开通 openActivate / openBatch('activate') PUT /business/project (改 end_time) biz_project.end_time
单条/批量执行单位评分 openSingleScore / openBatch('score') POST /business/project/rate (4 维度) + PUT /business/project (写聚合分) biz_project_rating + biz_project.manager_score
项目分配(单条+批量) doAssign / openAssign PUT /business/project (改 sponsor) + POST /business/project/{id}/assigns (先删后插) biz_project + biz_project_assign
删除公告(单条) confirmDeleteAnn PUT /business/project (清空 publishUrl/invitationUrl/supportLetterUrl) biz_project
导出报名专家 exportExperts POST /business/executionIntent/export?projectNo=... biz_execution_intent
新建/编辑/查看 跳独立页 /manager/projects/new/edit/:id/detail/:id biz_project

1.2 可见性(三层隔离)

来源 校验字段
前端菜单 AdminLayout.vue:98 只在 manager 角色菜单出现
路由守卫 router/index.js:61 meta: { role: 'manager' } 路由 meta.role
后端 BizProjectController.listselectList 无 user_id 过滤(manager 可看所有项目)

⚠️ manager 是全局视图角色(合规管理员), 看所有项目不过滤 — 设计正确

其他角色访问:

  • sponsor → GET /business/project/sponsorList (按 sponsor_admin_user_id = uid 过滤)
  • executor → GET /business/project/executorList (按 biz_project_assign 关联过滤)
  • 这两个不会进 manager/Projects.vue,但共用 controller

2. 筛选项 (filter-form)

UI label 控件 v-model 后端 SQL 命中表.字段 选项来源 原型对照
支持单位 el-select (multiple+remote) q.sponsorAdminUserIds (List) selectList line 167-173 sponsor_admin_user_id IN <foreach> biz_project.sponsor_admin_user_id /business/org/sponsorOptions (LIMIT 5) 原型有
服务机构 el-select (multiple+remote) q.execAdminUserIds (List) selectList line 173-181 EXISTS (biz_project_assign WHERE exec_user_id IN <foreach>) biz_project_assign.exec_user_id (MAIN user_id) /business/org/executorOptions (LIMIT 5) 原型有
项目编号 el-input q.projectNo line 156 project_no LIKE concat('%',?, '%') biz_project.project_no 原型有
项目名称 el-input q.projectName line 157 project_name LIKE concat('%',?, '%') biz_project.project_name 原型有
项目形式 el-select q.projectForm line 158 project_form = ? biz_project.project_form 写死: 线上/线下/线上+线下/其他 原型有
项目时间(起/止) el-date-picker type="date" + value-format="YYYY-MM-DD HH:mm:ss" q.startTime/endTime line 93-94 start_time >= ? AND end_time <= ? (sponsorList); line 134-135 同 biz_project.start_time/end_time 原型有
是否结题 el-select q.isFinished line 179 is_finished = ? biz_project.is_finished 写死: 已结题→'1' / 未结题→'0' 原型有
是否结算 el-select q.isSettled line 180 is_settled = ? biz_project.is_settled 写死: 已结算→'Y' / 未结算→'N' ⚠️ 原型 (实现新增)
评价得分 el-input q.managerScore line 188 manager_score = ? (BigDecimal, 跟 INSERT/UPDATE 同款 != null 检查) biz_project.manager_score 原型有

已修 (88df386):managerScore 过滤已加 selectList; selectSponsorList / selectExecutorList 故意不加 (评分字段与该角色无关,manager 端独有)


3. 工具栏按钮 (toolbar)

按钮 功能 接口 涉及表 原型对照
新建项目 跳转独立页 router.push('/manager/projects/new')ProjectsNew.vue biz_project 原型有
项目分配 弹 dialog (单条或批量,按勾选数) PUT /business/project (改 sponsor_admin_user_id) + POST /business/project/{id}/assigns biz_project + biz_project_assign 原型有
批量评分 弹 dialog,共用 4 维度评分 PUT /business/project (managerScore) + POST /business/project/rate × N biz_project + biz_project_rating 原型有
批量结题 弹 dialog,确认后批量改 is_finished='1' PUT /business/project × N biz_project.is_finished 原型有
批量开通 弹 dialog,选截止时间,批量改 end_time PUT /business/project × N biz_project.end_time 原型有
导出 (3 个按钮) 项目/评价/已报名专家导出 exportProjects / exportEval / exportExperts ⚠️ 前两个是 stub,仅 exportExperts 实装

⚠️ stub 函数:exportProjects (line 347) / exportEval (line 348) 仅弹 ElMessage.info('开发中') ⚠️ 遗漏:原型有 3 个导出按钮 (导出/项目评价导出/已报名专家信息导出), 实装只有 1 个 ⚠️ 额外按钮 (实现新增): 删除公告(原型的 #7), 不是"扩展"而是"实现"


4. 表格 (el-table)

4.1 数据来源 SQL(manager 通用列表 selectList)

SELECT p.project_id, p.project_no, p.project_name, p.total_sessions, p.done_sessions,
       p.todo_sessions, p.total_amount, p.available_amount, p.paid_labor_amount,
       p.paid_meeting_amount, p.manager_score, p.sponsor_score,
       p.sponsor_admin_user_name, p.project_form, p.is_finished, p.is_settled,
       p.sponsor_admin_user_id, p.lead_user_id, p.is_bid_project,
       p.create_by, p.create_time, p.update_by, p.update_time, p.manage_fee,
       p.start_time, p.end_time, p.submit_deadline_days,
       p.support_contract_url, p.execute_contract_url, p.invitation_url,
       p.support_letter_url, p.publish_url,
       o.org_name as sponsor_org_name,
       lu.user_name as lead_user_name,
       (SELECT group_concat(distinct o2.org_name separator ',')
        FROM biz_project_assign bpa
        JOIN biz_org o2 ON o2.org_id = bpa.execution_unit_id AND o2.org_type = 'executor'
        WHERE bpa.project_id = p.project_id) AS exec_org_names
FROM biz_project p
LEFT JOIN biz_org o ON o.user_id = p.sponsor_admin_user_id AND o.org_type = 'sponsor'
LEFT JOIN sys_user lu ON lu.user_id = p.lead_user_id
[WHERE 动态过滤...]

4.2 列映射

row.* DB 表.字段 原型对照
复选框
项目编号 row.projectNo biz_project.project_no
项目名称 row.projectName biz_project.project_name
总场次/总期数 row.totalSessions biz_project.total_sessions
已执行 row.doneSessions biz_project.done_sessions
未执行 row.todoSessions biz_project.todo_sessions
总金额 row.totalAmount (fmtMoney) biz_project.total_amount
可用金额 row.availableAmount (fmtMoney) biz_project.available_amount
已支付劳务费 row.paidLaborAmount (fmtMoney) biz_project.paid_labor_amount
已支付会务费 row.paidMeetingAmount (fmtMoney) biz_project.paid_meeting_amount
执行单位得分(合规) row.managerScore biz_project.manager_score
执行单位得分(支持) row.sponsorScore biz_project.sponsor_score
支持单位 row.sponsorOrgName biz_org.org_name (JOIN)
服务机构 row.execOrgNames GROUP_CONCAT of biz_org.org_name
项目形式 row.projectForm biz_project.project_form
是否结题 row.isFinished (tag 化) biz_project.is_finished
项目开始时间 row.startTime (fmtDate) biz_project.start_time
项目结束时间 row.endTime (fmtDate) biz_project.end_time
操作 6 个 link + dropdown 8 项操作齐全(建会/分配/结题/查看/编辑/开通/删除公告/执行单位评分)

5. 行内操作按钮

按钮 函数 接口 后端动作 涉及表.字段 原型对照
查看 viewDetail router.push('/manager/projects/detail/${row.projectId}') (独立页)
编辑 doEdit router.push('/manager/projects/edit/${row.projectId}') (独立页 ProjectsNew.vue) biz_project (全字段)
建会 doCreateMeeting router.push('/manager/meetings/new?projectId=${row.projectId}') (独立页 MeetingNew.vue)
项目分配 doAssign openAssign
结题 (单条) openClose + confirmClose PUT /business/project {projectId, isFinished: '1'} bizProjectService.updateByPrimaryKey biz_project.is_finished='1'
开通 (单条) openActivate + confirmActivate PUT /business/project {projectId, isFinished: '0'} 同上 biz_project.is_finished='0'
删除公告 openDeleteAnnouncement + confirmDeleteAnn PUT /business/project 清空 3 个 URL 同上 biz_project.{publish_url,invitation_url,support_letter_url}='\0'
执行单位评分 (单条) openSingleScore + confirmSingleScore PUT /business/project {managerScore} + POST /business/project/rate bizProjectRatingService.upsertRating biz_project.manager_score + biz_project_rating (新行或更新)
导出报名专家 exportExperts POST /business/executionIntent/export?projectNo=... (单独执行方意图表) biz_execution_intent
批量评分 openBatch('score') + confirmBatch PUT + POST /rate × N 同单条,按勾选遍历 biz_project + biz_project_rating
批量结题 openBatch('close') + confirmBatch PUT /business/project {isFinished: '1'} × N 同单条 biz_project.is_finished
批量开通 openBatch('activate') + confirmBatch PUT /business/project {endTime: activateDeadline} × N 同单条 biz_project.end_time

⚠️ 🐛 Bug: 单条"开通"(confirmActivate line 397-401) 改了 is_finished='0' 而不是 end_time — 应该跟批量开通一致改 end_time。改 is_finished='0' 等同于"取消结题",跟"开通"语义不符

🟢 正确性: 评分走 biz_project_rating 中间表 + upsertRating SQL 是 (project_id, rater_id, rater_role) 复合唯一,符合"一人评一次"的语义


6. Java Entity ↔ 数据库表 一致性

6.1 biz_project 主表 DDL (实测)

CREATE TABLE `biz_project` (
  `project_id` bigint NOT NULL AUTO_INCREMENT,
  `project_no` varchar(50) NOT NULL COMMENT '项目编号 ZH-2026-658',
  `project_name` varchar(200) NOT NULL COMMENT '项目名称',
  `project_form` varchar(20) DEFAULT NULL COMMENT '项目形式 线上/线下/线上+线下/其他',
  `total_sessions` int DEFAULT '0',
  `done_sessions` int DEFAULT '0',
  `todo_sessions` int DEFAULT '0',
  `total_amount` decimal(15,2) DEFAULT '0.00',
  `available_amount` decimal(15,2) DEFAULT '0.00',
  `paid_labor_amount` decimal(15,2) DEFAULT '0.00',
  `paid_meeting_amount` decimal(15,2) DEFAULT '0.00',
  `manage_fee` decimal(15,2) DEFAULT '0.00' COMMENT '管理费及税金',
  `is_finished` char(1) DEFAULT '0' COMMENT '是否结题 0否 1是',
  `is_settled` char(1) DEFAULT 'N' COMMENT '是否结算 N否 Y是',
  `manager_score` decimal(3,1) DEFAULT NULL,
  `sponsor_admin_user_id` bigint DEFAULT NULL,
  `sponsor_admin_user_name` varchar(200) DEFAULT NULL,
  `lead_user_id` bigint DEFAULT NULL,
  `is_bid_project` char(1) DEFAULT 'N' COMMENT '是否招标项目 Y/N',
  `start_time` datetime DEFAULT NULL,
  `end_time` datetime DEFAULT NULL,
  `submit_deadline_days` int DEFAULT '0',
  `support_contract_url` varchar(500) DEFAULT NULL,
  `execute_contract_url` varchar(500) DEFAULT NULL,
  `invitation_url` varchar(500) DEFAULT NULL,
  `support_letter_url` varchar(500) DEFAULT NULL,
  `publish_url` varchar(500) DEFAULT NULL,
  `create_by` varchar(64) DEFAULT '',
  `create_time` datetime DEFAULT NULL,
  `update_by` varchar(64) DEFAULT '',
  `update_time` datetime DEFAULT NULL,
  `sponsor_score` decimal(3,1) DEFAULT NULL,
  PRIMARY KEY (`project_id`),
  UNIQUE KEY `uk_project_no` (`project_no`),
  KEY `idx_project_form` (`project_form`),
  KEY `idx_is_finished` (`is_finished`),
  KEY `idx_is_settled` (`is_settled`)
) ENGINE=InnoDB

6.2 biz_project_assign / biz_project_rating DDL (实测)

biz_project_assign 索引:PRIMARY(assign_id) + idx_assign_project / idx_assign_unit / idx_assign_exec_user

biz_project_rating 索引:

  • PRIMARY (rating_id)
  • UNIQUE uk_rating (project_id, rater_id, rater_role) 一人评一次
  • idx_pr_project, idx_pr_rater, idx_pr_rating_time

6.3 Entity ↔ 表字段对照 (BizProject.java)

实体字段 中文列名 (DB COMMENT) 类型 表字段 类型 一致?
projectId 项目ID Long project_id bigint 已修 (2026-08-18, 见 §修复记录)
projectNo 项目编号 ZH-2026-658 String project_no varchar(50)
projectName 项目名称 String project_name varchar(200)
totalSessions 总场次/总期数 Long total_sessions int
doneSessions 已执行 Long done_sessions int
todoSessions 未执行 Long todo_sessions int
totalAmount 总金额 BigDecimal total_amount decimal(15,2)
availableAmount 可用金额 BigDecimal available_amount decimal(15,2)
paidLaborAmount 已支付劳务费 BigDecimal paid_labor_amount decimal(15,2)
paidMeetingAmount 已支付会务费 BigDecimal paid_meeting_amount decimal(15,2)
manageFee 管理费及税金 BigDecimal manage_fee decimal(15,2)
managerScore 合规管理员评均分 BigDecimal manager_score decimal(3,1)
sponsorScore 支持方评均分 BigDecimal sponsor_score decimal(3,1)
sponsorAdminUserName 赞助方负责人用户名(冗余) String sponsor_admin_user_name varchar(200)
sponsorAdminUserId 赞助方负责人用户ID Long sponsor_admin_user_id bigint
leadUserId 项目负责人 user_id Long lead_user_id bigint
projectForm 项目形式 线上/线下/线上+线下/其他 String project_form varchar(20)
isFinished 是否结题 0否 1是 String is_finished char(1)
isSettled 是否结算 N否 Y是 String is_settled char(1)
isBidProject 是否招标项目 Y/N (默认 N) String is_bid_project char(1)
startTime 项目开始时间 Date start_time datetime 已修 (2026-08-18, 加 @JsonFormat, 见 §修复记录)
endTime 项目结束时间 Date end_time datetime 已修 (2026-08-18, 同上)
submitDeadlineDays 提交材料截止天数 Integer submit_deadline_days int
supportContractUrl 支持合同文件URL String support_contract_url varchar(500)
executeContractUrl 执行合同文件URL String execute_contract_url varchar(500)
invitationUrl 邀请函文件URL String invitation_url varchar(500)
supportLetterUrl 支持函文件URL String support_letter_url varchar(500)
publishUrl 已发布公告URL String publish_url varchar(500)
createBy/createTime 创建者 / 创建时间 (BaseEntity)
updateBy/updateTime 更新者 / 更新时间 (BaseEntity)
assignedSessions (派生) Long 无对应字段 ⚠️ 派生字段,只在 executor 端 SQL 用
assignedAmount (派生) BigDecimal 无对应字段 ⚠️ 同上
sponsorOrgName (派生) String 无对应字段 ⚠️ JOIN biz_org 派生
execOrgNames (派生) String 无对应字段 ⚠️ GROUP_CONCAT 派生, 仅展示
execAdminUserIds (查询参数) List<Long> 无对应字段 biz_project_assign.exec_user_id 多选 select 透传, 走 EXISTS + IN 精确查询
leadUserName (派生) String 无对应字段 ⚠️ JOIN sys_user 派生
sponsorRating, sponsorQ1-4, sponsorRemark (DDL 中不存在) DDL 中不存在 🐛 残留支持方评分字段,未使用

修订标记


7. 索引检查

7.1 biz_project 索引

Key 用途覆盖
PRIMARY project_id ORDER BY, 主键
uk_project_no project_no (UNIQUE) 编号精确/模糊查询
idx_project_form project_form 项目形式过滤
idx_is_finished is_finished 是否结题过滤
idx_is_settled is_settled 是否结算过滤
idx_sponsor_admin_user_id sponsor_admin_user_id selectSponsorList WHERE sponsor_admin_user_id = ?
idx_start_end_time (start_time, end_time) 范围查询 start_time >= ? AND end_time <= ?,EXPLAIN 验证 type=range
lead_user_id ⚠️ 主表没用,但 JOIN sys_user 用 — 可接受,小数据量

7.2 biz_project_assign 索引

Key 用途覆盖
PRIMARY assign_id
idx_assign_project project_id SELECT WHERE project_id = ?
idx_assign_unit execution_unit_id
idx_assign_exec_user exec_user_id
uk_project_unit (project_id, execution_unit_id) UNIQUE 防重复分配 (实测 11 行 0 重复, 安全)

7.3 biz_project_rating 索引

Key 用途覆盖
PRIMARY rating_id
uk_rating (project_id, rater_id, rater_role) UNIQUE upsertRating 用 ON DUPLICATE KEY,完美
idx_pr_project project_id SELECT WHERE project_id = ?
idx_pr_rater rater_id
idx_pr_rating_time rating_time 按时间排序

结论:rating 表索引设计优秀(uk_rating 三元组唯一约束)。project 表索引已补齐 (7 个, 涵盖所有 WHERE 列)。assign 表唯一约束到位 (uk_project_unit 防重复分配)。


8. 与原型差异

8.1 实现新增 (原型没有)

位置 说明
项目分配 dialog assignOpen (line 119) 原型 project-assign.html 是独立页,实现合并到本 dialog,功能更全 (支持方下拉 + 总场次/金额校验)
评分明细反显 openSingleScore (line 424-449) 原型无,实装调用 /business/project/ratings 反显当前管理员历史评分
评分默认值 batchScoreForm default=5, singleScoreForm default=0 原型无
批量开通截止时间字段 activateDeadline 原型批量开通仅"确认"无时间字段,实装加了截止时间
isSettled 筛选 line 26-30 原型无"是否结算"筛选项

8.2 原型有但实现缺失

原型项 实现状态
导出 (项目) 🐛 stubexportProjectsElMessage.info('开发中')
项目评价导出 🐛 stubexportEvalElMessage.info('开发中')
已报名专家信息导出 实装exportExperts POST executionIntent/export
managerScore 评价得分筛选 后端已生效 — selectList 加 <if test="managerScore != null">and manager_score = #{managerScore}</if> (88df386)

8.3 文案/标签差异

原型 实现 差异
评分标准输入框 placeholder "1~5" el-input-number :min=0 :max=5 :step=1 实现用数字输入控件(更好)
"您确定要开通吗?" 弹框 仅"确定/取消",无截止时间字段 (单条版) 原型单条版也无,批量版实现加了
表格 min-width=2900px 横滚 el-table 自动布局 实现用 fixed="right" 操作列,列宽合理
状态颜色: 蓝色 status-activated (已开通) 实现无 "已开通" 状态列,只有"已结题/未结题"列 实现走 end_time 而非 is_finished,前端没标"已开通" tag

8.4 总结

  • 整体方向: 实现比原型更完整 (评分明细反显、批量开通截止时间、支持方/执行方下拉),但丢了 3 个导出按钮 (2 个 stub,1 个已报名专家实装)
  • 设计偏离: 把 is_finished 复用为"开通/结题"两种语义 (改值而非新增字段),导致前端状态显示混乱 (已结题是 tag,已开通没 tag)

9. 表字段冗余 / 设计问题

9.1 实体 vs 表不一致 (详见 §6.3)

  • 已修: projectId String → Long (见 §修复记录)
  • 已修: startTime/endTime String → Date (见 §修复记录)

9.2 JOIN 字段 vs 持久化字段混淆

  • sponsorOrgName / execOrgNames / leadUserName / assignedSessions / assignedAmount 全部是派生字段,不入库,只在 SQL JOIN 时填充,前端可读但不应让前端 PUT 回去
  • 当前 mapper 的 updateByPrimaryKey 已经用 <if test="..."> 防御,不会回写 — OK

9.3 唯一约束语义检查

  • biz_project: uk_project_no — 项目编号唯一,合理
  • biz_project_rating: uk_rating (project_id, rater_id, rater_role) — 一人评一次,合理
  • biz_project_assign: uk_project_unit (project_id, execution_unit_id) — 同一项目同一执行方只能分配一次

9.5 索引分析与 EXPLAIN (2026-08-20)

数据规模: biz_project 12 行 / biz_org 33 行 / biz_project_assign 12 行 / sys_user 47 行 (测试数据)

# 查询 命中索引 备注
Q1 selectSponsorOrgOptions idx_org_type_status (2 列) 优化器在小数据量倾向小索引, org_name LIKE 走 Using where 二次过滤, ORDER BY org_id DESC 走 filesort
Q2 selectExecutorOrgOptions idx_org_type_status + PRIMARY sys_user 同上, parent_user_id IS NULL 走 Using where
Q3 selectList 主项目 + sponsor_admin_user_id IN idx_sponsor_admin_user_id range + Using index 完美 covering, 无需回表
Q4 selectList 主项目 + EXISTS(exec_user_id IN) idx_assign_exec_user_project + LooseScan 完美 covering (新加索引, 见下)
Q5 selectList 主项目 + sponsor IN + 多 LEFT JOIN + GROUP_CONCAT 子查询 idx_project_form Backward scan + 3 个 LEFT JOIN 全 eq_ref/Using index + uk_project_unit GROUP_CONCAT 全部走索引
Q6 selectList 主项目 + 单独 exec EXISTS (无其他过滤) idx_assign_exec_user_project range + Start temporary ⚠️ 优化器会 material 子查询, 数据量小可接受
Q7 selectList 同时 sponsor IN + exec EXISTS idx_project_form 主表 + idx_assign_exec_user_project FirstMatch 子查询 组合最优, semi-join
Q8 selectList 仅 projectForm + isFinished index_merge(intersect(idx_project_form, idx_is_finished)) MySQL 8 自动交集

已加索引 (2026-08-20):

ALTER TABLE biz_project_assign ADD INDEX idx_assign_exec_user_project (exec_user_id, project_id);

理由: Q4/Q6/Q7 (服务机机构 EXISTS+IN) 原命中 idx_assign_exec_user(exec_user_id) 单列索引, 需回表读 project_id; 新加复合索引直接 covering, EXPLAIN 显示 Using index + LooseScan/range, 无需回表。

评估后未加的索引 (记录备查):

  • biz_org (org_type, org_id, org_name) — 用于消除 options 查询的 filesort。 评估: 强制 hint 后确实变 Backward index scan 无 filesort, 但优化器在 33 行数据下仍选 idx_org_type_status (2 列更小, 估算成本更低)。 决策: 数据量小不需要, DROP, 等生产数据增长到 1k+ 行再 EVAL。
  • biz_org (org_type, status, is_published) 等 — 无明显 QPS 压力场景, 不加。

过时注释修正 (2026-08-20):

  • BizOrgMapper.xml 旧注释写 "走 idx_org_user_type_name 索引", EXPLAIN 实际命中 idx_org_type_status, 已修正为实测命中索引 + Using where 描述

9.4 业务闭环检查

  • 项目分配 dialog: assignForm.execRows 行内 el-select 的 value=MAIN user_id,后端 BizProjectAssignServiceImpl 应该从 exec_user_id 反查 biz_org 写 execution_unit_id (mapper 注释有写,需确认 service 实现)
  • 项目分配 → 评分 → 公示 → 通知: 整条业务链已铺,但 manager_score 聚合分写的是 manager 评分的均值,逻辑合理
  • 批量开通: 改 end_time 而不是新建 is_activated 字段 — 用现有字段表达多语义,容易跟"结题"冲突,设计可商榷

10. 待修复列表 (按优先级)

# 问题 文件:行 修复建议 严重度
3 managerScore 筛选前端有,后端 selectList 不读 BizProjectMapper.xml:141-183 (selectList) 已修 (88df386,见 §修复记录)
4 下拉菜单 divided 冗余 (activate/delAnn 双分隔线) Projects.vue:100-101 activatedivided P1
5 生产代码残留 console.info/error (4 处) Projects.vue:299, 302, 306, 875 已修 (见 §修复记录)
6 评分默认值不一致 Projects.vue:501 vs 513 统一为 0 或 5 P2
7 batchScoreRows 死代码 Projects.vue:501 已修 (见 §修复记录)
8 isSettled 筛选但表格无列显示 Projects.vue:26-30 vs §4.2 表 加列 或 删筛选 P2
9 日期选择器 value-format="YYYY-MM-DD HH:mm:ss"type="date" 不匹配 Projects.vue:17, 19 已修 (见 §修复记录)
10 重复 score table HTML (批量评分 + 单条评分) Projects.vue:201-213, 257-269 抽公共组件 P2
11 2 个导出按钮是 stub (exportProjects / exportEval) Projects.vue:347-348 实装 或 删按钮 P2
12 biz_project 缺索引 sponsor_admin_user_id DB 已修 (见 §修复记录)
13 biz_project 缺索引 start_time, end_time (范围查询) DB 已修 (见 §修复记录)
14 biz_project_assign 缺 UNIQUE (project_id, execution_unit_id) DB 已修 (见 §修复记录)
15 is_finishedis_settled 状态值约定不一致 (0/1 vs Y/N) BizProject.java:65-68 + mapper 统一约定 (要么全 0/1 要么全 Y/N) P3
16 selectList / selectExecutorList 有 4 个相关子查询 BizProjectMapper.xml:46-50, 110-124 数据小可接受,但应记录为 P3 待优化 P3
17 .score-row CSS 死样式 Projects.vue:926-928 P3

11. 引用清单

前端

  • ry-vue3/src/views/manager/Projects.vue — 主列表(881 行)
  • ry-vue3/src/views/manager/ProjectsNew.vue — 新建/编辑独立页
  • ry-vue3/src/views/manager/ManagerProjectDetail.vue — 详情独立页
  • ry-vue3/src/layout/AdminLayout.vue:98 — 菜单项定义
  • ry-vue3/src/router/index.js:65-68 — 路由 (含 /new, /edit/:id, /detail/:id)
  • ry-vue3/src/api/public.jsbizList/bizAdd/bizUpdate/bizDelete 通用 CRUD
  • ry-vue3/src/api/system.jslistExecutorOrgs/listSponsorOrgs 项目分配用
  • ry-vue3/src/components/Preview.vue — 附件预览 (import 但未使用?)

后端 (Java)

  • ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectController.java — 项目主 controller (291 行, 14 个 endpoint)
  • ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java — 实体 (230 行, 字段定义含 5 个 DDL 不存在的字段)
  • ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectAssign.java — 分配实体
  • ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectRating.java — 评分实体
  • ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectRatingController.java — 评分 CRUD (单独 controller)
  • ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectAssignServiceImpl.java — 分配 service (分配时反查 execution_unit_id)
  • ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectRatingServiceImpl.java — 评分 upsert service

Mapper XML

  • ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectMapper.xml — 项目主 mapper (301 行, 4 个 select + insert + update + 2 个 delete)
  • ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectAssignMapper.xml — 分配 mapper (106 行)
  • ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectRatingMapper.xml — 评分 mapper (含 upsertRating 三元组 ON DUPLICATE KEY)

配置 / 工具

  • ry-api/ruoyi-admin/src/main/resources/application-druid.yml:9-10 — DB 连接 (root / cu2oh2co3)

原型

  • proto/html/manager.html:206 — 菜单入口
  • proto/html/components/project-manage.html — 项目管理原型 (676 行,含 5 个 modal)
  • proto/html/components/project-assign.html — 项目分配原型 (未深读,但 dialog 实装与之有偏差)

数据库 (实测)

  • biz_project — 11 行,5 索引 (含 1 唯一),缺 3 个索引
  • biz_project_assign — 11 行,4 索引,缺唯一约束
  • biz_project_rating — 21 行,5 索引 (含 1 唯一),设计优秀
  • biz_org — 33 行 (待查索引,本审查未深入)
  • sys_user — 40 行 (待查 role_type 索引)