feat(pms-base): pms-base 模块加固 + U1-U10 全链路实现 #2

Closed
ether wants to merge 0 commits from feat/pms-base-hardening into main
Owner

pms-base 模块加固 — U1-U10 全链路实现

本 PR 基于 docs/plans/2026-07-02-001-fix-pms-base-hardening-plan.md 计划,完成 10 个实现单元的安全/质量/可靠性加固。

变更概览

  • 101 文件变更,+4424/-193 行
  • 357 单元测试全部通过
  • 10 个实现单元(U1-U10)+ ce-code-review 修复 + ce-simplify-code 优化

实现单元清单

U1: AES 加密加固

  • AesEncryptUtil 迁移到 AES-256-GCM(IV 随机化、GCM 完整性校验)
  • V12__MigrateAesEcbToGcm.java:Flyway Java 迁移,回填历史 NULL id_no_hash
  • AES_KEY 通过环境变量注入(无默认值,未配置时启动失败)
  • GCM POC 测试验证 JDK 原生实现

U2: ProjectSecurityChecker

  • 保留现有实现,等待 feat/user-org-perm 合并后由 @PreAuthorize 替换

U3: BigDecimal → Long(分)全链路迁移

  • 8 张表金额字段 DECIMAL(15,2) 元 → BIGINT 分
  • V13__migrate_amount_to_long_fen.sql:UPDATE(×100) 先于 ALTER(避免 DECIMAL→BIGINT 舍入丢失分位)
  • Entity/DTO/Service/Mapper 全链路 Long amountFen 替换 BigDecimal amount

U4: phone/email 加密 + hash 索引

  • V14__encrypt_phone_email.sql:3 表新增 phone_hash/email_hash/phone_last4_hash/email_last4_hash + 9 个索引
  • Owner/Tenant/Enterprise 实体新增 hash 字段
  • ServiceImpl create/update 时加密+hash+last4hash 三件套
  • getById 时解密返回明文

U5: 输入校验

  • 11 个 Controller 的 @RequestBody 参数添加 @Valid
  • 8 个 DTO/SaveRequest 添加 @Pattern/@Email/@Size/@PositiveOrZero
  • GlobalExceptionHandler 已处理 MethodArgumentNotValidException

U6: 测试覆盖

  • 修复 24 个 LifecycleWriteGuard null 测试失败(@Mock 注入)
  • 新增 12 个 ServiceImpl 测试(Tenant/Owner/Enterprise/Project/Floor/DeviceCategory/ContractType/Room/Device/DeviceMaintenance/Contract/OwnerRoom)
  • 测试总数:357,0 失败

U7: Resilience4j 熔断 + Feign fallback

  • build.gradle 引入 resilience4j-spring-boot3
  • application.yml 配置熔断参数(50% 失败率、2s 慢调用阈值、30s open 持续)
  • AuthClient fallback 从 return success(null) 改为 throw ServiceCallException(503)
  • AuthClientFallbackTest 验证异常抛出

U8: @Transactional

  • DeviceCategoryServiceImpl/ContractTypeServiceImpl/ProjectServiceImpl 的 delete 方法添加 @Transactional(rollbackFor = Exception.class)

U9: @Lazy 循环依赖文档

  • CONCEPTS.md 新增「代码设计约定」章节
  • lazy-injection-reflectiontestutils-testing.md 补充「为什么不用事件总线解耦」设计依据

