Jelajahi Sumber

user 字典数据 init

hr~ 2 hari lalu
melakukan
0f22e32614
64 mengubah file dengan 3585 tambahan dan 0 penghapusan
  1. 43 0
      .gitignore
  2. 174 0
      pom.xml
  3. 38 0
      user-client/pom.xml
  4. 95 0
      user-common/pom.xml
  5. 13 0
      user-common/src/main/java/com/poyee/annotation/NoLogin.java
  6. 32 0
      user-common/src/main/java/com/poyee/annotation/UserLoginToken.java
  7. 167 0
      user-common/src/main/java/com/poyee/aspect/UserLoginTokenAspect.java
  8. 33 0
      user-common/src/main/java/com/poyee/aspect/weblog/RequestNoContext.java
  9. 67 0
      user-common/src/main/java/com/poyee/aspect/weblog/WebLog.java
  10. 113 0
      user-common/src/main/java/com/poyee/aspect/weblog/WebLogAspect.java
  11. 45 0
      user-common/src/main/java/com/poyee/config/I18nConfig.java
  12. 25 0
      user-common/src/main/java/com/poyee/config/MybatisPlusConfig.java
  13. 106 0
      user-common/src/main/java/com/poyee/config/RedisConfig.java
  14. 138 0
      user-common/src/main/java/com/poyee/config/SwaggerConfig.java
  15. 113 0
      user-common/src/main/java/com/poyee/domain/SysDictData.java
  16. 105 0
      user-common/src/main/java/com/poyee/domain/SysDictType.java
  17. 4 0
      user-common/src/main/java/com/poyee/domain/UserBase.java
  18. 36 0
      user-common/src/main/java/com/poyee/enums/DefaultEnum.java
  19. 33 0
      user-common/src/main/java/com/poyee/enums/StatusEnum.java
  20. 34 0
      user-common/src/main/java/com/poyee/exception/BusinessException.java
  21. 168 0
      user-common/src/main/java/com/poyee/exception/GlobalExceptionHandler.java
  22. 46 0
      user-common/src/main/java/com/poyee/mapstruct/SysDictMapStruct.java
  23. 11 0
      user-common/src/main/java/com/poyee/param/UserInfo.java
  24. 27 0
      user-common/src/main/java/com/poyee/req/DeleteReq.java
  25. 42 0
      user-common/src/main/java/com/poyee/req/SysDictDataQueryReq.java
  26. 80 0
      user-common/src/main/java/com/poyee/req/SysDictDataReq.java
  27. 42 0
      user-common/src/main/java/com/poyee/req/SysDictTypeQueryReq.java
  28. 54 0
      user-common/src/main/java/com/poyee/req/SysDictTypeReq.java
  29. 19 0
      user-common/src/main/java/com/poyee/req/page/Page.java
  30. 53 0
      user-common/src/main/java/com/poyee/res/Result.java
  31. 90 0
      user-common/src/main/java/com/poyee/res/SysDictDataRes.java
  32. 66 0
      user-common/src/main/java/com/poyee/res/SysDictTypeRes.java
  33. 34 0
      user-common/src/main/java/com/poyee/res/UserInfo.java
  34. 32 0
      user-common/src/main/java/com/poyee/threads/ThreadPoolExecutor.java
  35. 45 0
      user-common/src/main/java/com/poyee/utils/ApiUtils.java
  36. 99 0
      user-common/src/main/java/com/poyee/utils/I18nUtil.java
  37. 25 0
      user-common/src/main/java/com/poyee/utils/JwtUtils.java
  38. 62 0
      user-common/src/main/java/com/poyee/utils/ServletUtils.java
  39. 145 0
      user-web/pom.xml
  40. 19 0
      user-web/src/main/java/com/poyee/UserApplication.java
  41. 65 0
      user-web/src/main/java/com/poyee/controller/SysDictDataController.java
  42. 68 0
      user-web/src/main/java/com/poyee/controller/SysDictTypeController.java
  43. 11 0
      user-web/src/main/java/com/poyee/controller/UserBaseController.java
  44. 63 0
      user-web/src/main/java/com/poyee/facade/ISysDictDataFacade.java
  45. 62 0
      user-web/src/main/java/com/poyee/facade/ISysDictTypeFacade.java
  46. 125 0
      user-web/src/main/java/com/poyee/facade/impl/SysDictDataFacade.java
  47. 133 0
      user-web/src/main/java/com/poyee/facade/impl/SysDictTypeFacade.java
  48. 16 0
      user-web/src/main/java/com/poyee/mapper/SysDictDataMapper.java
  49. 16 0
      user-web/src/main/java/com/poyee/mapper/SysDictTypeMapper.java
  50. 9 0
      user-web/src/main/java/com/poyee/mapper/UserBaseMapper.java
  51. 16 0
      user-web/src/main/java/com/poyee/service/SysDictDataService.java
  52. 16 0
      user-web/src/main/java/com/poyee/service/SysDictTypeService.java
  53. 7 0
      user-web/src/main/java/com/poyee/service/UserBaseService.java
  54. 20 0
      user-web/src/main/java/com/poyee/service/impl/SysDictDataServiceImpl.java
  55. 20 0
      user-web/src/main/java/com/poyee/service/impl/SysDictTypeServiceImpl.java
  56. 11 0
      user-web/src/main/java/com/poyee/service/impl/UserBaseServiceImpl.java
  57. 44 0
      user-web/src/main/resources/application-dev.yml
  58. 42 0
      user-web/src/main/resources/application-prod.yml
  59. 35 0
      user-web/src/main/resources/application-test.yml
  60. 39 0
      user-web/src/main/resources/application.yml
  61. 19 0
      user-web/src/main/resources/i18n/messages.properties
  62. 16 0
      user-web/src/main/resources/i18n/messages_en_US.properties
  63. 16 0
      user-web/src/main/resources/i18n/messages_zh_TW.properties
  64. 143 0
      user-web/src/main/resources/logback-spring.xml

+ 43 - 0
.gitignore

@@ -0,0 +1,43 @@
+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

+ 174 - 0
pom.xml

