176 lines
16 KiB
Markdown
176 lines
16 KiB
Markdown
# 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 有权访问该接口。
|