luoweijian
ecc3c49636
feat(opportunity): 票 03 商机主表落地(Opportunity 实体 + 5 态枚举 + Mapper 骨架)
TDD 三切片(红→绿各一轮,19 测试全绿):
- OpportunityStatus:轴 1 归属流转 5 态枚举(1待领取/2推进中/3暂缓中/4已关闭/5已转项目),
implements HasValueEnum<Integer>;只钉值域,迁移逻辑归票 04
- Opportunity:主表实体(票 03 三簇 + Amendment 1),同构 crm-lead/Lead 写法
* 簇 A 业务字段 12 列(含 project_amount decimal(14,2) 选填,Amendment 1)
* 簇 B 归属三层(owner_user_id/owner_dept_id/origin_dept_id)+ 两轴落点
(opp_status/current_stage_id/stage_template_id+版本号冗余)+ 来源快照 4 列
+ 单计时器 claim_time/last_valid_follow_time
* C3 主客户冗余快照 2 列、暂缓附加 4 列落主表(E4 写/E5 清空)
* 乐观锁 @Version version=0(切主客户票 13 / 状态 CAS 票 04 复用,接线归后续票)
* @DataScope(module=opportunity, owner_user_id, owner_dept_id)——module 已在
crm-auth DataInitializer 预注册,不触发 ADR-0006 启动拒绝
* 索引:UNIQUE(source_lead_id) + (owner_user_id,opp_status) + (owner_dept_id,opp_status)
* 不建:pool_id/dept_id/recycle_deadline/expected_deal_date(票 05/10 定稿排除)
- OpportunityMapper:骨架 extends CrmBaseMapper(同构 LeadMapper)
- 模块装配:根 pom modules/dependencyManagement + crm-app 聚合依赖
- CONTEXT.md 待细化小节勾销主表项
边界守住:子表归 07/08/09,customerId 契约归 11,关联客户表归 13,均未触碰。
验证:全量 BOM 扫描 0 命中;全反应堆 clean test 9 模块 SUCCESS。
2 weeks ago
luoweijian
9a9d6a3270
docs(opportunity): 商机域 spec 定稿收尾(grill 补拍 20260821 回填 PRD/map/待确认清单 + OCR 存档)
- PRD V1.0 冻结态补拍:origin_dept_id、E8/E9/E10 迁移细化、切主客户一致性、待发通知表、
子表软删复用键 delete_key 约定等 grill 拍板内容
- 实现期回填两处一致性修正(票 03 code-review Spec 轴发现):
* §2.2 owner_name_snapshot varchar(100)→varchar(64)(对齐票 03 B1「对称 lead」,lead 即 64)
* §2.2 补登 version 乐观锁行(§7.1/§3.4 已引用但字段表漏登)
- 方案卡模板 OCR 原始存档(1787558225260_*.png + ocr-scheme-card/doc_0.md)
2 weeks ago
luoweijian
d768080ed7
commit
2 weeks ago
luoweijian
bb2dad7b58
commit
2 weeks ago
luoweijian
6583863b49
commit
2 weeks ago
luoweijian
3d0699df6f
commit
3 weeks ago
luoweijian
f11b840c3e
commit
3 weeks ago
luoweijian
162ecbf9d2
commit
3 weeks ago
luoweijian
e4b2a320ea
feat: 钉钉组织同步端点 + 管理员角色配置接口——dt-java 部门树/HTTP listbypage 用户幂等同步,dt_dept_id 与 account 作幂等键,负责人反写不映射角色(frontend-integration 票据 03)
3 weeks ago
luoweijian
ea212fa8b6
feat: 字典种子对齐蓝湖原型——lead_source 改渠道 38 项并新增 brand/product/scene(frontend-integration 票据 02)
3 weeks ago
luoweijian
a644f8db13
feat: 行政区划全量种子导入——RegionDataInitializer 幂等导入国标三级 3432 行(frontend-integration 票据 01)
3 weeks ago
luoweijian
56d8abcdc3
feat: 权限码全集下发——/me 增加 permCodes 与 perms→denyBehavior 映射(auth-perms-01)
3 weeks ago
luoweijian
2a67fd9be8
commit
3 weeks ago
luoweijian
1d6c9c6d7a
commit
3 weeks ago
luoweijian
fa476b4766
feat: 单端在线登录(顶号)——每 (用户, 端类型) 至多一个活会话
按 .scratch/single-device-login spec(票 01-07)落地:
- 会话反向索引 session:{userId}:{clientType}->jti(token 键为真相、索引为线索,
反查必验证、脏索引自愈),与 token 键 TTL 对齐并跟随滑动续期
- login/dingtalk 增可选 clientType(默认 pc、大小写不敏感、非法值 61016);
检测到设备在线不签正式 token,返回 61013 + 短命 preLoginToken(带 data 不抛异常)
- 新增 login/dingtalk/confirm:GETDEL 消费 preLoginToken(拿到即烧)-> 踢旧会话
(埋 kicked 标记 + 删旧 token)-> 签新 token 覆写索引;失效统一 61014
- 被顶设备下次请求:filter 埋 ATTR_KICKED_OFFLINE -> commence 输出 401 + 61015
- 登出改 CAS 删索引(Lua 脚本,防误删已被顶号覆写的新索引)
- crm-base Result 增 fail(code, msg, data) 重载;错误码 61013-61016 登记 README
- 存量会话(无 clientType)与老前端(不传 clientType)路径完全兼容;JWT 不变
- 测试:TokenServiceTest 18 / FilterTest 4 / HandlersTest 3 / AuthServiceImplTest +5,
crm-auth 183 全过,全仓 8 模块 BUILD SUCCESS
3 weeks ago
luoweijian
dfd1fc6f01
commit
3 weeks ago
luoweijian
70b53de34c
commit
3 weeks ago
luoweijian
3277f701de
commit
3 weeks ago
luoweijian
b345c4dc7b
commit
3 weeks ago
luoweijian
54aa290c89
commit
4 weeks ago
luoweijian
4a4b31d8e1
commit
4 weeks ago
luoweijian
78aa9748e4
feat: 数据权限按业务模块可配(ADR-0018)
将数据权限从「单一全局档位」改造为「按业务模块分别配置档位」:
- 新增 sys_data_scope_module 注册表 + sys_role_data_scope 角色×模块档位表
- @DataScope 注解新增必填 module 属性,启动时校验 code 在注册表中存在
- DataVisibility 持有 Map<String, DataScopeLevel> 替代单个 level
- DataVisibilityContext.currentScope(moduleCode) 加参数取该模块的 scope
- DataScopeInterceptor 顺序调换:先找表→查注解→取该模块 scope→注入 WHERE
- PermissionResolverImpl 查 sys_role_data_scope 按 module_code 分组取最宽
- 删除 SysRole.dataScope 列 + 删除 DataScopeEnum.CUSTOM(5)
- RoleController saveOrUpdate 用 moduleScopes JSON 替代 dataScope
- 新增 GET /api/data-scope/modules 端点
- saveRoleWithScopes 原子事务(角色保存+范围全量替换同提交同回滚)
- DataScopeModuleController 返回 DTO 不泄露 BaseEntity 审计字段
- 一次性迁移 SQL 脚本(单事务,CROSS JOIN 旧值,DROP 旧列)
- DataInitializer 初始化 4 个内置模块 + ADMIN 每模块 ALL 档位
- 全量测试通过:crm-base 80 tests + crm-auth 153 tests
4 weeks ago
luoweijian
96230913ae
refactor: SystemController IO to DTOs (ADR-0017 ticket 07)
- new DeptDTO (extends BaseDTO, children tree, fromEntity/toEntity)
- new AssignDeptDTO (plain POJO, no key semantics)
- rename UserStatsVO -> UserStatsDTO
- menuTree returns List<ResourceNodeDTO>; PermissionResolver.visibleMenuTree maps entity->DTO before tree build
- deptTree returns List<DeptDTO>; saveDept takes DeptDTO; assignUserDepts takes AssignDeptDTO
- userPage drops @RequestBody -> form binding (aligns with documented form-urlencoded convention)
- sync role-management doc RoleDetailVO -> RoleDTO
- DataScopeIntegrationTest updated for ResourceNodeDTO
4 weeks ago
luoweijian
ef7ad8e368
refactor: RoleController IO to RoleDTO/RoleParam, sink paging into service (ADR-0017 ticket 06)
- rename RoleDetailVO -> RoleDTO extending BaseDTO, add toEntity()
- new RoleParam extends BaseParam; page() now uses PageConverter (size clamp + orderBy anti-injection), fixing pagination bypass
- saveOrUpdate 6 @RequestParam -> RoleDTO; detail returns RoleDTO
- add PageConverterTest covering size clamp + orderBy injection guard
4 weeks ago
luoweijian
f989b566cc
refactor: rename ResourceNode to ResourceNodeDTO extending BaseDTO (ADR-0017 ticket 05)
4 weeks ago
luoweijian
e888aeeb42
chore: mark tickets 02/03/04 as resolved (InitBinder + Dict IO)
4 weeks ago
luoweijian
0b4c4d2bbb
refactor: align dict tests with DTO return types (ADR-0017 tickets 03/04)
4 weeks ago
luoweijian
1a9cf36785
chore: stop tracking build artifacts (target/, .idea/, test-output.txt)
4 weeks ago
luoweijian
a542e48f86
commit
4 weeks ago
luoweijian
b50002f47f
commit
4 weeks ago
luoweijian
80bc06304c
chore: mark ticket 01 as resolved (BaseDTO refactor complete)
4 weeks ago
luoweijian
ae103a1f1c
refactor: BaseDTO 重构为 abstract 基类 + IBaseService 清理死代码 (ADR-0017 ticket 01)
- BaseDTO 改为 abstract,仅保留 id/createTime/updateTime(均加 @JsonInclude NON_NULL)
- 移除 creatorId/updaterId/deleted 字段及默认值——防止出参侧泄露
- 移除泛型 toEntity(Supplier) 方法——转换方法下沉到各 DTO 类
- IBaseService 删除 saveDTO/updateDTO 死代码(零调用方)
- BeanCopyUtils javadoc 同步更新(移除 BaseDTO.toEntity 引用)
- README §3/§4/§10 同步更新 BaseDTO 和 IBaseService 说明
- 修复 ResourceController/RoleController 预存 UTF-8 BOM 问题
- 新增 ADR-0017、spec.md、7 个 implementation tickets
Refs: ADR-0017, .scratch/controller-io-conventions/
4 weeks ago
luoweijian
ffbbb73143
commit
4 weeks ago
luoweijian
0dc2da99d5
commit
4 weeks ago
luoweijian
51b7e2a749
feat(auth,dict,base): extract PermissionSeeder seam to replace shadow entities (ADR-0016)
- Add PermissionSeeder interface + PermissionModuleDescriptor/ButtonSeed records in crm-base
- Implement PermissionSeederImpl in crm-auth with find-or-create idempotent seeding
- Refactor DataInitializer to use seam (3 seedModule calls) + @Order(1)
- Refactor DictPermissionInitializer to use seam (1 seedModule call) + @Order(10)
- Delete 6 shadow entity/mapper files from crm-dict (SysMenuSeed, SysRoleSeed, SysRoleMenuSeed + mappers)
- Rewrite DictPermissionInitializerTest to mock PermissionSeeder (no more H2 auth tables)
- Add PermissionSeederImplTest (H2 integration test covering idempotency, catalog sharing, role-not-found)
- Add ADR-0016 documenting the decision, rationale, alternatives, and consequences
- Create crm-base/CONTEXT.md with PermissionSeeder domain glossary
- Update CONTEXT-MAP.md to link crm-base/CONTEXT.md
Eliminates schema leakage: crm-dict no longer knows sys_menu's physical schema.
Runtime dependency accepted: crm-dict needs crm-auth's PermissionSeederImpl at runtime (via crm-app classpath aggregation).
4 weeks ago
luoweijian
ceaff54863
commit
1 month ago
luoweijian
ab9f64483c
refactor(crm-dict): judgement call #6 #7 — pom version extraction + VO independent fields
#6 mybatis-spring 版本提取到父 pom:
- 父 pom properties 新增 mybatis-spring.version=3.0.3
- 父 pom dependencyManagement 新增 mybatis-spring 依赖声明
- crm-dict/pom.xml 去掉 version 和 scope=compile(由父 pom 统一管理)
#7 VO extends 实体重构为独立字段:
- DictGroupVO:不 extends DictGroup,独立定义 9 个字段(排除审计字段和软删内部字段)
- DictItemVO:不 extends DictItem,独立定义 14 个字段
- DictGroupServiceImpl:DictGroup::getId -> DictGroupVO::getId(fillItemCount)
- DictItemServiceImpl:DictItem::getGroupId -> DictItemVO::getGroupId(enrich)
- DictGroupServiceTest:pageGroups 断言 DictGroup::getCode -> DictGroupVO::getCode
全量 48/48 测试通过。
1 month ago
luoweijian
f3fba4a229
fix(dict): clear client-supplied audit fields in saveGroup/saveItem — prevent strictFill bypass forgery
saveGroup/saveItem accepted entity params whose createTime/creatorId/updaterId
were not sanitized; MetaObjectFillHandler uses strictInsertFill/strictUpdateFill
which only fills null fields, so client-supplied audit values were retained.
Now: null out all audit fields on entry (framework refills), restore
createTime/creatorId from exist on edit.
1 month ago
luoweijian
3d55eeb196
feat(dict): data dictionary module — two-level flat model, CRUD/default/cache/permission/seed (ADR-0015)
- 4 tables (dict_group/dict_item/dict_group_default/dict_ref_count), delete_key soft-delete reuse
- DictGroup/DictItem full CRUD + force-delete + status toggle + enabled-list
- DictReferenceService: increment/decrement/batchApply/isReferenced, same-tx ref_count, no cache
- DictQueryService: Caffeine 10s cache + 2s negative cache for empty results, group.status join
- Default item: INSERT ON DUPLICATE KEY UPDATE, hibernate on group disable, auto-restore
- Permission seeds: 10 perm codes → sys_menu tree, bound to ROLE_ADMIN, no crm-auth import
- Built-in dict initializer: idempotent upsert, builtin=false never touched (US-41), value not overwritten
- 48 tests pass; full project compiles
1 month ago
luoweijian
50f79a884d
commit
1 month ago
luoweijian
3499353b28
feat(file): thumbnail preview - async generation + sync fallback, PDFBox/LibreOffice renderers, retry (ADR-0013)
1 month ago
luoweijian
a263f1736c
feat(auth): role management — ancestor completion, CRUD, permission assignment, cascade delete (ADR-0012)
Implements ADR-0005 (backend ancestor completion) and ADR-0012 (role management
architecture) across 5 tracer-bullet tickets:
- ISysMenuService.getAncestorIds: batch query full table, in-memory parentId
traversal, returns complete ancestor set (ADR-0005)
- SysRole.builtin field + DataInitializer idempotent refactor + 5 button
permission seeds (crm:role:list/detail/save/delete/assign) bound to ADMIN
- RoleController (/api/roles/*): page, saveOrUpdate, detail, assign-resources,
delete — pure hasAuthority, POST + action suffix, form params
- SysRoleServiceImpl.saveRole: roleCode uniqueness, builtin protection,
dataScope 1-4 range, create can't set builtin=true
- assignResources: calls getAncestorIds for ancestor completion, then
full replacement (delete + insert) of sys_role_menu
- getRoleDetail: returns RoleDetailVO with basic info + resourceIds
- deleteRoleCascade: cleans sys_role_menu + sys_user_role, builtin rejection,
three-table transaction
- SystemController: all role endpoints removed, unused deps cleaned
- Dead code removed: assignMenus, getMenuIdsByRoleId (superseded by
assignResources, getRoleDetail)
- H2 test schemas updated with builtin column
- 150 tests pass (16 new in SysRoleServiceImplTest, 7 in SysMenuServiceImplTest)
1 month ago
luoweijian
f0f70c6137
refactor(auth): deduplicate user location logic in login flow
Extract locateUser() as the single source of truth for 'identity binding ->
mobile match' resolution. Both preCheckOrgMembership (read-only probe) and
resolveUser (login path) now share the same implementation, eliminating the
risk of rule drift between the two call sites.
Identity query is passed through to avoid redundant DB lookups.
1 month ago
luoweijian
4759518aa0
refactor(auth): remove dead MenuTypeEnum, unify node type vocabulary to MenuType (ADR-0011)
MenuTypeEnum (DIR/MENU/BUTTON) duplicated MenuType (CATALOG/MENU/BUTTON) with
no callers. Removing it eliminates the DIR-vs-CATALOG domain vocabulary split
and leaves a single source of truth for node type rules (isLegalChild etc.).
1 month ago
luoweijian
b38ab9ec94
feat(auth): 动态权限拦截 + 封遗留写路径
基于 apiUrl 的动态权限拦截机制(fail-open,Ant 风格匹配):
- ApiPermissionCache:Caffeine 缓存 button 节点 apiUrl→perms 规则
- ApiPermissionInterceptor:HandlerInterceptor 匹配 URL 校验 authority
- ApiPermissionRule:pattern→perms 映射 record
- WebMvcConfig:注册拦截器 /api/**
- ResourceServiceImpl:save/delete 后触发缓存失效
封遗留写路径 + 收拢 Mapper 到 Service 层:
- 删除 /menus/save 和 /menus/delete(ResourceController 已覆盖)
- ISysRoleService.assignMenus() + deleteRoleCascade() 事务保护
- IAuthUserService.assignRoles() 事务保护
- SystemController 不再持有 SysRoleMenuMapper/SysUserRoleMapper
测试:ApiPermissionInterceptorTest 7 个 + ResourceServiceImplTest 缓存失效验证
1 month ago
luoweijian
c877f0d6c4
refactor(auth): 权限解析收成深模块 PermissionResolver
将 PermissionServiceImpl(双职责、无接口、吞异常、混合返回类型)重构为深模块:
- PermissionResolver 接口:resolve(userId) + visibleMenuTree(userId)
- PermissionGrant 不可变值对象:结构化分离 permCodes / roleCodes,asAuthorities() 幂等归一 ROLE_ 前缀
- resolve 变纯函数:不装载 DataVisibilityContext,由过滤器负责
- fail-closed:删除 try/catch 吞异常,解析失败即请求失败(ADR-0006)
- 查询链 locality:resolve 与 visibleMenuTree 共用 userRolesOf + authorizedMenus
- JwtAuthenticationFilter / SystemController 切换到新接口
- DataScopeIntegrationTest 迁移到 resolve(),新增 6 个权限码并集场景
1 month ago
luoweijian
ae038a1398
feat: /api/resources/list 后端组树返回 — TreeUtils.buildTree + sort
- ResourceNode 新增 children 字段
- listAll() 改用 TreeUtils.buildTree 组树,按 sort 排序
- 前端无需自行组树,直接渲染 data
- 测试适配树结构,前端文档同步更新
1 month ago
luoweijian
170c16b226
refactor: ResourceController 对齐全局接口契约 — POST + 动作后缀 + 表单参数
- GET /api/resources → GET /api/resources/list
- POST /api/resources + @RequestBody → POST /api/resources/saveOrUpdate + 表单字段
- DELETE /api/resources/{id} → POST /api/resources/delete + @RequestParam
- 前端对接文档同步更新
1 month ago
luoweijian
4868051399
fix: validateHierarchy 增加 parentType null 守卫,防止 switch NPE
Spec review 发现:MenuType.fromCode() 对未知 DB 值返回 null 时,switch 表达式会抛 NPE 500,
改为提前抛 BusinessErrorException 并附带 menuType 值,便于排查脏数据。
1 month ago
luoweijian
a70ce78ddd
fix: 代码审查修复 — 安全问题 + 规范 + 空路由
- Spec#1 CRITICAL: 移除 /api/resources/** 白名单,改为 @PreAuthorize(hasRole('ADMIN'))
- Spec#2 WARNING: applyToEntity 空串路由不覆盖 DB path (null→isNotBlank)
- Standards#1: validateHierarchy 复用 MenuType.isLegalChild,消除重复代码
- Standards#2: 消除魔法数字 3→MenuType.BUTTON.getCode(),消除重复 switch
1 month ago