@@ -0,0 +1,174 @@
+<?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-user-service</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <name>user-service-parent</name>
+    <description>用户服务父模块</description>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.1.17.RELEASE</version>
+    </parent>
+
+    <modules>
+        <module>user-common</module>
+        <module>user-client</module>
+        <module>user-web</module>
+    </modules>
+
+    <properties>
+        <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>
+        <mapstruct.version>1.4.2.Final</mapstruct.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>
+        <pagehelper.starter.version>1.4.1</pagehelper.starter.version>
+        <pagehelper.version>5.3.2</pagehelper.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>
+            <!-- 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>com.poyee</groupId>
+                <artifactId>user-common</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.poyee</groupId>
+                <artifactId>user-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>
+
+            <!-- Commons Lang3 -->
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-lang3</artifactId>
+                <version>${commons.lang3.version}</version>
+            </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>
+
+            <!-- PageHelper -->
+            <dependency>
+                <groupId>com.github.pagehelper</groupId>
+                <artifactId>pagehelper-spring-boot-starter</artifactId>
+                <version>${pagehelper.starter.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.mybatis</groupId>
+                        <artifactId>mybatis</artifactId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>org.mybatis</groupId>
+                        <artifactId>mybatis-spring</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-maven-plugin</artifactId>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
+</project>

+ 38 - 0
user-client/pom.xml

@@ -0,0 +1,38 @@
+<?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-user-service</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>user-client</artifactId>
+    <name>user-client</name>
+    <description>用户服务客户端模块(调用外部服务)</description>
+
+    <dependencies>
+        <!-- 公共模块 -->
+        <dependency>
+            <groupId>com.poyee</groupId>
+            <artifactId>user-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>

+ 95 - 0
user-common/pom.xml

@@ -0,0 +1,95 @@
+<?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-user-service</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>user-common</artifactId>
+    <name>user-common</name>
+    <description>用户服务公共模块</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>knife4j-spring-boot-starter</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>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+        </dependency>
+
+        <!-- PageHelper -->
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper</artifactId>
+            <version>${pagehelper.version}</version>
+        </dependency>
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.mapstruct</groupId>
+                            <artifactId>mapstruct-processor</artifactId>
+                            <version>${mapstruct.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 13 - 0
user-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
user-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
user-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
user-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
user-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;
+
+}

+ 113 - 0
user-common/src/main/java/com/poyee/aspect/weblog/WebLogAspect.java

@@ -0,0 +1,113 @@
+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 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.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();
+        long currentTimeMillis = System.currentTimeMillis();
+        String requestURI = Objects.requireNonNull(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();
+    }
+
+
+    @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();
+    }
+
+    /**
+     * 根据方法和传入的参数获取请求参数
+     */
+    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;
+        }
+    }
+}

+ 45 - 0
user-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());
+    }
+}

+ 25 - 0
user-common/src/main/java/com/poyee/config/MybatisPlusConfig.java

@@ -0,0 +1,25 @@
+package com.poyee.config;
+
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MyBatis-Plus 配置
+ */
+@Configuration
+public class MybatisPlusConfig {
+
+    /**
+     * 配置 MyBatis-Plus 拦截器
+     * 包含乐观锁插件
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 乐观锁插件
+        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
+        return interceptor;
+    }
+}

+ 106 - 0
user-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
user-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());
+    }
+}

+ 113 - 0
user-common/src/main/java/com/poyee/domain/SysDictData.java

@@ -0,0 +1,113 @@
+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-17
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("sys_dict_data")
+public class SysDictData implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+    /**
+     * 标签
+     */
+    private String label;
+
+    /**
+     * 值
+     */
+    private String value;
+
+    /**
+     * 字典类型
+     */
+    private String dictType;
+
+    private String cssClass;
+    private String listClass;
+    private Integer defaultFlag;
+    private Integer status;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 版本
+     */
+    private Integer version;
+
+    /**
+     * 是否删除
+     */
+    private Integer deleteFlag;
+
+
+    @Override
+    public String toString() {
+        return "SysDictData{" +
+        ", id = " + id +
+        ", sort = " + sort +
+        ", label = " + label +
+        ", value = " + value +
+        ", cssClass = " + cssClass +
+        ", listClass = " + listClass +
+        ", defaultFlag = " + defaultFlag +
+        ", status = " + status +
+        ", createBy = " + createBy +
+        ", createTime = " + createTime +
+        ", updateBy = " + updateBy +
+        ", updateTime = " + updateTime +
+        ", version = " + version +
+        ", deleteFlag = " + deleteFlag +
+        "}";
+    }
+}

+ 105 - 0
user-common/src/main/java/com/poyee/domain/SysDictType.java

@@ -0,0 +1,105 @@
+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-17
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("sys_dict_type")
+public class SysDictType implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 名称
+     */
+    private String dictName;
+
+    /**
+     * 类型
+     */
+    private String dictType;
+
+    /**
+     * 状态
+     */
+    private Integer status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 逻辑删
+     */
+    private Integer deleteFlag;
+
+    /**
+     * 版本
+     */
+    private Integer version;
+
+
+    @Override
+    public String toString() {
+        return "SysDictType{" +
+                ", id = " + id +
+                ", dictName = " + dictName +
+                ", dictType = " + dictType +
+                ", status = " + status +
+                ", remark = " + remark +
+                ", createBy = " + createBy +
+                ", createTime = " + createTime +
+                ", updateBy = " + updateBy +
+                ", updateTime = " + updateTime +
+                ", deleteFlag = " + deleteFlag +
+                ", version = " + version +
+                "}";
+    }
+}

+ 4 - 0
user-common/src/main/java/com/poyee/domain/UserBase.java

@@ -0,0 +1,4 @@
+package com.poyee.domain;
+
+public class UserBase {
+}

+ 36 - 0
user-common/src/main/java/com/poyee/enums/DefaultEnum.java

@@ -0,0 +1,36 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 是否默认枚举
+ */
+public enum DefaultEnum {
+    YES(1, "是"),
+    NO(0, "否");
+
+    @Getter
+    final int code;
+    @Getter
+    final String message;
+
+    DefaultEnum(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public static DefaultEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (DefaultEnum defaultEnum : values()) {
+            if (defaultEnum.getCode() == code) {
+                return defaultEnum;
+            }
+        }
+        return null;
+    }
+}

