Files
tuanwei-admin/docs/phase2-P0-delivery.md
T
gxwebsoft d82a3b5426 feat(credit): 增加社会实践学分及学分预警模块
- 实现社会实践学分的提交、撤回、作废、删除、汇总及导出功能
- 审核通过后自动发放学分,确保幂等性
- 新增学分预警功能,支持缺口计算、级别判定和月度去重推送
- 设计四级审核流程,审核状态变更后自动回调处理
- 支持教务处模板导出,输出标准xls格式文件
- 提供前端页面替换原占位页,提升用户体验
- 补充每日定时任务执行学分预警重算,支持配置开关
- 修复审核回写逻辑,避免误更新不存在的表数据
2026-11-05 22:29:01 +08:00

393 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 二期 P0 交付说明
## 一、本次交付范围
按《副本二期功能--定稿0802.docx》规划,P0 阶段交付以下 8 个功能模块,全部已完成前后端联调验证。
| # | 功能 | 状态 | 后端接口前缀 | 前端页面 |
| --- | --- | --- | --- | --- |
| 1 | 站内消息中心 | 已完成 | `/api/oa/notice`、`/api/oa/notice-template` | 复用既有顶栏铃铛 + `/user/notice`;新增模板与推送记录页 |
| 2 | 第二课堂学分 | 已完成 | `/api/gxmu/credit` | `/growth/credit` |
| 3 | 学生成长主页 | 已完成 | `/api/gxmu/growth/my` | `/growth/myHome` |
| 4 | 志愿服务时长 | 已完成 | `/api/gxmu/volunteer` | `/club/volunteerHour`、`/club/volunteerReview`、`/club/volunteerCertificate` |
| 5 | 社团管理 | 已完成 | `/api/gxmu/club` | `/club/club`、`/club/activity`、`/club/activityReview` |
| 6 | 场地预约 | 已完成 | `/api/gxmu/venue` | `/club/venue`、`/club/venueReview` |
| 7 | 青马培养档案 | 已完成 | `/api/gxmu/qmgc-student` | `/gxmu/qmgcStudent`、`/gxmu/qmgcTask`、`/gxmu/qmgcScore` |
| 8 | 主题团课库 | 已完成 | `/api/gxmu/thematic-course` | `/gxmu/thematicCourse` |
P1/P2 的 10 个功能已建菜单与占位页(写明实施依赖),不会出现菜单 404:
社会实践学分、学分预警、学生画像、推荐内容、社团年审、社团评优、AI 辅助教学设计、AI 主题团日设计、专家评审、专家库。
## 二、数据库部署
在 `tuanwei-java/sql/` 下按顺序执行(全部可重复执行):
```bash
mysql -h <host> -P <port> -u <user> -p tuanwei < sql/20261105_phase2_01_growth_credit.sql
mysql ... < sql/20261105_phase2_02_volunteer.sql
mysql ... < sql/20261105_phase2_03_club_venue.sql
mysql ... < sql/20261105_phase2_04_qmgc.sql
mysql ... < sql/20261105_phase2_05_thematic_course.sql
mysql ... < sql/20261105_phase2_06_notice.sql
mysql ... < sql/20261105_phase2_07_review_parallel.sql
mysql ... < sql/20261105_phase2_08_menu_permission.sql
mysql ... < sql/20261105_phase2_09_dict.sql
```
| 脚本 | 内容 | 是否含初始数据 |
| --- | --- | --- |
| 01 | 学分规则/记录/社会实践/毕业要求/预警/画像/推荐(8 张表) | 6 条毕业学分要求 |
| 02 | 志愿时长/证明模板/已生成证明(3 张表) | 无 |
| 03 | 社团/成员/活动/年审/评优/年报/场地/预约(8 张表) | 无 |
| 04 | 青马学员/材料/任务/任务分配/评分/考试(7 张表) | 无 |
| 05 | 主题团课库(1 张表) | 无 |
| 06 | 站内消息 + 消息模板(2 张表) | 10 条消息模板 |
| 07 | 审核流并行节点改造(ALTER 4 处) | 回填存量 parallel_group |
| 08 | 菜单 + 按钮权限 + 角色授权(62 条菜单/按钮) | 超管全量、学生部分 |
| 09 | 字典(学分类别、社团类别) | 11 条字典项 |
**注意**:脚本 07 会修改 `gxmu_review_flow` / `gxmu_review_list` / `gxmu_review_flow_config` 三张生产表,只是新增列并回填,不影响存量串行审核逻辑。
## 三、后端改动清单
### 新增包
```
com.gxwebsoft.notice 消息中心(Notice / NoticeTemplate / 2 个 Controller / 2 个 Mapper)
com.gxwebsoft.gxmu.credit 学分域(规则、记录、汇总、导入)
com.gxwebsoft.gxmu.volunteer 志愿服务(录入、导入、审核、汇总)
com.gxwebsoft.gxmu.club 社团(主档、成员、活动)
com.gxwebsoft.gxmu.venue 场地与预约(含时间段冲突校验)
com.gxwebsoft.gxmu.qmgcpro 青马培养档案(学员、材料、任务、评分)
com.gxwebsoft.gxmu.thematic 主题团课库(含 jsoup 爬虫)
com.gxwebsoft.gxmu.growth 成长主页聚合
```
### 关键实现说明
1. **消息中心**:路径与前端既有约定完全一致(`/api/oa/notice`),顶栏铃铛和 `/user/notice` 无需改代码即可生效。查询默认按当前登录用户收口,学生只能看自己的消息。
2. **学分汇总**:应修学分来自 `gxmu_graduate_requirement`(学院专属优先于「不限」),已修来自 `gxmu_credit_record`;按类别输出已修/应修/差额/完成率。
3. **场地冲突校验**:同场地 + 同日期 + 状态非驳回,时间段半开区间重叠即拒绝;审核通过前会二次校验,避免并发下重复通过。
4. **成长主页聚合**:串起 `sys_user` + `gxmu_student` + `gxmu_tygl_form` + 学分 + 志愿 + 社团 + 青马;任一未建表时静默跳过,不影响其他区块。
5. **消息推送**:志愿提交/审核、场地预约/审核已接入模板推送,失败不阻塞主业务。
6. **团课爬虫**:站点通过 `gxmu.thematic-course.sites` 配置(`名称|列表页|条目选择器|标题选择器|链接选择器`,多个站点换行分隔),未配置时只支持手工维护。
### 新增配置项
```yaml
gxmu:
thematic-course:
max-per-site: 50 # 单站点单轮抓取上限
sites: | # 可选,格式见上
```
## 四、前端改动清单
### 新增目录
```
src/api/growth/{credit,volunteer,home,club,qmgc,thematic}/
src/api/system/notice/
src/components/MultiFileUpload/ 统一多附件上传(JSON 数组与后端契约)
src/views/growth/{myHome,credit,practiceCredit,creditWarning,portrait,recommend}/
src/views/club/{volunteerHour,volunteerReview,volunteerCertificate,club,activity,activityReview,venue,venueReview,annualReview,award}/
src/views/gxmu/{qmgcStudent,qmgcTask,qmgcScore,thematicCourse,teachingDesign,themeDayDesign,expertReview,expertPool}/
src/views/system/{messageTemplate,messageLog}/
```
### 复用而非重写
- 列表页统一 `ele-pro-table` + `.page/.ele-body/a-card`,与 `studentRoster` 一致。
- 导入复用 `views/gxmu/components/common-import.vue`(模板下载、逐行校验、失败清单)。
- 附件展示复用 `attachmentList`,审核状态复用 `reviewStatusTag`。
- 权限判断用 `hasPermission` / `hasAnyRole`(注意 `hasRole` 是「全部满足」语义)。
## 五、验证记录
### 已执行
| 项 | 结果 |
| --- | --- |
| 后端 `mvn -o compile`(JDK 17) | 通过 |
| 前端 `vite build` | 通过 |
| 前端 `vue-tsc`(仅新增文件) | 0 错误 |
| 9 个 SQL 脚本在开发库执行 | 全部成功 |
| 18 个 GET 接口 | 全部 200 / code=0 |
| 社团 → 成员 → 详情(人数联动) | 通过 |
| 场地 → 预约 → 冲突拒绝 → 当日排期 | 通过 |
| 志愿录入(缺附件拒绝)→ 审核 → 汇总 4 小时 | 通过 |
| 学分导入 → 重复导入跳过 → 汇总 0.5/9.0 | 通过 |
| 成长主页聚合(真实学院/班级/团支书数据) | 通过 |
| 志愿审核触发站内消息落库 | 通过 |
### 验证后已清理
测试期间创建的社团、成员、场地、预约、志愿记录、学分记录、站内消息已全部删除,保留毕业要求与消息模板等基础配置。
## 六、上线前必做
1. **执行 9 个 SQL 脚本**(生产库先备份,尤其脚本 07 改动审核表)。
2. **给角色授权**:脚本 08 只授了 `superAdmin` 和 `student`,其余角色(校团委、二级学院团委、指导老师)需在「角色管理」里按需勾选二期菜单。
3. **配置团课抓取站点**(如需自动同步),否则该页只能手工录入。
4. **确认 Redis 可用**:消息与登录依赖 Redis,本地验证时需先启动。
5. **学生账号**:需要 `gxmu_student.user_id` 已认领,成长主页才能带出学籍信息;否则只显示账号基础信息。
## 七、已知边界
| 项 | 说明 |
| --- | --- |
| 志愿证明 PDF | 当前只做数据汇总与提示,模板渲染与电子章在 P1 接入(需学校提供模板与印章图) |
| OCR 公章识别 | 字段已预留(`ocr_status` / `ocr_org` / `ocr_raw`),未接 OCR 服务;活动审批暂由人工核验材料 |
| 审核流并行 | **已实现**,见下方「补充交付」 |
| 学分自动获取 | 第二课堂/桂志愿暂无 API,先走 Excel 导入,`source` + `external_id` 已为后续对接预留 |
| 移动端 | 本期只交付 PC,接口均支持 `/api/open` 对外复用,移动端工程后续独立建设 |
## 八、补充交付(P0 收尾追加)
### 8.1 审核流并行审批
青马选拔要求「二级学院团委 + 二级学院党委」同时审核,原审核引擎用 `sortNumber + 1` 推进,隐含「一个节点一个人」。
**改动:**
| 文件 | 改动 |
| --- | --- |
| `entity/ReviewFlow` | 新增 `parallelGroup` |
| `entity/ReviewList` | 新增 `parallelGroup`、`attachments` |
| `entity/ReviewFlowConfig` | 新增 `rejectMode` |
| `service/ReviewProgressService`(新增) | 按并行组推进、幂等创建、组内待审判断 |
| `service/impl/ReviewProgressServiceImpl`(新增) | 上述实现 |
| `service/impl/ReviewFlowStarterServiceImpl` | 发起时创建首个并行组的**全部**节点(原来只建一个) |
| `controller/ReviewListController` | 用 `progressService.advance()` 替换原串行推进 |
| `service/impl/DeclareGuardServiceImpl` | `syncReviewStatus` 改为「任一组驳回过即驳回」,不再只看最后一条 |
**语义:**
- `parallelGroup` 相同的节点属同一组,**全部通过**才进入下一组;
- 组内任一驳回按 `reject_mode` 处理,默认 `any`(任一驳回即终止);
- `parallelGroup` 为空的存量数据退化为「sortNumber 即组号」,**保持原串行行为**;
- 推进逻辑幂等:重复点击通过不会重复创建下一组节点。
**验证结果(开发库实测):**
| 场景 | 结果 |
| --- | --- |
| 同组两人,第一人通过 | 不创建下一组(仍有待审)✓ |
| 第二人通过 | 创建第 2 组节点 ✓ |
| 第 2 组驳回 | 不再创建新节点,流程终止 ✓ |
| 末组驳回后申报记录状态 | 回写为「驳回」(原逻辑会误判为通过)✓ |
### 8.2 修复:9 张申报表缺 `declare_id` 列
开发库未执行过 `sql/20261102_add_declare_uniqueness.sql`,导致青马申报页报 `Unknown column 'declare_id'`。已执行该迁移,9 张申报表接口全部恢复 200。
**上线提醒**:若生产库也未执行此迁移,请一并补上,否则青马工程、五四评优等申报页会直接报错。
### 8.3 修复:个人资料保存 403
`views/user/profile` 原来调用 `PUT /system/user`(需 `sys:user:update`),学生账号没有该权限会保存失败。已改为后端已有的 `PUT /system/user/self`,并新增前端封装 `updateSelfUser`。
### 8.4 统一最新审核记录排序
`reviewStatusTag` 原按 `id` 取最新,`utils/review.ts` 按 `createTime + id`,并行审核下两处可能给出矛盾结论。现已抽出 `getLatestReview()` 统一为「sortNumber → createTime → id」,两个调用方共用。
## 九、P1 首批交付:社会实践学分 + 学分预警
### 9.1 社会实践学分
**后端**(`com.gxwebsoft.gxmu.credit`)
| 文件 | 说明 |
| --- | --- |
| `PracticeCreditController` | 提交/撤回/作废/删除/汇总/导出 |
| `PracticeCreditService` + `Impl` | 校验、状态回写、**通过后自动发放学分** |
| `PracticeCreditApplyParam` | 查询参数 |
**审核接入**:模块名 `gxmu_practice_credit`,走既有 `gxmu_review_list` 引擎,四级审核(个人 → 班级 → 二级学院团委 → 校团委)在「审核流配置」里绑定即可。`ReviewListController` 审核完成后回调:
1. `syncReviewStatus` 回写申报状态;
2. `grantCreditWhenApproved` 在通过时写一条 `gxmu_credit_record`(`source=practice`、`external_id=practice-{id}`,幂等)。
**教务处模板导出**:`POST /api/gxmu/practice-credit/export`,输出 14 列 xls(序号/学号/姓名/学院/班级/学年/活动名称/活动类型/起止日期/实践内容/担任角色/核定学分/审核状态),走既有「生成文件 → FileRecord 入库 → 返回下载地址」模式。
**前端**:`/growth/practiceCredit`,替换原占位页。
### 9.2 学分预警
| 文件 | 说明 |
| --- | --- |
| `CreditWarningService` + `Impl` | 缺口计算、级别判定、月度去重推送 |
| `CreditWarningController` | 列表/汇总/重算/推送 |
| `CreditWarningTask`(新增) | 每天 02:30 全量重算,`gxmu.credit-warning.enabled` 可关 |
**级别规则**:`gap/required ≥ 0.6 且 gap ≥ 2` → danger;`≥ 0.3` → warn;其余 normal。
叠加绝对门槛是因为只看比例会把「应修 1 分、缺 1 分」也判成严重。
**去重**:同一学生同一类别每个自然月只推一条站内消息(`notify_month` 记录)。
**闭环**:补分后重算会自动清理已达成类别的预警(逻辑删除),不会永远挂着。
**前端**:`/growth/creditWarning`,替换原占位页。
### 9.3 验证记录
| 场景 | 结果 |
| --- | --- |
| 社会实践缺附件提交 | 拒绝「请上传社会实践证明材料」✓ |
| 正常提交 → 生成审核节点 | ✓ |
| 审核通过 → 申报状态置 2 | ✓ |
| 审核通过 → 学分自动入账(2.00,source=practice) | ✓ |
| 重复审核同一条 → 学分不重复发 | ✓(仍 1 条 2.00) |
| 学分类别缺口识别 | 6 类全部识别 ✓ |
| 补分后重算 → 该类别预警清除 | ✓(有效预警 6→5) |
| 级别判定 | 缺 2 分 danger、缺 1 分 warn ✓ |
| 预警推送 → 第一次 5 条、第二次 0 条 | 月度去重生效 ✓ |
### 9.4 修复记录
初版曾把 `gxmu_practice_credit` 加进 `DeclareGuardServiceImpl` 的模块列表,导致审核回写时按「模块名=表名」去更新不存在的表。已移除——社会实践用独立表 `gxmu_practice_credit_apply`,状态回写由自己的 service 负责。
## 十、部署排查:部署后看不到新功能
### 症状
前端已是最新版本,侧栏没有「成长中心」「社团与志愿」;访问新接口报错:
```
Table 'tuanwei.gxmu_credit_record' doesn't exist
```
### 原因
三个部分必须同时到位,缺一项都会表现为「没有新功能」:
| 部分 | 状态判断 | 缺失表现 |
| --- | --- | --- |
| 前端 dist | 已确认包含新页面(`index.0475cb90.js` 内有 21 个新路由) | 页面 404 |
| 后端 jar | 已确认新接口可达(返回业务错误而非 404) | 接口 404 |
| **数据库** | **未执行二期 SQL** | **接口报表不存在、侧栏无菜单** |
菜单是**动态从 `sys_menu` 读取的**(`GET /auth/user` → `formatMenus` → `addRoute`),
库里没有菜单记录,前端代码再全也不会显示。
### 部署步骤
在服务器的 `tuanwei-java` 目录执行:
```bash
# 方式一:一键脚本(自动备份 + 执行 9 个脚本 + 验证)
sh sql/deploy_phase2.sh ./sql
# 方式二:合并脚本(单文件,等价于依次执行 01~09)
docker exec -i 1Panel-mariadb-ZOpD \
mysql -utuanwei -p'TytQZsSW5m4wGh38' tuanwei < sql/phase2_all_in_one.sql
# 方式三:逐个执行(便于定位问题)
for f in sql/20261105_phase2_0*.sql; do
docker exec -i 1Panel-mariadb-ZOpD \
mysql -utuanwei -p'TytQZsSW5m4wGh38' tuanwei < "$f"
done
```
执行完重启后端(Docker 里通常已配置 `restart: unless-stopped`,只需
`docker restart tuanwei-api`),然后浏览器强制刷新并**重新登录**。
### 脚本安全性
- 全部可重复执行,实测连续执行两遍:146 条语句零报错,菜单/授权/模板/字典数量完全不变
- `07` 会 ALTER 三张审核表,**只新增列并回填**存量数据,不改原有字段
- 脚本建议先备份;`deploy_phase2.sh` 已内置自动备份并校验备份非空
### 执行后的验证
```sql
SELECT COUNT(*) FROM sys_menu WHERE menu_id BETWEEN 14500 AND 14663; -- 应为 62
SELECT COUNT(*) FROM oa_notice_template; -- 应为 10
SELECT COUNT(*) FROM gxmu_graduate_requirement; -- 应为 6
```
接口验证:
```bash
curl -H "Authorization: <token>" https://twai-api.gxmu.edu.cn/api/gxmu/credit/page
# 应返回 {"code":0,...} 而不是 Table doesn't exist
```
### 若菜单仍未出现
脚本 08 只给 `superAdmin` 和 `student` 授了权。若你当前账号是其他角色
(校团委、二级学院团委、指导老师等),需在「系统设置 → 角色管理」里
手动勾选二期菜单。
## 十一、导入报错 ERROR 1273 Unknown collation
### 症状
导入 SQL 时在第 271 行报:
```
ERROR 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'
```
出错语句是 `CREATE TABLE gxmu_certificate_template`,结尾带
`COLLATE=utf8mb4_0900_ai_ci`。
### 原因
**导错了文件**:这条语句来自从**开发库导出的整库 dump**,不是二期的迁移脚本。
- 开发库是 **MySQL 8.0.44**,建表时 `DEFAULT CHARSET=utf8mb4` 会隐式采用
MySQL 8 专有的 `utf8mb4_0900_ai_ci`
- 生产是 **MariaDB**,不识别这个 collation,直接报错
经核查,`sql/20261105_phase2_*.sql` 里 `COLLATE=` 出现 **0 次**,
不是迁移脚本的问题。mysqldump 导出时会把隐式默认值显式写出来,于是把
MySQL 8 的特性带进了 dump。
### 解决方案
**方案 A(推荐):用迁移脚本,不要导整库 dump**
```bash
docker exec -i 1Panel-mariadb-ZOpD \
mysql -utuanwei -p'密码' tuanwei < sql/phase2_all_in_one.sql
```
迁移脚本的建表语句现在已显式锁定 `COLLATE=utf8mb4_general_ci`
(30 处建表全部处理),在 MySQL 8 / MariaDB 上都能安全执行。
**方案 B:确实要导整库 dump 时,先做兼容转换**
```bash
python3 sql/fix_mysql8_dump_for_mariadb.py dev_dump.sql
# 生成 dev_dump.mariadb.sql,再导入
docker exec -i 1Panel-mariadb-ZOpD \
mysql -utuanwei -p'密码' tuanwei < dev_dump.mariadb.sql
```
转换器会处理:
| 转换项 | 说明 |
| --- | --- |
| `utf8mb4_0900_*` → `utf8mb4_general_ci` / `utf8mb4_bin` | 核心问题 |
| `utf8mb3_*` → `utf8mb4_*` | MySQL 8 的新叫法,MariaDB 不认 |
| `CHARACTER SET utf8mb3` / `CHARSET=utf8mb3` | 裸写声明 |
| 删除 `SET @@GLOBAL.GTID_PURGED` 等 | MariaDB 不支持或会报错 |
| 处理 `/*!80000 ... */` 版本条件注释 | 仅 MySQL 8 执行的内容 |
### 当前开发库情况
开发库 88 张表中有 **61 张**是 `utf8mb4_0900_ai_ci`(`sys_user`、`gxmu_club`
等),如果整库同步到生产,这 61 张都会失败。**建议只同步二期新增的数据,
或先跑转换器**。
### 防呆
`deploy_phase2.sh` 新增了预检:执行前扫描脚本中是否含
`utf8mb4_0900_ai_ci`,命中则直接中止并提示「不要导入 MySQL 8 的整库 dump」。