Ver Fonte

订单后台模块init

hr~ há 1 dia atrás
commit
4eca9f7bf5
90 ficheiros alterados com 4507 adições e 0 exclusões
  1. 45 0
      .gitignore
  2. 45 0
      order-client/pom.xml
  3. 24 0
      order-client/src/main/java/com/poyee/api/app/AppFeignClient.java
  4. 19 0
      order-client/src/main/java/com/poyee/api/product/ProductFeignClient.java
  5. 80 0
      order-common/pom.xml
  6. 13 0
      order-common/src/main/java/com/poyee/annotation/NoLogin.java
  7. 32 0
      order-common/src/main/java/com/poyee/annotation/UserLoginToken.java
  8. 167 0
      order-common/src/main/java/com/poyee/aspect/UserLoginTokenAspect.java
  9. 33 0
      order-common/src/main/java/com/poyee/aspect/weblog/RequestNoContext.java
  10. 67 0
      order-common/src/main/java/com/poyee/aspect/weblog/WebLog.java
  11. 123 0
      order-common/src/main/java/com/poyee/aspect/weblog/WebLogAspect.java
  12. 28 0
      order-common/src/main/java/com/poyee/config/FeignConfig.java
  13. 39 0
      order-common/src/main/java/com/poyee/config/FeignDateConfig.java
  14. 45 0
      order-common/src/main/java/com/poyee/config/I18nConfig.java
  15. 56 0
      order-common/src/main/java/com/poyee/config/JacksonConfig.java
  16. 60 0
      order-common/src/main/java/com/poyee/config/LocalDateTimeZoneSerializerConfig.java
  17. 106 0
      order-common/src/main/java/com/poyee/config/RedisConfig.java
  18. 138 0
      order-common/src/main/java/com/poyee/config/SwaggerConfig.java
  19. 8 0
      order-common/src/main/java/com/poyee/constant/OrderConstant.java
  20. 139 0
      order-common/src/main/java/com/poyee/domain/OrderAddress.java
  21. 111 0
      order-common/src/main/java/com/poyee/domain/OrderBase.java
  22. 103 0
      order-common/src/main/java/com/poyee/domain/OrderItem.java
  23. 102 0
      order-common/src/main/java/com/poyee/domain/OrderLogistics.java
  24. 95 0
      order-common/src/main/java/com/poyee/domain/RecycleOrder.java
  25. 27 0
      order-common/src/main/java/com/poyee/enums/AddressTypeEnum.java
  26. 26 0
      order-common/src/main/java/com/poyee/enums/FromSourceEnum.java
  27. 25 0
      order-common/src/main/java/com/poyee/enums/ItemSpecEnum.java
  28. 25 0
      order-common/src/main/java/com/poyee/enums/ItemStatusEnum.java
  29. 25 0
      order-common/src/main/java/com/poyee/enums/ItemTypeEnum.java
  30. 26 0
      order-common/src/main/java/com/poyee/enums/PermissionEnum.java
  31. 27 0
      order-common/src/main/java/com/poyee/enums/ProductStatusEnum.java
  32. 25 0
      order-common/src/main/java/com/poyee/enums/ProductTypeEnum.java
  33. 25 0
      order-common/src/main/java/com/poyee/enums/SaleTypeEnum.java
  34. 39 0
      order-common/src/main/java/com/poyee/enums/SalesOrderStatusEnum.java
  35. 25 0
      order-common/src/main/java/com/poyee/enums/WhetherEnum.java
  36. 34 0
      order-common/src/main/java/com/poyee/exception/BusinessException.java
  37. 169 0
      order-common/src/main/java/com/poyee/exception/GlobalExceptionHandler.java
  38. 19 0
      order-common/src/main/java/com/poyee/mapstruct/OrderBaseMapstruct.java
  39. 11 0
      order-common/src/main/java/com/poyee/param/UserInfo.java
  40. 16 0
      order-common/src/main/java/com/poyee/req/OrderAddressAuditReq.java
  41. 26 0
      order-common/src/main/java/com/poyee/req/OrderBaseSearchReq.java
  42. 34 0
      order-common/src/main/java/com/poyee/req/OrderDeliveryReq.java
  43. 22 0
      order-common/src/main/java/com/poyee/req/RecycleListSearchReq.java
  44. 47 0
      order-common/src/main/java/com/poyee/req/client/app/ShippingAddressReq.java
  45. 26 0
      order-common/src/main/java/com/poyee/req/client/product/ProductItemSearchReq.java
  46. 18 0
      order-common/src/main/java/com/poyee/req/page/Page.java
  47. 55 0
      order-common/src/main/java/com/poyee/res/OrderBaseListRes.java
  48. 48 0
      order-common/src/main/java/com/poyee/res/RecycleListRes.java
  49. 53 0
      order-common/src/main/java/com/poyee/res/Result.java
  50. 34 0
      order-common/src/main/java/com/poyee/res/UserInfo.java
  51. 33 0
      order-common/src/main/java/com/poyee/res/client/app/ShippingAddressRes.java
  52. 24 0
      order-common/src/main/java/com/poyee/res/client/product/ProductItemListRes.java
  53. 32 0
      order-common/src/main/java/com/poyee/threads/ThreadPoolExecutor.java
  54. 45 0
      order-common/src/main/java/com/poyee/utils/ApiUtils.java
  55. 99 0
      order-common/src/main/java/com/poyee/utils/I18nUtil.java
  56. 25 0
      order-common/src/main/java/com/poyee/utils/JwtUtils.java
  57. 184 0
      order-common/src/main/java/com/poyee/utils/LocaleTimeZoneUtil.java
  58. 63 0
      order-common/src/main/java/com/poyee/utils/ServletUtils.java
  59. 145 0
      order-web/pom.xml
  60. 15 0
      order-web/src/main/java/com/poyee/OrderApplication.java
  61. 52 0
      order-web/src/main/java/com/poyee/controller/OrderBaseController.java
  62. 31 0
      order-web/src/main/java/com/poyee/controller/RecycleOrderController.java
  63. 37 0
      order-web/src/main/java/com/poyee/facade/IOrderBaseFacade.java
  64. 16 0
      order-web/src/main/java/com/poyee/facade/IRecycleOrderFacade.java
  65. 221 0
      order-web/src/main/java/com/poyee/facade/impl/OrderBaseFacade.java
  66. 123 0
      order-web/src/main/java/com/poyee/facade/impl/RecycleOrderFacade.java
  67. 16 0
      order-web/src/main/java/com/poyee/mapper/OrderAddressMapper.java
  68. 16 0
      order-web/src/main/java/com/poyee/mapper/OrderBaseMapper.java
  69. 16 0
      order-web/src/main/java/com/poyee/mapper/OrderItemMapper.java
  70. 16 0
      order-web/src/main/java/com/poyee/mapper/OrderLogisticsMapper.java
  71. 16 0
      order-web/src/main/java/com/poyee/mapper/RecycleOrderMapper.java
  72. 16 0
      order-web/src/main/java/com/poyee/service/OrderAddressService.java
  73. 16 0
      order-web/src/main/java/com/poyee/service/OrderBaseService.java
  74. 16 0
      order-web/src/main/java/com/poyee/service/OrderItemService.java
  75. 16 0
      order-web/src/main/java/com/poyee/service/OrderLogisticsService.java
  76. 16 0
      order-web/src/main/java/com/poyee/service/RecycleOrderService.java
  77. 20 0
      order-web/src/main/java/com/poyee/service/impl/OrderAddressServiceImpl.java
  78. 20 0
      order-web/src/main/java/com/poyee/service/impl/OrderBaseServiceImpl.java
  79. 20 0
      order-web/src/main/java/com/poyee/service/impl/OrderItemServiceImpl.java
  80. 20 0
      order-web/src/main/java/com/poyee/service/impl/OrderLogisticsServiceImpl.java
  81. 20 0
      order-web/src/main/java/com/poyee/service/impl/RecycleOrderServiceImpl.java
  82. 36 0
      order-web/src/main/resources/application-dev.yml
  83. 42 0
      order-web/src/main/resources/application-prod.yml
  84. 35 0
      order-web/src/main/resources/application-test.yml
  85. 39 0
      order-web/src/main/resources/application.yml
  86. 9 0
      order-web/src/main/resources/i18n/messages.properties
  87. 9 0
      order-web/src/main/resources/i18n/messages_en_US.properties
  88. 8 0
      order-web/src/main/resources/i18n/messages_en_zh_TW.properties
  89. 143 0
      order-web/src/main/resources/logback-spring.xml
  90. 196 0
      pom.xml

+ 45 - 0
.gitignore

@@ -0,0 +1,45 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+.kotlin
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
+/.idea
+/logs
+/.claude
+/.qoder
+/.idea
+.idea

+ 45 - 0
order-client/pom.xml

@@ -0,0 +1,45 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.poyee</groupId>
+        <artifactId>b2b-order-service</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>order-client</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+        <!-- 公共模块 -->
+        <dependency>
+            <groupId>com.poyee</groupId>
+            <artifactId>order-common</artifactId>
+        </dependency>
+
+        <!-- Spring Cloud OpenFeign -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 24 - 0
order-client/src/main/java/com/poyee/api/app/AppFeignClient.java

@@ -0,0 +1,24 @@
+package com.poyee.api.app;
+
+import com.poyee.config.FeignConfig;
+import com.poyee.config.FeignDateConfig;
+import com.poyee.req.client.app.ShippingAddressReq;
+import com.poyee.res.Result;
+import com.poyee.res.client.app.ShippingAddressRes;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+@FeignClient(name = "app-service", url = "http://localhost:8082", decode404 = true, configuration = {FeignConfig.class, FeignDateConfig.class})
+public interface AppFeignClient {
+
+    @PostMapping("/api/mine/shippingAddress")
+    Result<ShippingAddressRes> shippingAddress(@RequestBody ShippingAddressReq shippingAddressReq);
+
+    @PostMapping("/api/mine/holdSippingAddress")
+    Result<Void> holdShippingAddress(@RequestBody ShippingAddressReq shippingAddressReq);
+
+    @PostMapping("/api/mine/delShippingAddress")
+    Result<Void> delShippingAddress(@RequestBody ShippingAddressReq shippingAddressReq);
+
+}

+ 19 - 0
order-client/src/main/java/com/poyee/api/product/ProductFeignClient.java

@@ -0,0 +1,19 @@
+package com.poyee.api.product;
+
+import com.github.pagehelper.PageInfo;
+import com.poyee.config.FeignConfig;
+import com.poyee.config.FeignDateConfig;
+import com.poyee.req.client.product.ProductItemSearchReq;
+import com.poyee.res.Result;
+import com.poyee.res.client.product.ProductItemListRes;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+@FeignClient(name = "product-service", url = "http://localhost:8080", decode404 = true, configuration = {FeignConfig.class, FeignDateConfig.class})
+public interface ProductFeignClient {
+
+    @PostMapping("/order/service/item/list")
+    Result<PageInfo<ProductItemListRes>> list(@RequestBody @Validated ProductItemSearchReq productItemSearchReq);
+}

+ 80 - 0
order-common/pom.xml

