feat(channel): add channel-common SPI, channel tables, subject/account APIs and WeCom callback placeholder
This commit is contained in:
22
backend/mci-channel-common/pom.xml
Normal file
22
backend/mci-channel-common/pom.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.sino</groupId>
|
||||
<artifactId>sino-mci-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>mci-channel-common</artifactId>
|
||||
<name>mci-channel-common</name>
|
||||
<description>Channel adapter SPI and shared models</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.sino.mci.channel.common.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class CallbackPayload {
|
||||
|
||||
private String channelType;
|
||||
private String eventType;
|
||||
private String rawBody;
|
||||
private Map<String, Object> normalized;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sino.mci.channel.common.dto;
|
||||
|
||||
import com.sino.mci.channel.common.model.AccountType;
|
||||
import com.sino.mci.channel.common.model.ChannelType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ChannelInitRequest {
|
||||
|
||||
private ChannelType channelType;
|
||||
private AccountType accountType;
|
||||
private Long subjectId;
|
||||
private Map<String, Object> config;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sino.mci.channel.common.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ChannelInitResult {
|
||||
|
||||
private boolean success;
|
||||
private String accountId;
|
||||
private String accountName;
|
||||
private String qrCodeUrl;
|
||||
private String message;
|
||||
private Map<String, Object> extra;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.sino.mci.channel.common.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class SendMessageRequest {
|
||||
|
||||
private String conversationId;
|
||||
private String receiverId;
|
||||
private String contentType;
|
||||
private Map<String, Object> content;
|
||||
private Map<String, Object> extra;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.sino.mci.channel.common.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SendMessageResult {
|
||||
|
||||
private boolean success;
|
||||
private String channelMessageId;
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.sino.mci.channel.common.model;
|
||||
|
||||
public enum AccountType {
|
||||
WECOM_INTERNAL(1, "企微内部账号"),
|
||||
WECOM_AGENT(2, "企微应用"),
|
||||
WECHAT_PERSONAL(3, "pywechat 个人号"),
|
||||
DINGTALK_BOT(4, "钉钉机器人"),
|
||||
FEISHU_BOT(5, "飞书机器人");
|
||||
|
||||
private final int code;
|
||||
private final String label;
|
||||
|
||||
AccountType(int code, String label) {
|
||||
this.code = code;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public static AccountType of(int code) {
|
||||
for (AccountType type : values()) {
|
||||
if (type.code == code) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.channel.common.model;
|
||||
|
||||
public enum ChannelType {
|
||||
WECOM,
|
||||
WECHAT_PERSONAL,
|
||||
DINGTALK,
|
||||
FEISHU,
|
||||
IVR
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.sino.mci.channel.common.spi;
|
||||
|
||||
import com.sino.mci.channel.common.dto.CallbackPayload;
|
||||
import com.sino.mci.channel.common.dto.ChannelInitRequest;
|
||||
import com.sino.mci.channel.common.dto.ChannelInitResult;
|
||||
import com.sino.mci.channel.common.dto.SendMessageRequest;
|
||||
import com.sino.mci.channel.common.dto.SendMessageResult;
|
||||
import com.sino.mci.channel.common.model.ChannelType;
|
||||
|
||||
public interface ChannelAdapter {
|
||||
|
||||
ChannelType getChannelType();
|
||||
|
||||
ChannelInitResult initAccount(ChannelInitRequest request);
|
||||
|
||||
SendMessageResult sendMessage(SendMessageRequest request);
|
||||
|
||||
CallbackPayload parseCallback(String rawBody);
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
<groupId>com.sino</groupId>
|
||||
<artifactId>mci-shared</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sino</groupId>
|
||||
<artifactId>mci-channel-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.sino.mci.channel.controller;
|
||||
|
||||
import com.sino.mci.channel.dto.ChannelAccountInitRequest;
|
||||
import com.sino.mci.channel.dto.CreateChannelAccountRequest;
|
||||
import com.sino.mci.channel.service.ChannelAccountService;
|
||||
import com.sino.mci.shared.web.ApiResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/channel-account")
|
||||
@RequiredArgsConstructor
|
||||
public class ChannelAccountController {
|
||||
|
||||
private final ChannelAccountService accountService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<?> createAccount(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@Valid @RequestBody CreateChannelAccountRequest request) {
|
||||
return ApiResponse.ok(accountService.createAccount(tenantCode, request));
|
||||
}
|
||||
|
||||
@PostMapping("/init")
|
||||
public ApiResponse<?> initAccount(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@Valid @RequestBody ChannelAccountInitRequest request) {
|
||||
return ApiResponse.ok(accountService.initAccount(tenantCode, request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.sino.mci.channel.controller;
|
||||
|
||||
import com.sino.mci.channel.dto.CreateChannelSubjectRequest;
|
||||
import com.sino.mci.channel.service.ChannelSubjectService;
|
||||
import com.sino.mci.shared.web.ApiResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/channel-subject")
|
||||
@RequiredArgsConstructor
|
||||
public class ChannelSubjectController {
|
||||
|
||||
private final ChannelSubjectService subjectService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<?> createSubject(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@Valid @RequestBody CreateChannelSubjectRequest request) {
|
||||
return ApiResponse.ok(subjectService.createSubject(tenantCode, request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.sino.mci.channel.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Enumeration;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/callback/wecom")
|
||||
public class WeComCallbackController {
|
||||
|
||||
@GetMapping
|
||||
public String verifyUrl(HttpServletRequest request) {
|
||||
log.info("WeCom callback URL verification: {}", request.getQueryString());
|
||||
return request.getParameter("echostr") != null ? request.getParameter("echostr") : "success";
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public String receiveCallback(@RequestBody String body, HttpServletRequest request) {
|
||||
log.info("WeCom callback received, body length={}, query={}", body.length(), request.getQueryString());
|
||||
log.debug("WeCom callback body: {}", body);
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String name = headerNames.nextElement();
|
||||
log.debug("Header {}: {}", name, request.getHeader(name));
|
||||
}
|
||||
// Phase 2: only accept and acknowledge; event parsing and session archive pull will be implemented later
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.sino.mci.channel.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class ChannelAccountInitRequest {
|
||||
|
||||
@NotBlank
|
||||
private String channelType;
|
||||
|
||||
@NotNull
|
||||
private Integer accountType;
|
||||
|
||||
private Long subjectId;
|
||||
|
||||
private Map<String, Object> config;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.sino.mci.channel.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CreateChannelAccountRequest {
|
||||
|
||||
@NotNull
|
||||
private Integer accountType;
|
||||
|
||||
@NotBlank
|
||||
private String channelType;
|
||||
|
||||
private Long subjectId;
|
||||
|
||||
@NotBlank
|
||||
private String accountId;
|
||||
|
||||
private String accountName;
|
||||
private Integer sessionArchiveEnabled;
|
||||
private String serverHost;
|
||||
private String extInfo;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.sino.mci.channel.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CreateChannelSubjectRequest {
|
||||
|
||||
@NotBlank
|
||||
private String channelType;
|
||||
|
||||
@NotBlank
|
||||
private String subjectCode;
|
||||
|
||||
@NotBlank
|
||||
private String subjectName;
|
||||
|
||||
private String corpId;
|
||||
private String corpSecret;
|
||||
private String agentId;
|
||||
private String agentSecret;
|
||||
private Integer sessionArchiveEnabled;
|
||||
private String sessionArchivePrivateKey;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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_channel_account")
|
||||
public class ChannelAccount extends BaseEntity {
|
||||
|
||||
private String tenantCode;
|
||||
private Integer accountType;
|
||||
private String channelType;
|
||||
private Long subjectId;
|
||||
private String accountId;
|
||||
private String accountName;
|
||||
private Integer sessionArchiveEnabled;
|
||||
private String serverHost;
|
||||
private Integer loginStatus;
|
||||
private Integer onlineStatus;
|
||||
private Integer riskLevel;
|
||||
private String extInfo;
|
||||
private Integer status;
|
||||
private LocalDateTime lastHeartbeat;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("mci_channel_subject")
|
||||
public class ChannelSubject extends BaseEntity {
|
||||
|
||||
private String tenantCode;
|
||||
private String channelType;
|
||||
private String subjectCode;
|
||||
private String subjectName;
|
||||
private String corpId;
|
||||
private String corpSecret;
|
||||
private String agentId;
|
||||
private String agentSecret;
|
||||
private Integer sessionArchiveEnabled;
|
||||
private String sessionArchivePrivateKey;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.channel.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.channel.entity.ChannelAccount;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ChannelAccountMapper extends BaseMapper<ChannelAccount> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.channel.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.channel.entity.ChannelSubject;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ChannelSubjectMapper extends BaseMapper<ChannelSubject> {
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.sino.mci.channel.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.sino.mci.channel.dto.ChannelAccountInitRequest;
|
||||
import com.sino.mci.channel.dto.CreateChannelAccountRequest;
|
||||
import com.sino.mci.channel.entity.ChannelAccount;
|
||||
import com.sino.mci.channel.entity.ChannelSubject;
|
||||
import com.sino.mci.channel.repository.ChannelAccountMapper;
|
||||
import com.sino.mci.channel.repository.ChannelSubjectMapper;
|
||||
import com.sino.mci.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ChannelAccountService {
|
||||
|
||||
private final ChannelAccountMapper accountMapper;
|
||||
private final ChannelSubjectMapper subjectMapper;
|
||||
|
||||
@Transactional
|
||||
public ChannelAccount createAccount(String tenantCode, CreateChannelAccountRequest request) {
|
||||
if (request.getSubjectId() != null) {
|
||||
ChannelSubject subject = subjectMapper.selectById(request.getSubjectId());
|
||||
if (subject == null) {
|
||||
throw new BusinessException("渠道主体不存在");
|
||||
}
|
||||
}
|
||||
|
||||
ChannelAccount existing = accountMapper.selectOne(
|
||||
new LambdaQueryWrapper<ChannelAccount>()
|
||||
.eq(ChannelAccount::getTenantCode, tenantCode)
|
||||
.eq(ChannelAccount::getChannelType, request.getChannelType())
|
||||
.eq(ChannelAccount::getAccountId, request.getAccountId()));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("渠道账号已存在: " + request.getAccountId());
|
||||
}
|
||||
|
||||
ChannelAccount account = new ChannelAccount();
|
||||
account.setTenantCode(tenantCode);
|
||||
account.setAccountType(request.getAccountType());
|
||||
account.setChannelType(request.getChannelType());
|
||||
account.setSubjectId(request.getSubjectId());
|
||||
account.setAccountId(request.getAccountId());
|
||||
account.setAccountName(request.getAccountName());
|
||||
account.setSessionArchiveEnabled(request.getSessionArchiveEnabled() != null ? request.getSessionArchiveEnabled() : 0);
|
||||
account.setServerHost(request.getServerHost());
|
||||
account.setExtInfo(request.getExtInfo());
|
||||
account.setLoginStatus(0);
|
||||
account.setOnlineStatus(0);
|
||||
account.setRiskLevel(0);
|
||||
account.setStatus(1);
|
||||
accountMapper.insert(account);
|
||||
return account;
|
||||
}
|
||||
|
||||
public ChannelAccount initAccount(String tenantCode, ChannelAccountInitRequest request) {
|
||||
// Phase 2 placeholder: actual adapter dispatch will be implemented later
|
||||
throw new BusinessException("渠道账号初始化适配器尚未实现: " + request.getChannelType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.sino.mci.channel.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.sino.mci.channel.dto.CreateChannelSubjectRequest;
|
||||
import com.sino.mci.channel.entity.ChannelSubject;
|
||||
import com.sino.mci.channel.repository.ChannelSubjectMapper;
|
||||
import com.sino.mci.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ChannelSubjectService {
|
||||
|
||||
private final ChannelSubjectMapper subjectMapper;
|
||||
|
||||
@Transactional
|
||||
public ChannelSubject createSubject(String tenantCode, CreateChannelSubjectRequest request) {
|
||||
ChannelSubject existing = subjectMapper.selectOne(
|
||||
new LambdaQueryWrapper<ChannelSubject>()
|
||||
.eq(ChannelSubject::getTenantCode, tenantCode)
|
||||
.eq(ChannelSubject::getChannelType, request.getChannelType())
|
||||
.eq(ChannelSubject::getSubjectCode, request.getSubjectCode()));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("渠道主体已存在: " + request.getSubjectCode());
|
||||
}
|
||||
|
||||
ChannelSubject subject = new ChannelSubject();
|
||||
subject.setTenantCode(tenantCode);
|
||||
subject.setChannelType(request.getChannelType());
|
||||
subject.setSubjectCode(request.getSubjectCode());
|
||||
subject.setSubjectName(request.getSubjectName());
|
||||
subject.setCorpId(request.getCorpId());
|
||||
subject.setCorpSecret(request.getCorpSecret());
|
||||
subject.setAgentId(request.getAgentId());
|
||||
subject.setAgentSecret(request.getAgentSecret());
|
||||
subject.setSessionArchiveEnabled(request.getSessionArchiveEnabled() != null ? request.getSessionArchiveEnabled() : 0);
|
||||
subject.setSessionArchivePrivateKey(request.getSessionArchivePrivateKey());
|
||||
subject.setStatus(1);
|
||||
subjectMapper.insert(subject);
|
||||
return subject;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Configuration
|
||||
@MapperScan("com.sino.mci.admin.repository")
|
||||
@MapperScan({"com.sino.mci.admin.repository", "com.sino.mci.channel.repository"})
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
CREATE TABLE IF NOT EXISTS mci_channel_subject (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) COMMENT '所属租户,为空表示平台级共享主体',
|
||||
channel_type VARCHAR(32) NOT NULL COMMENT '渠道类型:wecom / dingtalk / feishu',
|
||||
subject_code VARCHAR(64) NOT NULL COMMENT '主体编码',
|
||||
subject_name VARCHAR(128) NOT NULL COMMENT '主体名称',
|
||||
corp_id VARCHAR(128) COMMENT '企微 corp_id(企微用)',
|
||||
corp_secret VARCHAR(512) COMMENT '通讯录 secret(企微用)',
|
||||
agent_id VARCHAR(64) COMMENT '应用 agent_id(企微用)',
|
||||
agent_secret VARCHAR(512) COMMENT '应用 secret(企微用)',
|
||||
session_archive_enabled TINYINT NOT NULL DEFAULT 0 COMMENT '是否开通会话存档能力:0否 1是',
|
||||
session_archive_private_key TEXT COMMENT '会话存档 RSA 私钥',
|
||||
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_channel_subject (tenant_code, channel_type, subject_code, deleted)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='渠道主体表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mci_channel_account (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
account_type TINYINT NOT NULL COMMENT '1企微内部账号 2企微应用 3pywechat个人号 4钉钉机器人 5飞书机器人',
|
||||
channel_type VARCHAR(32) NOT NULL COMMENT 'wecom / wechat_personal / dingtalk / feishu',
|
||||
subject_id BIGINT COMMENT '所属渠道主体 mci_channel_subject',
|
||||
account_id VARCHAR(128) COMMENT '账号唯一标识:企微员工=userid,企微应用=agent_id,个微=wxid/bot_code,机器人=robot_id',
|
||||
account_name VARCHAR(128) COMMENT '账号名称',
|
||||
session_archive_enabled TINYINT NOT NULL DEFAULT 0 COMMENT '是否开启会话存档(企微内部账号用)',
|
||||
server_host VARCHAR(128) COMMENT '所在服务器(pywechat 用)',
|
||||
login_status TINYINT NOT NULL DEFAULT 0 COMMENT '登录状态:0未登录 1登录中 2已登录',
|
||||
online_status TINYINT NOT NULL DEFAULT 0 COMMENT '在线状态:0离线 1在线',
|
||||
risk_level TINYINT NOT NULL DEFAULT 0 COMMENT '风险等级',
|
||||
ext_info JSON COMMENT '渠道扩展字段',
|
||||
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0禁用 1启用',
|
||||
last_heartbeat 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,
|
||||
UNIQUE KEY uk_tenant_channel_account (tenant_code, channel_type, account_id, deleted),
|
||||
KEY idx_subject_id (subject_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='渠道账号表';
|
||||
|
||||
ALTER TABLE mci_channel_account
|
||||
ADD CONSTRAINT fk_channel_account_subject_id FOREIGN KEY (subject_id) REFERENCES mci_channel_subject (id);
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.sino.mci.channel.controller;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@Transactional
|
||||
public class ChannelControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void shouldCreateChannelSubjectAndAccount() throws Exception {
|
||||
String tenantCode = "channel-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
String tenantPayload = String.format("{\"tenantCode\":\"%s\",\"tenantName\":\"渠道测试\"}", tenantCode);
|
||||
mockMvc.perform(post("/msg-platform/api/v1/account/tenant")
|
||||
.contextPath("/msg-platform")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(tenantPayload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0));
|
||||
|
||||
String subjectPayload = "{\"channelType\":\"wecom\",\"subjectCode\":\"sino\",\"subjectName\":\"Sino\"}";
|
||||
mockMvc.perform(post("/msg-platform/api/v1/channel-subject")
|
||||
.contextPath("/msg-platform")
|
||||
.header("X-Tenant-Code", tenantCode)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(subjectPayload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.channelType").value("wecom"));
|
||||
|
||||
String accountPayload = "{\"accountType\":1,\"channelType\":\"wecom\",\"accountId\":\"ZhangSan\",\"accountName\":\"张三\"}";
|
||||
mockMvc.perform(post("/msg-platform/api/v1/channel-account")
|
||||
.contextPath("/msg-platform")
|
||||
.header("X-Tenant-Code", tenantCode)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(accountPayload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.accountId").value("ZhangSan"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEchoStrForWeComCallbackVerification() throws Exception {
|
||||
mockMvc.perform(get("/msg-platform/callback/wecom")
|
||||
.contextPath("/msg-platform")
|
||||
.param("echostr", "hello"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(result -> result.getResponse().getContentAsString().equals("hello"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user