+ 33 - 0
user-common/src/main/java/com/poyee/enums/StatusEnum.java

@@ -0,0 +1,33 @@
+package com.poyee.enums;
+
+import lombok.Getter;
+
+/**
+ * 状态枚举
+ */
+public enum StatusEnum {
+    TRUE(1, "停用"),
+    FALSE(0, "正常");
+
+    @Getter
+    final int code;
+    @Getter
+    final String message;
+
+    StatusEnum(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public static StatusEnum getByCode(Integer code) {
+        if (code == null) {
+            return null;
+        }
+        for (StatusEnum statusEnum : values()) {
+            if (statusEnum.getCode() == code) {
+                return statusEnum;
+            }
+        }
+        return null;
+    }
+}

+ 34 - 0
user-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;
+    }
+}

+ 168 - 0
user-common/src/main/java/com/poyee/exception/GlobalExceptionHandler.java

@@ -0,0 +1,168 @@
+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, "系统繁忙,请稍后重试");
+    }
+}

+ 46 - 0
user-common/src/main/java/com/poyee/mapstruct/SysDictMapStruct.java

@@ -0,0 +1,46 @@
+package com.poyee.mapstruct;
+
+import com.poyee.domain.SysDictData;
+import com.poyee.domain.SysDictType;
+import com.poyee.req.SysDictDataReq;
+import com.poyee.req.SysDictTypeReq;
+import com.poyee.res.SysDictDataRes;
+import com.poyee.res.SysDictTypeRes;
+import org.mapstruct.Mapper;
+
+import java.util.List;
+
+@Mapper(componentModel = "spring")
+public interface SysDictMapStruct {
+
+
+    /**
+     * 字典类型 DO -> RES
+     */
+    SysDictTypeRes toTypeRes(SysDictType sysDictType);
+
+    /**
+     * 字典类型列表 DO -> RES
+     */
+    List<SysDictTypeRes> toTypeResList(List<SysDictType> sysDictTypes);
+
+    /**
+     * 字典类型 REQ -> DO
+     */
+    SysDictType toTypeDO(SysDictTypeReq req);
+
+    /**
+     * 字典数据 DO -> RES
+     */
+    SysDictDataRes toDataRes(SysDictData sysDictData);
+
+    /**
+     * 字典数据列表 DO -> RES
+     */
+    List<SysDictDataRes> toDataResList(List<SysDictData> sysDictDataList);
+
+    /**
+     * 字典数据 REQ -> DO
+     */
+    SysDictData toDataDO(SysDictDataReq req);
+}

+ 11 - 0
user-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 {
+
+}

+ 27 - 0
user-common/src/main/java/com/poyee/req/DeleteReq.java

@@ -0,0 +1,27 @@
+package com.poyee.req;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 删除请求实体
+ */
+@Data
+@ApiModel("删除Request")
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeleteReq implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("主键列表")
+    @NotEmpty(message = "delete_primary_key_list_cannot_be_empty")
+    private List<Long> ids;
+}

+ 42 - 0
user-common/src/main/java/com/poyee/req/SysDictDataQueryReq.java

@@ -0,0 +1,42 @@
+package com.poyee.req;
+
+import com.poyee.req.page.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 字典数据查询参数
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@ApiModel("字典数据查询Request")
+@NoArgsConstructor
+@AllArgsConstructor
+public class SysDictDataQueryReq extends Page implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典类型
+     */
+    @ApiModelProperty("字典类型")
+    private String dictType;
+
+    /**
+     * 字典标签
+     */
+    @ApiModelProperty("字典标签")
+    private String label;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ApiModelProperty("状态(0正常 1停用)")
+    private Integer status;
+}

+ 80 - 0
user-common/src/main/java/com/poyee/req/SysDictDataReq.java

@@ -0,0 +1,80 @@
+package com.poyee.req;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * 字典数据新增/编辑请求
+ */
+@Data
+@ApiModel("字典数据新增/编辑Request")
+@NoArgsConstructor
+@AllArgsConstructor
+public class SysDictDataReq implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键(编辑时必传)
+     */
+    @ApiModelProperty("主键(编辑时必传)")
+    private Long id;
+
+    /**
+     * 排序
+     */
+    @ApiModelProperty("排序")
+    private Integer sort;
+
+    /**
+     * 字典标签
+     */
+    @ApiModelProperty(value = "字典标签", required = true)
+    @NotBlank(message = "dictionary_labels_cannot_be_empty")
+    private String label;
+
+    /**
+     * 字典键值
+     */
+    @ApiModelProperty(value = "字典键值", required = true)
+    @NotBlank(message = "dictionary_value_cannot_be_empty")
+    private String value;
+
+    /**
+     * 字典类型
+     */
+    @ApiModelProperty(value = "字典类型", required = true)
+    @NotBlank(message = "dictionary_type_cannot_be_empty")
+    private String dictType;
+
+    /**
+     * 样式属性
+     */
+    @ApiModelProperty("样式属性")
+    private String cssClass;
+
+    /**
+     * 表格回显样式
+     */
+    @ApiModelProperty("表格回显样式")
+    private String listClass;
+
+    /**
+     * 是否默认(0否 1是)
+     */
+    @ApiModelProperty("是否默认(0否 1是)")
+    private Integer defaultFlag;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ApiModelProperty("状态(0正常 1停用)")
+    private Integer status;
+}

+ 42 - 0
user-common/src/main/java/com/poyee/req/SysDictTypeQueryReq.java

@@ -0,0 +1,42 @@
+package com.poyee.req;
+
+import com.poyee.req.page.Page;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 字典类型查询参数
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+@ApiModel("字典数据类型查询Request")
+@NoArgsConstructor
+@AllArgsConstructor
+public class SysDictTypeQueryReq extends Page implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典名称
+     */
+    @ApiModelProperty("字典名称")
+    private String dictName;
+
+    /**
+     * 字典类型
+     */
+    @ApiModelProperty("字典类型")
+    private String dictType;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ApiModelProperty("状态(0正常 1停用)")
+    private Integer status;
+}