@@ -0,0 +1,80 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.poyee</groupId>
+        <artifactId>b2b-order-service</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>order-common</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct</artifactId>
+            <version>${mapstruct.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct-processor</artifactId>
+            <version>${mapstruct.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok-mapstruct-binding</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+        </dependency>
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 13 - 0
order-common/src/main/java/com/poyee/annotation/NoLogin.java

@@ -0,0 +1,13 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 无需登录注解
+ * 标注在类或方法上表示不需要登录即可访问
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface NoLogin {
+}

+ 32 - 0
order-common/src/main/java/com/poyee/annotation/UserLoginToken.java

@@ -0,0 +1,32 @@
+package com.poyee.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 用户登录Token注解
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface UserLoginToken {
+
+    /**
+     * 是否需要登录,默认true
+     */
+    boolean required() default true;
+
+    /**
+     * 是否需要实名认证
+     */
+    boolean faceVerify() default false;
+
+    /**
+     * 放行的角色
+     */
+    String[] roles() default {};
+
+    /**
+     * 是否通过审计
+     */
+    boolean pass_aud() default false;
+}

+ 167 - 0
order-common/src/main/java/com/poyee/aspect/UserLoginTokenAspect.java

@@ -0,0 +1,167 @@
+package com.poyee.aspect;
+
+import cn.hutool.core.util.StrUtil;import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.poyee.annotation.NoLogin;
+import com.poyee.annotation.UserLoginToken;
+import com.poyee.exception.BusinessException;
+import com.poyee.utils.JwtUtils;
+import com.poyee.utils.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Base64;
+
+/**
+ * 用户登录Token切面
+ */
+@Slf4j
+@Aspect
+@Component
+public class UserLoginTokenAspect {
+
+    /**
+     * 配置织入点 - 拦截所有 Controller 方法
+     */
+    @Pointcut("execution(public * com.poyee.controller.*.*(..))")
+    public void controllerPointCut() {
+    }
+
+    /**
+     * 前置通知
+     */
+    @Before("controllerPointCut()")
+    public void doBefore(JoinPoint joinPoint) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        if (request == null) {
+            return;
+        }
+
+        // 获取方法上的注解
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+        Class<?> declaringClass = method.getDeclaringClass();
+
+        // 1. 检查是否有 @NoLogin 注解(方法级别优先)
+        NoLogin noLogin = method.getAnnotation(NoLogin.class);
+        if (noLogin == null) {
+            noLogin = declaringClass.getAnnotation(NoLogin.class);
+        }
+        // 如果有 @NoLogin 注解,直接放行
+        if (noLogin != null) {
+            return;
+        }
+
+        // 2. 没有 @NoLogin 注解,一律进行 Token 检查
+        String userInfoStr = request.getHeader("X-USER-BASE64");
+        if (StrUtil.isBlank(userInfoStr)) {
+            userInfoStr = request.getHeader("x-user-base64");
+        }
+
+        log.info("X-USER-BASE64 >>> {}", userInfoStr);
+        String authorization = request.getHeader("Authorization");
+        log.info("Authorization >>> {}", authorization);
+
+        // 3. 检查是否有 @UserLoginToken 注解(用于角色控制)
+        UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
+        if (userLoginToken == null) {
+            userLoginToken = declaringClass.getAnnotation(UserLoginToken.class);
+        }
+
+        // 执行登录检查
+        checkUserLoginToken(request, userInfoStr, authorization, userLoginToken);
+    }
+
+    /**
+     * 检查用户登录Token
+     */
+    private void checkUserLoginToken(HttpServletRequest request, String userInfoStr,
+                                     String authorization, UserLoginToken userLoginToken) {
+        boolean isAuthorization = false;
+        String token = userInfoStr;
+
+        // 1. 尝试从 X-USER-BASE64 获取
+        if (StrUtil.isBlank(token)) {
+            // 2. 尝试从 Authorization 获取
+            if (StrUtil.isNotBlank(authorization)) {
+                if (authorization.startsWith("Bearer ")) {
+                    isAuthorization = true;
+                    token = authorization.substring(7);
+                } else {
+                    token = authorization;
+                }
+            }
+        }
+
+        if (StrUtil.isBlank(token)) {
+            throw new BusinessException(401, "未登录或登录已过期");
+        }
+
+        // 保存原始token到session
+        ServletUtils.getSession().setAttribute("x-user-base64", token);
+
+        // 解析用户信息
+        JSONObject userInfo = parseUserInfo(token, isAuthorization);
+        if (userInfo == null) {
+            throw new BusinessException(402, "登录信息无效,请重新登录");
+        }
+
+        // 保存用户信息到request
+        request.setAttribute("currentUser", userInfo);
+        ServletUtils.getSession().setAttribute("userInfo", JSON.toJSONString(userInfo));
+
+        // 检查角色权限(如果有 @UserLoginToken 注解且配置了角色)
+        if (userLoginToken != null && userLoginToken.roles().length > 0) {
+            boolean hasRole = checkRole(userInfo, userLoginToken.roles());
+            if (!hasRole) {
+                throw new BusinessException(403, "无权访问");
+            }
+        }
+
+        log.info("用户登录验证通过: {}", userInfo.getString("username"));
+    }
+
+    /**
+     * 解析用户信息
+     */
+    private JSONObject parseUserInfo(String token, boolean isAuthorization) {
+        try {
+            if (isAuthorization) {
+                // JWT Token 解析
+                return JwtUtils.getTokenUserInfo(token);
+            } else {
+                // Base64 编码的用户信息
+                String jsonStr = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8);
+                return JSON.parseObject(jsonStr);
+            }
+        } catch (Exception e) {
+            log.error("解析用户信息失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 检查用户角色
+     */
+    private boolean checkRole(JSONObject userInfo, String[] requiredRoles) {
+        if (userInfo == null) {
+            return false;
+        }
+        String role = userInfo.getString("role");
+        if (role == null) {
+            return false;
+        }
+        return Arrays.asList(requiredRoles).contains(role);
+    }
+}

+ 33 - 0
order-common/src/main/java/com/poyee/aspect/weblog/RequestNoContext.java

@@ -0,0 +1,33 @@
+package com.poyee.aspect.weblog;
+
+public class RequestNoContext {
+    /**
+     * 接口调用进程代码
+     */
+    public static ThreadLocal<Long> currentThreadId = new ThreadLocal<>();
+
+    /**
+     * 调用接口路径
+     */
+    public static ThreadLocal<String> apiUrl = new ThreadLocal<>();
+
+    /**
+     * 请求开始时间戳
+     */
+    public static ThreadLocal<Long> callTime = new ThreadLocal<>();
+
+    /**
+     * 请求参数
+     */
+    public static ThreadLocal<String> requestParam = new ThreadLocal<>();
+
+    /**
+     * 清除ThreadLocal变量
+     */
+    public static void remove() {
+        currentThreadId.remove();
+        apiUrl.remove();
+        callTime.remove();
+        requestParam.remove();
+    }
+}

+ 67 - 0
order-common/src/main/java/com/poyee/aspect/weblog/WebLog.java

@@ -0,0 +1,67 @@
+package com.poyee.aspect.weblog;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * Controller层的日志封装类
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class WebLog {
+    /**
+     * 操作描述
+     */
+    private String description;
+
+    /**
+     * 操作用户
+     */
+    private String username;
+
+    /**
+     * 操作时间
+     */
+    private Long startTime;
+
+    /**
+     * 消耗时间
+     */
+    private Integer spendTime;
+
+    /**
+     * 根路径
+     */
+    private String basePath;
+
+    /**
+     * URI
+     */
+    private String uri;
+
+    /**
+     * URL
+     */
+    private String url;
+
+    /**
+     * 请求类型
+     */
+    private String method;
+
+    /**
+     * IP地址
+     */
+    private String ip;
+
+    /**
+     * 请求参数
+     */
+    private Object parameter;
+
+    /**
+     * 返回结果
+     */
+    private Object result;
+
+}

+ 123 - 0
order-common/src/main/java/com/poyee/aspect/weblog/WebLogAspect.java

@@ -0,0 +1,123 @@
+package com.poyee.aspect.weblog;
+
+import cn.hutool.json.JSONUtil;
+import com.poyee.utils.ServletUtils;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.*;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import org.slf4j.MDC;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * 统一日志处理切面
+ */
+@Aspect
+@Component
+@Order(1)
+public class WebLogAspect {
+    private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
+
+    @Pointcut("execution(public * com.poyee.controller..*.*(..))")
+    public void webLog() {
+    }
+
+    @Before("webLog()")
+    public void doBefore(JoinPoint joinPoint) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        String traceId = Objects.requireNonNull(request).getHeader("X-Trace-Id");
+        if (!StringUtils.hasText(traceId)) {
+            traceId = UUID.randomUUID().toString().replace("-", "");
+        }
+        MDC.put("traceId", traceId);
+        long currentTimeMillis = System.currentTimeMillis();
+        String requestURI = request.getRequestURI();
+        RequestNoContext.callTime.set(currentTimeMillis);
+        RequestNoContext.apiUrl.set(requestURI);
+        RequestNoContext.currentThreadId.set(currentTimeMillis);
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+        String argsJson = JSONUtil.toJsonStr(getParameter(method, joinPoint.getArgs()));
+        RequestNoContext.requestParam.set(argsJson);
+        String log = "\n===============[" + RequestNoContext.currentThreadId.get() + "]请求内容开始===============" +
+                "\n    请求地址:" + requestURI +
+                "\n    请求方式:" + request.getMethod() +
+                "\n    客户端 IP:" + request.getRemoteAddr() +
+                "\n    接口参数:" + argsJson +
+                "\n===============[" + RequestNoContext.currentThreadId.get() + "]请求内容结束===============";
+        logger.info(log);
+    }
+
+    @AfterReturning(value = "webLog()", returning = "returns")
+    public void afterReturning(Object returns) {
+        long costTime = System.currentTimeMillis() - RequestNoContext.callTime.get();
+        logger.info("\n--------------[{}]返回内容开始----------------\n    接口地址:{}\n    返回内容:{}\n    请求耗时:{}ms\n--------------[{}]返回内容结束----------------", RequestNoContext.currentThreadId.get(), RequestNoContext.apiUrl.get(), JSONUtil.toJsonStr(returns), costTime, RequestNoContext.currentThreadId.get());
+        RequestNoContext.remove();
+        MDC.remove("traceId");
+    }
+
+
+    @AfterThrowing(value = "webLog()", throwing = "e")
+    public void throwing(Throwable e) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        long costTime = System.currentTimeMillis() - RequestNoContext.callTime.get();
+        logger.error("\n--------------[{}]返回内容开始----------------\n    接口地址:{}\n    请求令牌:{}\n    接口参数:{}\n    请求失败:{}\n    请求耗时:{}ms\n--------------[{}]返回内容结束----------------", RequestNoContext.currentThreadId.get(), RequestNoContext.apiUrl.get(), Objects.requireNonNull(request).getHeader("Authorization"), RequestNoContext.requestParam.get(), e.getMessage(), costTime, RequestNoContext.currentThreadId.get());
+        RequestNoContext.remove();
+        MDC.remove("traceId");
+    }
+
+    /**
+     * 根据方法和传入的参数获取请求参数
+     */
+    private Object getParameter(Method method, Object[] args) {
+        Parameter[] parameters = method.getParameters();
+        List<Object> argList = IntStream.range(0, parameters.length)
+                .mapToObj(i -> {
+                    Parameter parameter = parameters[i];
+                    RequestBody requestBody = parameter.getAnnotation(RequestBody.class);
+                    if (requestBody != null) {
+                        return args[i];
+                    }
+
+                    RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
+                    if (requestParam != null) {
+                        Map<String, Object> map = new HashMap<>();
+                        String key = StringUtils.hasText(requestParam.value())
+                                ? requestParam.value()
+                                : parameter.getName();
+                        map.put(key, args[i]);
+                        return map;
+                    }
+                    return null;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (argList.isEmpty()) {
+            return null;
+        } else if (argList.size() == 1) {
+            return argList.get(0);
+        } else {
+            return argList;
+        }
+    }
+}

+ 28 - 0
order-common/src/main/java/com/poyee/config/FeignConfig.java

@@ -0,0 +1,28 @@
+package com.poyee.config;
+
+import cn.hutool.core.util.StrUtil;
+import feign.RequestInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Configuration
+public class FeignConfig {
+
+    @Bean
+    public RequestInterceptor requestInterceptor() {
+        return template -> {
+            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            if (attributes != null) {
+                HttpServletRequest request = attributes.getRequest();
+                String userBase64 = request.getHeader("X-USER-BASE64");
+                if (StrUtil.isNotBlank(userBase64)) {
+                    template.header("X-USER-BASE64", userBase64);
+                }
+            }
+        };
+    }
+}

+ 39 - 0
order-common/src/main/java/com/poyee/config/FeignDateConfig.java

@@ -0,0 +1,39 @@
+package com.poyee.config;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import feign.codec.Decoder;
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
+import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
+import org.springframework.cloud.openfeign.support.SpringDecoder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class FeignDateConfig {
+    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+    @Bean
+    public Decoder feignDecoder() {
+        ObjectMapper objectMapper = new ObjectMapper();
+        JavaTimeModule javaTimeModule = new JavaTimeModule();
+        // 配置 LocalDateTime 的反序列化
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
+        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
+        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
+        objectMapper.registerModule(javaTimeModule);
+        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        ObjectFactory<HttpMessageConverters> messageConverters = () -> {
+            MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper);
+            return new HttpMessageConverters(converter);
+        };
+
+        return new ResponseEntityDecoder(new SpringDecoder(messageConverters));
+    }
+}

+ 45 - 0
order-common/src/main/java/com/poyee/config/I18nConfig.java

@@ -0,0 +1,45 @@
+package com.poyee.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.LocaleResolver;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
+import org.springframework.web.servlet.i18n.SessionLocaleResolver;
+
+import java.util.Locale;
+
+/**
+ * 国际化配置
+ */
+@Configuration
+public class I18nConfig implements WebMvcConfigurer {
+
+    /**
+     * 默认解析器,默认语言为中文
+     */
+    @Bean
+    public LocaleResolver localeResolver() {
+        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
+        // 默认语言:中文
+        localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
+        return localeResolver;
+    }
+
+    /**
+     * 语言切换拦截器
+     * 通过 lang 参数切换语言,如 ?lang=en
+     */
+    @Bean
+    public LocaleChangeInterceptor localeChangeInterceptor() {
+        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
+        interceptor.setParamName("lang");
+        return interceptor;
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(localeChangeInterceptor());
+    }
+}

+ 56 - 0
order-common/src/main/java/com/poyee/config/JacksonConfig.java

@@ -0,0 +1,56 @@
+package com.poyee.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 全局 Jackson 配置
+ * <p>
+ * 注册时区感知序列化器 {@link LocalDateTimeZoneSerializerConfig}:
+ * 所有响应中的 {@link LocalDateTime} 字段将根据请求头 {@code Accept-Language}
+ * 自动从 UTC 转换为客户端对应的区域时间后输出。
+ * </p>
+ *
+ * <p>
+ * 注意:不使用 {@code JavaTimeModule} 注册自定义序列化器,
+ * 因为 Spring Boot 自动配置的 {@code Jackson2ObjectMapperBuilder} 已注册过 {@code JavaTimeModule},
+ * Jackson 的模块去重机制会导致第二次注册同类型模块被静默跳过。
+ * 改用独立命名的 {@code SimpleModule} 可绕过去重检查,确保自定义序列化器生效。
+ * </p>
+ */
+@Configuration
+public class JacksonConfig {
+
+    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    @Bean
+    public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
+        // 保留 Spring Boot 自动配置的所有默认模块,在此基础上叠加
+        ObjectMapper mapper = builder.createXmlMapper(false).build();
+
+        // 使用 SimpleModule(独立类名)注册,绕过 Jackson 对 JavaTimeModule 的去重跳过机制
+        SimpleModule overrideModule = new SimpleModule("LocalDateTimeZoneOverride");
+
+        // 序列化:UTC LocalDateTime → 客户端时区时间(根据 Accept-Language 自动转换)
+        overrideModule.addSerializer(LocalDateTime.class, new LocalDateTimeZoneSerializerConfig());
+
+        // 反序列化:客户端传入时间字符串 → LocalDateTime(业务层保证传入 UTC)
+        overrideModule.addDeserializer(
+                LocalDateTime.class,
+                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN))
+        );
+
+        mapper.registerModule(overrideModule);
+        // 禁用将时间序列化为时间戳
+        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+        return mapper;
+    }
+}

+ 60 - 0
order-common/src/main/java/com/poyee/config/LocalDateTimeZoneSerializerConfig.java

@@ -0,0 +1,60 @@
+package com.poyee.config;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.poyee.utils.LocaleTimeZoneUtil;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 时区感知的 LocalDateTime Jackson 序列化器
+ * <p>
+ * 数据库存储的是 UTC 时间({@link LocalDateTime} 不带时区信息),
+ * 本序列化器在序列化响应时,从请求头 {@code Accept-Language} 推断客户端所在时区,
+ * 将 UTC 时间转换为对应的区域时间后输出。
+ * </p>
+ *
+ * <pre>
+ * 示例:
+ *   DB UTC时间:   2026-03-20 06:00:00
+ *   Accept-Language: zh-CN
+ *   输出时间:      2026-03-20 14:00:00   (UTC+8)
+ *
+ *   Accept-Language: en-US
+ *   输出时间:      2026-03-20 02:00:00   (UTC-4, 夏令时)
+ * </pre>
+ */
+public class LocalDateTimeZoneSerializerConfig extends StdSerializer<LocalDateTime> {
+
+    private static final DateTimeFormatter FORMATTER =
+            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    public LocalDateTimeZoneSerializerConfig() {
+        super(LocalDateTime.class);
+    }
+
+    @Override
+    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider provider)
+            throws IOException {
+        if (value == null) {
+            gen.writeNull();
+            return;
+        }
+        // 1. 将数据库中的 LocalDateTime 视为 UTC 时间
+        ZonedDateTime utcDateTime = value.atZone(ZoneId.of("UTC"));
+
+        // 2. 根据请求 Accept-Language 解析目标时区
+        ZoneId targetZone = LocaleTimeZoneUtil.resolveZoneId();
+
+        // 3. 转换到目标时区
+        ZonedDateTime localDateTime = utcDateTime.withZoneSameInstant(targetZone);
+
+        // 4. 格式化输出
+        gen.writeString(localDateTime.format(FORMATTER));
+    }
+}

+ 106 - 0
order-common/src/main/java/com/poyee/config/RedisConfig.java

@@ -0,0 +1,106 @@
+package com.poyee.config;
+
+import cn.hutool.core.util.StrUtil;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.redisson.config.SentinelServersConfig;
+import org.redisson.config.SingleServerConfig;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.io.IOException;
+
+/**
+ * Redis配置
+ *
+ * @author zheng
+ */
+@Configuration
+@EnableCaching
+public class RedisConfig {
+    @Value("${spring.redis.host:127.0.0.1}")
+    private String host;
+    @Value("${spring.redis.port:6379}")
+    private String port;
+    @Value("${spring.redis.password}")
+    private String password;
+    @Value("${spring.redis.sentinel.nodes:127.0.0.1:6379}")
+    private String nodes;
+    @Value("${spring.redis.sentinel.master:master}")
+    private String master;
+
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
+        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
+        // 开启事务
+        //redisTemplate.setEnableTransactionSupport(true);
+        redisTemplate.setConnectionFactory(factory);
+        return redisTemplate;
+
+    }
+
+    @Bean
+    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForHash();
+    }
+
+    @Bean
+    public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
+        return redisTemplate.opsForValue();
+    }
+
+    @Bean
+    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForList();
+    }
+
+    @Bean
+    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForSet();
+    }
+
+    @Bean
+    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
+        return redisTemplate.opsForZSet();
+    }
+
+    @Bean(name = "redissonClient")
+    public RedissonClient redissonClientSingle() throws IOException {
+        Config config = new Config();
+        if (!"127.0.0.1:6379".equals(nodes) && !"master".equals(master)) {
+            SentinelServersConfig sentinelServers = config.useSentinelServers();
+            sentinelServers.addSentinelAddress("redis://" + nodes).setMasterName(master);
+            if (!StrUtil.isBlank(password)) {
+                sentinelServers.setPassword(password);
+            }
+        } else {
+            SingleServerConfig singleServer = config.useSingleServer();
+            singleServer.setAddress("redis://" + host + ":" + port);
+            if (!StrUtil.isBlank((password))) {
+                singleServer.setPassword(password);
+            }
+        }
+
+        return Redisson.create(config);
+    }
+
+    @Bean
+    public RedisMessageListenerContainer myRedisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
+        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
+        container.setConnectionFactory(connectionFactory);
+        return container;
+    }
+
+}

