16 KiB
DormNotice 重构任务计划(家校活动发布模块)
创建日期:2026-09-18 执行状态(2026-09-18):阶段一~四已完成,后端
mvn compileBUILD 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,artifactIdnotice,服务名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/querycurrentrole(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,localStoragecurrentRole非空时注入currentRoleheader。 - 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 行),mocksrc/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.*:
utils/UserContext.java(ThreadLocal 持有器 + UserInfo 快照 + wrap 异步包装 + clear)aspect/ControllerUserContextAspect.java(核心:BoaUser 参数 → BoaUserContext → request attribute 三级回退;currentroleheader 解析 UserSysRole;超管 schId=0;IP 取 X-Forwarded-For 链)model/UserSysRole.java、enums/UserType.java/UserRole.javaconfig/WebMvcConfig(如采用拦截器方案则一并移植UserContextInterceptor;二选一,以 Aspect 为主)- 所有业务代码取当前用户统一走
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)
- 删除 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 实现为准。
- 删
- 修复字典提交文字重复 bug(多层防御):
- 前端
DictCardManager.vuemodalSave:加 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 防连点。
- 前端
- 修 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、优化 | 测试清单全过,问题记录并修复 |
四、风险与待确认
- 日志表:DormNotice 现无日志实体,按 UserclassService 的
SysOperationLog全套移植(含 AOP/异步落库),若只需表+基础切面可裁剪重试字段。 - 测试账号:lijing / kegemo 的角色、学校、密钥需在基础平台确认;若为本地开发库账号,需在用户中心造数。
- Nacos 配置:服务改名后 dataId 变更,测试/生产环境需运维同步(本地默认关闭 Nacos 不受影响)。
- 旧数据:
dorm_notice库中 notice 等旧表是否保留、清理或迁移,需确认(计划:保留不删,仅代码层不再引用)。 - 已读粒度:mock 按班级标读(classRead),若产品要求学生粒度,
activity_read表结构需调整并同步前端 ReadDetail 展示。 - userclass 依赖:GetClassTree 需调班级服务
/oapi(appkey 签名),确认 DormNotice 的 app-key 有权访问该接口。