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

16 KiB
Raw Permalink Blame History

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 有权访问该接口。