+ 138 - 0
order-common/src/main/java/com/poyee/config/SwaggerConfig.java

@@ -0,0 +1,138 @@
+package com.poyee.config;
+
+import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
+import springfox.documentation.RequestHandler;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * Knife4j 接口文档配置
+ */
+@Configuration
+@EnableSwagger2
+@EnableKnife4j
+@Import(BeanValidatorPluginsConfiguration.class)
+public class SwaggerConfig {
+
+    private static String splitor = ";";
+
+    /** 是否开启swagger */
+    @Value("${swagger.enabled:false}")
+    private boolean enabled;
+
+    /**
+     * 创建API
+     */
+    @Bean
+    public Docket createRestApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                // 是否启用Swagger
+                .enable(enabled)
+                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+                .apiInfo(apiInfo())
+                // 设置哪些接口暴露给Swagger展示
+                .select()
+                // 扫描所有有注解的api,用这种方式更灵活
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // 扫描指定包中的swagger注解
+                .apis(RequestHandlerSelectors.basePackage("com.poyee"))
+                // 扫描所有
+                .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build();
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo apiInfo() {
+        // 用ApiInfoBuilder进行定制
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title("订单服务接口文档")
+                // 描述
+                .description("订单服务 API 接口文档")
+                // 作者信息
+                .contact(new Contact("poyee", null, null))
+                // 版本
+                .version("1.0")
+                .build();
+    }
+
+    /**
+     * 创建Partner API(分组示例)
+     */
+    @Bean
+    public Docket createPartnerRestApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .groupName("1.0")
+                // 是否启用Swagger
+                .enable(enabled)
+                // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息)
+                .apiInfo(partnerApiInfo())
+                // 设置哪些接口暴露给Swagger展示
+                .select()
+                // 扫描所有有注解的api,用这种方式更灵活
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // 扫描指定包中的swagger注解
+                .apis(basePackage("com.poyee.controller"))
+                // 扫描所有
+                .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build();
+    }
+
+    /**
+     * 添加摘要信息
+     */
+    private ApiInfo partnerApiInfo() {
+        // 用ApiInfoBuilder进行定制
+        return new ApiInfoBuilder()
+                // 设置标题
+                .title("订单服务接口文档")
+                // 描述
+                .description("订单服务 Partner API 接口文档")
+                // 作者信息
+                .contact(new Contact("poyee", null, null))
+                // 版本
+                .version("partner")
+                .build();
+    }
+
+    public static Predicate<RequestHandler> basePackage(final String basePackage) {
+        return input -> declaringClass(input).transform(handlerPackage(basePackage))
+                .or(true);
+    }
+
+    private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {
+        return input -> {
+            // 循环判断匹配
+            for (String strPackage : basePackage.split(splitor)) {
+                boolean isMatch = input.getPackage().getName().startsWith(strPackage);
+                if (isMatch) {
+                    return true;
+                }
+            }
+            return false;
+        };
+    }
+
+    private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
+        return Optional.fromNullable(input.declaringClass());
+    }
+}

+ 8 - 0
order-common/src/main/java/com/poyee/constant/OrderConstant.java

@@ -0,0 +1,8 @@
+package com.poyee.constant;
+
+public class OrderConstant {
+
+    public static final String ORDER = "ORDER";
+
+    public static final String RECYCLE = "RECYCLE";
+}

+ 139 - 0
order-common/src/main/java/com/poyee/domain/OrderAddress.java

@@ -0,0 +1,139 @@
+package com.poyee.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * <p>
+ * 订单地址表
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("order_address")
+public class OrderAddress implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 订单编号
+     */
+    private String relationNo;
+
+    /**
+     * 关联表
+     */
+    private String relationTable;
+
+    /**
+     * 国家
+     */
+    private String country;
+
+    /**
+     * 省
+     */
+    private String province;
+
+    /**
+     * 市
+     */
+    private String city;
+
+    /**
+     * 区
+     */
+    private String area;
+
+    /**
+     * 详细地址
+     */
+    private String address;
+
+    /**
+     * hs地址id
+     */
+    private Long relationAddressId;
+
+    /**
+     * 买家
+     */
+    private String buyerUser;
+
+    /**
+     * 手机号
+     */
+    private Integer phone;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+    private Integer deleteFlag;
+    private Integer version;
+
+    /***
+     * 1卖家地址 2买家地址
+     */
+    private Integer type;
+
+    private Integer valid;
+
+
+    @Override
+    public String toString() {
+        return "OrderAddress{" +
+                ", id = " + id +
+                ", relationNo = " + relationNo +
+                ", country = " + country +
+                ", province = " + province +
+                ", city = " + city +
+                ", area = " + area +
+                ", address = " + address +
+                ", relationAddressId = " + relationAddressId +
+                ", buyerUser = " + buyerUser +
+                ", phone = " + phone +
+                ", createBy = " + createBy +
+                ", createTime = " + createTime +
+                ", updateBy = " + updateBy +
+                ", updateTime = " + updateTime +
+                ", deleteFlag = " + deleteFlag +
+                ", version = " + version +
+                "}";
+    }
+}

+ 111 - 0
order-common/src/main/java/com/poyee/domain/OrderBase.java

@@ -0,0 +1,111 @@
+package com.poyee.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+@Data
+@Builder
+@TableName("order_base")
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderBase implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 订单号
+     */
+    private String orderNo;
+
+    /**
+     * 订单状态
+     */
+    private Integer orderStatus;
+
+
+    /**
+     * 支付金额
+     */
+    private BigDecimal paymentAmount;
+
+    /**
+     * 卖家
+     */
+    private String sellerUser;
+
+    /**
+     * 买家
+     */
+    private String buyerUser;
+
+    /**
+     * 支付时间
+     */
+    private LocalDateTime paymentTime;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+
+    private Integer deleteFlag;
+
+    private Integer version;
+
+
+    @Override
+    public String toString() {
+        return "OrderBase{" +
+        ", id = " + id +
+        ", orderNo = " + orderNo +
+        ", orderStatus = " + orderStatus +
+        ", paymentAmount = " + paymentAmount +
+        ", sellerUser = " + sellerUser +
+        ", buyerUser = " + buyerUser +
+        ", paymentTime = " + paymentTime +
+        ", createBy = " + createBy +
+        ", createTime = " + createTime +
+        ", updateBy = " + updateBy +
+        ", updateTime = " + updateTime +
+        "}";
+    }
+}

+ 103 - 0
order-common/src/main/java/com/poyee/domain/OrderItem.java

@@ -0,0 +1,103 @@
+package com.poyee.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * <p>
+ *
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-20
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName("order_item")
+public class OrderItem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 关联单号
+     */
+    private String relationNo;
+
+    /**
+     * 关联表 ORDER   RECYCLE
+     */
+    private String relationTable;
+
+    /**
+     * 关联商品
+     */
+    private Integer relationSku;
+
+    /**
+     * 名称
+     */
+    private String productName;
+
+    /**
+     * 规格
+     */
+    private Integer spec;
+
+    /**
+     * 数量
+     */
+    private Integer quantity;
+
+    /**
+     * 商品类型
+     */
+    private Integer itemType;
+    private String createBy;
+    private LocalDateTime createTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+    private Integer version;
+    private Integer deleteFlag;
+
+
+    @Override
+    public String toString() {
+        return "OrderItem{" +
+                ", id = " + id +
+                ", relationNo = " + relationNo +
+                ", relationTable = " + relationTable +
+                ", relationSku = " + relationSku +
+                ", productName = " + productName +
+                ", spec = " + spec +
+                ", quantity = " + quantity +
+                ", itemType = " + itemType +
+                ", createBy = " + createBy +
+                ", createTime = " + createTime +
+                ", updateBy = " + updateBy +
+                ", updateTime = " + updateTime +
+                ", version = " + version +
+                ", deleteFlag = " + deleteFlag +
+                "}";
+    }
+}

+ 102 - 0
order-common/src/main/java/com/poyee/domain/OrderLogistics.java

@@ -0,0 +1,102 @@
+package com.poyee.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+@Data
+@Builder
+@TableName("order_logistics")
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderLogistics implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+
+    /**
+     * 订单编号
+     */
+    private String relationNo;
+
+    /**
+     * 关联表
+     */
+    private String relationTable;
+
+
+    /**
+     * 物流名称
+     */
+    private String logisticsName;
+
+    /**
+     * 物流编码
+     */
+    private String logisticsCode;
+
+    /**
+     * 物流号
+     */
+    private String trackingNo;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    private LocalDateTime logisticsTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+    private Integer version;
+    private Integer deleteFlag;
+
+
+    @Override
+    public String toString() {
+        return "OrderLogistics{" +
+        ", id = " + id +
+        ", relationNo = " + relationNo +
+        ", logisticsName = " + logisticsName +
+        ", logisticsCode = " + logisticsCode +
+        ", trackingNo = " + trackingNo +
+        ", createBy = " + createBy +
+        ", createTime = " + createTime +
+        ", updateBy = " + updateBy +
+        ", updateTime = " + updateTime +
+        ", version = " + version +
+        ", deleteFlag = " + deleteFlag +
+        "}";
+    }
+}

+ 95 - 0
order-common/src/main/java/com/poyee/domain/RecycleOrder.java

@@ -0,0 +1,95 @@
+package com.poyee.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-20
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName("recycle_order")
+public class RecycleOrder implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 回收单号
+     */
+    private String recycleNo;
+
+    /**
+     * 回收总价
+     */
+    private BigDecimal recycleAmount;
+
+    /**
+     * 条码
+     */
+    private String barcode;
+
+    /**
+     * 版本
+     */
+    private Integer version;
+
+    /**
+     * 是否删除
+     */
+    private Integer deleteFlag;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+
+    @Override
+    public String toString() {
+        return "RecycleOrder{" +
+        ", id = " + id +
+        ", recycleNo = " + recycleNo +
+        ", recycleAmount = " + recycleAmount +
+        ", barcode = " + barcode +
+        ", version = " + version +
+        ", deleteFlag = " + deleteFlag +
+        ", createBy = " + createBy +
+        ", createTime = " + createTime +
+        ", updateBy = " + updateBy +
+        ", updateTime = " + updateTime +
+        "}";
+    }
+}

+ 27 - 0
order-common/src/main/java/com/poyee/enums/AddressTypeEnum.java

