Files
starRiverProperty/docs/superpowers/plans/2026-05-01-org-id-policy-fix.md
hpd840321 7b2bd307f1 Initial commit: reorganized source tree
- backend/: 13 Maven modules (cw-elevator-application, cloudwalk-cloud, intelligent-cwoscomponent, ninca-crk, etc.)
- frontend/: 4 Vue projects (elevator-front, cwos-portal, alarm-front, front_acs) + decompiled + scripts
- scripts/: build, test-env, tools (Docker Compose, service templates, API parity)
- docs/: AGENTS.md, superpowers specs, architecture docs
- .gitignore: standard Java/Maven exclusions

Moved from legacy maven-*/ root layout to backend/ organized structure.
2026-05-09 09:56:45 +08:00

24 KiB
Raw Permalink Blame History

租户访客楼层策略 org_id 粒度修复 — 实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal:tenant_visitor_floor_policy 的策略键从 business_id 改为 org_id,实现二选一语义(有策略用 allow,无策略用 floorList),修复 F1/F2/W2 问题。

Architecture: DDL 先上线(加列+改约束,不影响行为)→ 代码切换(Mapper/DAO/Service 三层的 business_id → org_id + 二选一逻辑)→ 数据迁移(运维 SQL 填 org_id)。整体改动控制在 7 个文件内,最小风险。

Tech Stack: Java 8, Spring Boot, MyBatis, MySQL 5.7

Spec: docs/superpowers/specs/2026-05-01-org-id-policy-fix-design.md


前置条件

  • Step 0: 确认分支与编译环境
git checkout -b fix/org-id-policy-granularity
cd maven-cw-elevator-application && mvn formatter:validate -Dformatter-maven-plugin.version=2.16.0

期望: formatter 校验通过。


Task 1: DDL — 策略表结构变更

Files:

  • Create: docs/sql/tenant_visitor_floor_policy_v2.sql

  • Step 1: 编写 DDL 脚本

-- 租户访客楼层策略:org_id 粒度修复
-- 执行顺序:先 DDL → 数据迁移(Task 5)→ 发应用包
-- 回滚:DROP INDEX uk_org_building, DROP COLUMN org_id, ADD UNIQUE KEY uk_biz_building (business_id, building_id)

USE `cw-elevator-application`;

-- 1. 新增 org_id 列
ALTER TABLE tenant_visitor_floor_policy
  ADD COLUMN org_id VARCHAR(32) NULL COMMENT '组织节点ID(cw_is_organization.ID)'
  AFTER business_id;

