Skip to content

内容库版本分支管理功能技术文档

1. 概述

1.1 功能目标

基于现有的内容库版本管理架构,扩展实现类似Git的分支管理功能,支持多分支并行开发、分支合并、冲突解决等核心功能。

1.2 设计原则

  • 核心思想:在数据源和版本中间增加一个"分支"层级

    原本:数据源->版本->内容

    改造完之后:数据源->分支->版本->内容

  • 基于现有数据结构,最小化改动

  • 支持多分支并行开发

  • 提供完整的版本历史追踪

  • 支持分支合并和冲突解决

2. 现有架构分析

2.1 核心数据结构

2.1.1 ContentVersion(版本表)

java
@Document(collection = "cms_version")
public class ContentVersion {
    private String id;                    // 版本ID
    private Integer datasourceId;         // 数据源ID
    private Integer version;              // 版本号(递增)
    private VersionSourceTypeEnum sourceType;  // 版本来源类型
    private VersionCompletionEnum completion;  // 版本完成度
    private Boolean isRelease;            // 是否为正式版本
    private String hash;                  // 7位短hash
    private ContentVersionStatusEnum status;   // 状态:ACTIVE/ARCHIVED
    private LocalDateTime createTime;     // 创建时间
    private Long creatorId;               // 创建人ID
}

2.1.2 Content(内容表)

java
@Document(collection = "cms_content")
public class Content {
    private String id;                    // 内容ID
    private Integer datasourceId;         // 数据源ID
    private Version version;              // 版本信息
    private String key;                   // 内容唯一标识
    private ContentStatusEnum status;     // 内容状态
    private Source source;                // 源文本信息
    private List<Translation> translations; // 翻译列表
    private Metadata metadata;            // 元数据
    private Integer isDeleted;            // 是否删除
    // ... 其他字段
}

2.2 现有版本管理机制

  • 版本类型:INIT(初始化)、SYNC(同步)、UPDATE(更新)、ORDER_DELIVERY(订单交付)
  • 版本状态:ACTIVE(活跃)、ARCHIVED(归档)
  • 版本完成度:PART(部分完成)、FULL(全部完成)
  • 版本限制:最多保留3个活跃的正式版本

3. 分支管理功能设计

3.1 数据结构扩展

3.1.1 新增分支表(ContentBranch)

java
@Document(collection = "cms_branch")
public class ContentBranch {
    @Id
    private String id;                    // 分支ID
    
    private Integer datasourceId;         // 数据源ID
    private String name;                  // 分支名称
    private String description;           // 分支描述
    private String baseVersionId;         // 基于的版本ID
    private String currentVersionId;      // 当前版本ID
    
    @Enumerated(EnumType.STRING)
    private BranchStatusEnum status;      // 分支状态:ACTIVE/MERGED/DELETED
    
    private Boolean isDefault;            // 是否为默认分支
    
    private LocalDateTime createTime;     // 创建时间
    private LocalDateTime updateTime;     // 更新时间
    private Long creatorId;               // 创建人ID
    private Long updaterId;               // 更新人ID
}

3.1.2 扩展ContentVersion表

java
// 在现有ContentVersion基础上新增字段
public class ContentVersion {
    // ... 现有字段
    
    private String branchId;              // 所属分支ID
    private List<String> mergeFromBranchIds; // 合并来源分支ID列表
    private MergeStatusEnum mergeStatus;  // 合并状态:NONE/MERGING/MERGED/CONFLICT
    private String mergeConflictInfo;     // 合并冲突信息(JSON格式,也就是将检测冲突的结果保存起来)
}

3.1.3 扩展DataSource表

java
// 在现有DataSource基础上新增字段
@Entity
@Table(name = "t_cms_datasource")
public class DataSource {
    // ... 现有字段
    
    /**
     * 当前选中的分支ID
     */
    private String currentBranchId;
    
    // ... 其他现有字段
}

3.1.4 新增枚举类型

java
// 分支状态枚举
public enum BranchStatusEnum {
    ACTIVE,     // 活跃
    MERGED,     // 已合并
    DELETED     // 已删除
}

// 合并状态枚举
public enum MergeStatusEnum {
    NONE,       // 无合并
    MERGING,    // 合并中
    MERGED,     // 已合并
    CONFLICT    // 冲突
}

3.2 分支管理核心功能

3.2.1 分支创建

java
public class BranchService {
    