@@ -0,0 +1,27 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum AddressTypeEnum {
+    SELLER(1,"卖家"),
+    BUYER(2, "买家"),;
+
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    AddressTypeEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static AddressTypeEnum fromCode(int code) {
+        for (AddressTypeEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown from source code: " + code);
+    }
+
+}

+ 26 - 0
order-common/src/main/java/com/poyee/enums/FromSourceEnum.java

@@ -0,0 +1,26 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum FromSourceEnum {
+    ECO_SALE(1, "生态购"),
+    RECYCLE(2, "回收"),
+    IDLE(3, "闲置");
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    FromSourceEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static FromSourceEnum fromCode(int code) {
+        for (FromSourceEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown from source code: " + code);
+    }
+}

+ 25 - 0
order-common/src/main/java/com/poyee/enums/ItemSpecEnum.java

@@ -0,0 +1,25 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum ItemSpecEnum {
+    BOX(1, "箱"),
+    CASE(2, "盒");
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    ItemSpecEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static ItemSpecEnum fromCode(int code) {
+        for (ItemSpecEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown item spec code: " + code);
+    }
+}

+ 25 - 0
order-common/src/main/java/com/poyee/enums/ItemStatusEnum.java

@@ -0,0 +1,25 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum ItemStatusEnum {
+    ON_SHELF(1, "上架"),
+    OFF_SHELF(2, "下架");
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    ItemStatusEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static ItemStatusEnum fromCode(int code) {
+        for (ItemStatusEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown item status code: " + code);
+    }
+}

+ 25 - 0
order-common/src/main/java/com/poyee/enums/ItemTypeEnum.java

@@ -0,0 +1,25 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum ItemTypeEnum {
+    FLASH_SALE(1, "闪购"),
+    IDLE(2, "闲置");
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    ItemTypeEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static ItemTypeEnum fromCode(int code) {
+        for (ItemTypeEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown item type code: " + code);
+    }
+}

+ 26 - 0
order-common/src/main/java/com/poyee/enums/PermissionEnum.java

@@ -0,0 +1,26 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum PermissionEnum {
+    ALL(1, "全部"),
+    MERCHANT(2, "商家"),
+    USER(3, "用户");
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    PermissionEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static PermissionEnum fromCode(int code) {
+        for (PermissionEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown permission code: " + code);
+    }
+}

+ 27 - 0
order-common/src/main/java/com/poyee/enums/ProductStatusEnum.java

@@ -0,0 +1,27 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum ProductStatusEnum {
+    PENDING_REVIEW(10, "待审核"),
+    ON_SHELF(20, "已上架"),
+    OFF_SHELF(30, "下架"),
+    REJECTED(40, "拒绝");
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    ProductStatusEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static ProductStatusEnum fromCode(int code) {
+        for (ProductStatusEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown product status code: " + code);
+    }
+}

+ 25 - 0
order-common/src/main/java/com/poyee/enums/ProductTypeEnum.java

@@ -0,0 +1,25 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum ProductTypeEnum {
+    STAR_CARD(1, "球星卡"),
+    OTHER(2, "其他");
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    ProductTypeEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static ProductTypeEnum fromCode(int code) {
+        for (ProductTypeEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown product type code: " + code);
+    }
+}

+ 25 - 0
order-common/src/main/java/com/poyee/enums/SaleTypeEnum.java

@@ -0,0 +1,25 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum SaleTypeEnum {
+    SPOT(1, "现货"),
+    PRE_SALE(2, "预售");
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    SaleTypeEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static SaleTypeEnum fromCode(int code) {
+        for (SaleTypeEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown sale type code: " + code);
+    }
+}

+ 39 - 0
order-common/src/main/java/com/poyee/enums/SalesOrderStatusEnum.java

@@ -0,0 +1,39 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum SalesOrderStatusEnum {
+    PENDING_PAYMENT(1001, "待支付"),              // 订单已创建,等待用户支付
+    PENDING_SHIPMENT(1002, "待发货"),            // 订单已处理,等待卖家发货
+    SHIPPED(1003, "已发货"),                     // 订单已发货,等待买家确认收货
+    COMPLETED(1004, "订单完成"),                 // 买家已确认收货,交易完成
+
+    // 退款相关状态(2001-2004)
+    REFUND_PENDING(2001, "退款审核中"),          // 用户发起退款,等待商家审核(闲置订单特有)
+    REFUND_PROCESSING(2002, "退款中"),           // 订单正在处理退款,用户资金等待银行确认
+    REFUND_COMPLETED(2003, "退款完成"),          // 订单已完成退款,用户资金已到账
+    REFUND_FAILED(2004, "退款失败"),             // 订单退款失败,用户资金未到账
+
+    // 异常状态
+    EXPIRED(3001, "订单超时");                   // 用户未在规定时间内完成操作,系统自动取消订单
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    SalesOrderStatusEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+
+    public static SalesOrderStatusEnum fromCode(int code) {
+        for (SalesOrderStatusEnum status : values()) {
+            if (status.code == code) {
+                return status;
+            }
+        }
+        throw new IllegalArgumentException("Unknown order status code: " + code);
+    }
+}

+ 25 - 0
order-common/src/main/java/com/poyee/enums/WhetherEnum.java

@@ -0,0 +1,25 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+public enum WhetherEnum {
+    YES(1, "是"),
+    NO(0, "否");
+
+    @Getter
+    private final int code;
+    @Getter
+    private final String description;
+
+    WhetherEnum(int code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    public static WhetherEnum fromCode(int code) {
+        for (WhetherEnum e : values()) {
+            if (e.code == code) return e;
+        }
+        throw new IllegalArgumentException("Unknown whether code: " + code);
+    }
+}

+ 34 - 0
order-common/src/main/java/com/poyee/exception/BusinessException.java

@@ -0,0 +1,34 @@
+package com.poyee.exception;
+
+import lombok.Getter;
+
+/**
+ * 业务异常
+ */
+@Getter
+public class BusinessException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 错误码
+     */
+    private final Integer code;
+
+    /**
+     * 错误消息
+     */
+    private final String message;
+
+    public BusinessException(String message) {
+        super(message);
+        this.code = 500;
+        this.message = message;
+    }
+
+    public BusinessException(Integer code, String message) {
+        super(message);
+        this.code = code;
+        this.message = message;
+    }
+}

+ 169 - 0
order-common/src/main/java/com/poyee/exception/GlobalExceptionHandler.java

@@ -0,0 +1,169 @@
+package com.poyee.exception;
+
+import com.poyee.res.Result;
+import com.poyee.utils.I18nUtil;
+import com.poyee.utils.ServletUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.exceptions.TokenExpiredException;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 全局异常处理器
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 处理业务异常
+     */
+    @ExceptionHandler(BusinessException.class)
+    public Result<Void> handleBusinessException(BusinessException e) {
+        log.warn("业务异常:{}", I18nUtil.getMessage(e.getMessage()));
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        return Result.error(e.getCode(), I18nUtil.getMessage(e.getMessage()));
+    }
+
+
+    @ExceptionHandler(RuntimeException.class)
+    public Result<Void> handleRuntimeException(RuntimeException e) {
+        log.warn("运行异常:{}", I18nUtil.getMessage(e.getMessage()));
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        return Result.error(I18nUtil.getMessage(e.getMessage()));
+    }
+
+    /**
+     * 处理其他异常
+     */
+    @ExceptionHandler(Exception.class)
+    public Result<Void> handleException(Exception e) {
+        log.error("系统异常", e);
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        return Result.error(500, "系统繁忙,请稍后重试");
+    }
+
+
+    /**
+     * 处理其他异常
+     */
+    @ExceptionHandler(IllegalArgumentException.class)
+    public Result<Void> handleIllegalArgumentException(IllegalArgumentException e) {
+        log.error("参数异常", e);
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
+        return Result.error(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
+    }
+
+    /**
+     * 参数错误
+     */
+    @ExceptionHandler(value = MethodArgumentNotValidException.class)
+    public Result<Void> methodArgumentNotValidExceptionHandle(MethodArgumentNotValidException ex, HttpServletRequest request) {
+        log.error("Unhandled RequestParamException from URL [{}]", request.getRequestURI(), ex);
+        List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
+        List<String> collect = allErrors.stream()
+                .map(error -> {
+                    String defaultMessage = error.getDefaultMessage();
+                    String i18nMessage = I18nUtil.getMessage(defaultMessage);
+                    return i18nMessage != null ? i18nMessage : defaultMessage;
+                })
+                .collect(Collectors.toList());
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
+        return Result.error(HttpServletResponse.SC_BAD_REQUEST, collect.toString());
+    }
+
+    /**
+     * 缺少请求参数
+     */
+    @ExceptionHandler(MissingServletRequestParameterException.class)
+    public Result<Void> handleMissingServletRequestParameter(MissingServletRequestParameterException e) {
+        log.warn("缺少请求参数:{}", e.getParameterName());
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
+        return Result.error(HttpServletResponse.SC_BAD_REQUEST, "缺少请求参数:" + e.getParameterName());
+    }
+
+    /**
+     * 参数类型不匹配
+     */
+    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+    public Result<Void> handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException e) {
+        log.warn("参数类型不匹配:{}", e.getName());
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
+        return Result.error(HttpServletResponse.SC_BAD_REQUEST, "参数类型不匹配:" + e.getName());
+    }
+
+    /**
+     * 请求体不可读(JSON格式错误等)
+     */
+    @ExceptionHandler(HttpMessageNotReadableException.class)
+    public Result<Void> handleHttpMessageNotReadable(HttpMessageNotReadableException e) {
+        log.warn("请求体解析失败:{}", e.getMessage());
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
+        return Result.error(HttpServletResponse.SC_BAD_REQUEST, "请求体格式错误");
+    }
+
+    /**
+     * 请求方法不支持
+     */
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    public Result<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e) {
+        log.warn("请求方法不支持:{}", e.getMethod());
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+        return Result.error(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "不支持的请求方法:" + e.getMethod());
+    }
+
+    /**
+     * 媒体类型不支持
+     */
+    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
+    public Result<Void> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException e) {
+        log.warn("媒体类型不支持:{}", e.getContentType());
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
+        return Result.error(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, "不支持的媒体类型:" + e.getContentType());
+    }
+
+    /**
+     * JWT Token 过期
+     */
+    @ExceptionHandler(TokenExpiredException.class)
+    public Result<Void> handleTokenExpired(TokenExpiredException e) {
+        log.warn("Token已过期:{}", e.getMessage());
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        return Result.error(HttpServletResponse.SC_UNAUTHORIZED, "Token已过期,请重新登录");
+    }
+
+
+    /**
+     * JWT Token 验证失败
+     */
+    @ExceptionHandler(JWTVerificationException.class)
+    public Result<Void> handleJWTVerification(JWTVerificationException e) {
+        log.warn("Token验证失败:{}", e.getMessage());
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        return Result.error(HttpServletResponse.SC_UNAUTHORIZED, "Token无效,请重新登录");
+    }
+
+    /**
+     * 空指针异常
+     */
+    @ExceptionHandler(NullPointerException.class)
+    public Result<Void> handleNullPointerException(NullPointerException e) {
+        log.error("空指针异常", e);
+        ServletUtils.getHttpResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        return Result.error(500, "系统繁忙,请稍后重试");
+    }
+}

+ 19 - 0
order-common/src/main/java/com/poyee/mapstruct/OrderBaseMapstruct.java

@@ -0,0 +1,19 @@
+package com.poyee.mapstruct;
+
+import com.poyee.domain.OrderBase;
+import com.poyee.res.OrderBaseListRes;
+import com.poyee.res.client.product.ProductItemListRes;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface OrderBaseMapstruct {
+
+
+    @Mapping(target = "id", source = "orderBase.id")
+    @Mapping(target = "itemType", source = "productItemListRes.itemType")
+    @Mapping(target = "itemName", source = "productItemListRes.itemName")
+    @Mapping(target = "spec", source = "productItemListRes.spec")
+    OrderBaseListRes toListRes(OrderBase orderBase, ProductItemListRes productItemListRes);
+
+}

+ 11 - 0
order-common/src/main/java/com/poyee/param/UserInfo.java

@@ -0,0 +1,11 @@
+package com.poyee.param;
+
+import lombok.Builder;
+import lombok.Data;
+
+
+@Data
+@Builder
+public class UserInfo {
+
+}

+ 16 - 0
order-common/src/main/java/com/poyee/req/OrderAddressAuditReq.java

@@ -0,0 +1,16 @@
+package com.poyee.req;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+@ApiModel("订单地址审批Request")
+public class OrderAddressAuditReq {
+
+    @NotBlank(message = "orderNo cannot be blank")
+    @ApiModelProperty("订单编号")
+    private String orderNo;
+}

+ 26 - 0
order-common/src/main/java/com/poyee/req/OrderBaseSearchReq.java

@@ -0,0 +1,26 @@
+package com.poyee.req;
+
+import com.poyee.req.page.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@Builder
+@ApiModel("售出订单列表Request")
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderBaseSearchReq extends Page {
+    @ApiModelProperty("商品名称")
+    private String productName;
+    @ApiModelProperty("订单状态")
+    private String orderStatus;
+    @ApiModelProperty("订单号")
+    private String orderNo;
+
+    @NotNull(message = "订单类型不能为空")
+    private Integer orderType;
+}

+ 34 - 0
order-common/src/main/java/com/poyee/req/OrderDeliveryReq.java

@@ -0,0 +1,34 @@
+package com.poyee.req;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+@Builder
+@ApiModel("订单发货Request")
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderDeliveryReq {
+
+    @ApiModelProperty("订单号")
+    @NotBlank(message = "order_no_can_not_be_empty")
+    private String orderNo;
+
+    @ApiModelProperty("物流公司编码")
+    @NotBlank(message = "logistics_code_can_not_be_empty")
+    private String logisticsCode;
+
+    @ApiModelProperty("物流公司名称")
+    @NotBlank(message = "logistics_name_can_not_be_empty")
+    private String logisticsName;
+
+    @ApiModelProperty("物流单号")
+    @NotBlank(message = "tracking_no_can_not_be_empty")
+    private String trackingNo;
+}

+ 22 - 0
order-common/src/main/java/com/poyee/req/RecycleListSearchReq.java

@@ -0,0 +1,22 @@
+package com.poyee.req;
+
+import com.poyee.req.page.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("闪购回收订单Request")
+public class RecycleListSearchReq extends Page {
+    @ApiModelProperty("商品名称")
+    private String productName;
+    @ApiModelProperty("回收订单状态")
+    private Integer recycleOrderStaus;
+    @ApiModelProperty("回收订单编号")
+    private String recycleOrderNo;
+
+}

+ 47 - 0
order-common/src/main/java/com/poyee/req/client/app/ShippingAddressReq.java

@@ -0,0 +1,47 @@
+package com.poyee.req.client.app;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Map;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ShippingAddressReq implements Serializable {
+    private static final long serialVersionUID = 1L;
+    //appid
+    private String appid;
+    //openid
+    private String openid;
+    //unionid
+    private String unionid;
+    //用户类型: WX_WEB (微信公众号) WX_APPLET (微信小程序) THIRD (第三方) WX_AUTH(微信授权登陆)
+    private String userType;
+    //userid
+    private Integer userId;
+    //账号
+    private String username;
+    //每页数据条数
+    private Integer pageSize =10;
+    //页码数
+    private Integer pageNo=1;
+    //其他数据
+    private Map<String,Object> data;
+    //部门id
+    private Integer departId;
+
+    private Integer offset;
+
+    private long timestamp;
+
+    private String version;
+    // 极光id
+    private String smsRegisterId;
+    //签名
+    private String sign;
+}

+ 26 - 0
order-common/src/main/java/com/poyee/req/client/product/ProductItemSearchReq.java

@@ -0,0 +1,26 @@
+package com.poyee.req.client.product;
+
+
+import com.poyee.req.page.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+
+import java.util.List;
+
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("子商品列表Request")
+@EqualsAndHashCode(callSuper = true)
+public class ProductItemSearchReq extends Page {
+
+    @ApiModelProperty("商品名称")
+    private String productName;
+
+    @ApiModelProperty("商品子列表")
+    private List<Long> itemList;
+
+}

+ 18 - 0
order-common/src/main/java/com/poyee/req/page/Page.java

@@ -0,0 +1,18 @@
+package com.poyee.req.page;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@ApiModel("分页数据")
+@NoArgsConstructor
+@AllArgsConstructor
+public class Page {
+    @ApiModelProperty("页数")
+    private int page = 1;
+    @ApiModelProperty("条数")
+    private Integer pageSize = 20;
+}

+ 55 - 0
order-common/src/main/java/com/poyee/res/OrderBaseListRes.java

@@ -0,0 +1,55 @@
+package com.poyee.res;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+@ApiModel("售出订单列表Response")
+@NoArgsConstructor
+@AllArgsConstructor
+public class OrderBaseListRes {
+
+    @ApiModelProperty("订单Id")
+    private Long id;
+    @ApiModelProperty("订单号")
+    private String orderNo;
+    @ApiModelProperty("关联子商品类型")
+    private Integer itemType;
+    @ApiModelProperty("关联子商品名称")
+    private String itemName;
+    @ApiModelProperty("关联子商品规格")
+    private Integer spec;
+    @ApiModelProperty("订单状态")
+    private Integer orderStatus;
+    @ApiModelProperty("下单数量")
+    private Integer quantity;
+    @ApiModelProperty("支付金额")
+    private BigDecimal paymentAmount;
+    @ApiModelProperty("卖家昵称")
+    private String sellerNickname;
+    @ApiModelProperty("卖家手机号")
+    private String sellerPhone;
+    @ApiModelProperty("买家昵称")
+    private String nickname;
+    @ApiModelProperty("买家姓名")
+    private String buyerUser;
+    @ApiModelProperty("收货地址")
+    private String address;
+    @ApiModelProperty("联系方式")
+    private String phone;
+    @ApiModelProperty("创建时间")
+    private LocalDateTime createTime;
+    @ApiModelProperty("支付时间")
+    private LocalDateTime paymentTime;
+    @ApiModelProperty("是否有修改地址申请")
+    private Boolean addressModificationFlag = Boolean.FALSE;
+
+}

+ 48 - 0
order-common/src/main/java/com/poyee/res/RecycleListRes.java

@@ -0,0 +1,48 @@
+package com.poyee.res;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("回收订单列表Response")
+public class RecycleListRes {
+
+    @ApiModelProperty("id")
+    private Long id;
+    @ApiModelProperty("回收单编码")
+    private String recycleNo;
+    @ApiModelProperty("产品名称")
+    private String productName;
+    @ApiModelProperty("规格")
+    private Integer spec;
+    @ApiModelProperty("数量")
+    private Integer quantity;
+    @ApiModelProperty("状态")
+    private Integer recycleStatus;
+    @ApiModelProperty("金额")
+    private BigDecimal recycleAmount;
+    @ApiModelProperty("条码")
+    private String barcode;
+    @ApiModelProperty("卖家昵称")
+    private String sellerNickname;
+    @ApiModelProperty("手机号")
+    private String phone;
+    @ApiModelProperty("物流")
+    private String logistics;
+    @ApiModelProperty("运单号")
+    private String trackingNo;
+    @ApiModelProperty("创建时间")
+    private LocalDateTime createTime;
+    @ApiModelProperty("修改时间")
+    private LocalDateTime updateTime;
+}

+ 53 - 0
order-common/src/main/java/com/poyee/res/Result.java

@@ -0,0 +1,53 @@
+package com.poyee.res;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class Result<T> implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Integer code;
+    private String message;
+    private T data;
+
+    public static <T> Result<T> success(T data) {
+        Result<T> result = new Result<>();
+        result.setCode(200);
+        result.setMessage("success");
+        result.setData(data);
+        return result;
+    }
+
+    public static <T> Result<T> success(T data, String message) {
+        Result<T> result = new Result<>();
+        result.setCode(200);
+        result.setMessage(message);
+        result.setData(data);
+        return result;
+    }
+
+    public static <T> Result<T> success() {
+        return success(null);
+    }
+
+    public static <T> Result<T> error(String message) {
+        Result<T> result = new Result<>();
+        result.setCode(500);
+        result.setMessage(message);
+        return result;
+    }
+
+    public static <T> Result<T> error(Integer code, String message) {
+        Result<T> result = new Result<>();
+        result.setCode(code);
+        result.setMessage(message);
+        return result;
+    }
+
+    public boolean successFlag() {
+        return code == 200;
+    }
+}

+ 34 - 0
order-common/src/main/java/com/poyee/res/UserInfo.java

@@ -0,0 +1,34 @@
+package com.poyee.res;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 用户信息
+ */
+@Data
+public class UserInfo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long id;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 昵称
+     */
+    private String nickname;
+
+    /**
+     * 角色
+     */
+    private String role;
+}

+ 33 - 0
order-common/src/main/java/com/poyee/res/client/app/ShippingAddressRes.java

@@ -0,0 +1,33 @@
+package com.poyee.res.client.app;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ShippingAddressRes implements Serializable {
+
+    private static final long serializableUID = 1L;
+    //id
+    private Integer id;
+    //用户id
+    private Integer userId;
+    //收货地址
+    private String address;
+    //详细地址
+    private String addressMore;
+    //联系人
+    private String linkname;
+    //联系方式
+    private String phone;
+    //是否默认地址
+    private boolean defult;
+
+    private String isDefult;
+}

+ 24 - 0
order-common/src/main/java/com/poyee/res/client/product/ProductItemListRes.java

@@ -0,0 +1,24 @@
+package com.poyee.res.client.product;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@ApiModel("子商品列表Response")
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductItemListRes {
+    @ApiModelProperty("商品类型")
+    private Integer itemType;
+    @ApiModelProperty("商品名称")
+    private String itemName;
+    @ApiModelProperty("商品规格")
+    private Integer spec;
+    @ApiModelProperty("商品id")
+    private Long id;
+}

+ 32 - 0
order-common/src/main/java/com/poyee/threads/ThreadPoolExecutor.java

@@ -0,0 +1,32 @@
+package com.poyee.threads;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+/**
+ * @author Hr
+ */
+@Configuration
+public class ThreadPoolExecutor {
+    //并发查询专用
+    @Bean("queryExecutor")
+    public ThreadPoolTaskExecutor queryExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        // 设置核心线程数
+        executor.setCorePoolSize(5);
+        // 设置最大线程数,此数量必须大于http处理线程最大数量
+        executor.setMaxPoolSize(20);
+        // 设置队列容量
+        executor.setQueueCapacity(1000);
+        // 设置线程活跃时间(秒)
+        executor.setKeepAliveSeconds(60);
+        // 设置默认线程名称
+        executor.setThreadNamePrefix("query_executor-");
+        // 设置拒绝策略
+        executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
+        // 等待所有任务结束后再关闭线程池
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+        return executor;
+    }
+}

+ 45 - 0
order-common/src/main/java/com/poyee/utils/ApiUtils.java

@@ -0,0 +1,45 @@
+package com.poyee.utils;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.json.JSONUtil;
+import com.poyee.res.Result;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * @author Hr
+ * Date 2024-03-19
+ */
+@Slf4j
+public class ApiUtils {
+
+    public static <T, R> R httpSuccess(Function<T,Result<R>> function, T request, R defaultValue, String msg) {
+        Assert.notNull(request, msg);
+        Result<R> response = function.apply(request);
+        if (response != null && response.successFlag()) {
+            return ObjectUtil.defaultIfNull(response.getData(), defaultValue);
+        } else {
+            log.error("{} error,request={}, response={}", msg, JSONUtil.toJsonStr(request), JSONUtil.toJsonStr(response));
+            assert response != null;
+            throw new RuntimeException(response.getMessage());
+        }
+    }
+
+    public static <T, R> R httpSuccess(Function<T, Result<R>> function, T request, String msg) {
+        return httpSuccess(function, request, null, msg);
+    }
+
+    public static <R> R httpSuccessNoParams(Supplier<Result<R>> function, R defaultValue, String msg) {
+        Assert.notNull(function, "Function must not be null");
+        Result<R> response = function.get();
+        if (response != null && response.successFlag()) {
+            return ObjectUtil.defaultIfNull(response.getData(), defaultValue);
+        } else {
+            log.error("{} error, response={}", msg, JSONUtil.toJsonStr(response));
+            throw new RuntimeException(msg);
+        }
+    }
+}

+ 99 - 0
order-common/src/main/java/com/poyee/utils/I18nUtil.java

@@ -0,0 +1,99 @@
+package com.poyee.utils;
+
+import cn.hutool.core.util.StrUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.MessageSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Locale;
+
+/**
+ * 国际化工具类
+ */
+@Component
+public class I18nUtil {
+
+    private static MessageSource staticMessageSource;
+
+    private static final Locale DEFAULT_LOCALE = Locale.CHINA;
+
+    @Autowired
+    private MessageSource messageSource;
+
+    @PostConstruct
+    public void init() {
+        staticMessageSource = messageSource;
+    }
+
+    /**
+     * 获取国际化消息
+     * @param code 消息编码
+     * @return 国际化消息
+     */
+    public static String getMessage(String code) {
+        return getMessage(code, null);
+    }
+
+    /**
+     * 获取国际化消息
+     * @param code 消息编码
+     * @param args 参数
+     * @return 国际化消息
+     */
+    public static String getMessage(String code, Object[] args) {
+        return getMessage(code, args, getCurrentLocale());
+    }
+
+    /**
+     * 获取国际化消息
+     * @param code 消息编码
+     * @param args 参数
+     * @param locale 语言环境
+     * @return 国际化消息
+     */
+    public static String getMessage(String code, Object[] args, Locale locale) {
+        if (staticMessageSource == null) {
+            return code;
+        }
+        try {
+            return staticMessageSource.getMessage(code, args, locale);
+        } catch (Exception e) {
+            // 如果获取不到国际化消息,返回原始 code
+            return code;
+        }
+    }
+
+    /**
+     * 获取当前语言环境
+     * 优先从请求头 Accept-Language 获取,如果没有则默认中文
+     */
+    public static Locale getCurrentLocale() {
+        ServletRequestAttributes attributes =
+                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes != null) {
+            HttpServletRequest request = attributes.getRequest();
+            String acceptLanguage = request.getHeader("Accept-Language");
+
+            if (StrUtil.isNotBlank(acceptLanguage)) {
+                // 解析 Accept-Language 头部,取第一个语言
+                String[] languages = acceptLanguage.split(",");
+                if (languages.length > 0) {
+                    String languageTag = languages[0].trim();
+                    // 处理类似 "zh-CN"、"en-US" 这样的格式
+                    if (languageTag.contains("-")) {
+                        String[] parts = languageTag.split("-");
+                        return new Locale(parts[0], parts[1]);
+                    } else {
+                        return new Locale(languageTag);
+                    }
+                }
+            }
+        }
+        // 默认返回中文
+        return DEFAULT_LOCALE;
+    }
+}

+ 25 - 0
order-common/src/main/java/com/poyee/utils/JwtUtils.java

@@ -0,0 +1,25 @@
+package com.poyee.utils;
+
+import cn.hutool.jwt.JWT;
+import cn.hutool.jwt.JWTUtil;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * JWT工具类(简化版)
+ */
+@Slf4j
+public class JwtUtils {
+
+    /**
+     * 从Token中获取用户信息
+     * 支持两种格式:
+     * 1. JWT Token (Bearer token)
+     * 2. Base64编码的用户信息
+     */
+    public static JSONObject getTokenUserInfo(String token) {
+        JWT jwt = JWTUtil.parseToken(token);
+        String payloads = jwt.getPayloads().toString();
+        return JSONObject.parseObject(payloads);
+    }
+}

+ 184 - 0
order-common/src/main/java/com/poyee/utils/LocaleTimeZoneUtil.java

@@ -0,0 +1,184 @@
+package com.poyee.utils;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.util.StrUtil;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 多语言 → 时区工具类
+ * <p>
+ * 根据请求头 Accept-Language 推断对应的时区,
+ * 用于将数据库中存储的 UTC 时间转换为客户端所在区域的本地时间。
+ * </p>
+ */
+public class LocaleTimeZoneUtil {
+
+    /** 默认时区:UTC */
+    public static final ZoneId UTC = ZoneId.of("UTC");
+
+    /**
+     * 语言标签 → 时区映射表
+     * 优先精确匹配(language-COUNTRY),其次匹配语言前缀(language)
+     */
+    private static final Map<String, ZoneId> LOCALE_ZONE_MAP = new HashMap<>();
+
+    static {
+        // 中文
+        LOCALE_ZONE_MAP.put("zh-CN", ZoneId.of("Asia/Shanghai"));   // 简体中文 → 北京时间 UTC+8
+        LOCALE_ZONE_MAP.put("zh-TW", ZoneId.of("Asia/Taipei"));     // 繁体中文(台湾) → UTC+8
+        LOCALE_ZONE_MAP.put("zh-HK", ZoneId.of("Asia/Hong_Kong"));  // 繁体中文(香港) → UTC+8
+        LOCALE_ZONE_MAP.put("zh-MO", ZoneId.of("Asia/Macau"));      // 繁体中文(澳门) → UTC+8
+        LOCALE_ZONE_MAP.put("zh-SG", ZoneId.of("Asia/Singapore"));  // 简体中文(新加坡) → UTC+8
+        LOCALE_ZONE_MAP.put("zh",    ZoneId.of("Asia/Shanghai"));   // 纯 zh → 默认北京时间
+
+        // 英文
+        LOCALE_ZONE_MAP.put("en-US", ZoneId.of("America/New_York")); // 美国东部 UTC-5/4
+        LOCALE_ZONE_MAP.put("en-GB", ZoneId.of("Europe/London"));    // 英国 UTC+0/1
+        LOCALE_ZONE_MAP.put("en-AU", ZoneId.of("Australia/Sydney")); // 澳大利亚东部 UTC+10/11
+        LOCALE_ZONE_MAP.put("en-CA", ZoneId.of("America/Toronto"));  // 加拿大东部
+        LOCALE_ZONE_MAP.put("en-SG", ZoneId.of("Asia/Singapore"));   // 新加坡英文
+        LOCALE_ZONE_MAP.put("en-HK", ZoneId.of("Asia/Hong_Kong"));   // 香港英文
+        LOCALE_ZONE_MAP.put("en",    ZoneId.of("UTC"));              // 纯 en → UTC
+
+        // 日文
+        LOCALE_ZONE_MAP.put("ja-JP", ZoneId.of("Asia/Tokyo"));       // 日本 UTC+9
+        LOCALE_ZONE_MAP.put("ja",    ZoneId.of("Asia/Tokyo"));
+
+        // 韩文
+        LOCALE_ZONE_MAP.put("ko-KR", ZoneId.of("Asia/Seoul"));       // 韩国 UTC+9
+        LOCALE_ZONE_MAP.put("ko",    ZoneId.of("Asia/Seoul"));
+
+        // 德文
+        LOCALE_ZONE_MAP.put("de-DE", ZoneId.of("Europe/Berlin"));    // 德国 UTC+1/2
+        LOCALE_ZONE_MAP.put("de",    ZoneId.of("Europe/Berlin"));
+
+        // 法文
+        LOCALE_ZONE_MAP.put("fr-FR", ZoneId.of("Europe/Paris"));     // 法国 UTC+1/2
+        LOCALE_ZONE_MAP.put("fr",    ZoneId.of("Europe/Paris"));
+
+        // 西班牙文
+        LOCALE_ZONE_MAP.put("es-ES", ZoneId.of("Europe/Madrid"));    // 西班牙 UTC+1/2
+        LOCALE_ZONE_MAP.put("es-MX", ZoneId.of("America/Mexico_City")); // 墨西哥
+        LOCALE_ZONE_MAP.put("es",    ZoneId.of("Europe/Madrid"));
+
+        // 葡萄牙文
+        LOCALE_ZONE_MAP.put("pt-BR", ZoneId.of("America/Sao_Paulo")); // 巴西 UTC-3
+        LOCALE_ZONE_MAP.put("pt-PT", ZoneId.of("Europe/Lisbon"));     // 葡萄牙 UTC+0/1
+        LOCALE_ZONE_MAP.put("pt",    ZoneId.of("Europe/Lisbon"));
+
+        // 阿拉伯文
+        LOCALE_ZONE_MAP.put("ar-SA", ZoneId.of("Asia/Riyadh"));      // 沙特 UTC+3
+        LOCALE_ZONE_MAP.put("ar-AE", ZoneId.of("Asia/Dubai"));       // 迪拜 UTC+4
+        LOCALE_ZONE_MAP.put("ar",    ZoneId.of("Asia/Riyadh"));
+
+        // 印地文
+        LOCALE_ZONE_MAP.put("hi-IN", ZoneId.of("Asia/Kolkata"));     // 印度 UTC+5:30
+        LOCALE_ZONE_MAP.put("hi",    ZoneId.of("Asia/Kolkata"));
+
+        // 俄文
+        LOCALE_ZONE_MAP.put("ru-RU", ZoneId.of("Europe/Moscow"));    // 莫斯科 UTC+3
+        LOCALE_ZONE_MAP.put("ru",    ZoneId.of("Europe/Moscow"));
+
+        // 马来文
+        LOCALE_ZONE_MAP.put("ms-MY", ZoneId.of("Asia/Kuala_Lumpur")); // 马来西亚 UTC+8
+        LOCALE_ZONE_MAP.put("ms",    ZoneId.of("Asia/Kuala_Lumpur"));
+
+        // 泰文
+        LOCALE_ZONE_MAP.put("th-TH", ZoneId.of("Asia/Bangkok"));     // 泰国 UTC+7
+        LOCALE_ZONE_MAP.put("th",    ZoneId.of("Asia/Bangkok"));
+
+        // 越南文
+        LOCALE_ZONE_MAP.put("vi-VN", ZoneId.of("Asia/Ho_Chi_Minh")); // 越南 UTC+7
+        LOCALE_ZONE_MAP.put("vi",    ZoneId.of("Asia/Ho_Chi_Minh"));
+
+        // 印尼文
+        LOCALE_ZONE_MAP.put("id-ID", ZoneId.of("Asia/Jakarta"));     // 印尼西部 UTC+7
+        LOCALE_ZONE_MAP.put("id",    ZoneId.of("Asia/Jakarta"));
+    }
+
+    /**
+     * 从当前 HTTP 请求的 Accept-Language 头部解析对应时区。
+     * <ul>
+     *   <li>优先精确匹配,如 {@code zh-CN} → {@code Asia/Shanghai}</li>
+     *   <li>其次匹配语言前缀,如 {@code zh} → {@code Asia/Shanghai}</li>
+     *   <li>均未匹配时返回 {@code UTC}</li>
+     * </ul>
+     *
+     * @return 解析出的 {@link ZoneId},默认 UTC
+     */
+    public static ZoneId resolveZoneId() {
+        ServletRequestAttributes attributes =
+                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes == null) {
+            return UTC;
+        }
+        HttpServletRequest request = attributes.getRequest();
+        String acceptLanguage = request.getHeader("Accept-Language");
+        return parseZoneId(acceptLanguage);
+    }
+
+
+    /**
+     * 根据 Accept-Language 字符串解析时区(可单独测试)。
+     *
+     * @param acceptLanguage HTTP Accept-Language 头部值,如 "zh-CN,zh;q=0.9,en;q=0.8"
+     * @return 匹配的 {@link ZoneId},未匹配返回 UTC
+     */
+    /**
+     * 获取当前 UTC 时间,用于统一存入数据库。
+     * <p>
+     * 全项目保存时间字段统一调用此方法,避免因服务器时区配置不同导致存入非 UTC 时间。
+     * </p>
+     * <pre>
+     * // 推荐用法(替代 LocalDateTime.now())
+     * entity.setCreateTime(LocaleTimeZoneUtil.nowUtc());
+     * entity.setUpdateTime(LocaleTimeZoneUtil.nowUtc());
+     * </pre>
+     *
+     * @return 当前 UTC 时间的 {@link LocalDateTime}
+     */
+    public static LocalDateTime nowUtc() {
+        return LocalDateTime.now(ZoneOffset.UTC);
+    }
+
+    public static ZoneId parseZoneId(String acceptLanguage) {
+        if (StrUtil.isBlank(acceptLanguage)) {
+            return UTC;
+        }
+        // Accept-Language 格式:zh-CN,zh;q=0.9,en-US;q=0.8
+        // 按逗号拆分,取第一个语言标签(优先级最高)
+        String[] languages = acceptLanguage.split(",");
+        for (String lang : languages) {
+            // 去掉权重部分,如 "zh-CN;q=0.9" → "zh-CN"
+            String tag = lang.split(";")[0].trim();
+            if (StrUtil.isBlank(tag)) {
+                continue;
+            }
+            // 统一格式:zh_CN → zh-CN
+            tag = tag.replace("_", "-");
+
+            // 1. 精确匹配
+            ZoneId zoneId = LOCALE_ZONE_MAP.get(tag);
+            if (zoneId != null) {
+                return zoneId;
+            }
+            // 2. 匹配语言前缀(取 "-" 之前部分)
+            if (tag.contains("-")) {
+                String prefix = tag.substring(0, tag.indexOf("-"));
+                zoneId = LOCALE_ZONE_MAP.get(prefix);
+                if (zoneId != null) {
+                    return zoneId;
+                }
+            }
+        }
+        return UTC;
+    }
+}

+ 63 - 0
order-common/src/main/java/com/poyee/utils/ServletUtils.java

@@ -0,0 +1,63 @@
+package com.poyee.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.poyee.res.UserInfo;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/**
+ * Servlet 工具类
+ */
+public class ServletUtils {
+
+    /**
+     * 获取当前请求
+     */
+    public static HttpServletRequest getRequest() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes == null) {
+            return null;
+        }
+        return attributes.getRequest();
+    }
+
+    public static HttpServletResponse getHttpResponse() {
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
+    }
+
+    /**
+     * 获取当前 Session
+     */
+    public static HttpSession getSession() {
+        HttpServletRequest request = getRequest();
+        if (request == null) {
+            return null;
+        }
+        return request.getSession();
+    }
+
+    /**
+     * 获取当前登录用户
+     */
+    public static JSONObject getCurrentUser() {
+        HttpServletRequest request = getRequest();
+        if (request == null) {
+            return null;
+        }
+        return (JSONObject) request.getAttribute("currentUser");
+    }
+
+    /**
+     * 设置当前登录用户
+     */
+    public static void setCurrentUser(UserInfo userInfo) {
+        HttpServletRequest request = getRequest();
+        if (request != null) {
+            request.setAttribute("currentUser", userInfo);
+        }
+    }
+}

