feat(crm): add person, identity, conversation, tag tables and APIs
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package com.sino.mci.channel.controller;
|
||||
|
||||
import com.sino.mci.channel.dto.BindConversationAccountRequest;
|
||||
import com.sino.mci.channel.dto.BindConversationTagsRequest;
|
||||
import com.sino.mci.channel.dto.CreateConversationRequest;
|
||||
import com.sino.mci.channel.service.ConversationService;
|
||||
import com.sino.mci.shared.web.ApiResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/conversation")
|
||||
@RequiredArgsConstructor
|
||||
public class ConversationController {
|
||||
|
||||
private final ConversationService conversationService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<?> createConversation(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@Valid @RequestBody CreateConversationRequest request) {
|
||||
return ApiResponse.ok(conversationService.createConversation(tenantCode, request));
|
||||
}
|
||||
|
||||
@GetMapping("/{conversation_id}")
|
||||
public ApiResponse<?> getConversation(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("conversation_id") Long conversationId) {
|
||||
return ApiResponse.ok(conversationService.getConversation(tenantCode, conversationId));
|
||||
}
|
||||
|
||||
@PostMapping("/{conversation_id}/bind-account")
|
||||
public ApiResponse<?> bindAccount(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("conversation_id") Long conversationId,
|
||||
@Valid @RequestBody BindConversationAccountRequest request) {
|
||||
conversationService.bindAccount(tenantCode, conversationId, request);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/{conversation_id}/bind-tags")
|
||||
public ApiResponse<?> bindTags(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("conversation_id") Long conversationId,
|
||||
@Valid @RequestBody BindConversationTagsRequest request) {
|
||||
conversationService.bindTags(tenantCode, conversationId, request);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.sino.mci.channel.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BindConversationAccountRequest {
|
||||
|
||||
@NotNull
|
||||
private Long accountId;
|
||||
|
||||
@NotNull
|
||||
private Integer bindingType;
|
||||
|
||||
private Integer sortOrder;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.sino.mci.channel.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BindConversationTagsRequest {
|
||||
|
||||
@NotEmpty
|
||||
private List<Long> tagIds;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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 CreateConversationRequest {
|
||||
|
||||
@NotNull
|
||||
private Integer conversationType;
|
||||
|
||||
@NotBlank
|
||||
private String channelType;
|
||||
|
||||
@NotBlank
|
||||
private String channelConversationId;
|
||||
|
||||
private Long ownerAccountId;
|
||||
private Long subjectId;
|
||||
private String conversationName;
|
||||
private Integer createSource;
|
||||
private Map<String, Object> receiveConfig;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
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_account_conversation")
|
||||
public class ChannelAccountConversation extends BaseEntity {
|
||||
|
||||
private Long accountId;
|
||||
private Long conversationId;
|
||||
private Integer bindingType;
|
||||
private Integer sortOrder;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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_conversation")
|
||||
public class Conversation extends BaseEntity {
|
||||
|
||||
private String tenantCode;
|
||||
private Integer conversationType;
|
||||
private String channelType;
|
||||
private String channelConversationId;
|
||||
private Long ownerAccountId;
|
||||
private Long subjectId;
|
||||
private String conversationName;
|
||||
private Integer createSource;
|
||||
private Long createTaskId;
|
||||
private Long institutionId;
|
||||
private Long supplierId;
|
||||
private Long contractId;
|
||||
private String channelCode;
|
||||
private String receiveConfig;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.sino.mci.channel.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("mci_conversation_participant")
|
||||
public class ConversationParticipant {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private Long conversationId;
|
||||
private Long personId;
|
||||
private Long channelIdentityId;
|
||||
private Integer participantType;
|
||||
private LocalDateTime joinTime;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.sino.mci.channel.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("mci_conversation_tag_binding")
|
||||
public class ConversationTagBinding {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String tenantCode;
|
||||
private Long tagId;
|
||||
private Long conversationId;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.channel.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.channel.entity.ChannelAccountConversation;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ChannelAccountConversationMapper extends BaseMapper<ChannelAccountConversation> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.channel.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.channel.entity.Conversation;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ConversationMapper extends BaseMapper<Conversation> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.channel.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.channel.entity.ConversationParticipant;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ConversationParticipantMapper extends BaseMapper<ConversationParticipant> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.channel.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.channel.entity.ConversationTagBinding;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ConversationTagBindingMapper extends BaseMapper<ConversationTagBinding> {
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.sino.mci.channel.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.sino.mci.channel.dto.BindConversationAccountRequest;
|
||||
import com.sino.mci.channel.dto.BindConversationTagsRequest;
|
||||
import com.sino.mci.channel.dto.CreateConversationRequest;
|
||||
import com.sino.mci.channel.entity.ChannelAccountConversation;
|
||||
import com.sino.mci.channel.entity.Conversation;
|
||||
import com.sino.mci.channel.entity.ConversationTagBinding;
|
||||
import com.sino.mci.channel.repository.ChannelAccountConversationMapper;
|
||||
import com.sino.mci.channel.repository.ConversationMapper;
|
||||
import com.sino.mci.channel.repository.ConversationTagBindingMapper;
|
||||
import com.sino.mci.crm.entity.Tag;
|
||||
import com.sino.mci.crm.repository.TagMapper;
|
||||
import com.sino.mci.exception.BusinessException;
|
||||
import com.sino.mci.shared.util.JsonUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ConversationService {
|
||||
|
||||
private final ConversationMapper conversationMapper;
|
||||
private final ChannelAccountConversationMapper bindingMapper;
|
||||
private final ConversationTagBindingMapper conversationTagBindingMapper;
|
||||
private final TagMapper tagMapper;
|
||||
|
||||
@Transactional
|
||||
public Conversation createConversation(String tenantCode, CreateConversationRequest request) {
|
||||
Conversation existing = conversationMapper.selectOne(
|
||||
new LambdaQueryWrapper<Conversation>()
|
||||
.eq(Conversation::getTenantCode, tenantCode)
|
||||
.eq(Conversation::getChannelType, request.getChannelType())
|
||||
.eq(Conversation::getChannelConversationId, request.getChannelConversationId()));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("会话已存在: " + request.getChannelConversationId());
|
||||
}
|
||||
|
||||
Conversation conversation = new Conversation();
|
||||
conversation.setTenantCode(tenantCode);
|
||||
conversation.setConversationType(request.getConversationType());
|
||||
conversation.setChannelType(request.getChannelType());
|
||||
conversation.setChannelConversationId(request.getChannelConversationId());
|
||||
conversation.setOwnerAccountId(request.getOwnerAccountId());
|
||||
conversation.setSubjectId(request.getSubjectId());
|
||||
conversation.setConversationName(request.getConversationName());
|
||||
conversation.setCreateSource(request.getCreateSource() != null ? request.getCreateSource() : 2);
|
||||
conversation.setReceiveConfig(JsonUtils.toJson(request.getReceiveConfig()));
|
||||
conversation.setStatus(1);
|
||||
conversationMapper.insert(conversation);
|
||||
return conversation;
|
||||
}
|
||||
|
||||
public Conversation getConversation(String tenantCode, Long conversationId) {
|
||||
Conversation conversation = conversationMapper.selectById(conversationId);
|
||||
if (conversation == null || !tenantCode.equals(conversation.getTenantCode())) {
|
||||
throw new BusinessException("会话不存在");
|
||||
}
|
||||
return conversation;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void bindAccount(String tenantCode, Long conversationId, BindConversationAccountRequest request) {
|
||||
getConversation(tenantCode, conversationId);
|
||||
|
||||
ChannelAccountConversation existing = bindingMapper.selectOne(
|
||||
new LambdaQueryWrapper<ChannelAccountConversation>()
|
||||
.eq(ChannelAccountConversation::getAccountId, request.getAccountId())
|
||||
.eq(ChannelAccountConversation::getConversationId, conversationId)
|
||||
.eq(ChannelAccountConversation::getBindingType, request.getBindingType()));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("该账号已绑定此会话的相同类型");
|
||||
}
|
||||
|
||||
ChannelAccountConversation binding = new ChannelAccountConversation();
|
||||
binding.setAccountId(request.getAccountId());
|
||||
binding.setConversationId(conversationId);
|
||||
binding.setBindingType(request.getBindingType());
|
||||
binding.setSortOrder(request.getSortOrder() != null ? request.getSortOrder() : 0);
|
||||
binding.setStatus(1);
|
||||
bindingMapper.insert(binding);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void bindTags(String tenantCode, Long conversationId, BindConversationTagsRequest request) {
|
||||
getConversation(tenantCode, conversationId);
|
||||
for (Long tagId : request.getTagIds()) {
|
||||
Tag tag = tagMapper.selectById(tagId);
|
||||
if (tag == null || !tenantCode.equals(tag.getTenantCode())) {
|
||||
throw new BusinessException("标签不存在: " + tagId);
|
||||
}
|
||||
}
|
||||
|
||||
conversationTagBindingMapper.delete(
|
||||
new LambdaQueryWrapper<ConversationTagBinding>()
|
||||
.eq(ConversationTagBinding::getConversationId, conversationId));
|
||||
|
||||
if (!CollectionUtils.isEmpty(request.getTagIds())) {
|
||||
List<ConversationTagBinding> bindings = request.getTagIds().stream()
|
||||
.distinct()
|
||||
.map(tagId -> {
|
||||
ConversationTagBinding binding = new ConversationTagBinding();
|
||||
binding.setTenantCode(tenantCode);
|
||||
binding.setTagId(tagId);
|
||||
binding.setConversationId(conversationId);
|
||||
return binding;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
for (ConversationTagBinding binding : bindings) {
|
||||
conversationTagBindingMapper.insert(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"})
|
||||
@MapperScan({"com.sino.mci.admin.repository", "com.sino.mci.channel.repository", "com.sino.mci.crm.repository"})
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.sino.mci.crm.controller;
|
||||
|
||||
import com.sino.mci.crm.dto.BindTagsRequest;
|
||||
import com.sino.mci.crm.dto.CreateIdentityRequest;
|
||||
import com.sino.mci.crm.dto.CreatePersonRequest;
|
||||
import com.sino.mci.crm.service.PersonService;
|
||||
import com.sino.mci.shared.web.ApiResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/person")
|
||||
@RequiredArgsConstructor
|
||||
public class PersonController {
|
||||
|
||||
private final PersonService personService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<?> createPerson(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@Valid @RequestBody CreatePersonRequest request) {
|
||||
return ApiResponse.ok(personService.createPerson(tenantCode, request));
|
||||
}
|
||||
|
||||
@GetMapping("/{person_id}")
|
||||
public ApiResponse<?> getPerson(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("person_id") Long personId) {
|
||||
return ApiResponse.ok(personService.getPerson(tenantCode, personId));
|
||||
}
|
||||
|
||||
@PostMapping("/{person_id}/identities")
|
||||
public ApiResponse<?> createIdentity(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("person_id") Long personId,
|
||||
@Valid @RequestBody CreateIdentityRequest request) {
|
||||
request.setPersonId(personId);
|
||||
return ApiResponse.ok(personService.createIdentity(tenantCode, request));
|
||||
}
|
||||
|
||||
@GetMapping("/{person_id}/identities")
|
||||
public ApiResponse<?> listIdentities(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("person_id") Long personId) {
|
||||
return ApiResponse.ok(personService.listIdentities(tenantCode, personId));
|
||||
}
|
||||
|
||||
@PostMapping("/{person_id}/bind-tags")
|
||||
public ApiResponse<?> bindTags(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@PathVariable("person_id") Long personId,
|
||||
@Valid @RequestBody BindTagsRequest request) {
|
||||
personService.bindTags(tenantCode, personId, request);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.sino.mci.crm.controller;
|
||||
|
||||
import com.sino.mci.crm.dto.CreateTagRequest;
|
||||
import com.sino.mci.crm.service.TagService;
|
||||
import com.sino.mci.shared.web.ApiResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/tag")
|
||||
@RequiredArgsConstructor
|
||||
public class TagController {
|
||||
|
||||
private final TagService tagService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<?> createTag(
|
||||
@RequestHeader("X-Tenant-Code") String tenantCode,
|
||||
@Valid @RequestBody CreateTagRequest request) {
|
||||
return ApiResponse.ok(tagService.createTag(tenantCode, request));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public ApiResponse<?> listTags(@RequestHeader("X-Tenant-Code") String tenantCode) {
|
||||
return ApiResponse.ok(tagService.listTags(tenantCode));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.sino.mci.crm.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BindTagsRequest {
|
||||
|
||||
@NotEmpty
|
||||
private List<Long> tagIds;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.sino.mci.crm.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class CreateIdentityRequest {
|
||||
|
||||
private Long personId;
|
||||
|
||||
@NotBlank
|
||||
private String channelType;
|
||||
|
||||
@NotBlank
|
||||
private String channelUserId;
|
||||
|
||||
private String channelUserName;
|
||||
private Long subjectId;
|
||||
private Map<String, Object> extInfo;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.sino.mci.crm.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CreatePersonRequest {
|
||||
|
||||
private String personCode;
|
||||
|
||||
@NotBlank
|
||||
private String personName;
|
||||
|
||||
private String phone;
|
||||
|
||||
@NotNull
|
||||
private Integer personType;
|
||||
|
||||
private Long institutionId;
|
||||
private Long supplierId;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.sino.mci.crm.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CreateTagRequest {
|
||||
|
||||
@NotNull
|
||||
private Long parentId;
|
||||
|
||||
@NotBlank
|
||||
private String tagName;
|
||||
|
||||
private String tagSource;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.sino.mci.crm.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_identity")
|
||||
public class ChannelIdentity extends BaseEntity {
|
||||
|
||||
private String tenantCode;
|
||||
private Long personId;
|
||||
private String channelType;
|
||||
private String channelUserId;
|
||||
private String channelUserName;
|
||||
private Long subjectId;
|
||||
private Integer status;
|
||||
private String extInfo;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.sino.mci.crm.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_person")
|
||||
public class Person extends BaseEntity {
|
||||
|
||||
private String tenantCode;
|
||||
private String personCode;
|
||||
private String personName;
|
||||
private String phone;
|
||||
private Integer personType;
|
||||
private Long institutionId;
|
||||
private Long supplierId;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.sino.mci.crm.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("mci_person_tag_binding")
|
||||
public class PersonTagBinding {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String tenantCode;
|
||||
private Long tagId;
|
||||
private Long personId;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.sino.mci.crm.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_tag")
|
||||
public class Tag extends BaseEntity {
|
||||
|
||||
private String tenantCode;
|
||||
private Long parentId;
|
||||
private String tagPath;
|
||||
private Integer level;
|
||||
private String tagName;
|
||||
private String tagSource;
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.crm.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.crm.entity.ChannelIdentity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ChannelIdentityMapper extends BaseMapper<ChannelIdentity> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.crm.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.crm.entity.Person;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PersonMapper extends BaseMapper<Person> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.crm.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.crm.entity.PersonTagBinding;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PersonTagBindingMapper extends BaseMapper<PersonTagBinding> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sino.mci.crm.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.sino.mci.crm.entity.Tag;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface TagMapper extends BaseMapper<Tag> {
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.sino.mci.crm.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.sino.mci.crm.dto.BindTagsRequest;
|
||||
import com.sino.mci.crm.dto.CreateIdentityRequest;
|
||||
import com.sino.mci.crm.dto.CreatePersonRequest;
|
||||
import com.sino.mci.crm.entity.ChannelIdentity;
|
||||
import com.sino.mci.crm.entity.Person;
|
||||
import com.sino.mci.crm.entity.PersonTagBinding;
|
||||
import com.sino.mci.crm.entity.Tag;
|
||||
import com.sino.mci.crm.repository.ChannelIdentityMapper;
|
||||
import com.sino.mci.crm.repository.PersonMapper;
|
||||
import com.sino.mci.crm.repository.PersonTagBindingMapper;
|
||||
import com.sino.mci.crm.repository.TagMapper;
|
||||
import com.sino.mci.exception.BusinessException;
|
||||
import com.sino.mci.shared.util.JsonUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PersonService {
|
||||
|
||||
private final PersonMapper personMapper;
|
||||
private final ChannelIdentityMapper identityMapper;
|
||||
private final PersonTagBindingMapper personTagBindingMapper;
|
||||
private final TagMapper tagMapper;
|
||||
|
||||
@Transactional
|
||||
public Person createPerson(String tenantCode, CreatePersonRequest request) {
|
||||
Person person = new Person();
|
||||
person.setTenantCode(tenantCode);
|
||||
person.setPersonCode(request.getPersonCode());
|
||||
person.setPersonName(request.getPersonName());
|
||||
person.setPhone(request.getPhone());
|
||||
person.setPersonType(request.getPersonType());
|
||||
person.setInstitutionId(request.getInstitutionId());
|
||||
person.setSupplierId(request.getSupplierId());
|
||||
person.setStatus(1);
|
||||
personMapper.insert(person);
|
||||
return person;
|
||||
}
|
||||
|
||||
public Person getPerson(String tenantCode, Long personId) {
|
||||
Person person = personMapper.selectById(personId);
|
||||
if (person == null || !tenantCode.equals(person.getTenantCode())) {
|
||||
throw new BusinessException("联系人不存在");
|
||||
}
|
||||
return person;
|
||||
}
|
||||
|
||||
public List<ChannelIdentity> listIdentities(String tenantCode, Long personId) {
|
||||
getPerson(tenantCode, personId);
|
||||
return identityMapper.selectList(
|
||||
new LambdaQueryWrapper<ChannelIdentity>()
|
||||
.eq(ChannelIdentity::getTenantCode, tenantCode)
|
||||
.eq(ChannelIdentity::getPersonId, personId));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ChannelIdentity createIdentity(String tenantCode, CreateIdentityRequest request) {
|
||||
getPerson(tenantCode, request.getPersonId());
|
||||
|
||||
ChannelIdentity existing = identityMapper.selectOne(
|
||||
new LambdaQueryWrapper<ChannelIdentity>()
|
||||
.eq(ChannelIdentity::getTenantCode, tenantCode)
|
||||
.eq(ChannelIdentity::getChannelType, request.getChannelType())
|
||||
.eq(ChannelIdentity::getChannelUserId, request.getChannelUserId()));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("渠道身份已存在: " + request.getChannelUserId());
|
||||
}
|
||||
|
||||
ChannelIdentity identity = new ChannelIdentity();
|
||||
identity.setTenantCode(tenantCode);
|
||||
identity.setPersonId(request.getPersonId());
|
||||
identity.setChannelType(request.getChannelType());
|
||||
identity.setChannelUserId(request.getChannelUserId());
|
||||
identity.setChannelUserName(request.getChannelUserName());
|
||||
identity.setSubjectId(request.getSubjectId());
|
||||
identity.setStatus(1);
|
||||
identity.setExtInfo(JsonUtils.toJson(request.getExtInfo()));
|
||||
identityMapper.insert(identity);
|
||||
return identity;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void bindTags(String tenantCode, Long personId, BindTagsRequest request) {
|
||||
getPerson(tenantCode, personId);
|
||||
for (Long tagId : request.getTagIds()) {
|
||||
Tag tag = tagMapper.selectById(tagId);
|
||||
if (tag == null || !tenantCode.equals(tag.getTenantCode())) {
|
||||
throw new BusinessException("标签不存在: " + tagId);
|
||||
}
|
||||
}
|
||||
|
||||
personTagBindingMapper.delete(
|
||||
new LambdaQueryWrapper<PersonTagBinding>()
|
||||
.eq(PersonTagBinding::getPersonId, personId));
|
||||
|
||||
if (!CollectionUtils.isEmpty(request.getTagIds())) {
|
||||
List<PersonTagBinding> bindings = request.getTagIds().stream()
|
||||
.distinct()
|
||||
.map(tagId -> {
|
||||
PersonTagBinding binding = new PersonTagBinding();
|
||||
binding.setTenantCode(tenantCode);
|
||||
binding.setTagId(tagId);
|
||||
binding.setPersonId(personId);
|
||||
return binding;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
for (PersonTagBinding binding : bindings) {
|
||||
personTagBindingMapper.insert(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.sino.mci.crm.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.sino.mci.crm.dto.CreateTagRequest;
|
||||
import com.sino.mci.crm.entity.Tag;
|
||||
import com.sino.mci.crm.repository.TagMapper;
|
||||
import com.sino.mci.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TagService {
|
||||
|
||||
private final TagMapper tagMapper;
|
||||
|
||||
@Transactional
|
||||
public Tag createTag(String tenantCode, CreateTagRequest request) {
|
||||
Long parentId = request.getParentId() != null ? request.getParentId() : 0L;
|
||||
int level = 1;
|
||||
String parentPath = "";
|
||||
|
||||
if (parentId > 0) {
|
||||
Tag parent = tagMapper.selectById(parentId);
|
||||
if (parent == null || !tenantCode.equals(parent.getTenantCode())) {
|
||||
throw new BusinessException("父标签不存在");
|
||||
}
|
||||
level = parent.getLevel() + 1;
|
||||
parentPath = parent.getTagPath();
|
||||
}
|
||||
|
||||
String tagPath = parentPath.isEmpty() ? request.getTagName() : parentPath + "/" + request.getTagName();
|
||||
|
||||
Tag existing = tagMapper.selectOne(
|
||||
new LambdaQueryWrapper<Tag>()
|
||||
.eq(Tag::getTenantCode, tenantCode)
|
||||
.eq(Tag::getTagPath, tagPath));
|
||||
if (existing != null) {
|
||||
throw new BusinessException("标签路径已存在: " + tagPath);
|
||||
}
|
||||
|
||||
Tag tag = new Tag();
|
||||
tag.setTenantCode(tenantCode);
|
||||
tag.setParentId(parentId);
|
||||
tag.setTagPath(tagPath);
|
||||
tag.setLevel(level);
|
||||
tag.setTagName(request.getTagName());
|
||||
tag.setTagSource(request.getTagSource() != null ? request.getTagSource() : "manual");
|
||||
tag.setStatus(1);
|
||||
tagMapper.insert(tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public List<Tag> listTags(String tenantCode) {
|
||||
return tagMapper.selectList(
|
||||
new LambdaQueryWrapper<Tag>()
|
||||
.eq(Tag::getTenantCode, tenantCode)
|
||||
.eq(Tag::getStatus, 1)
|
||||
.orderByAsc(Tag::getTagPath));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
CREATE TABLE IF NOT EXISTS mci_person (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
person_code VARCHAR(64) COMMENT '业务人员编码',
|
||||
person_name VARCHAR(128) NOT NULL COMMENT '姓名',
|
||||
phone VARCHAR(32) COMMENT '手机号',
|
||||
person_type TINYINT NOT NULL DEFAULT 1 COMMENT '类型:1内部人员 2供应商人员 3客户 4司机',
|
||||
institution_id BIGINT COMMENT '所属机构/公司',
|
||||
supplier_id BIGINT COMMENT '供应商 ID',
|
||||
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_person_code (tenant_code, person_code, deleted),
|
||||
KEY idx_person_name (person_name),
|
||||
KEY idx_phone (phone)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='联系人表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mci_channel_identity (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
person_id BIGINT NOT NULL COMMENT '联系人 ID',
|
||||
channel_type VARCHAR(32) NOT NULL COMMENT '渠道:wecom / wechat_personal / dingtalk / feishu',
|
||||
channel_user_id VARCHAR(128) NOT NULL COMMENT '渠道用户 ID',
|
||||
channel_user_name VARCHAR(128) COMMENT '渠道昵称',
|
||||
subject_id BIGINT COMMENT '所属渠道主体 mci_channel_subject',
|
||||
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0禁用 1启用',
|
||||
ext_info JSON 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_user (tenant_code, channel_type, channel_user_id, deleted),
|
||||
KEY idx_person_id (person_id),
|
||||
KEY idx_subject_id (subject_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='渠道身份表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mci_conversation (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
conversation_type TINYINT NOT NULL DEFAULT 1 COMMENT '1单聊 2群聊',
|
||||
channel_type VARCHAR(32) NOT NULL COMMENT 'wecom / wechat_personal / dingtalk / feishu',
|
||||
channel_conversation_id VARCHAR(128) NOT NULL COMMENT '渠道会话 ID',
|
||||
owner_account_id BIGINT COMMENT '群主/所有者账号',
|
||||
subject_id BIGINT COMMENT '所属渠道主体',
|
||||
conversation_name VARCHAR(256) COMMENT '会话名称',
|
||||
create_source TINYINT NOT NULL DEFAULT 1 COMMENT '来源:1渠道同步 2API 创建 3手动创建',
|
||||
create_task_id BIGINT COMMENT '建群任务 ID',
|
||||
institution_id BIGINT COMMENT '关联机构',
|
||||
supplier_id BIGINT COMMENT '关联供应商',
|
||||
contract_id BIGINT COMMENT '关联合同',
|
||||
channel_code VARCHAR(64) COMMENT '业务渠道编码',
|
||||
receive_config JSON COMMENT '收消息配置',
|
||||
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_conv (tenant_code, channel_type, channel_conversation_id, deleted),
|
||||
KEY idx_owner_account (owner_account_id),
|
||||
KEY idx_subject_id (subject_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mci_conversation_participant (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
conversation_id BIGINT NOT NULL COMMENT '会话 ID',
|
||||
person_id BIGINT COMMENT '人员 ID',
|
||||
channel_identity_id BIGINT COMMENT '渠道身份 ID',
|
||||
participant_type TINYINT NOT NULL DEFAULT 1 COMMENT '1成员 2群主 3管理员',
|
||||
join_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_conv_participant (conversation_id, person_id, channel_identity_id),
|
||||
KEY idx_conversation_id (conversation_id),
|
||||
KEY idx_person_id (person_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话参与者表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mci_channel_account_conversation (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
account_id BIGINT NOT NULL COMMENT '渠道账号 ID',
|
||||
conversation_id BIGINT NOT NULL COMMENT '会话 ID',
|
||||
binding_type TINYINT NOT NULL COMMENT '1发送主号 2发送备用号 3接收主号 4接收备用号',
|
||||
sort_order INT NOT NULL DEFAULT 0 COMMENT '优先级',
|
||||
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,
|
||||
UNIQUE KEY uk_account_conv_binding (account_id, conversation_id, binding_type),
|
||||
KEY idx_conversation_id (conversation_id),
|
||||
KEY idx_account_id (account_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账号-会话绑定表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mci_tag (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父标签 ID,0 表示根标签',
|
||||
tag_path VARCHAR(512) NOT NULL COMMENT '标签路径,如 销售/销售一部/华北组',
|
||||
level TINYINT NOT NULL DEFAULT 1 COMMENT '层级',
|
||||
tag_name VARCHAR(128) NOT NULL COMMENT '标签名',
|
||||
tag_source VARCHAR(32) NOT NULL DEFAULT 'manual' COMMENT '来源:system / business_sync / manual',
|
||||
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_tag_path (tenant_code, tag_path, deleted),
|
||||
KEY idx_parent_id (parent_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mci_person_tag_binding (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
tag_id BIGINT NOT NULL COMMENT '标签 ID',
|
||||
person_id BIGINT NOT NULL COMMENT '联系人 ID',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_person_tag (tag_id, person_id),
|
||||
KEY idx_person_id (person_id),
|
||||
KEY idx_tag_id (tag_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='人员标签绑定表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mci_conversation_tag_binding (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_code VARCHAR(64) NOT NULL COMMENT '所属租户',
|
||||
tag_id BIGINT NOT NULL COMMENT '标签 ID',
|
||||
conversation_id BIGINT NOT NULL COMMENT '会话 ID',
|
||||
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_conversation_tag (tag_id, conversation_id),
|
||||
KEY idx_conversation_id (conversation_id),
|
||||
KEY idx_tag_id (tag_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会话标签绑定表';
|
||||
|
||||
ALTER TABLE mci_channel_identity
|
||||
ADD CONSTRAINT fk_identity_person_id FOREIGN KEY (person_id) REFERENCES mci_person (id),
|
||||
ADD CONSTRAINT fk_identity_subject_id FOREIGN KEY (subject_id) REFERENCES mci_channel_subject (id);
|
||||
|
||||
ALTER TABLE mci_conversation
|
||||
ADD CONSTRAINT fk_conversation_owner_account FOREIGN KEY (owner_account_id) REFERENCES mci_channel_account (id),
|
||||
ADD CONSTRAINT fk_conversation_subject_id FOREIGN KEY (subject_id) REFERENCES mci_channel_subject (id);
|
||||
|
||||
ALTER TABLE mci_conversation_participant
|
||||
ADD CONSTRAINT fk_participant_conversation_id FOREIGN KEY (conversation_id) REFERENCES mci_conversation (id),
|
||||
ADD CONSTRAINT fk_participant_person_id FOREIGN KEY (person_id) REFERENCES mci_person (id),
|
||||
ADD CONSTRAINT fk_participant_identity_id FOREIGN KEY (channel_identity_id) REFERENCES mci_channel_identity (id);
|
||||
|
||||
ALTER TABLE mci_channel_account_conversation
|
||||
ADD CONSTRAINT fk_binding_account_id FOREIGN KEY (account_id) REFERENCES mci_channel_account (id),
|
||||
ADD CONSTRAINT fk_binding_conversation_id FOREIGN KEY (conversation_id) REFERENCES mci_conversation (id);
|
||||
|
||||
ALTER TABLE mci_person_tag_binding
|
||||
ADD CONSTRAINT fk_person_tag_tag_id FOREIGN KEY (tag_id) REFERENCES mci_tag (id),
|
||||
ADD CONSTRAINT fk_person_tag_person_id FOREIGN KEY (person_id) REFERENCES mci_person (id);
|
||||
|
||||
ALTER TABLE mci_conversation_tag_binding
|
||||
ADD CONSTRAINT fk_conv_tag_tag_id FOREIGN KEY (tag_id) REFERENCES mci_tag (id),
|
||||
ADD CONSTRAINT fk_conv_tag_conversation_id FOREIGN KEY (conversation_id) REFERENCES mci_conversation (id);
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.sino.mci.crm.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 CrmControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@Test
|
||||
void shouldCreatePersonTagAndConversation() throws Exception {
|
||||
String tenantCode = "crm-" + UUID.randomUUID().toString().substring(0, 8);
|
||||
String tenantPayload = String.format("{\"tenantCode\":\"%s\",\"tenantName\":\"CRM测试\"}", 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 personPayload = "{\"personCode\":\"P001\",\"personName\":\"张三\",\"phone\":\"13800138000\",\"personType\":3}";
|
||||
mockMvc.perform(post("/msg-platform/api/v1/person")
|
||||
.contextPath("/msg-platform")
|
||||
.header("X-Tenant-Code", tenantCode)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(personPayload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.personName").value("张三"));
|
||||
|
||||
String tagPayload = "{\"parentId\":0,\"tagName\":\"客户\"}";
|
||||
mockMvc.perform(post("/msg-platform/api/v1/tag")
|
||||
.contextPath("/msg-platform")
|
||||
.header("X-Tenant-Code", tenantCode)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(tagPayload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.tagPath").value("客户"));
|
||||
|
||||
String conversationPayload = "{\"conversationType\":2,\"channelType\":\"wecom\",\"channelConversationId\":\"roomid_123\",\"conversationName\":\"测试群\"}";
|
||||
mockMvc.perform(post("/msg-platform/api/v1/conversation")
|
||||
.contextPath("/msg-platform")
|
||||
.header("X-Tenant-Code", tenantCode)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(conversationPayload))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.conversationName").value("测试群"));
|
||||
|
||||
mockMvc.perform(get("/msg-platform/api/v1/tag/list")
|
||||
.contextPath("/msg-platform")
|
||||
.header("X-Tenant-Code", tenantCode))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.size()").value(1));
|
||||
}
|
||||
}
|
||||
@@ -21,5 +21,13 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.sino.mci.shared.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public final class JsonUtils {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private JsonUtils() {
|
||||
}
|
||||
|
||||
public static String toJson(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.writeValueAsString(value);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("JSON serialize failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String json, Class<T> clazz) {
|
||||
if (json == null || json.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return OBJECT_MAPPER.readValue(json, clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("JSON deserialize failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user