    /**
     * 创建新分支
     */
    public ContentBranch createBranch(CreateBranchParam param) {
        // 1. 验证基础版本是否存在
        ContentVersion baseVersion = contentSupport.getVersionById(param.getBaseVersionId());
        if (baseVersion == null) {
            throw new BusinessException(ExceptionEnum.VERSION_NOT_FOUND);
        }
        
        // 2. 验证分支名称唯一性
        if (isBranchNameExists(param.getDatasourceId(), param.getName())) {
            throw new BusinessException(ExceptionEnum.BRANCH_NAME_EXISTS);
        }
        
        // 3. 创建分支记录
        ContentBranch branch = new ContentBranch();
        branch.setDatasourceId(param.getDatasourceId());
        branch.setName(param.getName());
        branch.setDescription(param.getDescription());
        branch.setBaseVersionId(param.getBaseVersionId());
        branch.setCurrentVersionId(param.getBaseVersionId());
        branch.setStatus(BranchStatusEnum.ACTIVE);
        branch.setIsDefault(false);
        branch.setIsProtected(false);
        branch.setCreateTime(LocalDateTime.now());
        branch.setCreatorId(param.getUserId());
        
        return branchRepository.save(branch);
    }
}

3.2.2 分支切换(更新数据源表t_cms_datasource的currentBranchId字段)

java
/**
 * 切换到指定分支
 */
public void switchBranch(Integer datasourceId, String branchId) {
    // 1. 验证分支存在且活跃
    ContentBranch branch = branchRepository.findById(branchId)
        .orElseThrow(() -> new BusinessException(ExceptionEnum.BRANCH_NOT_FOUND));
    
    if (!BranchStatusEnum.ACTIVE.equals(branch.getStatus())) {
        throw new BusinessException(ExceptionEnum.BRANCH_NOT_ACTIVE);
    }
    
    // 2. 更新数据源的当前分支
    DataSource dataSource = datasourceRepository.findById(datasourceId)
        .orElseThrow(() -> new BusinessException(ExceptionEnum.DATASOURCE_NOT_FOUND));
    dataSource.setCurrentBranchId(branchId);
    dataSource.setGmtModified(LocalDateTime.now());
    datasourceRepository.save(dataSource);
}

3.2.3 分支合并(需不需要用颜色区分更新、新增、删除)

3.2.3.1 合并冲突定义

冲突类型定义: 在内容库分支管理中,合并冲突主要发生在以下情况:

1. 内容冲突(Content Conflict)

  • 定义:两个分支对同一个内容项(相同的key)进行了不同的修改
  • 触发条件
    • 源分支和目标分支都包含相同key的内容
    • 两个分支的内容在以下任一字段存在差异:
      • source.text(源文本)
      • translations(翻译内容)
      • metadata(元数据)
      • dynamicFields(动态字段)

2. 结构冲突(Structural Conflict)

以下三种情况算不算冲突?待确定

  • 定义:两个分支对内容结构进行了不兼容的修改
  • 触发条件
    • 源分支删除了目标分支中仍存在的内容
    • 源分支和目标分支对同一内容的翻译语言配置不同
    • 源分支和目标分支的元数据结构不一致

结构冲突示例:

示例1:内容删除冲突

json
// 目标分支(main)中的内容
{
  "key": "welcome_message",
  "source": {
    "text": "Welcome to our application",
    "language": "en-US"
  },
  "translations": [
    {
      "language": "zh-CN",
      "text": "欢迎使用我们的应用",
      "status": "TRANSLATED"
    }
  ]
}

// 源分支(feature/cleanup)中删除了该内容
// 结果:结构冲突 - 目标分支存在内容,源分支已删除

示例2:翻译语言配置冲突

json
// 目标分支(main)中的内容
{
  "key": "product_description",
  "source": {
    "text": "High quality product",
    "language": "en-US"
  },
  "translations": [
    {
      "language": "zh-CN",
      "text": "高质量产品",
      "status": "TRANSLATED"
    },
    {
      "language": "ja-JP",
      "text": "高品質製品",
      "status": "TRANSLATED"
    }
  ]
}

// 源分支(feature/internationalization)中的内容
{
  "key": "product_description",
  "source": {
    "text": "High quality product",
    "language": "en-US"
  },
  "translations": [
    {
      "language": "zh-CN",
      "text": "高质量产品",
      "status": "TRANSLATED"
    },
    {
      "language": "ko-KR",
      "text": "고품질 제품",
      "status": "TRANSLATED"
    }
  ]
}