+ 145 - 0
order-web/pom.xml

@@ -0,0 +1,145 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.poyee</groupId>
+        <artifactId>b2b-order-service</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>order-web</artifactId>
+
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <mysql-version>8.0.33</mysql-version>
+        <driud-version>1.2.20</driud-version>
+        <guava-version>31.1-jre</guava-version>
+    </properties>
+
+    <dependencies>
+        <!-- 内部模块 -->
+        <dependency>
+            <groupId>com.poyee</groupId>
+            <artifactId>order-common</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.poyee</groupId>
+            <artifactId>order-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- Spring Boot Web -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <!-- Spring Cloud OpenFeign -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+
+        <!-- MyBatis-Plus -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+
+        <!-- MySQL -->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>${mysql-version}</version>
+        </dependency>
+
+        <!-- Druid 连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+            <version>${driud-version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+        <!-- Guava -->
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>${guava-version}</version>
+        </dependency>
+
+        <!-- Knife4j -->
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- Redisson -->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- FastJSON -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <!-- Commons Lang3 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!-- AOP -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!-- Validation -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <!-- Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <!-- Test -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 15 - 0
order-web/src/main/java/com/poyee/OrderApplication.java

@@ -0,0 +1,15 @@
+package com.poyee;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+@SpringBootApplication
+@EnableFeignClients(basePackages = "com.poyee")
+@MapperScan("com.poyee.mapper")
+public class OrderApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(OrderApplication.class, args);
+    }
+}

