Files
aca-syllabu-service/重构任务计划.md
T
2026-10-14 16:15:16 +08:00

176 lines
16 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.
# DormNotice 重构任务计划(家校活动发布模块)
> 创建日期:2026-09-18
> **执行状态(2026-09-18):阶段一~四已完成,后端 `mvn compile` BUILD SUCCESS,前端 `npm run build` 构建成功。**
> 阶段五(双账号联调)需 CAS/用户中心与班级服务在线环境,待联调。
> 后端项目:`E:\YHXT\java\DormNotice`(当前根包 `com.yunhai.notice`,通知公告模块)
> 参考项目:`E:\YHXT\java\UserclassService`(用户/班级服务,UserContext 出处)
> 前端项目:`E:\YHXT\view\FamilyActivity`(Vue3 + alova + view-ui-plus,当前跑在 mock 上)
> 目标:将通知公告后端重构为 **家校活动发布(FamilyActivity)后端服务**,包名 `com.yunhai.dormnotice`,前后端真实联调。
---
## 一、现状调研摘要
### 1.1 DormNotice 后端现状
- Spring Boot 2.7.18 + Java 8 + JPA/Hibernate + MySQL,无 Redis / MyBatis / Feign / 测试代码。
- groupId `com.yunhai`,artifactId `notice`,服务名 `notice`,本地端口 **8105**,库 `dorm_notice`,生产 context-path `/notice`(`docker/application-prod.yml`)。
- 平台依赖:`yh-spring-boot-starter`(CAS SSO 认证、统一响应 `{code,msg,data}`、用户中心静态门面 `YschBaseOpenApi`)。
- 现有 10 个实体 + BaseEntity:Notice、NoticeRead、NoticeReceiver、NoticeTemplate、TemplateType、ContactsGroup、ContactsGroupUser、SysDict、UpFile、SmsModule、SmsSchoolConfig。
- **当前不存在任何日志表/日志实体**(grep `SysLog|OperLog` 零命中)。
- 认证:`UserContextHolder.get()`(平台包)+ `@Autowired CurrentBoaUser`,无自研 UserContext。
### 1.2 UserclassService 参考要点
- `UserContext`(`com.yunhai.bschclassmanage.utils.UserContext`):ThreadLocal 静态持有器,字段 userId / userName / roleId / schId / ip / type(1 超管、2 校管、3 普通用户),含 `UserInfo` 快照与 `wrap()` 异步包装。
- 配套组件(需一并复制):
- `aspect/ControllerUserContextAspect.java`——核心填充逻辑,切所有 controller,取用户优先级链:方法参数 `BoaUser` → `BoaUserContext.get()` → request attribute;从 header/query `currentrole`(URL 编码 JSON:`{childId, roleId, schId, userId}`)解析 `UserSysRole`;超管强制 `schId=0`;结束后 `clear()`。
- `model/tool/UserSysRole.java`、`interceptor/UserContextInterceptor.java`、`config/DWebMvcConfig.java`、`enums/UserType.java` / `UserRole.java`(1 教师、2 校领导、3 学生、4 家长…)。
- 日志参考实现:`entity/SysOperationLog.java`(表 `sys_operation_log`)+ `SysOperationLogAspect` / `SysOperationLogController`,字段含 log_level、operation_type、user_id、user_name、sch_id、request_ip/uri/method、class_method、request_params、response_data(GZIP)、status_code、cost_time、error_msg 等。
- 教师/学生业务 ID:`sch_teacher.tea_id`、`sch_student.stu_id`(用户中心业务 id,区别于本服务自增 id)——**新实体存这类 id,不存 account 账号**。
- 服务间调用:HTTP + `appkey/sign/t` 签名(`/oapi`),班级服务地址 `http://base.yunhaixingtong.com/userclass`。
### 1.3 FamilyActivity 前端现状
- Vue 3.4 + vite(端口 8078)+ alova(fetch,`credentials: include`,无 Bearer token,CAS 会话模式;业务码 302 跳 SSO);请求头注入 `yschapirequest/timeout/yschreferer`,localStorage `currentRole` 非空时注入 `currentRole` header。
- **dev 代理 `/api` → `http://localhost:8104`,与后端实际端口 8105 不一致(联调前必须修)**。
- 页面:教师端 ActivityList(我发布的/全部)、NewActivity、ActivityDetails、Signups(报名情况)、ActivityStats、ActivitySetting(字典 + 自定义组);家长端 ParentActivities(活动广场 / 我的报名,路由前缀 `/par`,靠 `SetRole` 切换)。
- 接口蓝本 `src/api/module/activity.ts`(约 40 个接口),类型契约 `src/types/activity.ts`(569 行),mock `src/assets/mock/activityapi.ts`(1552 行,实现了全部契约,可作为后端行为/种子数据规格)。
- **postForm 契约**:POST 参数只拼 URL query、不带 body(Spring `@RequestParam` 会把 query+body 重复参数合并成 `name=a,a`,这正是"字典文字重复"的根源之一,代码注释已记录)。
- 账号 lijing / kegemo 在前端与后端代码中均未出现,应为基础平台(CAS/用户中心)侧账号。
---
## 二、任务分解
### 任务 1:框架改名 `com.yunhai.notice` → `com.yunhai.dormnotice`
| # | 改动点 | 说明 |
|---|---|---|
| 1.1 | 源码目录 | `src/main/java/com/yunhai/notice/` → `com/yunhai/dormnotice/`,全部 `.java` 的 package / import 声明同步替换 |
| 1.2 | 主类 | `NoticeApplication` → `DormNoticeApplication`,`scanBasePackages = "com.yunhai.dormnotice"` |
| 1.3 | pom.xml | artifactId `notice` → `dormnotice`;name/description/finalName 同步改 |
| 1.4 | bootstrap.yml | `spring.application.name: notice` → `dormnotice`;Nacos dataId `notice.yaml` → `dormnotice.yaml`(Nacos 侧需同步建配置,本地开发不受影响) |
| 1.5 | docker/application-prod.yml | context-path `/notice` → `/dormnotice`;对外网关地址同步 |
| 1.6 | logback-spring.xml | 应用名/日志文件名中的 `notice` 字样同步改 |
| 1.7 | README / CI | Gitea 推送说明、`.idea` 库引用等辅助文件按需更新 |
验收:`mvn compile` 通过,全局 grep `com.yunhai.notice` 零残留,本地启动正常。
### 任务 2:删除旧实体,保留字典表,新建日志表
**删除**(9 个实体 + 对应 Repository/Service/Controller/model/enums/sql):
- 实体:Notice、NoticeRead、NoticeReceiver、NoticeTemplate、TemplateType、ContactsGroup、ContactsGroupUser、SmsModule、SmsSchoolConfig
- 连带清理:NoticeController、ContactsController(旧实现)、SmsController、TemplateController、INoticeService 等服务、model/notice 下 11 个 VO、enums 两个、`resources/sql/notice_*.sql` 三个脚本
- **UpFile 实体删除后按前端契约重建**(活动附件需要,见任务 3)
**保留**:
- `SysDict`(`sys_dict` 字典表)——字段微调见任务 3
- 基建:BaseEntity、审计 Listener(改名为通用名,如 `BaseAuditListener`)、BasePageQuery/BasePageResult、PageUtils、ValidationGroups、JacksonConfig、SwaggerConfig、Config(路径常量)
- FileController/FileService(上传下载,供附件复用,改造绑定逻辑)
**新建日志表**(DormNotice 当前没有,参考 UserclassService 移植):
- 实体 `SysOperationLog` → 表 `sys_operation_log`(字段照搬参考项目)
- `SysOperationLogAspect`(AOP 记录 + 异步落库 + 失败重试字段)、`LogController`(日志查询接口,可选)
- 移植时把包名改成 `com.yunhai.dormnotice.*`,依赖 UserContext(任务 4)
### 任务 3:参考前端创建新实体与业务(不存账号,只存教师/学生 id)
设计原则(对应需求 3):
- **实体一律不保存教师/学生/家长账号(account)**;发布人存 `publisher_id`(教师 id),报名存 `student_id`(学生 id),联系组成员存 `user_id`。
- 展示用姓名(publisher_name、student_name、class_name)允许冗余存储,避免列表页频繁调用户中心。
- 多租户:所有业务表带 `sch_id`,写入取 UserContext/BoaUser 当前角色 schId,查询强制过滤。
新实体(字段以 `src/types/activity.ts` + mock `activityapi.ts` 为最终契约,实现时逐一核对):
| 实体 | 表名 | 关键字段 |
|---|---|---|
| Activity 活动 | `activity` | id、sch_id、title、category(存分类字典值)、dept_id/dept_name(主办部门)、publisher_id(教师id)、publisher_name、venue、content(LONGTEXT 富文本)、start_time、end_time、signup_deadline、receipt_mode(none/signup/confirm)、quota(0 不限)、allow_accompany、cost、receive_list("ALL" 或班级 id 逗号串)、topmost、is_pulled、sms_flag、timing_send_time、act_status(draft/signup/closed/finished)+ 审计字段 |
| ActivityRead 已读 | `activity_read` | activity_id、class_id、student_id、read_time;家长打开详情按班级粒度标读(mock 为 classRead 班级粒度,实现时决定班级/学生粒度并与前端 ReadDetail 展示对齐) |
| Signup 报名回执 | `activity_signup` | activity_id、student_id、student_name、class_id、class_name、guardian_name、guardian_phone、join_count、status(signed/confirmed/canceled)、remark、signed_at;`(activity_id, student_id)` 唯一 |
| UpFile 附件(重建) | `up_file` | topic_type("activity")、topic_id、file_true_name、file_name、file_size、file_type |
| ContactGroup 自定义组 | `contact_group` | sch_id、name、create 审计字段 |
| ContactGroupUser 组成员 | `contact_group_user` | group_id、user_id(教师用户 id,**非账号**);`(group_id, user_id)` 唯一 |
| SysDict(保留微调) | `sys_dict` | 现有 dict_type/dict_key/dict_value/sort_order/enable_flag 结构够用;dict_type 取值约定 `dept` / `category` |
派生数据不落库:readCount/allowCount/progress/统计页全部实时聚合(按接收班级 studentCount 累加),与 mock 行为一致。
### 任务 4:移植 UserContext 用户上下文
从 UserclassService 复制并改包名为 `com.yunhai.dormnotice.*`:
1. `utils/UserContext.java`(ThreadLocal 持有器 + UserInfo 快照 + wrap 异步包装 + clear)
2. `aspect/ControllerUserContextAspect.java`(核心:BoaUser 参数 → BoaUserContext → request attribute 三级回退;`currentrole` header 解析 UserSysRole;超管 schId=0;IP 取 X-Forwarded-For 链)
3. `model/UserSysRole.java`、`enums/UserType.java` / `UserRole.java`
4. `config/WebMvcConfig`(如采用拦截器方案则一并移植 `UserContextInterceptor`;二选一,以 Aspect 为主)
5. 所有业务代码取当前用户统一走 `UserContext`;日志切面(任务 2)同样依赖它
外部依赖仅 `yh-auth-spring-boot-starter`(BoaUser/BoaUserContext,DormNotice 已具备)+ lombok,无需新增依赖。
### 任务 5:按前端契约实现后端接口
Controller 全部沿用 `/{platform}` 前缀(api/manage/mobiapi),方法名 PascalCase 对齐前端,写操作 POST;POST form 类接口参数只从 query 接收(`@RequestParam`)。
| Controller | 接口(前端已定义,路径以 activity.ts 为准) |
|---|---|
| BaseController | GetUserInfo、GetUserMod、GetSchoolAll、**GetClassTree(新增:调 userclass `/oapi` 取年级→班级树+studentCount)**、**SetRole(新增:教师/家长角色切换)**、FileUpload、Logout |
| ActivityController | List、InitializeRed、SendList、Detail、GetDetail、GetNextPrev、ReadDetail、ReceiveClasses、Create、Edit、Delete、UserPull、SetTopmost、Expedite、Signups、SignupDetail、Review、Remind、ExportSignups(CSV+BOM)、MyActivities、MySignup、Signup、CancelSignup、AllFileDown(zip)、Statis |
| DictController | List(**兼容 type=1/2 数字与 dept/category 字符串两种入参**)、ManageList、Add、Update、Delete、Sort、**Sync(新增:getSchModuleIsSms)** |
| FileController | Upload、Down、Preview、Delete、Bind、List(保留改造) |
| ContactsController | GetGroup、AddGroup、EditGroup、DelGroup、GetContactsTea、SetContactsTea、GetGroupTree(按新实体重写) |
实现要点:
- 统一响应 `{code,msg,data}` 由平台 ApiResponseAdvice 包装,Controller 返回裸对象。
- 分页返回对齐 `BasePageResult`:total/content/pageIndex/pageSize/totalPages/hasNext。
- 名额校验:Signup 时 quota>0 且按 join_count 合计已满则拒绝(mock 已有此行为,保持一致)。
- 家长端数据按 currentrole 的 childId/studentId 定位班级,只看本班。
### 任务 6:前端修改(删 mock + 字典重复 bug)
1. **删除 mock**:
- 删 `src/assets/mock/activityapi.ts`、`index.ts`,移除 `vite.config.ts` 的 `viteMockServe` 与依赖 `vite-plugin-mock`/`mockjs`,删 `.env.mock`、`dev:mock` 脚本及 `VITE_IS_MOCK` 引用(host.config.ts / Basics.d.ts)。
- mock 内容转做**后端种子数据/行为规格**:字典种子(dept 8 项、category 9 项)写入后端初始化 SQL;接口行为(标读、名额、CSV BOM、actStatus 流转)以 mock 实现为准。
2. **修复字典提交文字重复 bug**(多层防御):
- 前端 `DictCardManager.vue` modalSave:加 submitting 标志 + 提交按钮 `:loading`,防双击重复提交(当前无守卫,是同名卡片重复的直接原因)。
- 前端 `TypeSetBox.vue` 内联改名:`on-blur`/`on-enter` 双触发防护从"500ms 时间窗"改为"提交后直到下次进入编辑才重置"。
- 后端 `DictController.Add/Update`:按 `(sch_id, dict_type, dict_value)` 查重,重复直接报业务错,兜底 query+body 双发导致的 `name=x,x`。
- 回归确认 `postForm` 只拼 query 不带 body 的约定不被破坏。
- 顺带修 `ActivitySetting.vue` 的 groupModalSave 防连点。
3. **修 proxy 端口**:`vite.config.ts` 代理目标 `http://localhost:8104` → `http://localhost:8105`(新后端端口)。
### 任务 7:联调测试与优化(账号 lijing / a123123、kegemo / csxs1)
两个账号在前后端代码中均不存在,属基础平台(CAS SSO / 用户中心)账号;联调时先确认其角色(预期一个教师端、一个家长端)与所属学校 schId。
测试清单:
- 登录链路:CAS 跳转 → 会话建立 → GetUserInfo/GetUserMod 菜单加载 → currentRole header 传递 → UserContext 填充正确(userId/schId/roleId)。
- 教师端全流程:发布活动(立即/草稿/定时)→ 我发布的列表 → 编辑/复用/置顶/撤回/删除 → 报名情况(筛选/代确认/代取消/催报名/导出 CSV)→ 阅读情况 → 统计页 → 设置页(字典 CRUD、排序、启停用、自定义组及成员)。
- 家长端全流程(/par):活动广场分桶 → 打开详情标读 → 报名/改报名/取消 → confirm 型自动确认 → 名额满置灰 → 我的报名。
- 通用:附件上传/下载/打包下载、多租户隔离(换学校账号验证)、日志切面落库、字典文字重复 bug 回归。
已知问题/待优化(联调时逐项核实):
- proxy 端口不一致(任务 6.3)。
- dict type 数字/字符串双契约兼容(任务 5)。
- GetClassTree / SetRole / Dict Sync 三个后端缺失接口(任务 5)。
- ExportSignups CSV 需带 BOM(Excel 中文乱码);AllFileDown 空附件时的表现。
- 定时发布 timingSendTime 到点前家长端不可见——是否需要调度器或查询时过滤(倾向查询时过滤,简单可靠)。
- smsFlag 短信标记:DefaultSmsSender 是空占位,保持占位并打日志即可。
---
## 三、实施顺序
| 阶段 | 内容 | 产出/验收 |
|---|---|---|
| 一 | 任务 1 改名 + 任务 2 删实体、移植日志表 | `mvn compile` 通过,启动无旧实体报错,sys_dict/sys_operation_log 正常 |
| 二 | 任务 4 UserContext 移植 | 任意接口可打印 UserContext 六字段,请求结束 clear |
| 三 | 任务 3 新实体 + 任务 5 接口实现 | 全部 40 个接口按 activity.ts 契约可用(Postman/Swagger 自测) |
| 四 | 任务 6 前端删 mock、修 bug、修 proxy | `npm run dev` 走真实后端,字典新增不再重复 |
| 五 | 任务 7 双账号联调、查 bug、优化 | 测试清单全过,问题记录并修复 |
## 四、风险与待确认
1. **日志表**:DormNotice 现无日志实体,按 UserclassService 的 `SysOperationLog` 全套移植(含 AOP/异步落库),若只需表+基础切面可裁剪重试字段。
2. **测试账号**:lijing / kegemo 的角色、学校、密钥需在基础平台确认;若为本地开发库账号,需在用户中心造数。
3. **Nacos 配置**:服务改名后 dataId 变更,测试/生产环境需运维同步(本地默认关闭 Nacos 不受影响)。
4. **旧数据**:`dorm_notice` 库中 notice 等旧表是否保留、清理或迁移,需确认(计划:保留不删,仅代码层不再引用)。
5. **已读粒度**:mock 按班级标读(classRead),若产品要求学生粒度,`activity_read` 表结构需调整并同步前端 ReadDetail 展示。
6. **userclass 依赖**:GetClassTree 需调班级服务 `/oapi`(appkey 签名),确认 DormNotice 的 app-key 有权访问该接口。