14 KiB
Handoff: a7-parity 执行票 07→12 串行实施(2026-09-07)
注:OS 临时目录不可写(沙箱限制),按 .scratch 临时区惯例落盘于此。
会话状态
- effort:
.scratch/a7-parity/(蓝湖 A7 后台管理原型-代码对齐) - Map 决策阶段已收官:票 01-06 全 resolved,拍板存档见
issues/06-gap-verdict.md+gap-report.md - 用户指令:串行执行执行票 07→12(每票 claim→实现→resolve;最后票 12 收官验收)
- 票 07 已 claim、侦察完毕、设计定案、尚未写一行代码——本文档核心就是把侦察成果完整传递
- Todo:t07 IN_PROGRESS;t08-t12 PENDING
不重复的既有素材(直接引用,勿重读)
| 素材 | 路径 |
|---|---|
| 票面 07-12 | .scratch/a7-parity/issues/07-a71-a72-light-endpoints.md ~ 12-bruno-regen-baseline.md |
| 四张盘点矩阵 | 同目录 matrix-a7-1-a7-2.md / matrix-a7-3-upper.md / matrix-a7-3-lower.md / matrix-a7-4-a7-5.md |
| 缺口总报告 | gap-report.md(缺接口 9 组/语义差异 6/tag 12 处) |
| Map(含全部拍板行) | map.md |
| 原型文本库 | .scratch/opportunity-bugfix/lanhu-pages/A7-*.md(34 文件) |
| 票 08 素材 | matrix-a7-3-upper §推送规则;参照实现=crm-rule 的 OpportunitySchemeCardTemplate*(版本化 8 端点同构) |
| 票 09 素材 | matrix-a7-3-lower §查重;原型 A7-3-2-4-1_查重规则(详情页).md(602 行 4 步骤向导) |
| 票 10 素材 | 原型 A7-3-4_项目规则.md(264 行 5 区块);LeadPool 现状=crm-rule LeadPoolController + PoolMemberStore |
| 票 11 素材 | tag 8 处清单在票面;种子参照 crm-dict/.../config/DictPermissionInitializer.java(permission-seeder seam 先例) |
| AGENTS.md 纪律 | BOM 剥离脚本 / 孤立单大写字母 @Column 钉名 / ADR-0017 表单绑定 |
全局拍板(票 06 已定,勿再问用户)
① P0 全建,重接口(同步任务 5 端点、全局日志 5 端点+AOP)记账不建;② 新规则 Controller 全进 crm-rule;③ 日志形态 A;④ 数据共享不纳入。次级默认值:资源删除语义按原型改(级联→禁删)、LeadPool 部门成员=显式成员表 DEPT_MEMBER、查重「执行次数」=引用次数、幻影 3 端点保留挂运维 tag。全程不提交 git(后端仓+文档仓 E:\code\crm-api-docs 均留用户审查)。
票 07 侦察结论(本会话独有,重读代价高,务必信任)
代码事实
- crm-auth/pom.xml:依赖 crm-base+crm-file;easyexcel 4.0.3 在 crm-base(compile scope 可传递);StringRedisTemplate 直接注入有先例(SessionStoreImpl/CorpAccessTokenProviderImpl)
- SystemController(156 行):
/api/system下已有 menus/tree、depts/tree|save|delete、users/assign-roles(单用户全量替换,@RequestParam userId+roleIds 逗号串)、users/assign-depts、users/page、users/stats、sync/dingtalk、users/admin-role;底部有splitIds(String)私有工具 - RoleController(80 行):
/api/rolespage/saveOrUpdate/detail/assign-resources/delete;@PreAuthorize 用hasAuthority('crm:role:*');tag「A7 后台管理/权限管理/角色管理」✅ 不用动 - ResourceController(55 行):
/api/resourceslist/saveOrUpdate/delete/icon/upload;类级 @PreAuthorize("hasRole('ADMIN')");4 个端点 tag 均为「A7 后台管理/权限点管理」→ 待补「权限管理」一级 - SysRole 实体:仅 roleName/roleCode/sort/remark/builtin——缺 roleType/status 两列
- SysMenu 实体:status 列已存在(varchar(16) default 'enabled'),但只有 button 类型写值;catalog/menu 存量行 status=null → 一律视为 enabled
- AuthUser 实体:username/mobile/email/avatar/enabled/lastLoginTime/deptId/employmentStatus/title/account——筛选列全齐,无需 DDL
- UserPageParam:deptId/employmentStatus/loginFrom/loginTo(+BaseParam keyword/current/size/orderBy/asc)
- RoleParam:空壳(仅继承 BaseParam)
- UserStatsDTO:@AllArgsConstructor 4 参(deptCount/activeCount/resignedCount/unassignedCount)——加 3 字段后须同步改 UserQueryServiceImpl.stats() 的两处 new 调用(约 144 行空集分支、157 行正常分支)
- UserQueryServiceImpl(216 行):pageUserList 的 scopedUserIds 子树预筛→wrapper→批量拼装部门/角色名模式照抄扩展;stats() 计数模式
- AuthUserServiceImpl(168 行):assignRoles=先删后插全量替换;configureAdminRoles=增量先例(查已有行,仅插缺失/仅删命中)——批量增量照此模式
- SysRoleServiceImpl(192 行):pageRoles/saveRole(builtin 保护+code 唯一)/saveRoleWithScopes/getRoleDetail/assignResources(祖先补全)/deleteRoleCascade(builtin 保护+级联三表)——deleteRoleCascade 需补「在职使用人数=0 才可删」校验(矩阵三#7)
- ResourceServiceImpl(296 行):delete 现状=子节点检查+级联清理 sys_role_menu(103-127 行,要改写);save 校验链完整;applyToEntity 只对 button 写 status
- RoleScopeStore:load(roleId)/save(roleId, moduleScopes)/delete(roleId)——copy 角色复制数据范围档位用它
- IDtOrgSyncService:只有 syncOrg();lastSyncTime 无任何现成存储→定案见下
- AuthConstants 错误码 61xxx:已用至 61018(61009 是 ResourceServiceImpl 私有常量 CODE_RESOURCE_INVALID)→新码从 61019 起
- 权限码:crm:role:list/detail/save/delete/assign 已由 DataInitializer 种子化(ButtonSeed×5)。定案:新角色端点复用现有码(stats→crm:role:list;copy/enable/disable→crm:role:save),不改 DataInitializer
- 跨域 seam 先例:PermissionSeeder 接口在 crm-base.service、各业务模块实现 → UserTransferPort 同位放 crm-base.service
- 三域 owner 字段:Lead.ownerUserId(pool_id/dept_id/owner_user_id 三层)、Customer.ownerUserId(+owner_dept_id)、Opportunity.ownerUserId(+owner_dept_id+owner_name_snapshot);「项目」域=同表 oppStatus=5(已转项目)
- crm-opportunity 已有 E9 移交 transition(CAS,但只允许状态 2/3)→ 定案:port 实现直接批量 update 直改 owner(低频管理动作,覆盖所有状态),域内轨迹不做、依赖 sys_user_transfer_log 全局快照(轻票定位,Answer 记录)
- 业务模块全部依赖 crm-auth → port 实现可注入 AuthUserMapper(拿接收人 deptId,owner_dept_id 跟人走,Opportunity 注释明示「移交跟人走」)与 ISysDeptService
- 导出无先例(全仓无 export 端点);UserListDTO 有嵌套 List 不能直接喂 EasyExcel
设计定案(写码时照此执行)
- 批量分配角色=增量语义:⚠️ 票面写「多用户全量替换」与原型 u296 原文「提交后为 2 名员工增加"销售人员"角色;不会覆盖其已有角色,也不会修改钉钉资料」矛盾——按原型增量实现(对每个 userId 查已有 roleId 集,仅插缺失行;configureAdminRoles 同款),在票 07 Answer 记录此偏差及依据
- UserTransferPort(crm-base 新建):
String domain()/long countOwned(Long userId)/int transferOwned(Long fromUserId, Long toUserId);4 个实现:crm-lead×1(lead)、crm-customer×1(customer)、crm-opportunity×2(opportunity=oppStatus!=5 / project=oppStatus=5) - sys_user_transfer_log 新表(SysUserTransferLog 实体+Mapper):fromUserId/toUserId/domainsJson(各域计数快照)/operatorUserId/transferTime;命名过孤立单大写检查(from_user_id/domains_json ✅)
- UserTransferService(crm-auth 新):注入
List<UserTransferPort>(ObjectProvider 防空);preview(userId)→List(domain/domainName/count);transfer(userId, receiverId)→校验接收人存在在职启用+≠userId→逐 port transferOwned→写 transfer log(事务) - 角色 status/roleType:SysRole 加
roleType varchar(20)(存 code:SYS/MANAGE/BIZ/FUNC,服务层校验 4 值)+status varchar(16) default 'enabled'(对齐 SysMenu 先例);role_type ✅ 无命名坑 - copyRole:复制 roleType/remark/功能权限(sys_role_menu 全套)/数据范围(RoleScopeStore.load→save);不复制人员;生成 status=disabled 停用草稿;roleCode 服务端生成(原码+后缀或时间戳,唯一即可)
- enableRole 校验(原型 u163-u186):≥1 个 menu 类型授权(resourceIds 含 menu 节点)+ 四域(lead/opportunity/customer/project)数据范围档位齐全,否则报错;disableRole 直改 status
- userCount(在职使用人数):批量算——page 出参对当页 roleIds:selectList(sys_user_role in roleIds)→userIds→AuthUserMapper selectList(id in, employment_status='active')→按 role 归组计数;detail 同步填
- RoleStatsDTO:roleCount/enabledCount/disabledCount/zeroUsageCount(active 使用人数=0 的角色数)
- 资源 enable/disable/references:disable 时查 sys_role_menu 中引用该资源且角色 status=enabled 的行>0→禁止停用(61020);references(id)→List(roleId/roleName/roleStatus/activeUserCount);enable/disable 后 apiPermissionCache.invalidate()
- 资源 delete 语义改写(拍板项):删掉级联清理 sys_role_menu 代码;校验链=①无子节点②仅 status=disabled 可删(null 视为 enabled)③有任何 sys_role_menu 引用→禁止删除并提示引用方;发现现网调用方依赖级联语义则停下报票 06 复议(票面原文)
- lastSyncTime:IDtOrgSyncService 加
LocalDateTime getLastSyncTime();impl 注入 StringRedisTemplate,syncOrg() 成功尾部写 keycrm:auth:sync:last-sync-time(ISO 字符串),读时反解析;注释挂账「任务化时迁表」 - stats 三时间戳:UserStatsDTO +lastSyncTime(来自 Redis)/lastTransferTime/lastTransferUser(查 sys_user_transfer_log 最新一条,transferUser=接收人名)
- 导出:新建 UserExportRow 平铺 DTO(@ExcelProperty:姓名/账号/职务/手机号/主部门/兼职部门逗号串/在职状态/角色逗号串/最后登录时间);EasyExcel 写 ByteArrayOutputStream→
FileApi.upload(bytes, "用户列表-yyyyMMddHHmmss.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "system")→返回Result<FileInfoDTO>,前端拿 fileId 走既有下载端点 - UserPageParam 4 筛选落地:username/mobile/title=like;roleId=先 selectList(sys_user_role where role_id in)拿 userIds→wrapper.in(id, userIds)(空集短路返空页,同 scopedUserIds 模式)
- tag 修正(票 07 范围 5 处):ResourceController 4 处→「A7 后台管理/权限管理/权限点管理」;sync/dingtalk→「A7 后台管理/用户管理/同步任务」;幻影 3 项:depts/save、depts/delete 的 tag 从「部门管理」改挂「A7 后台管理/运维」,users/admin-role 已是「运维」不动;depts/tree 票面未列→保留「部门管理」不动
- 新错误码:61019 移交参数非法 / 61020 资源被启用角色引用禁操作 / 61021 角色启用校验不过(enable 校验失败)
待写文件清单(票 07 剩余工作)
新建:crm-base service/UserTransferPort.java;crm-auth domain/entity/SysUserTransferLog.java、mapper/SysUserTransferLogMapper.java、domain/dto/TransferDomainCountDTO.java、domain/dto/UserTransferResultDTO.java(可并入 Service 返回 Map 或简化)、domain/dto/RoleStatsDTO.java、domain/dto/ResourceReferenceDTO.java、domain/dto/UserExportRow.java、service/UserTransferService.java + service/impl/UserTransferServiceImpl.java
修改:UserPageParam(+4 筛选)、UserStatsDTO(+3 字段)、IUserQueryService/UserQueryServiceImpl(筛选落地+stats 时间戳+改两处 new)、IAuthUserService/AuthUserServiceImpl(assignRolesBatch 增量)、SysRole(+roleType/status)、RoleParam(+roleType/status)、RoleDTO(+roleType/status/userCount,fromEntity/toEntity 补映射)、ISysRoleService/SysRoleServiceImpl(roleStats/copyRole/enableRole/disableRole+delete 补人数校验+pageRoles 补筛选+userCount 批量)、IResourceService/ResourceServiceImpl(enable/disable/references+delete 改语义)、IDtOrgSyncService/DtOrgSyncServiceImpl(lastSyncTime)、AuthConstants(+61019/61020/61021)、SystemController(+4 端点+tag)、RoleController(+4 端点)、ResourceController(+3 端点+4 tag)、业务模块 4 个 port 实现(crm-lead×1/crm-customer×1/crm-opportunity×2,放各自 config 或 service 包,@Component 注册)
票 07 验收与收尾
- 全仓 *.java BOM 扫描 0(AGENTS.md 有 PowerShell 剥离脚本);
mvn compile过;单测全绿(若 crm-auth 既有测试受 SysRole 新列/构造签名变化影响需同步修) - SearchReplace 票 07:Status→resolved + Answer(含增量语义偏差记录、级联→禁删已改、记账项);map.md Decisions 追加票 07 行;TodoWrite t07 COMPLETE
- 票 08→12 依次推进;票 12 收官时跑 bruno-sync 全量重生成(文档仓 E:\code\crm-api-docs,bare collection 布局)+对账双向零差+基线验收
环境注意(踩过的坑)
- PowerShell 环境:
python -c内联与脚本执行不可靠(ExitCode 1);文件名清单用Get-ChildItem | Out-File -Encoding utf8;代码检索一律用 Grep/Glob/Read 工具(ripgrep,可靠) - mvn 编译若报
illegal character: '\uFEFF'= 文件带 BOM,按 AGENTS.md 脚本剥离 - 大段 CJK Markdown 编辑走 Write 新文件或小步 SearchReplace,避免超大单块替换
- 每票收工时对照 AGENTS.md「新模块接口接入 checklist」(URL 扁平动词/深 tag/bruno 配置)
Suggested skills
- implement(票 08-12 大块实现时可直接按票面+本文档定案实施,无需再侦察)
- bruno-sync(票 12 收官必用:全量重生成+对账)
- 无需 grilling(拍板已全部完成,遇到票面与原型冲突按原型+Answer 记录偏差处理;若发现现网依赖级联删除语义则停下报票 06 复议)