+ 52 - 0
order-web/src/main/java/com/poyee/controller/OrderBaseController.java

@@ -0,0 +1,52 @@
+package com.poyee.controller;
+
+
+import com.github.pagehelper.PageInfo;
+import com.poyee.facade.IOrderBaseFacade;
+import com.poyee.req.OrderAddressAuditReq;
+import com.poyee.req.OrderBaseSearchReq;
+import com.poyee.req.OrderDeliveryReq;
+import com.poyee.req.RecycleListSearchReq;
+import com.poyee.res.OrderBaseListRes;
+import com.poyee.res.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/order/base")
+@Api(tags = "订单管理")
+@AllArgsConstructor
+public class OrderBaseController {
+
+    private final IOrderBaseFacade orderBaseFacade;
+
+    @PostMapping("/list")
+    @ApiOperation("售出订单列表")
+    public Result<PageInfo<OrderBaseListRes>> list(@RequestBody @Validated OrderBaseSearchReq orderBaseSearchReq) {
+        return Result.success(orderBaseFacade.list(orderBaseSearchReq));
+    }
+
+    @PostMapping("/delivery")
+    @ApiOperation("发货")
+    public Result<Boolean> delivery(@RequestBody @Validated OrderDeliveryReq orderBaseSearchReq) {
+        return Result.success(orderBaseFacade.delivery(orderBaseSearchReq));
+    }
+
+    @PostMapping("/address/approve")
+    @ApiOperation("审核通过地址修改申请")
+    public Result<Boolean> approveAddress(@RequestBody @Validated OrderAddressAuditReq req) {
+        return Result.success(orderBaseFacade.approveAddress(req));
+    }
+
+    @PostMapping("/address/reject")
+    @ApiOperation("拒绝地址修改申请")
+    public Result<Boolean> rejectAddress(@RequestBody @Validated OrderAddressAuditReq req) {
+        return Result.success(orderBaseFacade.rejectAddress(req));
+    }
+}

+ 31 - 0
order-web/src/main/java/com/poyee/controller/RecycleOrderController.java

@@ -0,0 +1,31 @@
+package com.poyee.controller;
+
+
+import com.github.pagehelper.PageInfo;
+import com.poyee.facade.IRecycleOrderFacade;
+import com.poyee.req.RecycleListSearchReq;
+import com.poyee.res.RecycleListRes;
+import com.poyee.res.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/order/recycle")
+@Api(tags = "回收订单管理")
+@AllArgsConstructor
+public class RecycleOrderController {
+
+    private final IRecycleOrderFacade orderBaseFacade;
+
+    @PostMapping("/list")
+    @ApiOperation("售出订单列表")
+    public Result<PageInfo<RecycleListRes>> list(@RequestBody @Validated RecycleListSearchReq recycleListSearchReq) {
+        return Result.success(orderBaseFacade.list(recycleListSearchReq));
+    }
+}

+ 37 - 0
order-web/src/main/java/com/poyee/facade/IOrderBaseFacade.java

@@ -0,0 +1,37 @@
+package com.poyee.facade;
+
+import com.github.pagehelper.PageInfo;
+import com.poyee.req.OrderAddressAuditReq;
+import com.poyee.req.OrderBaseSearchReq;
+import com.poyee.req.OrderDeliveryReq;
+import com.poyee.res.OrderBaseListRes;
+
+public interface IOrderBaseFacade {
+
+
+    /**
+     * 订单列表
+     *
+     * @param orderBaseSearchReq orderBaseSearchReq
+     * @return OrderBaseListRes
+     */
+    PageInfo<OrderBaseListRes> list(OrderBaseSearchReq orderBaseSearchReq);
+
+    /**
+     * 发货
+     *
+     * @param orderBaseSearchReq orderBaseSearchReq
+     * @return Boolean
+     */
+    Boolean delivery(OrderDeliveryReq orderBaseSearchReq);
+
+    /**
+     * 审核通过地址修改申请
+     */
+    Boolean approveAddress(OrderAddressAuditReq req);
+
+    /**
+     * 拒绝地址修改申请
+     */
+    Boolean rejectAddress(OrderAddressAuditReq req);
+}

+ 16 - 0
order-web/src/main/java/com/poyee/facade/IRecycleOrderFacade.java

@@ -0,0 +1,16 @@
+package com.poyee.facade;
+
+import com.github.pagehelper.PageInfo;
+import com.poyee.req.RecycleListSearchReq;
+import com.poyee.res.RecycleListRes;
+
+public interface IRecycleOrderFacade {
+
+    /**
+     * 回收订单列表
+     *
+     * @param recycleListSearchReq recycleListSearchReq
+     * @return RecycleListRes
+     */
+    PageInfo<RecycleListRes> list(RecycleListSearchReq recycleListSearchReq);
+}

+ 221 - 0
order-web/src/main/java/com/poyee/facade/impl/OrderBaseFacade.java

@@ -0,0 +1,221 @@
+package com.poyee.facade.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.github.pagehelper.PageInfo;
+import com.google.common.collect.Maps;
+import com.poyee.api.product.ProductFeignClient;
+import com.poyee.constant.OrderConstant;
+import com.poyee.domain.OrderAddress;
+import com.poyee.domain.OrderBase;
+import com.poyee.domain.OrderItem;
+import com.poyee.domain.OrderLogistics;
+import com.poyee.enums.AddressTypeEnum;
+import com.poyee.enums.ItemTypeEnum;
+import com.poyee.enums.SalesOrderStatusEnum;
+import com.poyee.enums.WhetherEnum;
+import com.poyee.facade.IOrderBaseFacade;
+import com.poyee.mapstruct.OrderBaseMapstruct;
+import com.poyee.req.OrderAddressAuditReq;
+import com.poyee.req.OrderBaseSearchReq;
+import com.poyee.req.OrderDeliveryReq;
+import com.poyee.req.client.product.ProductItemSearchReq;
+import com.poyee.res.OrderBaseListRes;
+import com.poyee.res.client.product.ProductItemListRes;
+import com.poyee.service.OrderAddressService;
+import com.poyee.service.OrderBaseService;
+import com.poyee.service.OrderItemService;
+import com.poyee.service.OrderLogisticsService;
+import com.poyee.utils.ApiUtils;
+import com.poyee.utils.LocaleTimeZoneUtil;
+import com.poyee.utils.ServletUtils;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class OrderBaseFacade implements IOrderBaseFacade {
+
+
+    private final OrderBaseService orderBaseService;
+    private final OrderBaseMapstruct orderBaseMapstruct;
+    private final OrderLogisticsService orderLogisticsService;
+    private final OrderAddressService orderAddressService;
+    private final OrderItemService orderItemService;
+
+
+    @Override
+    public PageInfo<OrderBaseListRes> list(OrderBaseSearchReq orderBaseSearchReq) {
+        LambdaQueryWrapper<OrderBase> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OrderBase::getDeleteFlag, WhetherEnum.NO.getCode());
+        if (StrUtil.isNotBlank(orderBaseSearchReq.getOrderNo())) {
+            queryWrapper.eq(OrderBase::getOrderNo, orderBaseSearchReq.getOrderNo());
+        }
+        if (Objects.nonNull(orderBaseSearchReq.getOrderStatus())) {
+            queryWrapper.eq(OrderBase::getOrderStatus, orderBaseSearchReq.getOrderStatus());
+        }
+        if (StrUtil.isNotBlank(orderBaseSearchReq.getProductName())) {
+            List<OrderItem> orderItems = orderItemService.lambdaQuery()
+                    .eq(OrderItem::getRelationTable, OrderConstant.ORDER)
+                    .likeRight(OrderItem::getProductName, orderBaseSearchReq.getProductName())
+                    .list();
+            if (CollUtil.isEmpty(orderItems)) {
+                return PageInfo.of(Collections.emptyList());
+            }
+            queryWrapper.in(OrderBase::getOrderNo, orderItems.stream().map(OrderItem::getRelationNo).collect(Collectors.toList()));
+        }
+        PageInfo<OrderBase> orderBasePageInfo = new PageInfo<>(this.orderBaseService.list(queryWrapper));
+        if (CollUtil.isEmpty(orderBasePageInfo.getList())) {
+            return PageInfo.of(Collections.emptyList());
+        }
+        List<String> orderNoList = orderBasePageInfo.getList().stream()
+                .map(OrderBase::getOrderNo)
+                .collect(Collectors.toList());
+        // 批量查 orderItem,按 relationNo 分组取第一条
+        Map<String, OrderItem> orderItemMap = orderItemService.lambdaQuery()
+                .eq(OrderItem::getRelationTable, OrderConstant.ORDER)
+                .in(OrderItem::getRelationNo, orderNoList)
+                .list()
+                .stream()
+                .collect(Collectors.toMap(OrderItem::getRelationNo, item -> item, (a, b) -> a));
+        // 查有效地址 valid=0
+        List<OrderAddress> addressList = orderAddressService.lambdaQuery()
+                .eq(OrderAddress::getRelationTable, OrderConstant.ORDER)
+                .in(OrderAddress::getRelationNo, orderNoList)
+                .eq(OrderAddress::getValid, WhetherEnum.NO.getCode())
+                .eq(OrderAddress::getDeleteFlag, WhetherEnum.NO.getCode())
+                .list();
+        Map<String, OrderAddress> buyerAddressMap = addressList.stream()
+                .filter(address -> Objects.equals(address.getType(), AddressTypeEnum.BUYER.getCode()))
+                .collect(Collectors.toMap(OrderAddress::getRelationNo, address -> address));
+        Map<String, OrderAddress> sellerAddressMap = addressList.stream()
+                .filter(address -> Objects.equals(address.getType(), AddressTypeEnum.SELLER.getCode()))
+                .collect(Collectors.toMap(OrderAddress::getRelationNo, address -> address));
+        // 查待审核地址 valid=1
+        Set<String> pendingAddressOrderNoSet = orderAddressService.lambdaQuery()
+                .eq(OrderAddress::getRelationTable, OrderConstant.ORDER)
+                .in(OrderAddress::getRelationNo, orderNoList)
+                .eq(OrderAddress::getValid, WhetherEnum.YES.getCode())
+                .list()
+                .stream()
+                .map(OrderAddress::getRelationNo)
+                .collect(Collectors.toSet());
+        List<OrderBaseListRes> orderBaseListResList = orderBasePageInfo.getList().stream()
+                .map(orderBase -> {
+                    OrderItem orderItem = orderItemMap.get(orderBase.getOrderNo());
+                    OrderAddress buyerAddress = buyerAddressMap.get(orderBase.getOrderNo());
+                    OrderAddress sellerAddress = sellerAddressMap.get(orderBase.getOrderNo());
+                    OrderBaseListRes res = orderBaseMapstruct.toListRes(orderBase, null);
+                    if (Objects.nonNull(orderItem)) {
+                        res.setItemType(orderItem.getItemType());
+                        res.setItemName(orderItem.getProductName());
+                        res.setSpec(orderItem.getSpec());
+                        res.setQuantity(orderItem.getQuantity());
+                    }
+                    if (Objects.nonNull(buyerAddress)) {
+                        String fullAddress = String.format("%s%s%s%s%s",
+                                StrUtil.nullToEmpty(buyerAddress.getCountry()),
+                                StrUtil.nullToEmpty(buyerAddress.getProvince()),
+                                StrUtil.nullToEmpty(buyerAddress.getCity()),
+                                StrUtil.nullToEmpty(buyerAddress.getArea()),
+                                StrUtil.nullToEmpty(buyerAddress.getAddress()));
+                        res.setAddress(fullAddress);
+                        res.setPhone(Objects.nonNull(buyerAddress.getPhone()) ? StrUtil.toString(buyerAddress.getPhone()) : null);
+                    }
+                    if (Objects.nonNull(sellerAddress)) {
+                        res.setSellerNickname(sellerAddress.getBuyerUser());
+                        res.setSellerPhone(Objects.nonNull(sellerAddress.getPhone()) ? StrUtil.toString(sellerAddress.getPhone()) : null);
+                    }
+                    if (pendingAddressOrderNoSet.contains(orderBase.getOrderNo())) {
+                        res.setAddressModificationFlag(Boolean.TRUE);
+                    }
+                    return res;
+                })
+                .collect(Collectors.toList());
+        return PageInfo.of(orderBaseListResList);
+    }
+
+    @Transactional
+    @Override
+    public Boolean delivery(OrderDeliveryReq orderBaseSearchReq) {
+        OrderBase orderBase = orderBaseService.lambdaQuery().eq(OrderBase::getOrderNo, orderBaseSearchReq.getOrderNo()).one();
+        Assert.notNull(orderBase, "order_not_found");
+        Assert.isTrue(Objects.equals(orderBase.getOrderStatus(), SalesOrderStatusEnum.PENDING_SHIPMENT.getCode()),
+                "only_supports_shipping_for_flash_purchase_orders");
+        List<OrderItem> orderItemList = orderItemService.lambdaQuery().eq(OrderItem::getRelationTable, OrderConstant.ORDER).eq(OrderItem::getRelationNo, orderBaseSearchReq.getOrderNo()).list();
+        boolean noneMatch = orderItemList.stream().noneMatch(item -> Objects.equals(item.getItemType(), ItemTypeEnum.FLASH_SALE.getCode()));
+        Assert.isTrue(noneMatch, "only_supports_shipping_for_flash_purchase_orders");
+        List<OrderAddress> list = orderAddressService.lambdaQuery().eq(OrderAddress::getRelationNo, orderBase.getOrderNo())
+                .eq(OrderAddress::getRelationTable, OrderConstant.ORDER)
+                .eq(OrderAddress::getValid, WhetherEnum.YES.getCode()).list();
+        Assert.isTrue(CollUtil.isEmpty(list), "order_with_address_modification_request_Please_approve_and_redo_the_operation");
+        orderBase.setOrderStatus(SalesOrderStatusEnum.SHIPPED.getCode());
+        OrderLogistics orderLogistics = OrderLogistics.builder()
+                .relationNo(orderBase.getOrderNo())
+                .relationTable(OrderConstant.ORDER)
+                .logisticsName(orderBaseSearchReq.getLogisticsName())
+                .logisticsCode(orderBaseSearchReq.getLogisticsCode())
+                .trackingNo(orderBaseSearchReq.getTrackingNo())
+                .logisticsTime(LocaleTimeZoneUtil.nowUtc())
+                .createTime(LocaleTimeZoneUtil.nowUtc())
+                .createBy(Objects.nonNull(ServletUtils.getCurrentUser()) ? ServletUtils.getCurrentUser().getString("sub") : null)
+                .build();
+        orderLogisticsService.save(orderLogistics);
+        return this.orderBaseService.updateById(orderBase);
+    }
+
+    @Transactional
+    @Override
+    public Boolean approveAddress(OrderAddressAuditReq req) {
+        String operator = Objects.nonNull(ServletUtils.getCurrentUser()) ? ServletUtils.getCurrentUser().getString("sub") : null;
+        LocalDateTime now = LocaleTimeZoneUtil.nowUtc();
+        OrderAddress pending = orderAddressService.lambdaQuery()
+                .eq(OrderAddress::getRelationTable, req.getOrderNo())
+                .eq(OrderAddress::getRelationTable, OrderConstant.ORDER)
+                .eq(OrderAddress::getValid, WhetherEnum.YES.getCode())
+                .one();
+        Assert.notNull(pending, "no_pending_address_modification_request");
+        OrderAddress old = orderAddressService.lambdaQuery()
+                .eq(OrderAddress::getRelationNo, req.getOrderNo())
+                .eq(OrderAddress::getRelationTable, OrderConstant.ORDER)
+                .eq(OrderAddress::getValid, WhetherEnum.NO.getCode())
+                .eq(OrderAddress::getType, pending.getType())
+                .one();
+        if (Objects.nonNull(old)) {
+            old.setDeleteFlag(WhetherEnum.YES.getCode());
+            old.setUpdateTime(now);
+            old.setUpdateBy(operator);
+            orderAddressService.updateById(old);
+        }
+        pending.setValid(WhetherEnum.NO.getCode());
+        pending.setUpdateTime(now);
+        pending.setUpdateBy(operator);
+        return orderAddressService.updateById(pending);
+    }
+
+    @Transactional
+    @Override
+    public Boolean rejectAddress(OrderAddressAuditReq req) {
+        OrderAddress pending = orderAddressService.lambdaQuery()
+                .eq(OrderAddress::getRelationNo, req.getOrderNo())
+                .eq(OrderAddress::getRelationTable, OrderConstant.ORDER)
+                .eq(OrderAddress::getValid, WhetherEnum.YES.getCode())
+                .one();
+        Assert.notNull(pending, "no_pending_address_modification_request");
+        String operator = Objects.nonNull(ServletUtils.getCurrentUser()) ? ServletUtils.getCurrentUser().getString("sub") : null;
+        pending.setValid(2);//审核拒绝
+        pending.setUpdateTime(LocaleTimeZoneUtil.nowUtc());
+        pending.setUpdateBy(operator);
+        return orderAddressService.updateById(pending);
+    }
+}
+