// 结果:结构冲突 - 翻译语言配置不一致(ja-JP vs ko-KR)

示例3:元数据结构冲突

json
// 目标分支(main)中的内容
{
  "key": "user_guide",
  "source": {
    "text": "User guide content",
    "language": "en-US"
  },
  "metadata": {
    "ueKey": "user_guide",
    "ueNamespace": "ui",
    "sheetName": "UserGuide",
    "category": "documentation"
  }
}

// 源分支(feature/metadata-enhancement)中的内容
{
  "key": "user_guide",
  "source": {
    "text": "User guide content",
    "language": "en-US"
  },
  "metadata": {
    "ueKey": "user_guide",
    "ueNamespace": "ui",
    "sheetName": "UserGuide",
    "priority": "high",
    "tags": ["guide", "user"]
  }
}

// 结果:结构冲突 - 元数据字段结构不一致(category vs priority+tags)
3.2.3.2 冲突检测规则

冲突检测算法:

java
/**
 * 冲突检测规则定义
 */
public class ConflictDetectionRules {
    
    /**
     * 检测两个内容是否存在冲突
     */
    public static boolean hasConflict(Content sourceContent, Content targetContent) {
        // 1. 源文本冲突检测
        if (hasSourceTextConflict(sourceContent, targetContent)) {
            return true;
        }
        
        // 2. 翻译内容冲突检测
        if (hasTranslationConflict(sourceContent, targetContent)) {
            return true;
        }
        
        // 3. 元数据冲突检测
        if (hasMetadataConflict(sourceContent, targetContent)) {
            return true;
        }
        
        // 4. 动态字段冲突检测
        if (hasDynamicFieldsConflict(sourceContent, targetContent)) {
            return true;
        }
        
        return false;
    }
    
    /**
     * 源文本冲突检测
     */
    private static boolean hasSourceTextConflict(Content source, Content target) {
        String sourceText = source.getSource().getText();
        String targetText = target.getSource().getText();
        return !Objects.equals(sourceText, targetText);
    }
    