+ 54 - 0
user-common/src/main/java/com/poyee/req/SysDictTypeReq.java

@@ -0,0 +1,54 @@
+package com.poyee.req;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+/**
+ * 字典类型新增/编辑请求
+ */
+@Data
+@ApiModel("字典类型新增/编辑Request")
+@NoArgsConstructor
+@AllArgsConstructor
+public class SysDictTypeReq implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键(编辑时必传)
+     */
+    @ApiModelProperty("主键(编辑时必传)")
+    private Long id;
+
+    /**
+     * 字典名称
+     */
+    @ApiModelProperty(value = "字典名称", required = true)
+    @NotBlank(message = "dictionary_name_cannot_be_empty")
+    private String dictName;
+
+    /**
+     * 字典类型
+     */
+    @ApiModelProperty(value = "字典类型", required = true)
+    @NotBlank(message = "dictionary_type_cannot_be_empty")
+    private String dictType;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ApiModelProperty("状态(0正常 1停用)")
+    private Integer status;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+}

+ 19 - 0
user-common/src/main/java/com/poyee/req/page/Page.java

@@ -0,0 +1,19 @@
+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;
+}

+ 53 - 0
user-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;
+    }
+}

+ 90 - 0
user-common/src/main/java/com/poyee/res/SysDictDataRes.java

@@ -0,0 +1,90 @@
+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.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 字典数据响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("字典数据Response")
+public class SysDictDataRes implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ApiModelProperty("主键")
+    private Long id;
+
+    /**
+     * 排序
+     */
+    @ApiModelProperty("排序")
+    private Integer sort;
+
+    /**
+     * 字典标签
+     */
+    @ApiModelProperty("字典标签")
+    private String label;
+
+    /**
+     * 字典键值
+     */
+    @ApiModelProperty("字典键值")
+    private String value;
+
+    /**
+     * 字典类型
+     */
+    @ApiModelProperty("字典类型")
+    private String dictType;
+
+    /**
+     * 样式属性
+     */
+    @ApiModelProperty("样式属性")
+    private String cssClass;
+
+    /**
+     * 表格回显样式
+     */
+    @ApiModelProperty("表格回显样式")
+    private String listClass;
+
+    /**
+     * 是否默认(0否 1是)
+     */
+    @ApiModelProperty("是否默认(0否 1是)")
+    private Integer defaultFlag;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ApiModelProperty("状态(0正常 1停用)")
+    private Integer status;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    /**
+     * 修改时间
+     */
+    @ApiModelProperty("修改时间")
+    private LocalDateTime updateTime;
+}

+ 66 - 0
user-common/src/main/java/com/poyee/res/SysDictTypeRes.java

@@ -0,0 +1,66 @@
+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.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 字典类型响应
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel("字典类型Response")
+public class SysDictTypeRes implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @ApiModelProperty("主键")
+    private Long id;
+
+    /**
+     * 字典名称
+     */
+    @ApiModelProperty("字典名称")
+    private String dictName;
+
+    /**
+     * 字典类型
+     */
+    @ApiModelProperty("字典类型")
+    private String dictType;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ApiModelProperty("状态(0正常 1停用)")
+    private Integer status;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    /**
+     * 修改时间
+     */
+    @ApiModelProperty("修改时间")
+    private LocalDateTime updateTime;
+}

+ 34 - 0
user-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;
+}

+ 32 - 0
user-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
user-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
user-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
user-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);
+    }
+}

+ 62 - 0
user-common/src/main/java/com/poyee/utils/ServletUtils.java

@@ -0,0 +1,62 @@
+package com.poyee.utils;
+
+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 UserInfo getCurrentUser() {
+        HttpServletRequest request = getRequest();
+        if (request == null) {
+            return null;
+        }
+        return (UserInfo) request.getAttribute("currentUser");
+    }
+
+    /**
+     * 设置当前登录用户
+     */
+    public static void setCurrentUser(UserInfo userInfo) {
+        HttpServletRequest request = getRequest();
+        if (request != null) {
+            request.setAttribute("currentUser", userInfo);
+        }
+    }
+}

+ 145 - 0
user-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-user-service</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>user-web</artifactId>
+    <name>user-web</name>
+    <description>用户服务 Web 启动模块</description>
+    <packaging>jar</packaging>
+
+    <properties>
+        <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>user-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.poyee</groupId>
+            <artifactId>user-client</artifactId>
+        </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>
+
+        <!-- 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>
+
+        <!-- PageHelper -->
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</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>

+ 19 - 0
user-web/src/main/java/com/poyee/UserApplication.java

@@ -0,0 +1,19 @@
+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;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+@SpringBootApplication
+@EnableFeignClients(basePackages = "com.poyee")
+@MapperScan("com.poyee.mapper")
+@EnableAspectJAutoProxy(proxyTargetClass = true)
+public class UserApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(UserApplication.class, args);
+    }
+
+}

+ 65 - 0
user-web/src/main/java/com/poyee/controller/SysDictDataController.java

@@ -0,0 +1,65 @@
+package com.poyee.controller;
+
+import com.github.pagehelper.PageInfo;
+import com.poyee.facade.ISysDictDataFacade;
+import com.poyee.req.DeleteReq;
+import com.poyee.req.SysDictDataQueryReq;
+import com.poyee.req.SysDictDataReq;
+import com.poyee.res.Result;
+import com.poyee.res.SysDictDataRes;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 字典数据Controller
+ */
+@RestController
+@RequestMapping("/api/dict/data")
+@Api(tags = "字典数据管理")
+@AllArgsConstructor
+public class SysDictDataController {
+
+    private final ISysDictDataFacade sysDictDataFacade;
+
+    @PostMapping("/list")
+    @ApiOperation("分页查询字典数据列表")
+    public Result<PageInfo<SysDictDataRes>> list(@RequestBody SysDictDataQueryReq query) {
+        return Result.success(sysDictDataFacade.list(query));
+    }
+
+    @GetMapping("/detail/{id}")
+    @ApiOperation("查询字典数据详情")
+    public Result<SysDictDataRes> detail(@ApiParam("字典数据ID") @PathVariable("id") Long id) {
+        return Result.success(sysDictDataFacade.detail(id));
+    }
+
+    @PostMapping("/add")
+    @ApiOperation("新增字典数据")
+    public Result<Boolean> add(@RequestBody @Validated SysDictDataReq req) {
+        return Result.success(sysDictDataFacade.add(req));
+    }
+
+    @PostMapping("/edit")
+    @ApiOperation("编辑字典数据")
+    public Result<Boolean> edit(@RequestBody @Validated SysDictDataReq req) {
+        return Result.success(sysDictDataFacade.edit(req));
+    }
+
+    @PostMapping("/delete")
+    @ApiOperation("删除字典数据")
+    public Result<Boolean> delete(@RequestBody DeleteReq req) {
+        return Result.success(sysDictDataFacade.delete(req));
+    }
+
+    @GetMapping("/type/{dictType}")
+    @ApiOperation("根据字典类型获取字典数据")
+    public Result<List<SysDictDataRes>> getByDictType(@ApiParam("字典类型") @PathVariable("dictType") String dictType) {
+        return Result.success(sysDictDataFacade.getByDictType(dictType));
+    }
+}