+ 123 - 0
order-web/src/main/java/com/poyee/facade/impl/RecycleOrderFacade.java

@@ -0,0 +1,123 @@
+package com.poyee.facade.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.github.pagehelper.PageInfo;
+import com.poyee.constant.OrderConstant;
+import com.poyee.domain.OrderAddress;
+import com.poyee.domain.OrderItem;
+import com.poyee.domain.OrderLogistics;
+import com.poyee.domain.RecycleOrder;
+import com.poyee.enums.AddressTypeEnum;
+import com.poyee.enums.WhetherEnum;
+import com.poyee.facade.IRecycleOrderFacade;
+import com.poyee.req.RecycleListSearchReq;
+import com.poyee.res.RecycleListRes;
+import com.poyee.service.OrderAddressService;
+import com.poyee.service.OrderItemService;
+import com.poyee.service.OrderLogisticsService;
+import com.poyee.service.RecycleOrderService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component
+@AllArgsConstructor
+public class RecycleOrderFacade implements IRecycleOrderFacade {
+
+    private final RecycleOrderService recycleOrderService;
+    private final OrderItemService orderItemService;
+    private final OrderAddressService orderAddressService;
+    private final OrderLogisticsService orderLogisticsService;
+
+    @Override
+    public PageInfo<RecycleListRes> list(RecycleListSearchReq req) {
+        LambdaQueryWrapper<RecycleOrder> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(RecycleOrder::getDeleteFlag, WhetherEnum.NO.getCode());
+        if (StrUtil.isNotBlank(req.getRecycleOrderNo())) {
+            queryWrapper.eq(RecycleOrder::getRecycleNo, req.getRecycleOrderNo());
+        }
+        if (StrUtil.isNotBlank(req.getProductName())) {
+            List<OrderItem> orderItems = orderItemService.lambdaQuery()
+                    .eq(OrderItem::getRelationTable, OrderConstant.RECYCLE)
+                    .likeRight(OrderItem::getProductName, req.getProductName())
+                    .list();
+            if (CollUtil.isEmpty(orderItems)) {
+                return PageInfo.of(Collections.emptyList());
+            }
+            queryWrapper.in(RecycleOrder::getRecycleNo, orderItems.stream().map(OrderItem::getRelationNo).collect(Collectors.toList()));
+        }
+        PageInfo<RecycleOrder> pageInfo = new PageInfo<>(recycleOrderService.list(queryWrapper));
+        if (CollUtil.isEmpty(pageInfo.getList())) {
+            return PageInfo.of(Collections.emptyList());
+        }
+        List<String> recycleNoList = pageInfo.getList().stream()
+                .map(RecycleOrder::getRecycleNo)
+                .collect(Collectors.toList());
+        // 批量查 orderItem,按 relationNo 分组取第一条
+        Map<String, OrderItem> orderItemMap = orderItemService.lambdaQuery()
+                .eq(OrderItem::getRelationTable, OrderConstant.RECYCLE)
+                .in(OrderItem::getRelationNo, recycleNoList)
+                .list()
+                .stream()
+                .collect(Collectors.toMap(OrderItem::getRelationNo, item -> item, (a, b) -> a));
+        // 查卖家地址 type=1, valid=0
+        Map<String, OrderAddress> sellerAddressMap = orderAddressService.lambdaQuery()
+                .eq(OrderAddress::getRelationTable, OrderConstant.RECYCLE)
+                .in(OrderAddress::getRelationNo, recycleNoList)
+                .eq(OrderAddress::getType, AddressTypeEnum.SELLER.getCode())
+                .eq(OrderAddress::getValid, WhetherEnum.NO.getCode())
+                .eq(OrderAddress::getDeleteFlag, WhetherEnum.NO.getCode())
+                .list()
+                .stream()
+                .collect(Collectors.toMap(OrderAddress::getRelationNo, a -> a, (a, b) -> a));
+        // 查物流
+        Map<String, OrderLogistics> logisticsMap = orderLogisticsService.lambdaQuery()
+                .eq(OrderLogistics::getRelationTable, OrderConstant.RECYCLE)
+                .in(OrderLogistics::getRelationNo, recycleNoList)
+                .list()
+                .stream()
+                .collect(Collectors.toMap(OrderLogistics::getRelationNo, l -> l, (a, b) -> a));
+        List<RecycleListRes> resList = pageInfo.getList().stream()
+                .map(order -> {
+                    OrderItem item = orderItemMap.get(order.getRecycleNo());
+                    OrderAddress sellerAddress = sellerAddressMap.get(order.getRecycleNo());
+                    OrderLogistics logistics = logisticsMap.get(order.getRecycleNo());
+                    RecycleListRes res = RecycleListRes.builder()
+                            .id(order.getId())
+                            .recycleNo(order.getRecycleNo())
+                            .recycleAmount(order.getRecycleAmount())
+                            .barcode(order.getBarcode())
+                            .createTime(order.getCreateTime())
+                            .updateTime(order.getUpdateTime())
+                            .build();
+                    if (Objects.nonNull(item)) {
+                        res.setProductName(item.getProductName());
+                        res.setSpec(item.getSpec());
+                        res.setQuantity(item.getQuantity());
+                    }
+                    if (Objects.nonNull(sellerAddress)) {
+                        res.setSellerNickname(sellerAddress.getBuyerUser());
+                        res.setPhone(Objects.nonNull(sellerAddress.getPhone()) ? StrUtil.toString(sellerAddress.getPhone()) : null);
+                    }
+                    if (Objects.nonNull(logistics)) {
+                        res.setLogistics(logistics.getLogisticsName());
+                        res.setTrackingNo(logistics.getTrackingNo());
+                    }
+                    return res;
+                })
+                .collect(Collectors.toList());
+        return PageInfo.of(resList);
+    }
+
+
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/mapper/OrderAddressMapper.java

@@ -0,0 +1,16 @@
+package com.poyee.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.poyee.domain.OrderAddress;
+
+/**
+ * <p>
+ * 订单地址表 Mapper 接口
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+public interface OrderAddressMapper extends BaseMapper<OrderAddress> {
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/mapper/OrderBaseMapper.java

@@ -0,0 +1,16 @@
+package com.poyee.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.poyee.domain.OrderBase;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+public interface OrderBaseMapper extends BaseMapper<OrderBase> {
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/mapper/OrderItemMapper.java

@@ -0,0 +1,16 @@
+package com.poyee.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.poyee.domain.OrderItem;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-20
+ */
+public interface OrderItemMapper extends BaseMapper<OrderItem> {
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/mapper/OrderLogisticsMapper.java

@@ -0,0 +1,16 @@
+package com.poyee.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.poyee.domain.OrderLogistics;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+public interface OrderLogisticsMapper extends BaseMapper<OrderLogistics> {
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/mapper/RecycleOrderMapper.java

@@ -0,0 +1,16 @@
+package com.poyee.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.poyee.domain.RecycleOrder;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-20
+ */
+public interface RecycleOrderMapper extends BaseMapper<RecycleOrder> {
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/service/OrderAddressService.java

@@ -0,0 +1,16 @@
+package com.poyee.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.poyee.domain.OrderAddress;
+
+/**
+ * <p>
+ * 订单地址表 服务类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+public interface OrderAddressService extends IService<OrderAddress> {
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/service/OrderBaseService.java

@@ -0,0 +1,16 @@
+package com.poyee.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.poyee.domain.OrderBase;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+public interface OrderBaseService extends IService<OrderBase> {
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/service/OrderItemService.java

@@ -0,0 +1,16 @@
+package com.poyee.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.poyee.domain.OrderItem;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-20
+ */
+public interface OrderItemService extends IService<OrderItem> {
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/service/OrderLogisticsService.java

@@ -0,0 +1,16 @@
+package com.poyee.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.poyee.domain.OrderLogistics;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+public interface OrderLogisticsService extends IService<OrderLogistics> {
+
+}

+ 16 - 0
order-web/src/main/java/com/poyee/service/RecycleOrderService.java

@@ -0,0 +1,16 @@
+package com.poyee.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.poyee.domain.RecycleOrder;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-20
+ */
+public interface RecycleOrderService extends IService<RecycleOrder> {
+
+}

+ 20 - 0
order-web/src/main/java/com/poyee/service/impl/OrderAddressServiceImpl.java

@@ -0,0 +1,20 @@
+package com.poyee.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.poyee.domain.OrderAddress;
+import com.poyee.mapper.OrderAddressMapper;
+import com.poyee.service.OrderAddressService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 订单地址表 服务实现类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+@Service
+public class OrderAddressServiceImpl extends ServiceImpl<OrderAddressMapper, OrderAddress> implements OrderAddressService {
+
+}

+ 20 - 0
order-web/src/main/java/com/poyee/service/impl/OrderBaseServiceImpl.java

@@ -0,0 +1,20 @@
+package com.poyee.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.poyee.domain.OrderBase;
+import com.poyee.mapper.OrderBaseMapper;
+import com.poyee.service.OrderBaseService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+@Service
+public class OrderBaseServiceImpl extends ServiceImpl<OrderBaseMapper, OrderBase> implements OrderBaseService {
+
+}

+ 20 - 0
order-web/src/main/java/com/poyee/service/impl/OrderItemServiceImpl.java

@@ -0,0 +1,20 @@
+package com.poyee.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.poyee.domain.OrderItem;
+import com.poyee.mapper.OrderItemMapper;
+import com.poyee.service.OrderItemService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-20
+ */
+@Service
+public class OrderItemServiceImpl extends ServiceImpl<OrderItemMapper, OrderItem> implements OrderItemService {
+
+}

+ 20 - 0
order-web/src/main/java/com/poyee/service/impl/OrderLogisticsServiceImpl.java

@@ -0,0 +1,20 @@
+package com.poyee.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.poyee.domain.OrderLogistics;
+import com.poyee.mapper.OrderLogisticsMapper;
+import com.poyee.service.OrderLogisticsService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-18
+ */
+@Service
+public class OrderLogisticsServiceImpl extends ServiceImpl<OrderLogisticsMapper, OrderLogistics> implements OrderLogisticsService {
+
+}

+ 20 - 0
order-web/src/main/java/com/poyee/service/impl/RecycleOrderServiceImpl.java

@@ -0,0 +1,20 @@
+package com.poyee.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.poyee.domain.RecycleOrder;
+import com.poyee.mapper.RecycleOrderMapper;
+import com.poyee.service.RecycleOrderService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author Hr
+ * @since 2026-03-20
+ */
+@Service
+public class RecycleOrderServiceImpl extends ServiceImpl<RecycleOrderMapper, RecycleOrder> implements RecycleOrderService {
+
+}

+ 36 - 0
order-web/src/main/resources/application-dev.yml

@@ -0,0 +1,36 @@
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:poyee_order}?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
+    username: ${DB_USERNAME:root}
+    password: ${DB_PASSWORD:root}
+    druid:
+      initial-size: ${DB_INITIAL_SIZE:5}
+      min-idle: ${DB_MIN_IDLE:5}
+      max-active: ${DB_MAX_ACTIVE:20}
+      max-wait: ${DB_MAX_WAIT:60000}
+  redis:
+    database: ${REDIS_DATABASE:1}
+    password: ${REDIS_PASSWORD:Pass2010}
+    host: ${REDIS_HOST:192.168.50.8}
+    port: ${REDIS_PORT:6379}
+    lettuce.pool:
+      max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)
+      max-wait: -1ms    # 连接池最大阻塞等待时间(使用负值表示没有限制)
+      max-idle: 10      # 连接池中的最大空闲连接
+      min-idle: 5       # 连接池中的最小空闲连接
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: health,info,metrics
+  endpoint:
+    health:
+      show-details: always
+
+logging:
+  level:
+    com.poyee: debug
+    com.baomidou.mybatisplus: debug

+ 42 - 0
order-web/src/main/resources/application-prod.yml

@@ -0,0 +1,42 @@
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://${DB_HOST:prod-db-host}:${DB_PORT:3306}/${DB_NAME:poyee_order}?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
+    username: ${DB_USERNAME}
+    password: ${DB_PASSWORD}
+    druid:
+      initial-size: ${DB_INITIAL_SIZE:20}
+      min-idle: ${DB_MIN_IDLE:20}
+      max-active: ${DB_MAX_ACTIVE:100}
+      max-wait: ${DB_MAX_WAIT:30000}
+      validation-query: ${DB_VALIDATION_QUERY:SELECT 1}
+      test-on-borrow: ${DB_TEST_ON_BORROW:true}
+      test-while-idle: ${DB_TEST_WHILE_IDLE:true}
+      time-between-eviction-runs-millis: ${DB_EVICTION_INTERVAL:60000}
+  redis:
+    database: ${REDIS_DATABASE:1}
+    password: ${REDIS_PASSWORD:Pass2010}
+    host: ${REDIS_HOST:192.168.50.8}
+    port: ${REDIS_PORT:6379}
+    lettuce.pool:
+      max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)
+      max-wait: -1ms    # 连接池最大阻塞等待时间(使用负值表示没有限制)
+      max-idle: 10      # 连接池中的最大空闲连接
+      min-idle: 5       # 连接池中的最小空闲连接
+swagger:
+  enabled: ${SWAGGER_ENABLED:false}
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: health,info
+  endpoint:
+    health:
+      show-details: never
+
+logging:
+  level:
+    com.poyee: warn
+    com.baomidou.mybatisplus: warn

+ 35 - 0
order-web/src/main/resources/application-test.yml

@@ -0,0 +1,35 @@
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://${DB_HOST:192.168.50.10}:${DB_PORT:3306}/${DB_NAME:b2b_order}?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
+    username: ${DB_USERNAME:b2b_order}
+    password: ${DB_PASSWORD:123456}
+    druid:
+      initial-size: ${DB_INITIAL_SIZE:10}
+      min-idle: ${DB_MIN_IDLE:10}
+      max-active: ${DB_MAX_ACTIVE:50}
+      max-wait: ${DB_MAX_WAIT:60000}
+  redis:
+    database: ${REDIS_DATABASE:1}
+    password: ${REDIS_PASSWORD:Pass2010}
+    host: ${REDIS_HOST:192.168.50.8}
+    port: ${REDIS_PORT:6379}
+    lettuce.pool:
+      max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)
+      max-wait: -1ms    # 连接池最大阻塞等待时间(使用负值表示没有限制)
+      max-idle: 10      # 连接池中的最大空闲连接
+      min-idle: 5       # 连接池中的最小空闲连接
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: health,info,metrics
+  endpoint:
+    health:
+      show-details: when_authorized
+
+logging:
+  level:
+    com.poyee: info

+ 39 - 0
order-web/src/main/resources/application.yml

@@ -0,0 +1,39 @@
+server:
+  port: 10010
+
+spring:
+  application:
+    name: b2b-order-service
+  profiles:
+    active: dev
+  messages:
+    basename: i18n/messages
+    encoding: UTF-8
+    cache-duration: 3600
+
+swagger:
+  enabled: true
+
+mybatis-plus:
+  type-aliases-package: com.poyee.domain
+  configuration:
+    map-underscore-to-camel-case: true
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  global-config:
+    db-config:
+      id-type: auto
+      logic-delete-field: deleteFlag
+      logic-delete-value: 1
+      logic-not-delete-value: 0
+
+feign:
+  client:
+    config:
+      default:
+        connectTimeout: 5000
+        readTimeout: 5000
+        loggerLevel: basic
+
+logging:
+  level:
+    com.poyee: info

+ 9 - 0
order-web/src/main/resources/i18n/messages.properties

@@ -0,0 +1,9 @@
+call_product_item_api_error=\u83B7\u53D6\u5B50\u5546\u54C1\u6570\u636E\u51FA\u9519
+order_no_can_not_be_empty=\u8BA2\u5355\u53F7\u4E0D\u80FD\u4E3A\u7A7A
+logistics_code_can_not_be_empty=\u5FEB\u9012\u7F16\u7801\u4E0D\u80FD\u4E3A\u7A7A
+logistics_name_can_not_be_empty=\u5FEB\u9012\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
+tracking_no_can_not_be_empty=\u5FEB\u9012\u5355\u53F7\u4E0D\u80FD\u4E3A\u7A7A
+order_not_found=\u8BA2\u5355\u4E0D\u5B58\u5728
+order_status_error=\u8BA2\u5355\u72B6\u6001\u9519\u8BEF
+order_with_address_modification_request_Please_approve_and_redo_the_operation=\u8BA2\u5355\u6709\u5730\u5740\u4FEE\u6539\u7533\u8BF7,\u8BF7\u5BA1\u6279\u540E\u91CD\u65B0\u64CD\u4F5C
+only_supports_shipping_for_flash_purchase_orders=\u4EC5\u652F\u6301\u5BF9\u95EA\u8D2D\u8BA2\u5355\u53D1\u8D27

+ 9 - 0
order-web/src/main/resources/i18n/messages_en_US.properties

@@ -0,0 +1,9 @@
+call_product_item_api_error=call product item api error
+order_no_can_not_be_empty=order no can not be empty
+logistics_code_can_not_be_empty=logistics code can not be empty
+logistics_name_can_not_be_empty=logistics name can not be empty
+tracking_no_can_not_be_empty=tracking no can not be empty
+order_not_found=order not found
+order_status_error=order status error
+order_with_address_modification_request_Please_approve_and_redo_the_operation=order with address modification request Please approve and redo the operation
+only_supports_shipping_for_flash_purchase_orders=Only supports shipping for flash purchase orders

+ 8 - 0
order-web/src/main/resources/i18n/messages_en_zh_TW.properties

@@ -0,0 +1,8 @@
+call_product_item_api_error=\u7372\u53D6\u5B50\u5546\u54C1\u6578\u64DA\u51FA\u932F
+logistics_code_can_not_be_empty=\u5FEB\u905E\u7DE8\u78BC\u4E0D\u80FD\u70BA\u7A7A
+logistics_name_can_not_be_empty=\u5FEB\u905E\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A
+tracking_no_can_not_be_empty=\u5FEB\u905E\u55AE\u865F\u4E0D\u80FD\u70BA\u7A7A
+order_not_found=\u8A02\u55AE\u4E0D\u5B58\u5728
+order_status_error=\u8A02\u55AE\u72C0\u614B\u932F\u8AA4
+order_with_address_modification_request_Please_approve_and_redo_the_operation=\u8A02\u55AE\u6709\u5730\u5740\u4FEE\u6539\u7533\u8ACB\uFF0C\u8ACB\u5BE9\u6279\u5F8C\u91CD\u65B0\u64CD\u4F5C\uFF0C\u8ACB\u5BE9\u6279\u5F8C\u91CD\u65B0\u64CD\u4F5C
+only_supports_shipping_for_flash_purchase_orders=\u50C5\u652F\u6301\u5C0D\u9583\u8CFC\u8A02\u55AE\u767C\u8CA8

+ 143 - 0
order-web/src/main/resources/logback-spring.xml

@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!-- 定义日志文件的存储地址 -->
+    <property name="LOG_PATH" value="logs"/>
+    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L [%X{traceId}] - %msg%n"/>
+
+    <!-- 控制台输出 -->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- INFO 级别日志文件 -->
+    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_PATH}/info.log</file>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>NEUTRAL</onMismatch>
+        </filter>
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_PATH}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+    </appender>
+
+    <!-- WARN 级别日志文件 -->
+    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_PATH}/warn.log</file>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>WARN</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+    </appender>
+
+    <!-- ERROR 级别日志文件 -->
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_PATH}/error.log</file>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <maxHistory>30</maxHistory>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+    </appender>
+
+    <!-- 异步输出配置 -->
+    <appender name="ASYNC_INFO" class="ch.qos.logback.classic.AsyncAppender">
+        <discardingThreshold>0</discardingThreshold>
+        <queueSize>512</queueSize>
+        <appender-ref ref="INFO_FILE"/>
+    </appender>
+
+    <appender name="ASYNC_WARN" class="ch.qos.logback.classic.AsyncAppender">
+        <discardingThreshold>0</discardingThreshold>
+        <queueSize>512</queueSize>
+        <appender-ref ref="WARN_FILE"/>
+    </appender>
+
+    <appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
+        <discardingThreshold>0</discardingThreshold>
+        <queueSize>512</queueSize>
+        <appender-ref ref="ERROR_FILE"/>
+    </appender>
+
+    <!-- 开发环境配置 -->
+    <springProfile name="dev">
+        <root level="INFO">
+            <appender-ref ref="CONSOLE"/>
+            <appender-ref ref="ASYNC_INFO"/>
+            <appender-ref ref="ASYNC_WARN"/>
+            <appender-ref ref="ASYNC_ERROR"/>
+        </root>
+        <logger name="com.poyee" level="DEBUG"/>
+        <logger name="org.springframework.web" level="INFO"/>
+        <logger name="org.mybatis" level="DEBUG"/>
+    </springProfile>
+
+    <!-- 测试环境配置 -->
+    <springProfile name="test">
+        <root level="INFO">
+            <appender-ref ref="CONSOLE"/>
+            <appender-ref ref="ASYNC_INFO"/>
+            <appender-ref ref="ASYNC_WARN"/>
+            <appender-ref ref="ASYNC_ERROR"/>
+        </root>
+        <logger name="com.poyee" level="INFO"/>
+    </springProfile>
+
+    <!-- 生产环境配置 -->
+    <springProfile name="prod">
+        <root level="WARN">
+            <appender-ref ref="CONSOLE"/>
+            <appender-ref ref="ASYNC_INFO"/>
+            <appender-ref ref="ASYNC_WARN"/>
+            <appender-ref ref="ASYNC_ERROR"/>
+        </root>
+        <logger name="com.poyee" level="INFO"/>
+    </springProfile>
+
+    <!-- 默认配置(无 profile 时) -->
+    <springProfile name="!dev,!test,!prod">
+        <root level="INFO">
+            <appender-ref ref="CONSOLE"/>
+            <appender-ref ref="ASYNC_INFO"/>
+            <appender-ref ref="ASYNC_WARN"/>
+            <appender-ref ref="ASYNC_ERROR"/>
+        </root>
+        <logger name="com.poyee" level="DEBUG"/>
+    </springProfile>
+
+</configuration>