    /**
     * 翻译内容冲突检测
     */
    private static boolean hasTranslationConflict(Content source, Content target) {
        List<Content.Translation> sourceTranslations = source.getTranslations();
        List<Content.Translation> targetTranslations = target.getTranslations();
        
        // 检查翻译语言是否一致
        Set<String> sourceLanguages = sourceTranslations.stream()
            .map(Content.Translation::getLanguage)
            .collect(Collectors.toSet());
        Set<String> targetLanguages = targetTranslations.stream()
            .map(Content.Translation::getLanguage)
            .collect(Collectors.toSet());
        
        if (!sourceLanguages.equals(targetLanguages)) {
            return true;
        }
        
        // 检查相同语言的翻译内容是否一致
        for (Content.Translation sourceTrans : sourceTranslations) {
            Content.Translation targetTrans = targetTranslations.stream()
                .filter(t -> t.getLanguage().equals(sourceTrans.getLanguage()))
                .findFirst()
                .orElse(null);
            
            if (targetTrans != null && !Objects.equals(sourceTrans.getText(), targetTrans.getText())) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 元数据冲突检测
     */
    private static boolean hasMetadataConflict(Content source, Content target) {
        Content.Metadata sourceMetadata = source.getMetadata();
        Content.Metadata targetMetadata = target.getMetadata();
        
        // 检查关键元数据字段
        return !Objects.equals(sourceMetadata.getUeKey(), targetMetadata.getUeKey()) ||
               !Objects.equals(sourceMetadata.getUeNamespace(), targetMetadata.getUeNamespace()) ||
               !Objects.equals(sourceMetadata.getSheetName(), targetMetadata.getSheetName());
    }
    
    /**
     * 动态字段冲突检测
     */
    private static boolean hasDynamicFieldsConflict(Content source, Content target) {
        Map<String, Object> sourceFields = source.getDynamicFields();
        Map<String, Object> targetFields = target.getDynamicFields();
        
        if (sourceFields == null && targetFields == null) {
            return false;
        }
        
        if (sourceFields == null || targetFields == null) {
            return true;
        }
        
        return !sourceFields.equals(targetFields);
    }
}
3.2.3.3 冲突解决策略

冲突解决选项:

  1. 保留源分支内容(KEEP_SOURCE):使用源分支的完整内容
  2. 保留目标分支内容(KEEP_TARGET):使用目标分支的完整内容
  3. 手动合并(MANUAL_MERGE):用户手动选择合并策略(难实现!)暂不实现
  4. 智能合并(SMART_MERGE):系统自动选择最优合并策略 暂不实现

智能合并规则:

  • 如果只有一个分支修改了某个字段,使用修改后的值
  • 如果两个分支都修改了同一字段,优先使用目标分支的值
  • 对于翻译内容,合并所有语言的翻译,冲突时使用目标分支的值
3.2.3.4 分支合并实现
java
/**
 * 合并分支
 */
public MergeResult mergeBranch(MergeBranchParam param) {
    // 1. 验证源分支和目标分支
    ContentBranch sourceBranch = validateBranch(param.getSourceBranchId());
    ContentBranch targetBranch = validateBranch(param.getTargetBranchId());
    
    // 2. 检查是否有冲突
    List<ContentConflict> conflicts = detectConflicts(sourceBranch, targetBranch);
    
    if (!conflicts.isEmpty()) {
        // 存在冲突,返回冲突信息
        return MergeResult.conflict(conflicts);
    }
    
    // 3. 执行合并
    return executeMerge(sourceBranch, targetBranch, param.getUserId());
}

/**
 * 检测冲突
 */
private List<ContentConflict> detectConflicts(ContentBranch sourceBranch, ContentBranch targetBranch) {
    List<ContentConflict> conflicts = new ArrayList<>();
    
    // 获取两个分支的最新内容
    List<Content> sourceContents = contentSupport.getContentListByBranch(sourceBranch.getId());
    List<Content> targetContents = contentSupport.getContentListByBranch(targetBranch.getId());
    
    // 构建key到content的映射
    Map<String, Content> sourceMap = sourceContents.stream()
        .collect(Collectors.toMap(Content::getKey, c -> c));
    Map<String, Content> targetMap = targetContents.stream()
        .collect(Collectors.toMap(Content::getKey, c -> c));
    
    // 检测冲突
    for (String key : sourceMap.keySet()) {
        if (targetMap.containsKey(key)) {
            Content sourceContent = sourceMap.get(key);
            Content targetContent = targetMap.get(key);
            
            // 使用冲突检测规则检查是否有冲突
            if (ConflictDetectionRules.hasConflict(sourceContent, targetContent)) {
                conflicts.add(new ContentConflict(key, sourceContent, targetContent));
            }
        }
    }
    
    return conflicts;
}

/**
 * 执行合并
 */
private MergeResult executeMerge(ContentBranch sourceBranch, ContentBranch targetBranch, Long userId) {
    // 1. 创建新的合并版本
    ContentVersion mergeVersion = createMergeVersion(sourceBranch, targetBranch, userId);
    
    // 2. 合并内容
    List<Content> mergedContents = mergeContents(sourceBranch, targetBranch, mergeVersion);
    
    // 3. 保存合并结果
    contentSupport.saveContents(
        targetBranch.getDatasourceId(),
        null,
        null,
        TaskTypeEnum.MERGE,
        VersionCompletionEnum.FULL,
        true,
        mergedContents,
        null,
        null,
        userId,
        null
    );
    
    return MergeResult.success(mergeVersion.getId(), mergedContents.size());
}

3.2.4 冲突解决

java
/**
 * 解决合并冲突
 */
public void resolveConflict(ResolveConflictParam param) {
    // 1. 验证冲突是否存在
    ContentConflict conflict = conflictRepository.findById(param.getConflictId())
        .orElseThrow(() -> new BusinessException(ExceptionEnum.CONFLICT_NOT_FOUND));
    
    // 2. 根据用户选择解决冲突
    Content resolvedContent = resolveConflictContent(conflict, param.getResolution());
    
    // 3. 更新内容
    contentSupport.updateContent(resolvedContent);
    
    // 4. 标记冲突为已解决
    conflict.setStatus(ConflictStatusEnum.RESOLVED);
    conflict.setResolvedBy(param.getUserId());
    conflict.setResolvedAt(LocalDateTime.now());
    conflictRepository.save(conflict);
}

/**
 * 根据解决策略解析冲突内容
 */
private Content resolveConflictContent(ContentConflict conflict, ConflictResolution resolution) {
    switch (resolution.getType()) {
        case KEEP_SOURCE:
            return conflict.getSourceContent();
        case KEEP_TARGET:
            return conflict.getTargetContent();
        case MANUAL_MERGE:
            return resolution.getCustomContent();
        case SMART_MERGE:
            return smartMergeContent(conflict.getSourceContent(), conflict.getTargetContent());
        default:
            throw new BusinessException(ExceptionEnum.INVALID_RESOLUTION_TYPE);
    }
}

/**
 * 智能合并内容
 */
private Content smartMergeContent(Content source, Content target) {
    Content merged = new Content();
    
    // 复制基础字段
    merged.setId(target.getId());
    merged.setKey(target.getKey());
    merged.setDatasourceId(target.getDatasourceId());
    
    // 智能合并源文本
    merged.setSource(smartMergeSource(source.getSource(), target.getSource()));
    
    // 智能合并翻译
    merged.setTranslations(smartMergeTranslations(source.getTranslations(), target.getTranslations()));
    
    // 智能合并元数据
    merged.setMetadata(smartMergeMetadata(source.getMetadata(), target.getMetadata()));
    
    // 智能合并动态字段
    merged.setDynamicFields(smartMergeDynamicFields(source.getDynamicFields(), target.getDynamicFields()));
    
    return merged;
}

3.3 数据迁移策略(尽量不迁移,舍弃掉之前所有数据)

3.3.1 现有数据迁移

java
/**
 * 迁移现有数据到分支管理
 */
public void migrateToBranchManagement(Integer datasourceId) {
    // 1. 创建默认分支
    ContentBranch defaultBranch = createDefaultBranch(datasourceId);
    
    // 2. 为现有版本添加分支信息
    List<ContentVersion> versions = contentSupport.getAllVersions(datasourceId);
    for (ContentVersion version : versions) {
        version.setBranchId(defaultBranch.getId());
        version.setMergeStatus(MergeStatusEnum.NONE);
        contentSupport.updateVersion(version);
    }
    
    // 3. 为现有内容添加分支信息
    List<Content> contents = contentSupport.getAllContents(datasourceId);
    for (Content content : contents) {
        // 内容的分支信息通过version.branchId获取
        contentSupport.updateContent(content);
    }
    
    // 4. 更新数据源的当前分支
    DataSource dataSource = datasourceRepository.findById(datasourceId)
        .orElseThrow(() -> new BusinessException(ExceptionEnum.DATASOURCE_NOT_FOUND));
    dataSource.setCurrentBranchId(defaultBranch.getId());
    dataSource.setGmtModified(LocalDateTime.now());
    datasourceRepository.save(dataSource);
}

4. API接口设计

4.1 分支管理接口

4.1.1 创建分支

接口地址: POST /api/branches

请求参数:

json
{
  "datasourceId": 123,
  "name": "feature/new-ui",
  "description": "新UI功能开发分支",
  "baseVersionId": "version_001",
  "userId": 1001
}

响应结果:

json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": "branch_001",
    "datasourceId": 123,
    "name": "feature/new-ui",
    "description": "新UI功能开发分支",
    "baseVersionId": "version_001",
    "currentVersionId": "version_001",
    "status": "ACTIVE",
    "isDefault": false,
    "isProtected": false,
    "createTime": "2024-01-01T10:00:00Z",
    "updateTime": "2024-01-01T10:00:00Z",
    "creatorId": 1001,
    "updaterId": 1001
  }
}