+ 68 - 0
user-web/src/main/java/com/poyee/controller/SysDictTypeController.java

@@ -0,0 +1,68 @@
+package com.poyee.controller;
+
+import com.github.pagehelper.PageInfo;
+import com.poyee.facade.ISysDictTypeFacade;
+import com.poyee.req.DeleteReq;
+import com.poyee.req.SysDictTypeQueryReq;
+import com.poyee.req.SysDictTypeReq;
+import com.poyee.res.Result;
+import com.poyee.res.SysDictTypeRes;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 字典类型Controller
+ */
+@RestController
+@RequestMapping("/api/dict/type")
+@Api(tags = "字典类型管理")
+@AllArgsConstructor
+public class SysDictTypeController {
+
+    private final ISysDictTypeFacade sysDictTypeFacade;
+
+    @PostMapping("/list")
+    @ApiOperation("分页查询字典类型列表")
+    public Result<PageInfo<SysDictTypeRes>> list(@RequestBody SysDictTypeQueryReq query) {
+        return Result.success(sysDictTypeFacade.list(query));
+    }
+
+    @GetMapping("/detail/{id}")
+    @ApiOperation("查询字典类型详情")
+    public Result<SysDictTypeRes> detail(@ApiParam("字典类型ID") @PathVariable("id") Long id) {
+        return Result.success(sysDictTypeFacade.detail(id));
+    }
+
+    @PostMapping("/add")
+    @ApiOperation("新增字典类型")
+    public Result<Boolean> add(@RequestBody @Validated SysDictTypeReq req) {
+        return Result.success(sysDictTypeFacade.add(req));
+    }
+
+    @PostMapping("/edit")
+    @ApiOperation("编辑字典类型")
+    public Result<Boolean> edit(@RequestBody @Validated SysDictTypeReq req) {
+        return Result.success(sysDictTypeFacade.edit(req));
+    }
+
+    @PostMapping("/delete")
+    @ApiOperation("删除字典类型")
+    public Result<Boolean> delete(@RequestBody DeleteReq req) {
+        return Result.success(sysDictTypeFacade.delete(req));
+    }
+
+    @GetMapping("/dropdown")
+    @ApiOperation("获取字典类型下拉框")
+    public Result<List<SysDictTypeRes>> dropdown() {
+        return Result.success(sysDictTypeFacade.dropdown());
+    }
+
+    //TODO 图片上传等ossKey secret
+
+}

+ 11 - 0
user-web/src/main/java/com/poyee/controller/UserBaseController.java

@@ -0,0 +1,11 @@
+package com.poyee.controller;
+
+import io.swagger.annotations.Api;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/user")
+@Api(tags = "用户管理")
+public class UserBaseController {
+}

+ 63 - 0
user-web/src/main/java/com/poyee/facade/ISysDictDataFacade.java

@@ -0,0 +1,63 @@
+package com.poyee.facade;
+
+import com.github.pagehelper.PageInfo;
+import com.poyee.req.DeleteReq;
+import com.poyee.req.SysDictDataQueryReq;
+import com.poyee.req.SysDictDataReq;
+import com.poyee.res.SysDictDataRes;
+
+import java.util.List;
+
+/**
+ * 字典数据Facade接口
+ */
+public interface ISysDictDataFacade {
+
+    /**
+     * 分页查询字典数据列表
+     *
+     * @param query 查询参数
+     * @return 分页结果
+     */
+    PageInfo<SysDictDataRes> list(SysDictDataQueryReq query);
+
+    /**
+     * 查询字典数据详情
+     *
+     * @param id 主键
+     * @return 字典数据详情
+     */
+    SysDictDataRes detail(Long id);
+
+    /**
+     * 新增字典数据
+     *
+     * @param req 字典数据请求
+     * @return 是否成功
+     */
+    Boolean add(SysDictDataReq req);
+
+    /**
+     * 编辑字典数据
+     *
+     * @param req 字典数据请求
+     * @return 是否成功
+     */
+    Boolean edit(SysDictDataReq req);
+
+    /**
+     * 删除字典数据
+     *
+     * @param req 删除请求
+     * @return 是否成功
+     */
+    Boolean delete(DeleteReq req);
+
+    /**
+     * 根据字典类型获取字典数据
+     *
+     * @param dictType 字典类型
+     * @return 字典数据列表
+     */
+    List<SysDictDataRes> getByDictType(String dictType);
+}

+ 62 - 0
user-web/src/main/java/com/poyee/facade/ISysDictTypeFacade.java