-- 2. 替换唯一约束(business_id → org_id
ALTER TABLE tenant_visitor_floor_policy
  DROP INDEX uk_biz_building,
  ADD UNIQUE KEY uk_org_building (org_id, building_id);

-- 3. 标记 business_id 为废弃
ALTER TABLE tenant_visitor_floor_policy
  MODIFY COLUMN business_id VARCHAR(64) NULL COMMENT 'DEPRECATED: 已废弃,以 org_id 为准';

-- 验证
SELECT COLUMN_NAME, COLUMN_KEY, COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'cw-elevator-application'
  AND TABLE_NAME = 'tenant_visitor_floor_policy'
ORDER BY ORDINAL_POSITION;
  • Step 2: 在开发库执行 DDL
mysql -h 192.168.3.12 -P 3307 -u root -p123456 cw-elevator-application < docs/sql/tenant_visitor_floor_policy_v2.sql

期望: 无错误,org_id 列存在,uk_org_building 索引存在,uk_biz_building 已删除。

  • Step 3: 提交
git add docs/sql/tenant_visitor_floor_policy_v2.sql
git commit -m "feat: add org_id column and uk_org_building constraint to tenant_visitor_floor_policy"

Task 2: DTO — 新增 orgId 字段

Files:

  • Modify: maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/dto/TenantVisitorFloorPolicyDto.java

  • Step 1: 添加 orgId 字段 + getter/setter

businessId 的 setter 之后插入:

// 新增字段
private String orgId;

public String getOrgId() {
    return orgId;
}

public void setOrgId(String orgId) {
    this.orgId = orgId;
}

注意:businessId 字段保留不删,兼容旧序列化。

  • Step 2: 验证编译
cd maven-cw-elevator-application && mvn compile -pl cw-elevator-application-data -am -DskipTests

期望: BUILD SUCCESS。

  • Step 3: 提交
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/dto/TenantVisitorFloorPolicyDto.java
git commit -m "feat: add orgId field to TenantVisitorFloorPolicyDto"

Task 3: Mapper — SQL 切换 business_id → org_id

Files:

  • Modify: maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/mapper/TenantVisitorFloorPolicyMapper.xml

  • Modify: maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/mapper/TenantVisitorFloorPolicyMapper.java

  • Step 1: 修改 Mapper XML — WHERE 条件 + 映射

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.cloudwalk.elevator.person.mapper.TenantVisitorFloorPolicyMapper">

    <select id="selectEnabledByOrgId" resultType="cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto">
        SELECT id,
               org_id         AS orgId,
               policy_type    AS policyType,
               allow_zone_ids AS allowZoneIds,
               building_id    AS buildingId,
               enabled        AS enabled,
               policy_version AS policyVersion
        FROM tenant_visitor_floor_policy
        WHERE org_id = #{orgId,jdbcType=VARCHAR}
          AND enabled = 1
          AND policy_type = 'INTERSECT_ALLOWLIST'
          AND (building_id IS NULL OR building_id = '')
        ORDER BY updated_at DESC, policy_version DESC
        LIMIT 1
    </select>

    <!-- 旧方法保留作历史参考(可选删除)
    <select id="selectEnabledTenantDefault" resultType="...">
        ... business_id ...
    </select>
    -->
</mapper>
  • Step 2: 修改 Mapper 接口
package cn.cloudwalk.elevator.person.mapper;

import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
import org.apache.ibatis.annotations.Param;

public interface TenantVisitorFloorPolicyMapper {

    /**
     * 按组织节点 ID 查询启用中的 INTERSECT_ALLOWLIST 策略(building_id 为空)。
     */
    TenantVisitorFloorPolicyDto selectEnabledByOrgId(@Param("orgId") String orgId);

    // 旧方法(废弃,保留以兼容编译)
    // TenantVisitorFloorPolicyDto selectEnabledTenantDefault(@Param("businessId") String businessId);
}
  • Step 3: 验证编译
cd maven-cw-elevator-application && mvn compile -pl cw-elevator-application-data -am -DskipTests

期望: BUILD SUCCESS。

  • Step 4: 提交
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/mapper/TenantVisitorFloorPolicyMapper.xml
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/mapper/TenantVisitorFloorPolicyMapper.java
git commit -m "feat: change policy query from business_id to org_id in TenantVisitorFloorPolicyMapper"

Task 4: DAO — 接口与实现切换

Files:

  • Modify: maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/dao/TenantVisitorFloorPolicyDao.java

  • Modify: maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/impl/TenantVisitorFloorPolicyDaoImpl.java

  • Step 1: 修改 DAO 接口

package cn.cloudwalk.elevator.person.dao;

import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;

public interface TenantVisitorFloorPolicyDao {

    /**
     * 按组织节点 ID 查询启用中的 INTERSECT_ALLOWLIST 策略(building_id 为空)。
     *
     * @param orgId 组织节点 IDcw_is_organization.ID
     * @return 无配置时 null
     */
    TenantVisitorFloorPolicyDto selectEnabledByOrgId(String orgId);

    // 旧方法(废弃)
    // TenantVisitorFloorPolicyDto selectEnabledTenantDefault(String businessId);
}
  • Step 2: 修改 DAO 实现
package cn.cloudwalk.elevator.person.impl;

import cn.cloudwalk.elevator.person.dao.TenantVisitorFloorPolicyDao;
import cn.cloudwalk.elevator.person.dto.TenantVisitorFloorPolicyDto;
import cn.cloudwalk.elevator.person.mapper.TenantVisitorFloorPolicyMapper;
import javax.annotation.Resource;
import org.springframework.stereotype.Repository;

@Repository
public class TenantVisitorFloorPolicyDaoImpl implements TenantVisitorFloorPolicyDao {

    @Resource
    private TenantVisitorFloorPolicyMapper tenantVisitorFloorPolicyMapper;

    @Override
    public TenantVisitorFloorPolicyDto selectEnabledByOrgId(String orgId) {
        return this.tenantVisitorFloorPolicyMapper.selectEnabledByOrgId(orgId);
    }
}
  • Step 3: 验证编译
cd maven-cw-elevator-application && mvn compile -pl cw-elevator-application-data -am -DskipTests

期望: BUILD SUCCESS。

  • Step 4: 提交
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/dao/TenantVisitorFloorPolicyDao.java
git add maven-cw-elevator-application/cw-elevator-application-data/src/main/java/cn/cloudwalk/elevator/person/impl/TenantVisitorFloorPolicyDaoImpl.java
git commit -m "feat: update DAO interface and impl to use org_id query"

Task 5: Service — addVisitor 核心逻辑重写

Files:

  • Modify: maven-cw-elevator-application/cw-elevator-application-service/src/main/java/cn/cloudwalk/elevator/person/impl/PersonRuleServiceImpl.java

这是改动最大的文件。分 3 个子步骤。

  • Step 1: 重写 addVisitor 方法(第 174-275 行)

完整替换:

@CloudwalkParamsValidate
public CloudwalkResult<Boolean> addVisitor(AcsPersonAddVisitorParam param, CloudwalkCallContext context)
    throws ServiceException {
    this.logger.info("根据被访人添加访客派梯权限开始,AcsPersonAddVisitorParam=[{}], CloudwalkCallContext=[{}]",
        JSONObject.toJSONString(param), JSONObject.toJSONString(context));
    try {
        // ===== Step 1: 获取被访人信息(UC-01/02 都需要) =====
        PersonDetailParam detailParam = new PersonDetailParam();
        detailParam.setId(param.getPersonId());
        detailParam.setBusinessId(context.getCompany().getCompanyId());
        CloudwalkResult<PersonResult> detail = this.personService.detail(detailParam, context);
        if (detail == null || !detail.isSuccess()) {
            String code = detail != null ? detail.getCode() : "76260531";
            String msg = detail != null ? detail.getMessage() : getMessage("76260531");
            return CloudwalkResult.fail(code, msg);
        }
        PersonResult personResult = (PersonResult) detail.getData();
        if (personResult == null) {
            return CloudwalkResult.fail("76260531", getMessage("76260531"));
        }
        List<String> hostFloors = personResult.getFloorList();
        if (CollectionUtils.isEmpty(hostFloors)) {
            return CloudwalkResult.fail("76260531", getMessage("76260531"));
        }

        // ===== Step 2: 按 org_id 查找策略 =====
        TenantVisitorFloorPolicyDto policy = findPolicyByOrgIds(personResult.getOrganizationIds());

        // ===== Step 3: 确定生效楼层(二选一,不求交) =====
        List<String> effectiveFloors;
        boolean callerProvidedFloors = !CollectionUtils.isEmpty(param.getFloorIds());

        if (policy != null) {
            // 有策略:直接用 allow,忽略调用方 floorIds
            effectiveFloors = resolveEffectiveFloors(
                callerProvidedFloors ? param.getFloorIds() : hostFloors,
                hostFloors, policy, param.getPersonId());
        } else {
            // 无策略:用调用方 floorIds 或 hostFloors
            effectiveFloors = callerProvidedFloors ? param.getFloorIds() : hostFloors;
            if (callerProvidedFloors) {
                // UC-02 软校验:记录不在 hostFloors 中的楼层
                Set<String> hostSet = new HashSet<>(hostFloors);
                List<String> outliers = param.getFloorIds().stream()
                    .filter(f -> !hostSet.contains(f))
                    .collect(Collectors.toList());
                if (!outliers.isEmpty()) {
                    this.logger.warn("UC-02 传入非被访人授权楼层 businessId={} personId={} outliers={}",
                        context.getCompany().getCompanyId(), param.getPersonId(), outliers);
                }
            }
        }

        if (CollectionUtils.isEmpty(effectiveFloors)) {
            return CloudwalkResult.fail("76260531", getMessage("76260531"));
        }
        param.setFloorIds(effectiveFloors);

        // ===== Step 4: 落库(不变) =====
        ZoneQueryParam zoneQueryParam = new ZoneQueryParam();
        zoneQueryParam.setId(param.getFloorIds().get(0));
        zoneQueryParam.setRowsOfPage(10);
        zoneQueryParam.setCurrentPage(1);
        CloudwalkResult<CloudwalkPageAble<ZoneResult>> zonePage = this.zoneService.page(zoneQueryParam, context);
        List<ZoneResult> zoneResults = (List<ZoneResult>) ((CloudwalkPageAble) zonePage.getData()).getDatas();
        String imageStoreId =
            this.deviceImageStoreDao.getByBuildingId(((ZoneResult) zoneResults.get(0)).getParentId());
        List<ImageRuleRefAddDto> insertList = new ArrayList<>();
        for (String floorId : param.getFloorIds()) {
            ImageRuleRefResultDto defaultRule = this.imageRuleRefDao.getDefaultByZoneId(floorId);
            ImageRuleRefAddDto addDto = new ImageRuleRefAddDto();
            addDto.setId(genUUID());
            addDto.setBusinessId(context.getCompany().getCompanyId());
            addDto.setPersonId(param.getVisitorId());
            addDto.setParentRule(defaultRule.getId());
            addDto.setName(defaultRule.getName());
            addDto.setZoneId(defaultRule.getZoneId());
            addDto.setZoneName(defaultRule.getZoneName());
            addDto.setCreateTime(Long.valueOf(System.currentTimeMillis()));
            addDto.setLastUpdateTime(Long.valueOf(System.currentTimeMillis()));
            addDto.setPersonDelete(Integer.valueOf(0));
            insertList.add(addDto);
        }
        this.logger.info("访客添加派梯权限开始,数据为=[{}]", JSONObject.toJSONString(insertList));
        if (!CollectionUtils.isEmpty(insertList)) {
            this.imageRuleRefDao.insertList(insertList);
        }
        ImageStorePersonBindParam imageStorePersonBindParam = new ImageStorePersonBindParam();
        imageStorePersonBindParam.setImageStoreId(imageStoreId);
        imageStorePersonBindParam.setPersonIds(Collections.singletonList(param.getVisitorId()));
        imageStorePersonBindParam.setNullDateIsLongTerm(Boolean.valueOf(true));
        imageStorePersonBindParam.setExpiryBeginDate(param.getBegVisitorTime());
        imageStorePersonBindParam.setExpiryEndDate(param.getEndVisitorTime());
        this.logger.info("远程调用绑定人员图库开始,imageStorePersonBindParam=[{}], CloudwalkCallContext=[{}]",
            JSONObject.toJSONString(imageStorePersonBindParam), JSONObject.toJSONString(context));
        CloudwalkResult<ImgStoreBatchBindPersonResult> bindResult =
            this.imageStorePersonService.batchBind(imageStorePersonBindParam, context);
        if (!bindResult.isSuccess()) {
            this.logger.error("远程调用绑定人员图库异常,原因:[{}],失败人员id:[{}]", bindResult.getMessage(), param.getVisitorId());
            return CloudwalkResult.fail(bindResult.getCode(), bindResult.getMessage());
        }
        UpdateGroupPersonRefParam refParam = new UpdateGroupPersonRefParam();
        refParam.setBusinessId(context.getCompany().getCompanyId());
        refParam.setPersonIds(Collections.singletonList(param.getVisitorId()));
        refParam.setImageStoreId(imageStoreId);
        this.imageStorePersonService.updateGroupPersonRef(refParam, context);
    } catch (ServiceException e) {
        throw e;
    } catch (Exception e) {
        this.logger.error("根据被访人添加访客派梯权限失败,原因:[{}]", e);
        throw new ServiceException("76260530", getMessage("76260530"));
    }
    return CloudwalkResult.success(Boolean.valueOf(true));
}
  • Step 2: 添加两个新辅助方法 + 修改 W2(JSON 日志升级)

addVisitor 方法之后插入:

/**
 * 按 org_id 查找策略,遍历 organizationIds 取第一个命中。
 */
private TenantVisitorFloorPolicyDto findPolicyByOrgIds(List<String> orgIds) {
    if (CollectionUtils.isEmpty(orgIds)) return null;
    for (String orgId : orgIds) {
        TenantVisitorFloorPolicyDto p = this.tenantVisitorFloorPolicyDao.selectEnabledByOrgId(orgId);
        if (p != null && p.getEnabled() != null && p.getEnabled().intValue() == 1) {
            List<String> allow = parseAllowZoneIds(p.getAllowZoneIds());
            if (!CollectionUtils.isEmpty(allow)) return p;
        }
    }
    return null;
}

/**
 * 二选一:用 allow 替换 fallbackFloors。
 * 约束:allow 必须是 hostFloors 的子集,否则拒绝(76260533)。
 */
private List<String> resolveEffectiveFloors(
        List<String> fallbackFloorsUnused, List<String> hostFloors,
        TenantVisitorFloorPolicyDto policy, String personId) {
    List<String> allow = parseAllowZoneIds(policy.getAllowZoneIds());
    if (CollectionUtils.isEmpty(allow)) return fallbackFloorsUnused;

    // 安全校验:allow 中每个值必须在 hostFloors 中存在
    Set<String> hostSet = new HashSet<>(hostFloors);
    List<String> unknownAllow = allow.stream()
        .filter(a -> !hostSet.contains(a))
        .collect(Collectors.toList());
    if (!unknownAllow.isEmpty()) {
        this.logger.error("策略配置错误:allow 包含不在被访人 floorList 中的 zoneId"
            + "orgId={} policyId={} personId={} unknownAllow={} hostFloors={}",
            policy.getOrgId(), policy.getId(), personId, unknownAllow, hostFloors);
        throw new ServiceException("76260533",
            "策略配置了被访人无权访问的楼层,请联系管理员");
    }

    this.logger.info("策略生效 orgId={} policyId={} v={} allowSize={} hostSize={}",
        policy.getOrgId(), policy.getId(), policy.getPolicyVersion(),
        allow.size(), hostFloors.size());
    return allow;
}

同时修改 parseAllowZoneIds 的 catch 块(W2 修复):

// 旧代码:
// this.logger.warn("allow_zone_ids JSON 无效,按无策略处理: {}", e.getMessage());

// 新代码:
this.logger.error("allow_zone_ids JSON 无效,策略失效!policyId={} raw={}", 
    "policy.id", json, e);  // 注意:此处无法获取 policy.id,改用实际可用字段

实际实现时,parseAllowZoneIds 不持有 policyId,可以在 resolveEffectiveFloors 中调用 parseAllowZoneIds 之前先做 null 检查,将 ERROR 日志放在调用处:

private List<String> resolveEffectiveFloors(...) {
    String rawJson = policy.getAllowZoneIds();
    List<String> allow = parseAllowZoneIds(rawJson);
    if (CollectionUtils.isEmpty(allow)) {
        if (!StringUtils.isBlank(rawJson)) {
            this.logger.error("allow_zone_ids JSON 无效或为空,策略失效!orgId={} policyId={} raw={}",
                policy.getOrgId(), policy.getId(), rawJson);
        }
        return fallbackFloorsUnused;
    }
    // ... 后续校验
}
  • Step 3: 删除旧辅助方法 intersectPreserveHostOrder(不再需要)

该方法已被 resolveEffectiveFloors 替代,可删除或保留(无调用方即可)。

  • Step 4: 验证编译
cd maven-cw-elevator-application && mvn compile -DskipTests

期望: BUILD SUCCESS。

  • Step 5: 提交
git add maven-cw-elevator-application/cw-elevator-application-service/src/main/java/cn/cloudwalk/elevator/person/impl/PersonRuleServiceImpl.java
git commit -m "feat: rewrite addVisitor with org_id policy lookup and either-or semantics

- Replace business_id policy key with org_id from PersonResult.getOrganizationIds()
- Change from intersection (floorList ∩ allow) to either-or (policy? allow : floorList)
- Add resolveEffectiveFloors with allow ⊆ floorList safety check (76260533)
- UC-02 now also checks policy (policy takes precedence over caller floorIds)
- Upgrade JSON parse failure log from WARN to ERROR
- Remove unused intersectPreserveHostOrder method"

Task 6: 错误码注册(76260533

Files:

  • Check: maven-cw-elevator-application/cw-elevator-application-starter/src/main/resources/access-control.properties(或对应的 messages 资源文件)

  • Step 1: 查找错误码资源文件

grep -rn "76260531\|76260532" --include="*.properties" --include="*.xml" maven-cw-elevator-application/
  • Step 2: 在对应的 messages 文件中新增
76260533=策略配置了被访人无权访问的楼层,请联系管理员
  • Step 3: 提交
git add <错误码资源文件路径>
git commit -m "feat: add error code 76260533 for policy-host floor mismatch"

Task 7: 数据迁移 SQL

Files:

  • Create: docs/sql/tenant_visitor_floor_policy_migrate_org_id.sql

  • Step 1: 编写迁移脚本

-- 租户访客楼层策略:business_id → org_id 数据迁移
-- 前提:DDLTask 1)已执行
-- 执行方式:人工确认 org_id 对应关系后逐行执行

USE cw-elevator-application;

-- 1. 列出所有公司级组织节点(供确认)
--    在 component-organization 库执行:
--    SELECT o.ID, o.NAME, o.PARENT_ID
--    FROM `component-organization`.cw_is_organization o
--    WHERE o.BUSINESS_ID = '2524639890ba4f2cba9ba1a4eeaa4015'
--      AND o.IS_DEL = 0
--    ORDER BY o.NAME;

-- 2. 为现有策略行填入 org_id(示例:广发基金)
--    请先确认 NAME 匹配正确
UPDATE tenant_visitor_floor_policy
SET org_id = '<广发基金的 org_id>',
    business_id = NULL   -- 可选:标记 business_id 已废弃
WHERE id = 'gf_vstr_policy_guangfa_fund_001x';

-- 3. 为其他公司新增策略行(模板)
-- INSERT INTO tenant_visitor_floor_policy
-- (id, org_id, policy_type, allow_zone_ids, building_id, enabled, policy_version, remark, created_at, updated_at)
-- VALUES
-- (REPLACE(UUID(),'-',''), '<公司 org_id>', 'INTERSECT_ALLOWLIST',
--  '["<zone_id>"]', NULL, 1, 1, '', UNIX_TIMESTAMP(NOW())*1000, UNIX_TIMESTAMP(NOW())*1000);

-- 4. 验证迁移结果
SELECT id, org_id, business_id, policy_type, allow_zone_ids, enabled
FROM tenant_visitor_floor_policy
ORDER BY org_id;
  • Step 2: 提交
git add docs/sql/tenant_visitor_floor_policy_migrate_org_id.sql
git commit -m "docs: add org_id data migration SQL for tenant_visitor_floor_policy"

Task 8: 构建验证 + 发布准备

  • Step 1: 全量构建
cd maven-cw-elevator-application && mvn clean install -DskipTests

期望: BUILD SUCCESS,无编译错误。

  • Step 2: formatter 校验
cd maven-cw-elevator-application && mvn formatter:validate -Dformatter-maven-plugin.version=2.16.0

期望: 无格式化违规。

  • Step 3: 生成发布包
bash scripts/release-cw-elevator-application.sh 2.0.10
  • Step 4: 提交发布包
git add releases/
git commit -m "release: cw-elevator-application v2.0.10 with org_id policy fix"

回滚方案

步骤 操作
1. 回滚应用包 部署旧版本 JAR(用 business_id 查询的代码)
2. 回滚 DDL(可选) DROP INDEX uk_org_building; ALTER TABLE ... DROP COLUMN org_id; ADD UNIQUE KEY uk_biz_building (business_id, building_id);
3. 恢复数据(可选) UPDATE tenant_visitor_floor_policy SET business_id = '252463...' WHERE org_id IS NOT NULL;

DDL 回滚不影响旧代码行为(旧代码不查 org_id 列)。


发布顺序(生产环境)

1. DDL 上线(Task 1)      → 表结构变更,不影响线上行为
2. 数据迁移(Task 7)       → 运维手工填 org_id
3. 发应用包(Task 8       → 代码切换到 org_id 查询
4. 验证(Task 8 后)        → 抽样确认策略生效

完成检查清单

  • DDL 在开发库执行成功
  • TenantVisitorFloorPolicyDtoorgId 字段
  • Mapper XML/Java 使用 org_id 查询
  • DAO 接口/实现已切换
  • addVisitor 使用 findPolicyByOrgIds + resolveEffectiveFloors
  • W2 修复:JSON 解析失败打 ERROR 而非 WARN
  • 错误码 76260533 已在资源文件注册
  • 数据迁移 SQL 已编写
  • mvn clean install 通过
  • mvn formatter:validate 通过