4.1.2 获取分支列表

接口地址: GET /api/branches?datasourceId=123

请求参数:

datasourceId: 123

响应结果:

json
{
  "code": 200,
  "message": "success",
  "data": [
    {
      "id": "branch_001",
      "name": "main",
      "description": "主分支",
      "status": "ACTIVE",
      "isDefault": true,
      "isProtected": true,
      "currentVersionId": "version_005",
      "createTime": "2024-01-01T10:00:00Z",
      "creatorName": "张三",
      "contentCount": 150
    },
    {
      "id": "branch_002",
      "name": "feature/new-ui",
      "description": "新UI功能开发分支",
      "status": "ACTIVE",
      "isDefault": false,
      "isProtected": false,
      "currentVersionId": "version_003",
      "createTime": "2024-01-02T14:30:00Z",
      "creatorName": "李四",
      "contentCount": 25
    }
  ]
}

4.1.3 切换分支

接口地址: POST /api/branches/switch

请求参数:

json
{
  "datasourceId": 123,
  "branchId": "branch_002"
}

响应结果:

json
{
  "code": 200,
  "message": "success",
  "data": null
}

4.1.4 合并分支

接口地址: POST /api/branches/merge

请求参数:

json
{
  "sourceBranchId": "branch_002",
  "targetBranchId": "branch_001",
  "mergeStrategy": "AUTO",
  "userId": 1001
}