@@ -0,0 +1,62 @@
+package com.poyee.facade;
+
+import com.github.pagehelper.PageInfo;
+import com.poyee.req.DeleteReq;
+import com.poyee.req.SysDictTypeQueryReq;
+import com.poyee.req.SysDictTypeReq;
+import com.poyee.res.SysDictTypeRes;
+
+import java.util.List;
+
+/**
+ * 字典类型Facade接口
+ */
+public interface ISysDictTypeFacade {
+
+    /**
+     * 分页查询字典类型列表
+     *
+     * @param query 查询参数
+     * @return 分页结果
+     */
+    PageInfo<SysDictTypeRes> list(SysDictTypeQueryReq query);
+
+    /**
+     * 查询字典类型详情
+     *
+     * @param id 主键
+     * @return 字典类型详情
+     */
+    SysDictTypeRes detail(Long id);
+
+    /**
+     * 新增字典类型
+     *
+     * @param req 字典类型请求
+     * @return 是否成功
+     */
+    Boolean add(SysDictTypeReq req);
+
+    /**
+     * 编辑字典类型
+     *
+     * @param req 字典类型请求
+     * @return 是否成功
+     */
+    Boolean edit(SysDictTypeReq req);
+
+    /**
+     * 删除字典类型
+     *
+     * @param req 删除请求
+     * @return 是否成功
+     */
+    Boolean delete(DeleteReq req);
+
+    /**
+     * 获取所有字典类型(下拉框)
+     *
+     * @return 字典类型列表
+     */
+    List<SysDictTypeRes> dropdown();
+}

+ 125 - 0
user-web/src/main/java/com/poyee/facade/impl/SysDictDataFacade.java

@@ -0,0 +1,125 @@
+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.PageHelper;
+import com.github.pagehelper.PageInfo;
+import com.poyee.domain.SysDictData;
+import com.poyee.enums.DefaultEnum;
+import com.poyee.enums.StatusEnum;
+import com.poyee.facade.ISysDictDataFacade;
+import com.poyee.mapstruct.SysDictMapStruct;
+import com.poyee.req.DeleteReq;
+import com.poyee.req.SysDictDataQueryReq;
+import com.poyee.req.SysDictDataReq;
+import com.poyee.res.SysDictDataRes;
+import com.poyee.service.SysDictDataService;
+import com.poyee.utils.I18nUtil;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 字典数据Facade实现类
+ */
+@Component
+@AllArgsConstructor
+public class SysDictDataFacade implements ISysDictDataFacade {
+
+    private final SysDictDataService sysDictDataService;
+    private final SysDictMapStruct sysDictMapStruct;
+
+    @Override
+    public PageInfo<SysDictDataRes> list(SysDictDataQueryReq query) {
+        PageHelper.startPage(query.getPage(), query.getPageSize());
+        LambdaQueryWrapper<SysDictData> wrapper = new LambdaQueryWrapper<>();
+        if (StrUtil.isNotBlank(query.getDictType())) {
+            wrapper.eq(SysDictData::getDictType, query.getDictType());
+        }
+        if (StrUtil.isNotBlank(query.getLabel())) {
+            wrapper.likeRight(SysDictData::getLabel, query.getLabel());
+        }
+        if (Objects.nonNull(query.getStatus())) {
+            wrapper.eq(SysDictData::getStatus, query.getStatus());
+        }
+        wrapper.eq(SysDictData::getDeleteFlag, StatusEnum.FALSE.getCode());
+        wrapper.orderByAsc(SysDictData::getSort);
+        wrapper.orderByDesc(SysDictData::getCreateTime);
+        List<SysDictData> list = sysDictDataService.list(wrapper);
+        if (CollUtil.isEmpty(list)) {
+            return PageInfo.emptyPageInfo();
+        }
+        return PageInfo.of(sysDictMapStruct.toDataResList(list));
+    }
+
+    @Override
+    public SysDictDataRes detail(Long id) {
+        SysDictData dictData = sysDictDataService.getById(id);
+        Assert.notNull(dictData, I18nUtil.getMessage("dict_data_notFound"));
+        return sysDictMapStruct.toDataRes(dictData);
+    }
+
+    @Override
+    @Transactional
+    public Boolean add(SysDictDataReq req) {
+        SysDictData dictData = sysDictMapStruct.toDataDO(req);
+        dictData.setSort(Objects.nonNull(req.getSort()) ? req.getSort() : 0);
+        dictData.setDefaultFlag(Objects.nonNull(req.getDefaultFlag()) ? req.getDefaultFlag() : DefaultEnum.NO.getCode());
+        dictData.setStatus(Objects.nonNull(req.getStatus()) ? req.getStatus() : StatusEnum.FALSE.getCode());
+        dictData.setDeleteFlag(StatusEnum.FALSE.getCode());
+        dictData.setVersion(1);
+        dictData.setCreateTime(LocalDateTime.now());
+        return sysDictDataService.save(dictData);
+    }
+
+    @Override
+    @Transactional
+    public Boolean edit(SysDictDataReq req) {
+        Assert.notNull(req.getId(), I18nUtil.getMessage("dict_data_idNotNull"));
+        SysDictData dictData = sysDictDataService.getById(req.getId());
+        Assert.notNull(dictData, I18nUtil.getMessage("dict_data_notFound"));
+        dictData.setSort(req.getSort());
+        dictData.setLabel(req.getLabel());
+        dictData.setValue(req.getValue());
+        dictData.setDictType(req.getDictType());
+        dictData.setCssClass(req.getCssClass());
+        dictData.setListClass(req.getListClass());
+        dictData.setDefaultFlag(req.getDefaultFlag());
+        dictData.setStatus(req.getStatus());
+        dictData.setUpdateTime(LocalDateTime.now());
+        return sysDictDataService.updateById(dictData);
+    }
+
+    @Override
+    @Transactional
+    public Boolean delete(DeleteReq req) {
+        if (CollUtil.isEmpty(req.getIds())) {
+            return true;
+        }
+        // 逻辑删除
+        List<SysDictData> list = sysDictDataService.listByIds(req.getIds());
+        list.forEach(item -> {
+            item.setDeleteFlag(StatusEnum.TRUE.getCode());
+            item.setUpdateTime(LocalDateTime.now());
+        });
+        return sysDictDataService.updateBatchById(list);
+    }
+
+    @Override
+    public List<SysDictDataRes> getByDictType(String dictType) {
+        Assert.notBlank(dictType, I18nUtil.getMessage("dict_data_typeNotNull"));
+        LambdaQueryWrapper<SysDictData> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SysDictData::getDictType, dictType);
+        wrapper.eq(SysDictData::getStatus, StatusEnum.FALSE.getCode());
+        wrapper.eq(SysDictData::getDeleteFlag, StatusEnum.FALSE.getCode());
+        wrapper.orderByAsc(SysDictData::getSort);
+        List<SysDictData> list = sysDictDataService.list(wrapper);
+        return sysDictMapStruct.toDataResList(list);
+    }
+}