+ 196 - 0
pom.xml

@@ -0,0 +1,196 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.poyee</groupId>
+    <artifactId>b2b-order-service</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <modules>
+        <module>order-client</module>
+        <module>order-web</module>
+        <module>order-common</module>
+    </modules>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.1.17.RELEASE</version>
+    </parent>
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <spring.cloud.version>Greenwich.SR6</spring.cloud.version>
+        <mybatis.plus.version>3.4.3.4</mybatis.plus.version>
+        <knife4j.version>2.0.4</knife4j.version>
+        <swagger.version>2.9.2</swagger.version>
+        <redisson.version>3.17.6</redisson.version>
+        <lombok.version>1.18.30</lombok.version>
+        <fastjson.version>1.2.83</fastjson.version>
+        <pagehelper.version>1.4.1</pagehelper.version>
+        <mapstruct.version>1.4.2.Final</mapstruct.version>
+        <commons.lang3.version>3.12.0</commons.lang3.version>
+        <jwt.version>3.3.0</jwt.version>
+        <hutoll.version>5.8.11</hutoll.version>
+        <mapstruct.lombok-version>0.2.0</mapstruct.lombok-version>
+    </properties>
+
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.hibernate.validator</groupId>
+                <artifactId>hibernate-validator</artifactId>
+            </dependency>
+            <dependency>
+                <groupId>org.mapstruct</groupId>
+                <artifactId>mapstruct</artifactId>
+                <version>${mapstruct.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.mapstruct</groupId>
+                <artifactId>mapstruct-processor</artifactId>
+                <version>${mapstruct.version}</version>
+                <scope>provided</scope>
+            </dependency>
+            <!-- Spring Cloud -->
+            <dependency>
+                <groupId>org.springframework.cloud</groupId>
+                <artifactId>spring-cloud-dependencies</artifactId>
+                <version>${spring.cloud.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutoll.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.auth0</groupId>
+                <artifactId>java-jwt</artifactId>
+                <version>${jwt.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok-mapstruct-binding</artifactId>
+                <version>${mapstruct.lombok-version}</version>
+            </dependency>
+            <!-- 内部模块 -->
+            <dependency>
+                <groupId>com.poyee</groupId>
+                <artifactId>order-common</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.poyee</groupId>
+                <artifactId>order-client</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
+            <!-- MyBatis-Plus -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis.plus.version}</version>
+            </dependency>
+
+            <!-- Knife4j -->
+            <dependency>
+                <groupId>com.github.xiaoymin</groupId>
+                <artifactId>knife4j-spring-boot-starter</artifactId>
+                <version>${knife4j.version}</version>
+            </dependency>
+
+            <!-- Redisson -->
+            <dependency>
+                <groupId>org.redisson</groupId>
+                <artifactId>redisson-spring-boot-starter</artifactId>
+                <version>${redisson.version}</version>
+            </dependency>
+
+            <!-- Lombok -->
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+                <scope>provided</scope>
+            </dependency>
+
+            <!-- FastJSON -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.pagehelper</groupId>
+                <artifactId>pagehelper-spring-boot-starter</artifactId>
+                <version>${pagehelper.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.mybatis</groupId>
+                        <artifactId>mybatis</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>org.mybatis</groupId>
+                        <artifactId>mybatis-spring</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <!-- Commons Lang3 -->
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-lang3</artifactId>
+                <version>${commons.lang3.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-maven-plugin</artifactId>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <configuration>
+                        <annotationProcessorPaths>
+                            <!-- ① Lombok 必须在 MapStruct 之前 -->
+                            <path>
+                                <groupId>org.projectlombok</groupId>
+                                <artifactId>lombok</artifactId>
+                                <version>${lombok.version}</version>
+                            </path>
+                            <!-- ② 绑定器,确保顺序 -->
+                            <path>
+                                <groupId>org.projectlombok</groupId>
+                                <artifactId>lombok-mapstruct-binding</artifactId>
+                                <version>0.2.0</version>
+                            </path>
+                            <!-- ③ MapStruct 最后执行,此时 getter 已生成 -->
+                            <path>
+                                <groupId>org.mapstruct</groupId>
+                                <artifactId>mapstruct-processor</artifactId>
+                                <version>${mapstruct.version}</version>
+                            </path>
+                        </annotationProcessorPaths>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+
+    </build>
+</project>