响应结果(无冲突):

json
{
  "code": 200,
  "message": "success",
  "data": {
    "mergeId": "merge_001",
    "status": "SUCCESS",
    "mergedVersionId": "version_006",
    "mergedContentCount": 25,
    "conflictCount": 0,
    "createTime": "2024-01-03T16:45:00Z"
  }
}

响应结果(有冲突):

json
{
  "code": 200,
  "message": "success",
  "data": {
    "mergeId": "merge_002",
    "status": "CONFLICT",
    "conflictCount": 3,
    "conflicts": [
      {
        "id": "conflict_001",
        "contentKey": "welcome_message",
        "sourceContent": {
          "id": "content_001",
          "key": "welcome_message",
          "source": {
            "text": "Welcome to our application",
            "language": "en-US"
          },
          "translations": [
            {
              "language": "zh-CN",
              "text": "欢迎使用我们的应用",
              "status": "TRANSLATED"
            }
          ]
        },
        "targetContent": {
          "id": "content_002",
          "key": "welcome_message",
          "source": {
            "text": "Welcome to our new application",
            "language": "en-US"
          },
          "translations": [
            {
              "language": "zh-CN",
              "text": "欢迎使用我们的新应用",
              "status": "TRANSLATED"
            }
          ]
        }
      }
    ],
    "createTime": "2024-01-03T16:45:00Z"
  }
}

4.1.5 解决冲突

接口地址: POST /api/branches/conflicts/resolve

请求参数:

json
{
  "conflictId": "conflict_001",
  "resolution": {
    "type": "KEEP_SOURCE",
    "customContent": null
  },
  "userId": 1001
}

响应结果:

json
{
  "code": 200,
  "message": "success",
  "data": null
}

5. 数据库设计

5.1 新增集合

5.1.1 cms_branch(分支表)

javascript
{
  "_id": "branch_id",
  "datasourceId": 123,
  "name": "feature/new-ui",
  "description": "新UI功能开发分支",
  "baseVersionId": "version_001",
  "currentVersionId": "version_005",
  "status": "ACTIVE",
  "isDefault": false,
  "isProtected": false,
  "createTime": "2024-01-01T10:00:00Z",
  "updateTime": "2024-01-01T10:00:00Z",
  "creatorId": 1001,
  "updaterId": 1001
}

5.1.2 cms_conflict(冲突表)

javascript
{
  "_id": "conflict_id",
  "mergeId": "merge_001",
  "contentKey": "welcome_message",
  "sourceContent": { /* 源分支内容 */ },
  "targetContent": { /* 目标分支内容 */ },
  "status": "PENDING",
  "createTime": "2024-01-01T10:00:00Z",
  "resolvedBy": null,
  "resolvedAt": null
}

5.2 索引设计

javascript
// cms_branch 索引
db.cms_branch.createIndex({"datasourceId": 1, "name": 1}, {unique: true})
db.cms_branch.createIndex({"datasourceId": 1, "status": 1})
db.cms_branch.createIndex({"isDefault": 1})

// cms_version 新增索引
db.cms_version.createIndex({"branchId": 1, "version": -1})
db.cms_version.createIndex({"mergeFromBranchIds": 1})

// cms_conflict 索引
db.cms_conflict.createIndex({"mergeId": 1})
db.cms_conflict.createIndex({"status": 1})

6. 部署和迁移

6.1 部署步骤

  1. 数据库迁移

    • 创建新的集合:cms_branchcms_conflict
    • 为现有集合添加新字段
    • 创建必要的索引
  2. 代码部署

    • 部署新的分支管理功能代码
    • 运行数据迁移脚本
    • 验证功能正常
  3. 功能开关

    • 使用功能开关控制分支管理功能的启用
    • 逐步迁移现有数据源

6.2 数据迁移脚本

java
@Component
public class BranchMigrationService {
    
    @EventListener(ApplicationReadyEvent.class)
    public void migrateExistingData() {
        log.info("开始迁移现有数据到分支管理系统");
        
        // 获取所有数据源
        List<DataSource> dataSources = datasourceSupport.getAllDataSources();
        
        for (DataSource dataSource : dataSources) {
            try {
                migrateToBranchManagement(dataSource.getId());
                log.info("数据源 {} 迁移完成", dataSource.getId());
            } catch (Exception e) {
                log.error("数据源 {} 迁移失败: {}", dataSource.getId(), e.getMessage());
            }
        }
        
        log.info("数据迁移完成");
    }
}