feat: L2契约对齐-添加validation注解和全局异常处理 [AC-MCA-08][AC-MCA-12]

This commit is contained in:
MerCry 2026-02-24 11:42:36 +08:00
parent f09f22f447
commit 0786e6a040
7 changed files with 120 additions and 18 deletions

View File

@ -0,0 +1,46 @@
package com.wecom.robot.config;
import com.wecom.robot.dto.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleValidationException(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining("; "));
log.warn("参数校验失败: {}", message);
return ApiResponse.error(400, message);
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleConstraintViolationException(ConstraintViolationException ex) {
String message = ex.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "));
log.warn("约束校验失败: {}", message);
return ApiResponse.error(400, message);
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponse<Void> handleException(Exception ex) {
log.error("服务器内部错误", ex);
return ApiResponse.error(500, "服务器内部错误");
}
}

View File

@ -13,6 +13,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -31,7 +32,8 @@ public class SessionController {
@GetMapping @GetMapping
public ApiResponse<List<SessionInfo>> getSessions( public ApiResponse<List<SessionInfo>> getSessions(
@RequestParam(required = false) String status, @RequestParam(required = false) String status,
@RequestParam(required = false) String csId) { @RequestParam(required = false) String csId,
@RequestParam(required = false) String channelType) {
LambdaQueryWrapper<Session> query = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Session> query = new LambdaQueryWrapper<>();
if (status != null) { if (status != null) {
@ -42,6 +44,10 @@ public class SessionController {
query.eq(Session::getManualCsId, csId); query.eq(Session::getManualCsId, csId);
} }
if (channelType != null) {
query.eq(Session::getChannelType, channelType);
}
query.orderByDesc(Session::getUpdatedAt); query.orderByDesc(Session::getUpdatedAt);
List<Session> sessions = sessionMapper.selectList(query); List<Session> sessions = sessionMapper.selectList(query);
@ -51,6 +57,7 @@ public class SessionController {
info.setSessionId(session.getSessionId()); info.setSessionId(session.getSessionId());
info.setCustomerId(session.getCustomerId()); info.setCustomerId(session.getCustomerId());
info.setKfId(session.getKfId()); info.setKfId(session.getKfId());
info.setChannelType(session.getChannelType());
info.setStatus(session.getStatus()); info.setStatus(session.getStatus());
info.setManualCsId(session.getManualCsId()); info.setManualCsId(session.getManualCsId());
info.setCreatedAt(session.getCreatedAt()); info.setCreatedAt(session.getCreatedAt());
@ -87,6 +94,7 @@ public class SessionController {
info.setSessionId(session.getSessionId()); info.setSessionId(session.getSessionId());
info.setCustomerId(session.getCustomerId()); info.setCustomerId(session.getCustomerId());
info.setKfId(session.getKfId()); info.setKfId(session.getKfId());
info.setChannelType(session.getChannelType());
info.setStatus(session.getStatus()); info.setStatus(session.getStatus());
info.setManualCsId(session.getManualCsId()); info.setManualCsId(session.getManualCsId());
info.setCreatedAt(session.getCreatedAt()); info.setCreatedAt(session.getCreatedAt());
@ -120,12 +128,9 @@ public class SessionController {
} }
@PostMapping("/{sessionId}/accept") @PostMapping("/{sessionId}/accept")
public ApiResponse<Void> acceptSession(@PathVariable String sessionId, @RequestBody AcceptSessionRequest request) { public ApiResponse<Void> acceptSession(
String csId = request.getCsId(); @PathVariable String sessionId,
if (csId == null || csId.isEmpty()) { @Valid @RequestBody AcceptSessionRequest request) {
return ApiResponse.error(400, "客服ID不能为空");
}
Session session = sessionMapper.selectById(sessionId); Session session = sessionMapper.selectById(sessionId);
if (session == null) { if (session == null) {
return ApiResponse.error(404, "会话不存在"); return ApiResponse.error(404, "会话不存在");
@ -135,14 +140,16 @@ public class SessionController {
return ApiResponse.error(400, "会话状态不正确"); return ApiResponse.error(400, "会话状态不正确");
} }
sessionManagerService.acceptTransfer(sessionId, csId); sessionManagerService.acceptTransfer(sessionId, request.getCsId());
webSocketService.notifySessionAccepted(sessionId, csId); webSocketService.notifySessionAccepted(sessionId, request.getCsId());
return ApiResponse.success(null); return ApiResponse.success(null);
} }
@PostMapping("/{sessionId}/message") @PostMapping("/{sessionId}/message")
public ApiResponse<Void> sendMessage(@PathVariable String sessionId, @RequestBody SendMessageRequest request) { public ApiResponse<Void> sendMessage(
@PathVariable String sessionId,
@Valid @RequestBody SendMessageRequest request) {
Session session = sessionMapper.selectById(sessionId); Session session = sessionMapper.selectById(sessionId);
if (session == null) { if (session == null) {
return ApiResponse.error(404, "会话不存在"); return ApiResponse.error(404, "会话不存在");
@ -159,7 +166,7 @@ public class SessionController {
); );
if (!success) { if (!success) {
return ApiResponse.error("消息发送失败"); return ApiResponse.error(500, "消息发送失败");
} }
sessionManagerService.saveMessage( sessionManagerService.saveMessage(

View File

@ -2,8 +2,13 @@ package com.wecom.robot.dto;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data @Data
public class AcceptSessionRequest { public class AcceptSessionRequest {
@NotBlank(message = "客服ID不能为空")
@Size(min = 1, max = 64, message = "客服ID长度必须在1-64之间")
private String csId; private String csId;
} }

View File

@ -11,7 +11,7 @@ public class ApiResponse<T> {
public static <T> ApiResponse<T> success(T data) { public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>(); ApiResponse<T> response = new ApiResponse<>();
response.setCode(200); response.setCode(0);
response.setMessage("success"); response.setMessage("success");
response.setData(data); response.setData(data);
return response; return response;

View File

@ -2,16 +2,31 @@ package com.wecom.robot.dto;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data @Data
public class MessageInfo { public class MessageInfo {
@NotBlank
@Size(min = 1, max = 128)
private String msgId; private String msgId;
@NotBlank
@Size(min = 1, max = 64)
private String sessionId; private String sessionId;
@NotBlank
private String senderType; private String senderType;
@Size(max = 64)
private String senderId; private String senderId;
@NotBlank
@Size(min = 1, max = 4096)
private String content; private String content;
private String msgType; private String msgType;
private LocalDateTime createdAt;
private java.time.LocalDateTime createdAt;
} }

View File

@ -2,9 +2,15 @@ package com.wecom.robot.dto;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data @Data
public class SendMessageRequest { public class SendMessageRequest {
@NotBlank(message = "消息内容不能为空")
@Size(min = 1, max = 4096, message = "消息内容长度必须在1-4096之间")
private String content; private String content;
private String msgType; private String msgType;
} }

View File

@ -2,20 +2,43 @@ package com.wecom.robot.dto;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Data @Data
public class SessionInfo { public class SessionInfo {
@NotBlank
@Size(min = 1, max = 64)
private String sessionId; private String sessionId;
@NotBlank
@Size(min = 1, max = 64)
private String customerId; private String customerId;
@Size(max = 64)
private String kfId; private String kfId;
@Size(max = 64)
private String channelType;
@NotBlank
private String status; private String status;
@Size(max = 64)
private String manualCsId; private String manualCsId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt; @Size(max = 4096)
private String metadata;
private int messageCount;
private String lastMessage; private String lastMessage;
private LocalDateTime lastMessageTime; private LocalDateTime lastMessageTime;
private int messageCount;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private String metadata;
} }