U10: @PreAuthorize 方法级鉴权

  • 28 个 Controller 添加 142 个 @PreAuthorize 注解(permission code base:xxx:manage
  • V15__add_base_permissions.sql:20 个权限码(ID 300-319),ROLE_ADMIN 全部授权,ROLE_PROPERTY 授权 15 个
  • 依赖 feat/user-org-perm 合并的 Spring Security 基础设施

ce-code-review 修复

  • P1 数据精度 bug:V13 迁移原顺序 ALTER(DECIMAL→BIGINT) 先于 UPDATE(×100),MySQL 舍入丢失分位后 ×100 放大误差。修复:UPDATE 先(仍为 DECIMAL 无损),ALTER 后(值为整数无舍入)
  • P2 安全EnterpriseSaveRequest.roomIds 添加 @Size(max=500) 约束

ce-simplify-code 优化

  • V12 fillNullHash 改为 addBatch/executeBatch(每 500 条 flush),N 次网络往返 → N/500 次
  • ProjectServiceImpl.create 删除冗余 setProjectId(null)

Advisory 项(未在本 PR 处理,记录待后续)

  • V14 缺 phone/email hash 回填(需 Java 迁移解密历史数据,feature gap)
  • AesEncryptUtil 为 pms-base 本地实现(业务代码使用 pms-common CryptoUtil),plan U1 决策保留
  • 6× encrypt+hash 模式跨 3 个 ServiceImpl 重复(跨模块抽取 helper 需设计)
  • ProjectServiceImpl.update/deletelifecycleWriteGuard 调用(行为变更,非简化范畴)

测试

./gradlew :pms-base:test
BUILD SUCCESSFUL in 21s
357 tests, 0 failures

关联

  • 计划文档:docs/plans/2026-07-02-001-fix-pms-base-hardening-plan.md
  • 依赖分支:feat/user-org-perm(已合并 main,提供 Spring Security 基础设施)
  • 生成管线:LFG 10 步(ce-plan → ce-work U1-U10 → ce-code-review → ce-simplify-code → ce-commit-push-pr)
## pms-base 模块加固 — U1-U10 全链路实现 本 PR 基于 `docs/plans/2026-07-02-001-fix-pms-base-hardening-plan.md` 计划,完成 10 个实现单元的安全/质量/可靠性加固。 ### 变更概览 - 101 文件变更,+4424/-193 行 - 357 单元测试全部通过 - 10 个实现单元(U1-U10)+ ce-code-review 修复 + ce-simplify-code 优化 ### 实现单元清单 #### U1: AES 加密加固 - `AesEncryptUtil` 迁移到 AES-256-GCM(IV 随机化、GCM 完整性校验) - `V12__MigrateAesEcbToGcm.java`:Flyway Java 迁移,回填历史 NULL `id_no_hash` - AES_KEY 通过环境变量注入(无默认值,未配置时启动失败) - GCM POC 测试验证 JDK 原生实现 #### U2: ProjectSecurityChecker - 保留现有实现,等待 `feat/user-org-perm` 合并后由 `@PreAuthorize` 替换 #### U3: BigDecimal → Long(分)全链路迁移 - 8 张表金额字段 DECIMAL(15,2) 元 → BIGINT 分 - `V13__migrate_amount_to_long_fen.sql`:UPDATE(×100) 先于 ALTER(避免 DECIMAL→BIGINT 舍入丢失分位) - Entity/DTO/Service/Mapper 全链路 `Long amountFen` 替换 `BigDecimal amount` #### U4: phone/email 加密 + hash 索引 - `V14__encrypt_phone_email.sql`:3 表新增 `phone_hash`/`email_hash`/`phone_last4_hash`/`email_last4_hash` + 9 个索引 - `Owner`/`Tenant`/`Enterprise` 实体新增 hash 字段 - ServiceImpl create/update 时加密+hash+last4hash 三件套 - getById 时解密返回明文 #### U5: 输入校验 - 11 个 Controller 的 `@RequestBody` 参数添加 `@Valid` - 8 个 DTO/SaveRequest 添加 `@Pattern`/`@Email`/`@Size`/`@PositiveOrZero` - GlobalExceptionHandler 已处理 `MethodArgumentNotValidException` #### U6: 测试覆盖 - 修复 24 个 `LifecycleWriteGuard` null 测试失败(`@Mock` 注入) - 新增 12 个 ServiceImpl 测试(Tenant/Owner/Enterprise/Project/Floor/DeviceCategory/ContractType/Room/Device/DeviceMaintenance/Contract/OwnerRoom) - 测试总数:357,0 失败 #### U7: Resilience4j 熔断 + Feign fallback - `build.gradle` 引入 `resilience4j-spring-boot3` - `application.yml` 配置熔断参数(50% 失败率、2s 慢调用阈值、30s open 持续) - `AuthClient` fallback 从 `return success(null)` 改为 `throw ServiceCallException`(503) - `AuthClientFallbackTest` 验证异常抛出 #### U8: @Transactional - `DeviceCategoryServiceImpl`/`ContractTypeServiceImpl`/`ProjectServiceImpl` 的 delete 方法添加 `@Transactional(rollbackFor = Exception.class)` #### U9: @Lazy 循环依赖文档 - `CONCEPTS.md` 新增「代码设计约定」章节 - `lazy-injection-reflectiontestutils-testing.md` 补充「为什么不用事件总线解耦」设计依据 #### U10: @PreAuthorize 方法级鉴权 - 28 个 Controller 添加 142 个 `@PreAuthorize` 注解(permission code `base:xxx:manage`) - `V15__add_base_permissions.sql`:20 个权限码(ID 300-319),ROLE_ADMIN 全部授权,ROLE_PROPERTY 授权 15 个 - 依赖 `feat/user-org-perm` 合并的 Spring Security 基础设施 ### ce-code-review 修复 - **P1 数据精度 bug**:V13 迁移原顺序 ALTER(DECIMAL→BIGINT) 先于 UPDATE(×100),MySQL 舍入丢失分位后 ×100 放大误差。修复:UPDATE 先(仍为 DECIMAL 无损),ALTER 后(值为整数无舍入) - **P2 安全**:`EnterpriseSaveRequest.roomIds` 添加 `@Size(max=500)` 约束 ### ce-simplify-code 优化 - V12 `fillNullHash` 改为 `addBatch/executeBatch`(每 500 条 flush),N 次网络往返 → N/500 次 - `ProjectServiceImpl.create` 删除冗余 `setProjectId(null)` ### Advisory 项(未在本 PR 处理,记录待后续) - V14 缺 phone/email hash 回填(需 Java 迁移解密历史数据,feature gap) - `AesEncryptUtil` 为 pms-base 本地实现(业务代码使用 pms-common CryptoUtil),plan U1 决策保留 - 6× encrypt+hash 模式跨 3 个 ServiceImpl 重复(跨模块抽取 helper 需设计) - `ProjectServiceImpl.update/delete` 缺 `lifecycleWriteGuard` 调用(行为变更,非简化范畴) ### 测试 ``` ./gradlew :pms-base:test BUILD SUCCESSFUL in 21s 357 tests, 0 failures ``` ### 关联 - 计划文档:`docs/plans/2026-07-02-001-fix-pms-base-hardening-plan.md` - 依赖分支:`feat/user-org-perm`(已合并 main,提供 Spring Security 基础设施) - 生成管线:LFG 10 步(ce-plan → ce-work U1-U10 → ce-code-review → ce-simplify-code → ce-commit-push-pr)
ether added 13 commits 2026-07-02 18:11:26 +08:00
9ceb9274b2 plan(pms-base): 基础数据模块加固修复计划
分阶段修复 AES GCM 加密、BigDecimal→Long、手机号邮箱加密、
@AuditLog 标注、测试覆盖、Feign 熔断等。鉴权相关延后至
feat/user-org-perm 合并。
874386c1a8 feat(pms-base): U2 @AuditLog full coverage + PII desensitization
- Add @AuditLog to 18 missing write methods across 25 Controllers
- Set recordParams=false for 6 PII methods (OwnerCommittee/Project/WorkshopLease create+update)
- Add AuditLogCoverageTest (38 parameterized tests) as guard for future regressions
- Exempt InternalController#batchGetRooms (internal Feign query, not business write)
- All 38 tests pass
39a74135e3 feat(pms-base): U3 BigDecimal to Long (fen) full-link migration
- V13 Flyway: DECIMAL(15,2) -> BIGINT, value * 100 (8 tables, 11 columns)
- 8 entities: BigDecimal amount fields -> Long xxxFen with @TableField preserving column names
- 7 DTOs: amount/deposit/rent/price/cost -> xxxFen (Long)
- 7 ServiceImpls: getter/setter updates + ratio-based calc uses BigDecimal intermediate
- 2 Service interfaces: monthlySummary return Long, allocateCamCharge param Long
- 2 Controllers: CamCharge/PublicRevenue param/return type updates
- 3 Mapper XMLs: add AS xxx_fen aliases for resultType mapping
- 3 T2 flow tests fixed (Office/Residential/Park)
- 24 pre-existing LifecycleWriteGuard null failures unchanged
f09773bcee feat(pms-base): U4 phone/email encryption + hash index
Add phone/email AES-256-GCM encryption and hash fields for Owner, Tenant, Enterprise
entities to enable secure storage and fast lookup. Reuses CryptoUtil (consistent with
idNo pattern). Hash fields support full-hash exact match and last4-hash suffix queries.

- V14 Flyway migration: add phone_hash/phone_last4_hash/email_hash/email_last4_hash
  columns + emergency_phone_hash (Owner), contact_phone_hash/contact_phone_last4_hash
  (Enterprise) across 3 tables, with 9 indexes for query performance
- Owner/Tenant/Enterprise entities: add hash fields with JavaDoc
- OwnerServiceImpl/TenantServiceImpl/EnterpriseServiceImpl: encrypt phone/email on
  create+update, fill hash fields; decrypt on getById; null/empty skip; decryption
  failure logged with WARN (fault tolerance)
e8641df701 feat(pms-base): U8 @Transactional on 3 delete methods + U9 @Lazy doc
U8: Add @Transactional(rollbackFor = Exception.class) to delete methods in
DeviceCategoryServiceImpl, ContractTypeServiceImpl, ProjectServiceImpl.
These were the only 3 delete methods missing the annotation.

U9: Document the @Lazy circular dependency between LifecycleServiceImpl and
ArchiveServiceImpl as a known design decision:
- CONCEPTS.md: new "代码设计约定" section with @Lazy entry explaining the
  bidirectional dependency, why event bus is not used (transactional consistency),
  and testing note (ReflectionTestUtils.setField)
- docs/solutions/conventions/lazy-injection-reflectiontestutils-testing.md: add
  "Design Rationale" section explaining why MQ event bus is not used for the
  LifecycleService ↔ ArchiveService interop (same-transaction requirement)
6207d1d1bf feat(pms-base): U5 input validation - @Valid + field annotations
Add @Valid to @RequestBody params in 11 Controllers and field-level validation
annotations in 8 DTO/SaveRequest classes:

- 11 Controllers: WorkshopLease, SafetyInspection, EnterpriseService,
  PublicRevenue, LeaseContract, MeetingRoom, CommunityActivity, EnergyMeter,
  EnterpriseProfile, Renovation, OwnerCommittee — all @RequestBody params
  now have @Valid
- OwnerSaveRequest: @Pattern on phone/emergencyPhone/idNo, @Email on email,
  @Size on string fields
- TenantSaveRequest: @Pattern on phone/idNo, @Email on email, @Size on fields
- EnterpriseSaveRequest: @Pattern on contactPhone, @Email on email, @Size
- ContractSaveRequest/RenewRequest: @PositiveOrZero on amountFen/depositFen
- DeviceSaveRequest: @PositiveOrZero on priceFen
- DeviceMaintenanceSaveRequest: @PositiveOrZero on costFen
- ProjectSaveRequest: @Pattern on contactPhone, @Size on string fields

GlobalExceptionHandler already handles MethodArgumentNotValidException (400 +
field error message). build.gradle already has spring-boot-starter-validation.
8efa10e197 feat(pms-base): U10 @PreAuthorize on 28 Controllers + V15 permissions
Add method-level @PreAuthorize annotations to all 28 business Controllers in
pms-base (142 methods total). Excludes InternalController (internal Feign auth),
DashboardController, LookupController (login-only).

Permission codes follow the module:entity:manage pattern established by
pms-auth (system:xxx:manage). 20 base module permission codes defined:
base:owner, base:tenant, base:building, base:room, base:floor, base:project,
base:contract, base:lease, base:device, base:energy, base:renovation,
base:revenue, base:finance, base:enterprise, base:committee, base:workshop,
base:meeting, base:activity, base:inspection, base:lifecycle

V15 Flyway migration:
- t_permission: 20 codes (ID 300-319), perm_type=3, ON DUPLICATE KEY UPDATE
- t_role_permission: ROLE_ADMIN gets all 20 (ID 123-142)
- t_role_permission: ROLE_PROPERTY gets 15 core property permissions (ID 143-157),
  excluding project/revenue/finance/enterprise/lifecycle (management level)

This unblocks R2/R11 which were deferred pending feat/user-org-perm merge.
a7f375ab4e test(pms-base): U6 fix 24 LifecycleWriteGuard null + 12 new ServiceImpl tests
Part A — Fix 24 pre-existing test failures:
BuildingServiceTest, OfficeT2FlowTest, ParkT2FlowTest, ResidentialT2FlowTest
all had LifecycleWriteGuard null NPE because @Mock was missing after the
lifecycle management merge added the guard as a constructor-injected field.
Added @Mock private LifecycleWriteGuard to each file.

Part B — 12 new ServiceImpl test files (64 new tests):
TenantServiceImplTest, OwnerServiceImplTest, EnterpriseServiceImplTest,
ProjectServiceImplTest, FloorServiceImplTest, DeviceCategoryServiceImplTest,
ContractTypeServiceImplTest, RoomServiceImplTest, DeviceServiceImplTest,
DeviceMaintenanceServiceImplTest, ContractServiceImplTest, OwnerRoomServiceImplTest

Coverage: CRUD main flow, encryption/hash field verification (Owner/Tenant/
Enterprise), Long fen amount validation (Device/DeviceMaintenance), notFound
error paths, code duplication checks, auto-generated fields.

Result: 355 tests, 0 failures, 0 errors.
2fc721b1d8 feat(pms-base): U7 Resilience4j circuit breaker + AuthClient fallback fix
Add Resilience4j circuit breaker to pms-base Feign clients and fix
AuthClient fallback to throw exception instead of silently returning null.

- build.gradle: add resilience4j-spring-boot3 dependency
- application.yml: Resilience4j config (failure rate 50%, slow call 2s,
  half-open 10 requests, 30s wait in open state, sliding window 20)
- AuthClient.java: fallback now throws ServiceCallException instead of
  returning Result.success(null), preventing downstream NPE when
  auth-service is unavailable. Pattern consistent with ChargeClient.
- AuthClientFallbackTest: 2 tests verifying exception thrown + cause preserved

ChargeClient already throws ServiceCallException (correct).
NotifyClient silently degrades (correct for non-critical notifications).
6d66941df8 fix(pms-base): ce-code-review 修复 V13 数据精度 + roomIds 长度校验
P1 数据精度 bug:V13 迁移原顺序为 ALTER(DECIMAL→BIGINT) 先于
UPDATE(×100),MySQL 在 ALTER 时对 DECIMAL→BIGINT 四舍五入会丢失
分位,再 ×100 放大误差(如 1000.50 → 1001 → 100100,应为 100050)。
修复:调整为 UPDATE 先(×100 时仍为 DECIMAL 无精度损失),ALTER 后
(值为整数,BIGINT 转换无舍入)。

P2 安全发现:EnterpriseSaveRequest.roomIds 缺少 @Size 约束,补充
max=500 限制避免超长字符串攻击。

Tests: BUILD SUCCESSFUL, 357 tests, 0 failures.
04e772b0f2 refactor(pms-base): ce-simplify-code 批量更新 + 删除冗余赋值
V12 迁移 fillNullHash 改为 addBatch/executeBatch(每 500 条 flush),
将 N 次 UPDATE 网络往返压缩为 N/500 次,降低大表迁移超时风险。

ProjectServiceImpl.create 删除冗余 project.setProjectId(null),
new Project() 已默认 null,无行为变化。

Tests: BUILD SUCCESSFUL, 357 tests, 0 failures.

跳过项(advisory,非简化范畴):
- V14 缺 hash 回填(feature gap,需设计决策)
- AesEncryptUtil 死代码(plan U1 决策保留)
- 6× encrypt+hash 重复(跨模块抽取 helper,需设计)
- ProjectServiceImpl 缺 lifecycleWriteGuard(行为变更,非简化)
- ContractServiceImpl magic numbers(合同特有状态码,非 STATUS_ENABLED)
ether closed this pull request 2026-07-03 21:53:06 +08:00

Pull request closed

Sign in to join this conversation.
No reviewers
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: ether/ether#2
No description provided.