Commit ac69e55780c9980b2ec9e4340954fbed5fcb9baf

Authored by zichun
1 parent 08969463

feat(mod): unified Result + global exception handler REQ-MOD-001

backend/src/main/java/com/xly/erp/common/exception/BizException.java 0 → 100644
  1 +package com.xly.erp.common.exception;
  2 +
  3 +public class BizException extends RuntimeException {
  4 + private final int code;
  5 +
  6 + public BizException(int code, String msg) {
  7 + super(msg);
  8 + this.code = code;
  9 + }
  10 +
  11 + public int getCode() {
  12 + return code;
  13 + }
  14 +}
... ...
backend/src/main/java/com/xly/erp/common/exception/GlobalExceptionHandler.java 0 → 100644
  1 +package com.xly.erp.common.exception;
  2 +
  3 +import com.xly.erp.common.response.Result;
  4 +import org.slf4j.Logger;
  5 +import org.slf4j.LoggerFactory;
  6 +import org.springframework.validation.FieldError;
  7 +import org.springframework.web.bind.MethodArgumentNotValidException;
  8 +import org.springframework.web.bind.annotation.ExceptionHandler;
  9 +import org.springframework.web.bind.annotation.RestControllerAdvice;
  10 +
  11 +@RestControllerAdvice
  12 +public class GlobalExceptionHandler {
  13 +
  14 + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
  15 +
  16 + @ExceptionHandler(BizException.class)
  17 + public Result<Void> handleBiz(BizException e) {
  18 + log.warn("BizException code={} msg={}", e.getCode(), e.getMessage());
  19 + return Result.fail(e.getCode(), e.getMessage());
  20 + }
  21 +
  22 + @ExceptionHandler(MethodArgumentNotValidException.class)
  23 + public Result<Void> handleValidation(MethodArgumentNotValidException e) {
  24 + FieldError fe = e.getBindingResult().getFieldError();
  25 + String msg = fe == null
  26 + ? "参数校验失败"
  27 + : fe.getField() + ": " + fe.getDefaultMessage();
  28 + log.warn("ValidationException {}", msg);
  29 + return Result.fail(40001, msg);
  30 + }
  31 +
  32 + @ExceptionHandler(Exception.class)
  33 + public Result<Void> handleAny(Exception e) {
  34 + log.error("Unhandled exception", e);
  35 + return Result.fail(50000, "系统繁忙");
  36 + }
  37 +}
... ...
backend/src/main/java/com/xly/erp/common/response/Result.java 0 → 100644
  1 +package com.xly.erp.common.response;
  2 +
  3 +public class Result<T> {
  4 + private int code;
  5 + private String msg;
  6 + private T data;
  7 +
  8 + public Result() {
  9 + }
  10 +
  11 + public Result(int code, String msg, T data) {
  12 + this.code = code;
  13 + this.msg = msg;
  14 + this.data = data;
  15 + }
  16 +
  17 + public static <T> Result<T> ok(T data) {
  18 + return new Result<>(0, "ok", data);
  19 + }
  20 +
  21 + public static <T> Result<T> ok() {
  22 + return new Result<>(0, "ok", null);
  23 + }
  24 +
  25 + public static <T> Result<T> fail(int code, String msg) {
  26 + return new Result<>(code, msg, null);
  27 + }
  28 +
  29 + public int getCode() {
  30 + return code;
  31 + }
  32 +
  33 + public void setCode(int code) {
  34 + this.code = code;
  35 + }
  36 +
  37 + public String getMsg() {
  38 + return msg;
  39 + }
  40 +
  41 + public void setMsg(String msg) {
  42 + this.msg = msg;
  43 + }
  44 +
  45 + public T getData() {
  46 + return data;
  47 + }
  48 +
  49 + public void setData(T data) {
  50 + this.data = data;
  51 + }
  52 +}
... ...
backend/src/test/java/com/xly/erp/common/exception/GlobalExceptionHandlerTest.java 0 → 100644
  1 +package com.xly.erp.common.exception;
  2 +
  3 +import com.xly.erp.common.response.Result;
  4 +import jakarta.validation.Valid;
  5 +import jakarta.validation.constraints.NotBlank;
  6 +import org.junit.jupiter.api.BeforeEach;
  7 +import org.junit.jupiter.api.Test;
  8 +import org.springframework.test.web.servlet.MockMvc;
  9 +import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  10 +import org.springframework.web.bind.annotation.GetMapping;
  11 +import org.springframework.web.bind.annotation.PostMapping;
  12 +import org.springframework.web.bind.annotation.RequestBody;
  13 +import org.springframework.web.bind.annotation.RestController;
  14 +
  15 +import static org.assertj.core.api.Assertions.assertThat;
  16 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
  17 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
  18 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
  19 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  20 +
  21 +class GlobalExceptionHandlerTest {
  22 +
  23 + private MockMvc mockMvc;
  24 +
  25 + @BeforeEach
  26 + void setUp() {
  27 + mockMvc = MockMvcBuilders.standaloneSetup(new StubController())
  28 + .setControllerAdvice(new GlobalExceptionHandler())
  29 + .build();
  30 + }
  31 +
  32 + @Test
  33 + void bizException_returnsResultWithBizCode() throws Exception {
  34 + mockMvc.perform(get("/__test/throw-biz"))
  35 + .andExpect(status().isOk())
  36 + .andExpect(jsonPath("$.code").value(30001))
  37 + .andExpect(jsonPath("$.msg").value("business x"));
  38 + }
  39 +
  40 + @Test
  41 + void validationException_returns40001WithFieldHint() throws Exception {
  42 + mockMvc.perform(post("/__test/throw-validate")
  43 + .contentType("application/json")
  44 + .content("{}"))
  45 + .andExpect(status().isOk())
  46 + .andExpect(jsonPath("$.code").value(40001))
  47 + .andExpect(jsonPath("$.msg", org.hamcrest.Matchers.containsString("name")));
  48 + }
  49 +
  50 + @Test
  51 + void uncaughtException_returns50000() throws Exception {
  52 + mockMvc.perform(get("/__test/throw-runtime"))
  53 + .andExpect(status().isOk())
  54 + .andExpect(jsonPath("$.code").value(50000));
  55 + }
  56 +
  57 + @Test
  58 + void resultOkHelper_buildsZeroCode() {
  59 + Result<String> r = Result.ok("hi");
  60 + assertThat(r.getCode()).isZero();
  61 + assertThat(r.getData()).isEqualTo("hi");
  62 + }
  63 +
  64 + @RestController
  65 + static class StubController {
  66 + @GetMapping("/__test/throw-biz")
  67 + public Result<Void> throwBiz() {
  68 + throw new BizException(30001, "business x");
  69 + }
  70 +
  71 + @PostMapping("/__test/throw-validate")
  72 + public Result<Void> throwValidate(@Valid @RequestBody StubBody body) {
  73 + return Result.ok();
  74 + }
  75 +
  76 + @GetMapping("/__test/throw-runtime")
  77 + public Result<Void> throwRuntime() {
  78 + throw new IllegalStateException("boom");
  79 + }
  80 + }
  81 +
  82 + static class StubBody {
  83 + @NotBlank
  84 + public String name;
  85 + }
  86 +}
... ...