+ 133 - 0
user-web/src/main/java/com/poyee/facade/impl/SysDictTypeFacade.java

@@ -0,0 +1,133 @@
+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.PageHelper;
+import com.github.pagehelper.PageInfo;
+import com.poyee.domain.SysDictType;
+import com.poyee.enums.StatusEnum;
+import com.poyee.exception.BusinessException;
+import com.poyee.facade.ISysDictTypeFacade;
+import com.poyee.mapstruct.SysDictMapStruct;
+import com.poyee.req.DeleteReq;
+import com.poyee.req.SysDictTypeQueryReq;
+import com.poyee.req.SysDictTypeReq;
+import com.poyee.res.SysDictTypeRes;
+import com.poyee.service.SysDictTypeService;
+import com.poyee.utils.I18nUtil;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 字典类型Facade实现类
+ */
+@Component
+@AllArgsConstructor
+public class SysDictTypeFacade implements ISysDictTypeFacade {
+
+    private final SysDictTypeService sysDictTypeService;
+    private final SysDictMapStruct sysDictMapStruct;
+
+    @Override
+    public PageInfo<SysDictTypeRes> list(SysDictTypeQueryReq query) {
+        PageHelper.startPage(query.getPage(), query.getPageSize());
+        LambdaQueryWrapper<SysDictType> wrapper = new LambdaQueryWrapper<>();
+        if (StrUtil.isNotBlank(query.getDictName())) {
+            wrapper.likeRight(SysDictType::getDictName, query.getDictName());
+        }
+        if (StrUtil.isNotBlank(query.getDictType())) {
+            wrapper.likeRight(SysDictType::getDictType, query.getDictType());
+        }
+        if (Objects.nonNull(query.getStatus())) {
+            wrapper.eq(SysDictType::getStatus, query.getStatus());
+        }
+        wrapper.eq(SysDictType::getDeleteFlag, StatusEnum.FALSE.getCode());
+        wrapper.orderByDesc(SysDictType::getCreateTime);
+        List<SysDictType> list = sysDictTypeService.list(wrapper);
+        if (CollUtil.isEmpty(list)) {
+            return PageInfo.emptyPageInfo();
+        }
+        return PageInfo.of(sysDictMapStruct.toTypeResList(list));
+    }
+
+    @Override
+    public SysDictTypeRes detail(Long id) {
+        SysDictType dictType = sysDictTypeService.getById(id);
+        Assert.notNull(dictType, I18nUtil.getMessage("dict_type_notFound"));
+        return sysDictMapStruct.toTypeRes(dictType);
+    }
+
+    @Override
+    @Transactional
+    public Boolean add(SysDictTypeReq req) {
+        // 校验字典类型是否已存在
+        long count = sysDictTypeService.lambdaQuery()
+                .eq(SysDictType::getDictType, req.getDictType())
+                .eq(SysDictType::getDeleteFlag, StatusEnum.FALSE.getCode())
+                .count();
+        if (count > 0) {
+            throw new BusinessException(I18nUtil.getMessage("dict_type_alreadyExists"));
+        }
+        SysDictType dictType = sysDictMapStruct.toTypeDO(req);
+        dictType.setStatus(Objects.nonNull(req.getStatus()) ? req.getStatus() : StatusEnum.FALSE.getCode());
+        dictType.setDeleteFlag(StatusEnum.FALSE.getCode());
+        dictType.setVersion(1);
+        dictType.setCreateTime(LocalDateTime.now());
+        return sysDictTypeService.save(dictType);
+    }
+
+    @Override
+    @Transactional
+    public Boolean edit(SysDictTypeReq req) {
+        Assert.notNull(req.getId(), I18nUtil.getMessage("dict_type_idNotNull"));
+        SysDictType dictType = sysDictTypeService.getById(req.getId());
+        Assert.notNull(dictType, I18nUtil.getMessage("dict_type_notFound"));
+        // 校验字典类型是否已存在(排除自己)
+        long count = sysDictTypeService.lambdaQuery()
+                .eq(SysDictType::getDictType, req.getDictType())
+                .ne(SysDictType::getId, req.getId())
+                .eq(SysDictType::getDeleteFlag, StatusEnum.FALSE.getCode())
+                .count();
+        if (count > 0) {
+            throw new BusinessException(I18nUtil.getMessage("dict_type_alreadyExists"));
+        }
+        dictType.setDictName(req.getDictName());
+        dictType.setDictType(req.getDictType());
+        dictType.setStatus(req.getStatus());
+        dictType.setRemark(req.getRemark());
+        dictType.setUpdateTime(LocalDateTime.now());
+        return sysDictTypeService.updateById(dictType);
+    }
+
+    @Override
+    @Transactional
+    public Boolean delete(DeleteReq req) {
+        if (CollUtil.isEmpty(req.getIds())) {
+            return true;
+        }
+        // 逻辑删除
+        List<SysDictType> list = sysDictTypeService.listByIds(req.getIds());
+        list.forEach(item -> {
+            item.setDeleteFlag(StatusEnum.TRUE.getCode());
+            item.setUpdateTime(LocalDateTime.now());
+        });
+        return sysDictTypeService.updateBatchById(list);
+    }
+
+    @Override
+    public List<SysDictTypeRes> dropdown() {
+        LambdaQueryWrapper<SysDictType> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(SysDictType::getStatus, StatusEnum.FALSE.getCode());
+        wrapper.eq(SysDictType::getDeleteFlag, StatusEnum.FALSE.getCode());
+        wrapper.orderByDesc(SysDictType::getCreateTime);
+        List<SysDictType> list = sysDictTypeService.list(wrapper);
+        return sysDictMapStruct.toTypeResList(list);
+    }
+}

+ 16 - 0
user-web/src/main/java/com/poyee/mapper/SysDictDataMapper.java

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

+ 16 - 0
user-web/src/main/java/com/poyee/mapper/SysDictTypeMapper.java

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

