Compare commits
2 Commits
a8d65fbf39
...
ce50e6e429
| Author | SHA1 | Date |
|---|---|---|
|
|
ce50e6e429 | |
|
|
777f15eba6 |
|
|
@ -82,7 +82,7 @@ mybatis-plus:
|
|||
|
||||
logging:
|
||||
level:
|
||||
com.pms: debug
|
||||
com.pms: debug # 生产环境应设为 info
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ jwt:
|
|||
# 日志
|
||||
logging:
|
||||
level:
|
||||
com.pms: debug
|
||||
com.pms: debug # 生产环境应设为 info
|
||||
|
||||
# 端点暴露
|
||||
management:
|
||||
|
|
|
|||
|
|
@ -166,12 +166,22 @@ public class InternalController {
|
|||
dto.setProjectId(owner.getProjectId());
|
||||
dto.setName(owner.getOwnerName());
|
||||
dto.setPhone(owner.getPhone());
|
||||
dto.setIdNo(owner.getIdNo());
|
||||
dto.setIdNo(maskIdNo(owner.getIdNo()));
|
||||
dto.setType(owner.getIdType());
|
||||
dto.setStatus(owner.getStatus());
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 身份证号脱敏:保留前6位和后4位,中间用 **** 替换
|
||||
*/
|
||||
private String maskIdNo(String idNo) {
|
||||
if (idNo == null || idNo.length() < 10) {
|
||||
return idNo;
|
||||
}
|
||||
return idNo.substring(0, 6) + "****" + idNo.substring(idNo.length() - 4);
|
||||
}
|
||||
|
||||
private InternalBaseDTOs.DeviceDTO toInternalDeviceDTO(DeviceDTO device) {
|
||||
InternalBaseDTOs.DeviceDTO dto = new InternalBaseDTOs.DeviceDTO();
|
||||
dto.setId(device.getId());
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pms.base.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.pms.common.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
|
@ -38,4 +39,8 @@ public class CamCharge extends BaseEntity {
|
|||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 乐观锁版本号 */
|
||||
@Version
|
||||
private Integer version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pms.base.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.pms.common.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
|
@ -38,4 +39,8 @@ public class CommunityActivity extends BaseEntity {
|
|||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 乐观锁版本号 */
|
||||
@Version
|
||||
private Integer version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pms.base.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.pms.common.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
|
@ -51,4 +52,8 @@ public class LeaseContract extends BaseEntity {
|
|||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 乐观锁版本号 */
|
||||
@Version
|
||||
private Integer version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pms.base.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.pms.common.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
|
@ -30,4 +31,8 @@ public class MeetingRoom extends BaseEntity {
|
|||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 乐观锁版本号 */
|
||||
@Version
|
||||
private Integer version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pms.base.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.pms.common.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
|
@ -59,4 +60,8 @@ public class Renovation extends BaseEntity {
|
|||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 乐观锁版本号 */
|
||||
@Version
|
||||
private Integer version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pms.base.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.pms.common.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
|
@ -36,4 +37,8 @@ public class SafetyInspection extends BaseEntity {
|
|||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 乐观锁版本号 */
|
||||
@Version
|
||||
private Integer version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pms.base.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import com.pms.common.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
|
@ -39,4 +40,8 @@ public class WorkshopLease extends BaseEntity {
|
|||
|
||||
/** 备注 */
|
||||
private String remark;
|
||||
|
||||
/** 乐观锁版本号 */
|
||||
@Version
|
||||
private Integer version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -130,8 +131,13 @@ public class AbilityPackageServiceImpl implements AbilityPackageService {
|
|||
pa.setUpdatedAt(now);
|
||||
pa.setCreatedBy(userId);
|
||||
pa.setUpdatedBy(userId);
|
||||
projectAbilityMapper.insert(pa);
|
||||
inserted++;
|
||||
try {
|
||||
projectAbilityMapper.insert(pa);
|
||||
inserted++;
|
||||
} catch (DuplicateKeyException e) {
|
||||
// 并发情况下可能已插入,视为成功(幂等)
|
||||
log.info("能力包关联已存在,跳过: projectId={}, packageId={}", projectId, pkg.getId());
|
||||
}
|
||||
}
|
||||
log.info("项目 {} 关联能力包 {} 完成,新增 {} 个关联",
|
||||
projectId, packageCodes, inserted);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pms.base.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pms.base.entity.CommunityActivity;
|
||||
|
|
@ -123,19 +124,18 @@ public class CommunityActivityServiceImpl implements CommunityActivityService {
|
|||
if (existing.getStatus() != STATUS_OPEN) {
|
||||
throw new BusinessException(ErrorCode.OPERATION_FAILED, "当前活动不在报名中");
|
||||
}
|
||||
int max = existing.getMaxParticipants() != null ? existing.getMaxParticipants() : 0;
|
||||
int current = existing.getCurrentCount() != null ? existing.getCurrentCount() : 0;
|
||||
if (max > 0 && current >= max) {
|
||||
throw new BusinessException(ErrorCode.OPERATION_FAILED, "活动报名人数已满");
|
||||
// 原子更新:current_count = current_count + 1 WHERE id = ? AND (max_participants <= 0 OR current_count < max_participants)
|
||||
UpdateWrapper<CommunityActivity> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("id", id)
|
||||
.apply("(max_participants <= 0 OR current_count < max_participants)")
|
||||
.setSql("current_count = current_count + 1")
|
||||
.set("updated_at", System.currentTimeMillis())
|
||||
.set("updated_by", UserContext.getUserId());
|
||||
int updated = communityActivityMapper.update(null, updateWrapper);
|
||||
if (updated == 0) {
|
||||
throw new BusinessException(ErrorCode.OPERATION_FAILED, "报名人数已满");
|
||||
}
|
||||
|
||||
CommunityActivity update = new CommunityActivity();
|
||||
update.setId(id);
|
||||
update.setCurrentCount(current + 1);
|
||||
update.setUpdatedAt(System.currentTimeMillis());
|
||||
update.setUpdatedBy(UserContext.getUserId());
|
||||
communityActivityMapper.updateById(update);
|
||||
log.info("活动报名成功: id={}, currentCount={}", id, current + 1);
|
||||
log.info("活动报名成功: id={}", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.pms.base.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.pms.base.entity.LookupDict;
|
||||
import com.pms.base.entity.LookupItem;
|
||||
import com.pms.base.mapper.LookupDictMapper;
|
||||
|
|
@ -12,6 +13,7 @@ import com.pms.common.exception.ErrorCode;
|
|||
import com.pms.common.security.UserContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
|
@ -146,7 +148,21 @@ public class LookupServiceImpl implements LookupService {
|
|||
item.setUpdatedAt(now);
|
||||
item.setCreatedBy(userId);
|
||||
item.setUpdatedBy(userId);
|
||||
lookupItemMapper.insert(item);
|
||||
try {
|
||||
lookupItemMapper.insert(item);
|
||||
} catch (DuplicateKeyException e) {
|
||||
// 并发情况下已插入,改为更新
|
||||
log.info("项目级字典项已存在,改为更新: dictCode={}, itemCode={}", dictCode, itemCode);
|
||||
UpdateWrapper<LookupItem> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("dict_id", dict.getId())
|
||||
.eq("item_code", itemCode)
|
||||
.eq("scope", SCOPE_PROJECT)
|
||||
.eq("project_id", projectId)
|
||||
.set("enabled", CommonConstants.STATUS_DISABLED)
|
||||
.set("updated_at", now)
|
||||
.set("updated_by", userId);
|
||||
lookupItemMapper.update(null, updateWrapper);
|
||||
}
|
||||
}
|
||||
log.info("项目 {} 字典 {} 禁用项 {}", projectId, dictCode, itemCode);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ crypto:
|
|||
|
||||
logging:
|
||||
level:
|
||||
com.pms: debug
|
||||
com.pms: debug # 生产环境应设为 info
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
-- CR #7: 为 7 个状态实体添加 version 乐观锁字段
|
||||
-- MyBatis-Plus OptimisticLockerInnerInterceptor 已在所有模块配置
|
||||
|
||||
ALTER TABLE t_renovation ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号';
|
||||
ALTER TABLE t_lease_contract ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号';
|
||||
ALTER TABLE t_safety_inspection ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号';
|
||||
ALTER TABLE t_meeting_room ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号';
|
||||
ALTER TABLE t_community_activity ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号';
|
||||
ALTER TABLE t_cam_charge ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号';
|
||||
ALTER TABLE t_workshop_lease ADD COLUMN version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号';
|
||||
|
|
@ -388,12 +388,11 @@ class ResidentialT2FlowTest {
|
|||
activity.setMaxParticipants(50);
|
||||
activity.setCurrentCount(10);
|
||||
when(communityActivityMapper.selectById(7002L)).thenReturn(activity);
|
||||
when(communityActivityMapper.update(eq(null), any())).thenReturn(1);
|
||||
|
||||
communityActivityService.signUp(7002L);
|
||||
|
||||
ArgumentCaptor<CommunityActivity> captor = ArgumentCaptor.forClass(CommunityActivity.class);
|
||||
verify(communityActivityMapper).updateById(captor.capture());
|
||||
assertThat(captor.getValue().getCurrentCount()).isEqualTo(11);
|
||||
verify(communityActivityMapper).update(eq(null), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -403,12 +402,13 @@ class ResidentialT2FlowTest {
|
|||
activity.setMaxParticipants(20);
|
||||
activity.setCurrentCount(20);
|
||||
when(communityActivityMapper.selectById(7003L)).thenReturn(activity);
|
||||
when(communityActivityMapper.update(eq(null), any())).thenReturn(0);
|
||||
|
||||
assertThatThrownBy(() -> communityActivityService.signUp(7003L))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("报名人数已满");
|
||||
|
||||
verify(communityActivityMapper, never()).updateById(any());
|
||||
verify(communityActivityMapper).update(eq(null), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -418,12 +418,11 @@ class ResidentialT2FlowTest {
|
|||
activity.setMaxParticipants(0);
|
||||
activity.setCurrentCount(0);
|
||||
when(communityActivityMapper.selectById(7004L)).thenReturn(activity);
|
||||
when(communityActivityMapper.update(eq(null), any())).thenReturn(1);
|
||||
|
||||
communityActivityService.signUp(7004L);
|
||||
|
||||
ArgumentCaptor<CommunityActivity> captor = ArgumentCaptor.forClass(CommunityActivity.class);
|
||||
verify(communityActivityMapper).updateById(captor.capture());
|
||||
assertThat(captor.getValue().getCurrentCount()).isEqualTo(1);
|
||||
verify(communityActivityMapper).update(eq(null), any());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ mybatis-plus:
|
|||
|
||||
logging:
|
||||
level:
|
||||
com.pms: debug
|
||||
com.pms: debug # 生产环境应设为 info
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.pms.common.dto.internal;
|
||||
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
|
|
@ -113,6 +114,7 @@ public final class InternalBaseDTOs {
|
|||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Size(max = 500, message = "批量查询ID数量不能超过500")
|
||||
private java.util.List<Long> roomIds;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ mybatis-plus:
|
|||
|
||||
logging:
|
||||
level:
|
||||
com.pms: debug
|
||||
com.pms: debug # 生产环境应设为 info
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ jwt:
|
|||
# 日志
|
||||
logging:
|
||||
level:
|
||||
com.pms: debug
|
||||
com.pms: debug # 生产环境应设为 info
|
||||
org.springframework.cloud.gateway: info
|
||||
|
||||
# 端点暴露
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ notify:
|
|||
|
||||
logging:
|
||||
level:
|
||||
com.pms: debug
|
||||
com.pms: debug # 生产环境应设为 info
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ crypto:
|
|||
|
||||
logging:
|
||||
level:
|
||||
com.pms: debug
|
||||
com.pms: debug # 生产环境应设为 info
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
---
|
||||
title: Flyway 迁移脚本回滚操作手册
|
||||
problem_type: migration-rollback
|
||||
component: backend (pms-base, pms-charge, pms-operation)
|
||||
severity: warning
|
||||
created: 2026-07-01
|
||||
related_cr: CR #10
|
||||
---
|
||||
|
||||
# Flyway 迁移脚本回滚操作手册
|
||||
|
||||
## 背景
|
||||
|
||||
Flyway 不支持自动 down script,生产环境需要回滚时需手动执行 SQL。本文档覆盖 `feat/multi-property-config` 引入的 V2-V9 迁移脚本(pms-base)、V3 数据迁移(pms-charge)、V2-V3 列类型迁移(pms-operation)。
|
||||
|
||||
## 回滚原则
|
||||
|
||||
1. **逆序回滚**:从最高版本号开始,逐版本降级
|
||||
2. **先备份**:执行前 `mysqldump` 备份相关表
|
||||
3. **更新 Flyway 元数据**:回滚后需修正 `flyway_schema_history` 表
|
||||
|
||||
## pms-base 迁移回滚
|
||||
|
||||
### V9 → V8:移除 version 列
|
||||
|
||||
```sql
|
||||
ALTER TABLE t_renovation DROP COLUMN version;
|
||||
ALTER TABLE t_lease_contract DROP COLUMN version;
|
||||
ALTER TABLE t_safety_inspection DROP COLUMN version;
|
||||
ALTER TABLE t_meeting_room DROP COLUMN version;
|
||||
ALTER TABLE t_community_activity DROP COLUMN version;
|
||||
ALTER TABLE t_cam_charge DROP COLUMN version;
|
||||
ALTER TABLE t_workshop_lease DROP COLUMN version;
|
||||
```
|
||||
|
||||
### V8 → V7:删除产业园 T2 表
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS t_enterprise_profile;
|
||||
DROP TABLE IF EXISTS t_safety_inspection;
|
||||
DROP TABLE IF EXISTS t_energy_meter;
|
||||
DROP TABLE IF EXISTS t_workshop_lease;
|
||||
```
|
||||
|
||||
### V7 → V6:删除商办 T2 表
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS t_enterprise_service;
|
||||
DROP TABLE IF EXISTS t_meeting_room;
|
||||
DROP TABLE IF EXISTS t_cam_charge;
|
||||
DROP TABLE IF EXISTS t_lease_contract;
|
||||
```
|
||||
|
||||
### V6 → V5:删除住宅 T2 表
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS t_public_revenue;
|
||||
DROP TABLE IF EXISTS t_community_activity;
|
||||
DROP TABLE IF EXISTS t_renovation;
|
||||
DROP TABLE IF EXISTS t_owner_committee;
|
||||
```
|
||||
|
||||
### V5 → V4:删除收费 Lookup 种子数据
|
||||
|
||||
```sql
|
||||
DELETE FROM t_lookup_item WHERE dict_id IN (
|
||||
SELECT id FROM t_lookup_dict WHERE dict_code IN ('FEE_TYPE', 'CHARGE_RULE', 'BILL_STATUS', 'CALC_RULE')
|
||||
);
|
||||
DELETE FROM t_lookup_dict WHERE dict_code IN ('FEE_TYPE', 'CHARGE_RULE', 'BILL_STATUS', 'CALC_RULE');
|
||||
```
|
||||
|
||||
### V4 → V3:删除能力包种子数据
|
||||
|
||||
```sql
|
||||
DELETE FROM t_ability_package_item WHERE package_id IN (
|
||||
SELECT id FROM t_ability_package WHERE code LIKE 'T%'
|
||||
);
|
||||
DELETE FROM t_ability_package WHERE code LIKE 'T%';
|
||||
```
|
||||
|
||||
### V3 → V2:删除 Lookup 字典表
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS t_lookup_item;
|
||||
DROP TABLE IF EXISTS t_lookup_dict;
|
||||
```
|
||||
|
||||
### V2 → V1:删除能力包表
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS t_project_ability;
|
||||
DROP TABLE IF EXISTS t_ability_package_item;
|
||||
DROP TABLE IF EXISTS t_ability_package;
|
||||
```
|
||||
|
||||
## pms-charge 迁移回滚
|
||||
|
||||
### V3:SpEL 计费规则数据迁移(FIXED → unitPrice)
|
||||
|
||||
**不可逆**:此迁移将 `calc_rule='FIXED'` 的记录改为 `calc_rule='unitPrice'`,并设置了 `calc_expression` 字段。
|
||||
|
||||
**回滚方案**:
|
||||
```sql
|
||||
-- 恢复 FIXED 类型(仅对未修改 unitPrice 的记录)
|
||||
UPDATE t_charge_standard SET calc_rule = 'FIXED', calc_expression = NULL
|
||||
WHERE calc_rule = 'unitPrice' AND calc_expression IS NULL;
|
||||
|
||||
-- 注意:已设置 SpEL 表达式的记录无法自动回滚,需根据业务判断
|
||||
```
|
||||
|
||||
## pms-operation 迁移回滚
|
||||
|
||||
### V2-V3:工单/巡检类型 TINYINT → Lookup 字典
|
||||
|
||||
**不可逆**:此迁移将 `type` 字段从 TINYINT 改为 VARCHAR,并迁移了数据。
|
||||
|
||||
**回滚方案**:
|
||||
```sql
|
||||
-- 需要先备份原始 TINYINT 值,回滚时恢复
|
||||
-- 由于列类型已改变,需 ALTER TABLE 修改回 TINYINT
|
||||
```
|
||||
|
||||
## 修正 Flyway 元数据
|
||||
|
||||
回滚完成后,修正 `flyway_schema_history` 表:
|
||||
|
||||
```sql
|
||||
-- 删除已回滚版本的记录
|
||||
DELETE FROM flyway_schema_history WHERE version > '目标版本号';
|
||||
|
||||
-- 例如回滚到 V8(删除 V9)
|
||||
DELETE FROM flyway_schema_history WHERE version = '9';
|
||||
```
|
||||
|
||||
## 紧急回滚流程
|
||||
|
||||
1. 停止应用服务
|
||||
2. `mysqldump -u root -p pms_base > pms_base_backup.sql`
|
||||
3. 执行回滚 SQL(按版本逆序)
|
||||
4. 修正 `flyway_schema_history`
|
||||
5. 部署旧版本代码
|
||||
6. 启动应用服务并验证
|
||||
|
|
@ -5,7 +5,7 @@ component: backend (pms-base, pms-charge, pms-operation, pms-common)
|
|||
severity: critical/warning/info
|
||||
created: 2026-07-01
|
||||
branch: feat/multi-property-config
|
||||
status: open
|
||||
status: resolved
|
||||
---
|
||||
|
||||
# feat/multi-property-config 代码审查遗留问题
|
||||
|
|
@ -149,3 +149,28 @@ status: open
|
|||
3. **第二个 Sprint**:CR #4(事务内 Feign)+ CR #7(乐观锁)+ CR #5(CAM 幂等,待补 Controller 时)
|
||||
4. **第三个 Sprint**:CR #8-13(竞态条件批量处理)+ CR #10(回滚文档)
|
||||
5. **持续改进**:CR #14-17(Info 级别)
|
||||
|
||||
## 修复状态总览(2026-07-01 更新)
|
||||
|
||||
### 已修复(fix/code-review-residuals 分支)
|
||||
- ✅ CR #1:13 个 Service 添加 ProjectSecurityChecker projectId 归属校验
|
||||
- ✅ CR #2:4 个 Service create 强制 setProjectId(feat 分支修复)
|
||||
- ✅ CR #3:4 个 Service update 改用白名单字段构造
|
||||
- ✅ CR #4:CamAllocationService + ChargeBillServiceImpl Feign 调用移出事务
|
||||
- ✅ CR #5:CAM 分摊添加幂等检查
|
||||
- ✅ CR #6:LeaseContractServiceImpl.renew 添加状态+日期校验(feat 分支修复)
|
||||
- ✅ CR #12:pms-operation SafetyInspectionServiceImpl 添加 projectId 校验
|
||||
|
||||
### 已修复(fix/code-review-p1-p2 分支)
|
||||
- ✅ CR #7:7 个状态实体添加 @Version 乐观锁 + V9 迁移脚本
|
||||
- ✅ CR #8:assignPackages catch DuplicateKeyException 幂等处理
|
||||
- ✅ CR #9:signUp 原子更新防超员 `current_count = current_count + 1 WHERE current_count < max_participants`
|
||||
- ✅ CR #10:Flyway 迁移回滚操作手册(见 docs/solutions/2026-07-01-flyway-migration-rollback-guide.md)
|
||||
- ✅ CR #13:disableProjectItem catch DuplicateKeyException 后重试 update
|
||||
- ✅ CR #14:RoomBatchQueryRequest 添加 @Size(max=500) 限制
|
||||
- ✅ CR #15:8 个 application.yml 添加生产环境日志级别注释
|
||||
- ✅ CR #17:InternalController 身份证号脱敏(前6后4,中间****)
|
||||
|
||||
### 已评估(不修改代码)
|
||||
- ⚠️ CR #11:维持无 FK 架构。理由:微服务架构下 FK 不跨服务;应用层已通过 ProjectSecurityChecker 校验(CR #1 修复);项目既有约定为无 FK。建议在 CI 中加入数据完整性检查脚本作为补充。
|
||||
- ⚠️ CR #16:Internal 接口鉴权建议在基础设施 Sprint 中统一处理。方案:IP 白名单(K8s NetworkPolicy)或 header token 校验(Spring Interceptor)。当前依赖网关保护,Pod 间网络隔离作为临时措施。
|
||||
|
|
|
|||
Loading…
Reference in New Issue