feat(mci-server): Phase 1 数据模型调整
- 新增 V11 媒体文件表 mci_message_media 及实体/Mapper - 新增 V12 建群任务表 mci_conversation_create_task 及实体/Mapper - 新增 V13 策略组表 mci_policy_group 及实体/Mapper/Service/Controller - V14 将 mci_flow_definition.flow_type 由 VARCHAR 迁移为 TINYINT,实体改为 Integer - FlowDefinitionService 兼容字符串/数值 flowType,FlowEngine 按数值 1 过滤入站流程 - MybatisPlusConfig 增加 message、policy 仓库扫描包
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
package com.sino.mci.channel.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.sino.mci.shared.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("mci_conversation_create_task")
|
||||
public class ConversationCreateTask extends BaseEntity {
|
||||
|
||||
private String tenantCode;
|
||||
private Integer taskType;
|
||||
private String channelType;
|
||||
private Long accountId;
|
||||
private Long conversationId;
|
||||
private String participantIds;
|
||||
private Integer taskStatus;
|
||||
private String channelResult;
|
||||
private Integer retryCount;
|
||||
private LocalDateTime finishTime;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.channel.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.channel.entity.ConversationCreateTask;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ConversationCreateTaskMapper extends BaseMapper<ConversationCreateTask> {
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
@MapperScan({"com.sino.mci.admin.repository", "com.sino.mci.channel.repository", "com.sino.mci.crm.repository", "com.sino.mci.flow.repository", "com.sino.mci.inbound.repository", "com.sino.mci.send.repository"})
|
||||
@MapperScan({"com.sino.mci.admin.repository", "com.sino.mci.channel.repository", "com.sino.mci.crm.repository", "com.sino.mci.flow.repository", "com.sino.mci.inbound.repository", "com.sino.mci.message.repository", "com.sino.mci.policy.repository", "com.sino.mci.send.repository"})
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -13,7 +13,7 @@ public class FlowDefinition extends BaseEntity {
|
||||
private String tenantCode;
|
||||
private String flowCode;
|
||||
private String flowName;
|
||||
private String flowType;
|
||||
private Integer flowType;
|
||||
private String definitionJson;
|
||||
private Integer status;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class FlowDefinitionService {
|
||||
flow.setTenantCode(tenantCode);
|
||||
flow.setFlowCode(request.getFlowCode());
|
||||
flow.setFlowName(request.getFlowName());
|
||||
flow.setFlowType(request.getFlowType());
|
||||
flow.setFlowType(parseFlowType(request.getFlowType()));
|
||||
flow.setDefinitionJson(JsonUtils.toJson(request.getDefinitionJson()));
|
||||
flow.setStatus(0);
|
||||
flowDefinitionMapper.insert(flow);
|
||||
@@ -62,7 +62,7 @@ public class FlowDefinitionService {
|
||||
public FlowDefinition updateFlow(String tenantCode, String flowCode, CreateFlowRequest request) {
|
||||
FlowDefinition flow = getFlow(tenantCode, flowCode);
|
||||
flow.setFlowName(request.getFlowName());
|
||||
flow.setFlowType(request.getFlowType());
|
||||
flow.setFlowType(parseFlowType(request.getFlowType()));
|
||||
flow.setDefinitionJson(JsonUtils.toJson(request.getDefinitionJson()));
|
||||
flowDefinitionMapper.updateById(flow);
|
||||
return flow;
|
||||
@@ -81,4 +81,16 @@ public class FlowDefinitionService {
|
||||
flow.setStatus(0);
|
||||
flowDefinitionMapper.updateById(flow);
|
||||
}
|
||||
|
||||
private Integer parseFlowType(String flowType) {
|
||||
if (flowType == null || flowType.isBlank()) {
|
||||
return 1;
|
||||
}
|
||||
String normalized = flowType.trim().toLowerCase();
|
||||
return switch (normalized) {
|
||||
case "inbound", "1" -> 1;
|
||||
case "ivr", "2" -> 2;
|
||||
default -> 1;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class FlowEngine {
|
||||
List<FlowDefinition> flows = flowDefinitionMapper.selectList(
|
||||
new LambdaQueryWrapper<FlowDefinition>()
|
||||
.eq(FlowDefinition::getTenantCode, tenantCode)
|
||||
.eq(FlowDefinition::getFlowType, "inbound")
|
||||
.eq(FlowDefinition::getFlowType, 1)
|
||||
.eq(FlowDefinition::getStatus, 1)
|
||||
.orderByDesc(FlowDefinition::getUpdateTime));
|
||||
return flows.isEmpty() ? null : flows.get(0);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.sino.mci.message.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.sino.mci.shared.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("mci_message_media")
|
||||
public class MessageMedia extends BaseEntity {
|
||||
|
||||
private String tenantCode;
|
||||
private String msgid;
|
||||
private String sdkfileid;
|
||||
private String mediaType;
|
||||
private String fileName;
|
||||
private Long fileSize;
|
||||
private String md5sum;
|
||||
private Integer storageType;
|
||||
private String bucket;
|
||||
private String objectKey;
|
||||
private String accessUrl;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.message.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.message.entity.MessageMedia;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface MessageMediaMapper extends BaseMapper<MessageMedia> {
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.sino.mci.policy.controller;
|
||||
|
||||
import com.sino.mci.policy.dto.CreatePolicyGroupRequest;
|
||||
import com.sino.mci.policy.dto.UpdatePolicyGroupRequest;
|
||||
import com.sino.mci.policy.dto.UpdatePolicyGroupStatusRequest;
|
||||
import com.sino.mci.policy.entity.PolicyGroup;
|
||||
import com.sino.mci.policy.service.PolicyGroupService;
|
||||
import com.sino.mci.shared.web.ApiResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/policy-group")
|
||||
@RequiredArgsConstructor
|
||||
public class PolicyGroupController {
|
||||
|
||||
private final PolicyGroupService policyGroupService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<PolicyGroup> createPolicyGroup(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@Valid @RequestBody CreatePolicyGroupRequest request) {
|
||||
return ApiResponse.ok(policyGroupService.createPolicyGroup(tenantCode, request));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public ApiResponse<List<PolicyGroup>> listPolicyGroups(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode) {
|
||||
return ApiResponse.ok(policyGroupService.listPolicyGroups(tenantCode));
|
||||
}
|
||||
|
||||
@GetMapping("/{groupCode}")
|
||||
public ApiResponse<PolicyGroup> getPolicyGroup(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("groupCode") String groupCode) {
|
||||
return ApiResponse.ok(policyGroupService.getPolicyGroup(tenantCode, groupCode));
|
||||
}
|
||||
|
||||
@PostMapping("/{groupCode}")
|
||||
public ApiResponse<PolicyGroup> updatePolicyGroup(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("groupCode") String groupCode,
|
||||
@Valid @RequestBody UpdatePolicyGroupRequest request) {
|
||||
return ApiResponse.ok(policyGroupService.updatePolicyGroup(tenantCode, groupCode, request));
|
||||
}
|
||||
|
||||
@PostMapping("/{groupCode}/status")
|
||||
public ApiResponse<PolicyGroup> updatePolicyGroupStatus(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("groupCode") String groupCode,
|
||||
@Valid @RequestBody UpdatePolicyGroupStatusRequest request) {
|
||||
return ApiResponse.ok(policyGroupService.updatePolicyGroupStatus(tenantCode, groupCode, request.getStatus()));
|
||||
}
|
||||
|
||||
@PostMapping("/{groupCode}/delete")
|
||||
public ApiResponse<?> deletePolicyGroup(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("groupCode") String groupCode) {
|
||||
policyGroupService.deletePolicyGroup(tenantCode, groupCode);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.sino.mci.policy.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class CreatePolicyGroupRequest {
|
||||
|
||||
@NotBlank
|
||||
private String groupCode;
|
||||
|
||||
@NotBlank
|
||||
private String groupName;
|
||||
|
||||
private String channelType;
|
||||
|
||||
@NotNull
|
||||
private Map<String, Object> policies;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sino.mci.policy.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class UpdatePolicyGroupRequest {
|
||||
|
||||
@NotBlank
|
||||
private String groupName;
|
||||
|
||||
private String channelType;
|
||||
|
||||
@NotNull
|
||||
private Map<String, Object> policies;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.sino.mci.policy.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UpdatePolicyGroupStatusRequest {
|
||||
|
||||
@NotNull
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sino.mci.policy.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.sino.mci.shared.entity.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("mci_policy_group")
|
||||
public class PolicyGroup extends BaseEntity {
|
||||
|
||||
private String tenantCode;
|
||||
private String groupCode;
|
||||
private String groupName;
|
||||
private String channelType;
|
||||
private String policies;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.policy.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.policy.entity.PolicyGroup;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PolicyGroupMapper extends BaseMapper<PolicyGroup> {
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.sino.mci.policy.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.sino.mci.exception.BusinessException;
|
||||
import com.sino.mci.policy.dto.CreatePolicyGroupRequest;
|
||||
import com.sino.mci.policy.dto.UpdatePolicyGroupRequest;
|
||||
import com.sino.mci.policy.entity.PolicyGroup;
|
||||
import com.sino.mci.policy.repository.PolicyGroupMapper;
|
||||
import com.sino.mci.shared.util.JsonUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PolicyGroupService {
|
||||
|
||||
private final PolicyGroupMapper policyGroupMapper;
|
||||
|
||||
@Transactional
|
||||
public PolicyGroup createPolicyGroup(String tenantCode, CreatePolicyGroupRequest request) {
|
||||
PolicyGroup existing = policyGroupMapper.selectOne(
|
||||
new LambdaQueryWrapper<PolicyGroup>()
|
||||
.eq(PolicyGroup::getTenantCode, tenantCode)
|
||||
.eq(PolicyGroup::getGroupCode, request.getGroupCode()));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("策略组编码已存在: " + request.getGroupCode());
|
||||
}
|
||||
|
||||
PolicyGroup group = new PolicyGroup();
|
||||
group.setTenantCode(tenantCode);
|
||||
group.setGroupCode(request.getGroupCode());
|
||||
group.setGroupName(request.getGroupName());
|
||||
group.setChannelType(request.getChannelType());
|
||||
group.setPolicies(JsonUtils.toJson(request.getPolicies()));
|
||||
group.setStatus(1);
|
||||
policyGroupMapper.insert(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
public PolicyGroup getPolicyGroup(String tenantCode, String groupCode) {
|
||||
PolicyGroup group = policyGroupMapper.selectOne(
|
||||
new LambdaQueryWrapper<PolicyGroup>()
|
||||
.eq(PolicyGroup::getTenantCode, tenantCode)
|
||||
.eq(PolicyGroup::getGroupCode, groupCode));
|
||||
if (group == null) {
|
||||
throw new BusinessException("策略组不存在: " + groupCode);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
public List<PolicyGroup> listPolicyGroups(String tenantCode) {
|
||||
return policyGroupMapper.selectList(
|
||||
new LambdaQueryWrapper<PolicyGroup>()
|
||||
.eq(PolicyGroup::getTenantCode, tenantCode)
|
||||
.orderByDesc(PolicyGroup::getCreateTime));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PolicyGroup updatePolicyGroup(String tenantCode, String groupCode, UpdatePolicyGroupRequest request) {
|
||||
PolicyGroup group = getPolicyGroup(tenantCode, groupCode);
|
||||
group.setGroupName(request.getGroupName());
|
||||
group.setChannelType(request.getChannelType());
|
||||
group.setPolicies(JsonUtils.toJson(request.getPolicies()));
|
||||
policyGroupMapper.updateById(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PolicyGroup updatePolicyGroupStatus(String tenantCode, String groupCode, Integer status) {
|
||||
PolicyGroup group = getPolicyGroup(tenantCode, groupCode);
|
||||
group.setStatus(status);
|
||||
policyGroupMapper.updateById(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deletePolicyGroup(String tenantCode, String groupCode) {
|
||||
PolicyGroup group = getPolicyGroup(tenantCode, groupCode);
|
||||
policyGroupMapper.deleteById(group.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE IF NOT EXISTS mci_message_media (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
msgid VARCHAR(128) COMMENT '关联的消息 ID',
|
||||
sdkfileid VARCHAR(256) COMMENT '企微媒体文件 ID',
|
||||
media_type VARCHAR(32) COMMENT '媒体类型:image / voice / video / file / emotion',
|
||||
file_name VARCHAR(256) COMMENT '文件名',
|
||||
file_size BIGINT COMMENT '文件大小',
|
||||
md5sum VARCHAR(64) COMMENT 'MD5',
|
||||
storage_type TINYINT NOT NULL DEFAULT 2 COMMENT '存储类型:2SeaweedFS',
|
||||
bucket VARCHAR(128) COMMENT 'SeaweedFS bucket',
|
||||
object_key VARCHAR(512) COMMENT 'SeaweedFS object key',
|
||||
access_url VARCHAR(512) COMMENT '访问 URL',
|
||||
status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0未下载 1下载中 2已下载 3下载失败',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
KEY idx_tenant_msgid (tenant_code, msgid),
|
||||
KEY idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='媒体文件表';
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE IF NOT EXISTS mci_conversation_create_task (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
task_type TINYINT NOT NULL DEFAULT 1 COMMENT '任务类型:1创建群聊 2邀请成员',
|
||||
channel_type VARCHAR(32) NOT NULL COMMENT '渠道:wecom / wechat_personal / dingtalk / feishu',
|
||||
account_id BIGINT COMMENT '使用哪个账号创建',
|
||||
conversation_id BIGINT COMMENT '创建成功后回填的会话 ID',
|
||||
participant_ids JSON COMMENT '参与者人员 ID 列表',
|
||||
task_status TINYINT NOT NULL DEFAULT 0 COMMENT '任务状态:0待执行 1执行中 2成功 3失败',
|
||||
channel_result JSON COMMENT '渠道返回结果',
|
||||
retry_count INT NOT NULL DEFAULT 0 COMMENT '重试次数',
|
||||
finish_time DATETIME COMMENT '完成时间',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
KEY idx_tenant_status (tenant_code, task_status),
|
||||
KEY idx_conversation_id (conversation_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='建群任务表';
|
||||
@@ -0,0 +1,14 @@
|
||||
CREATE TABLE IF NOT EXISTS mci_policy_group (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
group_code VARCHAR(64) NOT NULL COMMENT '策略组编码',
|
||||
group_name VARCHAR(128) NOT NULL COMMENT '策略组名称',
|
||||
channel_type VARCHAR(32) COMMENT '渠道类型:wecom / wechat_personal / dingtalk / feishu',
|
||||
policies JSON NOT NULL COMMENT '策略规则 JSON',
|
||||
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0禁用 1启用',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted TINYINT NOT NULL DEFAULT 0,
|
||||
UNIQUE KEY uk_tenant_group_code (tenant_code, group_code, deleted),
|
||||
KEY idx_tenant_status (tenant_code, status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='策略组表';
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 将流程类型的字符串值安全迁移为数值,之后改为 TINYINT 以对齐架构定义
|
||||
UPDATE mci_flow_definition
|
||||
SET flow_type = '1'
|
||||
WHERE LOWER(flow_type) = 'inbound';
|
||||
|
||||
UPDATE mci_flow_definition
|
||||
SET flow_type = '2'
|
||||
WHERE LOWER(flow_type) = 'ivr';
|
||||
|
||||
-- 兜底:任何未识别的旧值统一按入站处理
|
||||
UPDATE mci_flow_definition
|
||||
SET flow_type = '1'
|
||||
WHERE flow_type NOT IN ('1', '2');
|
||||
|
||||
ALTER TABLE mci_flow_definition
|
||||
MODIFY COLUMN flow_type TINYINT NOT NULL DEFAULT 1 COMMENT '流程类型:1入站 2IVR';
|
||||
Reference in New Issue
Block a user