+ 9 - 0
user-web/src/main/java/com/poyee/mapper/UserBaseMapper.java

@@ -0,0 +1,9 @@
+package com.poyee.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.poyee.domain.UserBase;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface UserBaseMapper extends BaseMapper<UserBase> {
+}

+ 16 - 0
user-web/src/main/java/com/poyee/service/SysDictDataService.java

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

+ 16 - 0
user-web/src/main/java/com/poyee/service/SysDictTypeService.java

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

+ 7 - 0
user-web/src/main/java/com/poyee/service/UserBaseService.java

@@ -0,0 +1,7 @@
+package com.poyee.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.poyee.domain.UserBase;
+
+public interface UserBaseService extends IService<UserBase> {
+}

+ 20 - 0
user-web/src/main/java/com/poyee/service/impl/SysDictDataServiceImpl.java

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

+ 20 - 0
user-web/src/main/java/com/poyee/service/impl/SysDictTypeServiceImpl.java

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

+ 11 - 0
user-web/src/main/java/com/poyee/service/impl/UserBaseServiceImpl.java

@@ -0,0 +1,11 @@
+package com.poyee.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.poyee.domain.UserBase;
+import com.poyee.mapper.UserBaseMapper;
+import com.poyee.service.UserBaseService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UserBaseServiceImpl extends ServiceImpl<UserBaseMapper, UserBase> implements UserBaseService {
+}

+ 44 - 0
user-web/src/main/resources/application-dev.yml

@@ -0,0 +1,44 @@
+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_user}?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+    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
+feign:
+  client:
+    checklist:
+      url: ${CHECKLIST_URL:http://localhost:8084}
+    order:
+      url: ${ORDER_URL:http://localhost:8081}
+    dict:
+      url: ${DICT_URL:http://localhost:8082}

+ 42 - 0
user-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=Asia/Shanghai
+    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
user-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_user}?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
+    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
user-web/src/main/resources/application.yml

@@ -0,0 +1,39 @@
+server:
+  port: 10012
+
+spring:
+  application:
+    name: b2b-user-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

+ 19 - 0
user-web/src/main/resources/i18n/messages.properties

@@ -0,0 +1,19 @@
+
+# \u5B57\u5178\u7C7B\u578B\u76F8\u5173
+dict_type_notFound=\u5B57\u5178\u7C7B\u578B\u4E0D\u5B58\u5728
+dict_type_alreadyExists=\u5B57\u5178\u7C7B\u578B\u5DF2\u5B58\u5728
+dict_type_idNotNull=\u5B57\u5178\u7C7B\u578BID\u4E0D\u80FD\u4E3A\u7A7A
+
+# \u5B57\u5178\u6570\u636E\u76F8\u5173
+dict_data_notFound=\u5B57\u5178\u6570\u636E\u4E0D\u5B58\u5728
+dict_data_idNotNull=\u5B57\u5178\u6570\u636EID\u4E0D\u80FD\u4E3A\u7A7A
+dict_data_typeNotNull=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
+
+delete_primary_key_list_cannot_be_empty=\u5220\u9664Id\u5217\u8868\u4E0D\u80FD\u4E3A\u7A7A
+dictionary_labels_cannot_be_empty=\u5B57\u6BB5\u6807\u7B7E\u4E0D\u80FD\u4E3A\u7A7A
+dictionary_value_cannot_be_empty=\u5B57\u5178\u503C\u4E0D\u80FD\u4E3A\u7A7A
+dictionary_type_cannot_be_empty=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
+dictionary_name_cannot_be_empty=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
+
+
+

+ 16 - 0
user-web/src/main/resources/i18n/messages_en_US.properties

@@ -0,0 +1,16 @@
+
+# Dict Type
+dict_type_notFound=Dict Type Not Found
+dict_type_alreadyExists=Dict Type Already Exists
+dict_type_idNotNull=Dict Type ID Cannot Be Null
+
+# Dict Data
+dict_data_notFound=Dict Data Not Found
+dict_data_idNotNull=Dict Data ID Cannot Be Null
+dict_data_typeNotNull=Dict Type Cannot Be Null
+
+delete_primary_key_list_cannot_be_empty=delete primary key list cannot be empty
+dictionary_labels_cannot_be_empty=dictionary labels cannot_be empty
+dictionary_value_cannot_be_empty=dictionary value cannot be empty
+dictionary_type_cannot_be_empty=dictionary type cannot be empty
+dictionary_name_cannot_be_empty=dictionary name cannot be empty

+ 16 - 0
user-web/src/main/resources/i18n/messages_zh_TW.properties

@@ -0,0 +1,16 @@
+
+# \u5B57\u5178\u985E\u578B\u76F8\u95DC
+dict_type_notFound=\u5B57\u5178\u985E\u578B\u4E0D\u5B58\u5728
+dict_type_alreadyExists=\u5B57\u5178\u985E\u578B\u5DF2\u5B58\u5728
+dict_type_idNotNull=\u5B57\u5178\u985E\u578BID\u4E0D\u80FD\u70BA\u7A7A
+
+# \u5B57\u5178\u6578\u64DA\u76F8\u95DC
+dict_data_notFound=\u5B57\u5178\u6578\u64DA\u4E0D\u5B58\u5728
+dict_data_idNotNull=\u5B57\u5178\u6578\u64DAID\u4E0D\u80FD\u70BA\u7A7A
+dict_data_typeNotNull=\u5B57\u5178\u985E\u578B\u4E0D\u80FD\u70BA\u7A7A
+
+delete_primary_key_list_cannot_be_empty=\u5220\u9664Id\u6E05\u55AE\u4E0D\u80FD\u70BA\u7A7A
+dictionary_labels_cannot_be_empty=\u6B04\u4F4D\u6A19\u7C64\u4E0D\u80FD\u70BA\u7A7A
+dictionary_value_cannot_be_empty=\u5B57\u5178\u503C\u4E0D\u80FD\u70BA\u7A7A
+dictionary_type_cannot_be_empty=\u5B57\u5178\u985E\u578B\u4E0D\u80FD\u70BA\u7A7A
+dictionary_name_cannot_be_empty=\u5B57\u5178\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A

+ 143 - 0
user-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} - %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>