Parcourir la source

add:律师端微服务

ssk il y a 1 mois
Parent
commit
c88d8b016d
26 fichiers modifiés avec 1747 ajouts et 15 suppressions
  1. 33 0
      alien-lawyer/.gitignore
  2. 321 0
      alien-lawyer/pom.xml
  3. 21 0
      alien-lawyer/src/main/java/shop/alien/lawyer/AlienLawyerApplication.java
  4. 386 0
      alien-lawyer/src/main/java/shop/alien/lawyer/config/BaseRedisService.java
  5. 69 0
      alien-lawyer/src/main/java/shop/alien/lawyer/config/DruidConfig.java
  6. 60 0
      alien-lawyer/src/main/java/shop/alien/lawyer/config/MyBatisFieldHandler.java
  7. 13 0
      alien-lawyer/src/main/java/shop/alien/lawyer/config/MyBatisPlusPageConfig.java
  8. 57 0
      alien-lawyer/src/main/java/shop/alien/lawyer/config/SwaggerConfig.java
  9. 49 0
      alien-lawyer/src/main/java/shop/alien/lawyer/config/TokenInfoArgumentResolver.java
  10. 16 0
      alien-lawyer/src/main/java/shop/alien/lawyer/config/WebConfig.java
  11. 23 0
      alien-lawyer/src/main/java/shop/alien/lawyer/config/WebSocketConfig.java
  12. 241 0
      alien-lawyer/src/main/java/shop/alien/lawyer/config/WebSocketProcess.java
  13. 43 0
      alien-lawyer/src/main/java/shop/alien/lawyer/controller/TestController.java
  14. 12 0
      alien-lawyer/src/main/java/shop/alien/lawyer/feign/AlienSecondFeign.java
  15. 12 0
      alien-lawyer/src/main/java/shop/alien/lawyer/feign/AlienStoreFeign.java
  16. 11 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/TestService.java
  17. 17 0
      alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/TestServiceImpl.java
  18. 100 0
      alien-lawyer/src/main/java/shop/alien/lawyer/util/DateUtils.java
  19. 20 0
      alien-lawyer/src/main/resources/bootstrap-dev.yml
  20. 22 0
      alien-lawyer/src/main/resources/bootstrap-prod.yml
  21. 22 0
      alien-lawyer/src/main/resources/bootstrap-test.yml
  22. 22 0
      alien-lawyer/src/main/resources/bootstrap-uat.yml
  23. 3 0
      alien-lawyer/src/main/resources/bootstrap.yml
  24. 173 0
      alien-lawyer/src/main/resources/logback-spring.xml
  25. 0 15
      alien-store/pom.xml
  26. 1 0
      pom.xml

+ 33 - 0
alien-lawyer/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 321 - 0
alien-lawyer/pom.xml

@@ -0,0 +1,321 @@
+<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>shop.alien</groupId>
+        <artifactId>alien-cloud</artifactId>
+        <version>1.0.0</version>
+    </parent>
+
+    <artifactId>alien-lawyer</artifactId>
+    <version>1.0.0</version>
+    <name>alien-lawyer</name>
+    <description>律师端后台接口</description>
+
+    <properties>
+        <java.version>1.8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.core</groupId>
+                    <artifactId>jackson-databind</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- mybatis-plus代码生成器 Start -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>jsqlparser</artifactId>
+                    <groupId>com.github.jsqlparser</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>mybatis</artifactId>
+                    <groupId>org.mybatis</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>mybatis-spring</artifactId>
+                    <groupId>org.mybatis</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-annotation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-extension</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-core</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <!-- mybatisPlus Freemarker 模版引擎 -->
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+        </dependency>
+        <!-- mybatis-plus代码生成器 End -->
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+
+        <!--Swagger Start-->
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>swagger-annotations</artifactId>
+                    <groupId>io.swagger</groupId>
+                </exclusion>
+                <exclusion>
+                    <artifactId>swagger-models</artifactId>
+                    <groupId>io.swagger</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-models</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.xiaoymin</groupId>
+            <artifactId>swagger-bootstrap-ui</artifactId>
+        </dependency>
+        <!--Swagger End-->
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!--token-->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>dysmsapi20170525</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.netease.yidun</groupId>
+            <artifactId>yidun-java-sdk</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>shop.alien</groupId>
+            <artifactId>alien-entity</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>shop.alien</groupId>
+            <artifactId>alien-util</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>shop.alien</groupId>
+            <artifactId>alien-config</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+        </dependency>
+
+        <!--允许你使用流(Flux和Mono)来处理异步、非阻塞的数据-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>3.1.2</version> <!-- 使用最新版本 -->
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>prepare-package</phase> <!-- 在package之前执行 -->
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${project.build.directory}/lib</outputDirectory> <!-- 指定输出目录 -->
+                            <includeScope>runtime</includeScope> <!-- 仅包含运行时依赖 -->
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>shop.alien.store.AlienStoreApplication</mainClass>
+                    <layout>ZIP</layout>
+                    <includes>
+                        <include>
+                            <groupId>nothing</groupId>
+                            <artifactId>nothing</artifactId>
+                        </include>
+                    </includes>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 21 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/AlienLawyerApplication.java

@@ -0,0 +1,21 @@
+package shop.alien.lawyer;
+
+import com.github.xiaoymin.swaggerbootstrapui.annotations.EnableSwaggerBootstrapUI;
+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.ComponentScan;
+
+@ComponentScan({"shop.alien.lawyer.*", "shop.alien.util.*", "shop.alien.config.http", "shop.alien.config.properties"})
+@EnableSwaggerBootstrapUI
+@MapperScan({"shop.alien.mapper"})
+@SpringBootApplication
+@EnableFeignClients(basePackages = "shop.alien.lawyer.feign")
+public class AlienLawyerApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(AlienLawyerApplication.class, args);
+    }
+
+}

+ 386 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/config/BaseRedisService.java

@@ -0,0 +1,386 @@
+package shop.alien.lawyer.config;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.geo.*;
+import org.springframework.data.redis.connection.RedisGeoCommands;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2022/3/14 15:14
+ */
+@Component
+@RequiredArgsConstructor
+public class BaseRedisService {
+
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 添加List值, 向右
+     *
+     * @param key   键
+     * @param value 值
+     */
+    public void setListRight(String key, String value) {
+        stringRedisTemplate.opsForList().rightPush(key, value);
+    }
+
+    /**
+     * 取出并删除列表中的所有元素(原子操作)
+     */
+    public List<String> popBatchFromList(String key) {
+        String luaScript =
+                "local elements = redis.call('LRANGE', KEYS[1], 0, -1) " +
+                        "redis.call('DEL', KEYS[1]) " +
+                        "return elements ";
+
+        RedisScript<List> script = RedisScript.of(luaScript, List.class);
+        return stringRedisTemplate.execute(script, Collections.singletonList(key));
+    }
+
+    private static final String SAVE_OR_OVERWRITE_SCRIPT =
+            "if redis.call('EXISTS', KEYS[1]) == 1 then " +
+                    "   redis.call('DEL', KEYS[1]) " +
+                    "end " +
+                    "return redis.call('RPUSH', KEYS[1], unpack(ARGV))";
+
+    /**
+     * 如果key不存在则存入,存在则覆盖
+     * @param key
+     * @param values
+     */
+    public void setSaveOrOverwriteScriptList(String key, List<String> values) {
+
+        // 创建DefaultRedisScript对象
+        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
+        redisScript.setScriptText(SAVE_OR_OVERWRITE_SCRIPT);
+        redisScript.setResultType(Long.class);
+
+        // 执行脚本
+        stringRedisTemplate.execute(
+                redisScript,
+                Collections.singletonList(key),
+                values.toArray()
+        );
+    }
+
+    /**
+     * 判断是否存在指定key
+     * @param key
+     * @return
+     */
+    public boolean hasKey(String key) {
+        Boolean exists = stringRedisTemplate.hasKey(key);
+        return exists != null && exists;
+    }
+
+    /**
+     * 从List中删除所有匹配的字符串
+     * @param key Redis key
+     * @param value 要删除的值
+     * @return 删除的元素数量
+     */
+    public Long removeAllOccurrences(String key, String value) {
+        return stringRedisTemplate.opsForList().remove(key, 0, value);
+    }
+
+    /**
+     * 添加List, 所有
+     *
+     * @param key   键
+     * @param value 值
+     */
+    public void setList(String key, List<String> value) {
+        stringRedisTemplate.opsForList().rightPushAll(key, value);
+    }
+
+    /**
+     * 从List左边取值并删除
+     *
+     * @param key 键
+     * @return 删除的值
+     */
+    public String getListLeft(String key) {
+        return stringRedisTemplate.opsForList().leftPop(key);
+    }
+
+    /**
+     * 获取List所有
+     *
+     * @param key 键
+     * @return List<String>
+     */
+    public List<String> getList(String key) {
+        return stringRedisTemplate.opsForList().range(key, 0, -1);
+    }
+
+
+    /**
+     * 添加Set
+     *
+     * @param key   键
+     * @param value 值
+     */
+    public void setSetList(String key, String value) {
+        stringRedisTemplate.opsForSet().add(key, value);
+    }
+
+    /**
+     * 获取Set
+     *
+     * @param key 键
+     * @return Set<String>
+     */
+    public Set<String> getSetList(String key) {
+        return stringRedisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 添加String值, 不设置过期时间
+     *
+     * @param key   键
+     * @param value 值
+     */
+    public void setString(String key, String value) {
+        set(key, value, null);
+    }
+
+    /**
+     * 添加String值, 并设置过期时间
+     *
+     * @param key     键
+     * @param value   值
+     * @param timeOut 时长(秒)
+     */
+    public void setString(String key, String value, Long timeOut) {
+        set(key, value, timeOut);
+    }
+
+    /**
+     * 设置超时时间
+     *
+     * @param key     键
+     * @param timeOut 时长(秒)
+     */
+    public void setTimeOut(String key, Long timeOut) {
+        stringRedisTemplate.expire(key, timeOut, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 传入object对象
+     *
+     * @param key     键
+     * @param value   值
+     * @param timeOut 时长(秒)
+     */
+    private void set(String key, Object value, Long timeOut) {
+        if (value != null) {
+            if (value instanceof String) {
+                String setValue = (String) value;
+                stringRedisTemplate.opsForValue().set(key, setValue);
+            }
+            //设置有效期
+            if (timeOut != null) {
+                stringRedisTemplate.expire(key, timeOut, TimeUnit.SECONDS);
+            }
+        }
+    }
+
+    /**
+     * 使用key查找redis信息
+     *
+     * @param key 键
+     * @return
+     */
+    public String getString(String key) {
+        return stringRedisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 使用key删除redis信息
+     *
+     * @param key 键
+     */
+    public void delete(String key) {
+        stringRedisTemplate.delete(key);
+    }
+
+
+    /**
+     * 添加地理信息
+     *
+     * @param point
+     * @param content
+     * @param type
+     * @return
+     */
+    public Long inGeolocation(Point point, String content, String type) {
+        return stringRedisTemplate.opsForGeo().add(type, point, content);
+    }
+
+
+    /**
+     * 计算两个位置距离
+     *
+     * @param content
+     * @param contentII
+     * @param type
+     * @return
+     */
+    public Distance computeDistance(String content, String contentII, String type) {
+        return stringRedisTemplate.opsForGeo().distance(type, content, contentII, Metrics.KILOMETERS);
+
+    }
+
+    /**
+     * 获取成员经纬度
+     *
+     * @param contentArray
+     * @return
+     */
+    public List<Point> positions(String type, String... contentArray) {
+        return stringRedisTemplate.opsForGeo().position(type, contentArray);
+    }
+
+
+    /**
+     * 根据content查询附近商家
+     *
+     * @param content
+     * @param distance 搜索半径(单位:千米)
+     * @param count    获取几条
+     * @param type     name or 主键
+     * @return
+     */
+    public GeoResults<RedisGeoCommands.GeoLocation<String>> radius(String content, double distance, long count, String type) {
+
+        return stringRedisTemplate.opsForGeo()
+                .radius(type,
+                        content,
+                        new Distance(distance, Metrics.KILOMETERS),
+                        RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
+                                .includeDistance()   // 包含距离信息
+                                .includeCoordinates()// 包含坐标信息
+                                .sortAscending()     // 按距离升序
+                                .limit(count));   // 限制返回数量
+    }
+
+    /**
+     * 根据坐标查询附近商家
+     *
+     * @param point    经度 纬度
+     * @param distance 搜索半径(单位:千米)
+     * @param count    最大返回数量
+     * @return 附近商家地理信息结果集
+     */
+    public GeoResults<RedisGeoCommands.GeoLocation<String>> radius(Point point, double distance, long count, String type) {
+        return stringRedisTemplate.opsForGeo()
+                .radius(type,
+                        new Circle(point, new Distance(distance, Metrics.KILOMETERS)),
+                        RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
+                                .includeDistance()
+                                .includeCoordinates()
+                                .sortAscending()
+                                .limit(count));
+    }
+
+
+    /**
+     * 删除GEO类型中的成员
+     *
+     * @param type    GEO数据对应的键名(对应你代码中的type参数)
+     * @param members 要删除的成员名称
+     * @return 实际删除的成员数量
+     */
+    public Long removeGeoMember(String type, String... members) {
+        return stringRedisTemplate.opsForZSet().remove(type, (Object[]) members);
+    }
+
+    /**
+     * 获取分布式锁
+     * @param lockKey 锁的标识
+     * @return 锁的value值,用于释放锁时验证,null表示获取失败
+     */
+    public String lock(String lockKey) {
+        return lock(lockKey, 30000, 10000);
+    }
+
+    /**
+     * 获取分布式锁
+     * @param lockKey 锁的标识
+     * @param expireTime 锁的过期时间(毫秒)
+     * @param acquireTimeout 获取锁的超时时间(毫秒)
+     * @return 锁的value值,用于释放锁时验证,null表示获取失败
+     */
+    public String lock(String lockKey, long expireTime, long acquireTimeout) {
+        // 生成唯一标识,用于释放锁时验证
+        String identifier = UUID.randomUUID().toString();
+        // 完整的锁键
+        String lockKeyWithPrefix = "inventory:lock:" + lockKey;
+        // 超时时间戳
+        long endTime = System.currentTimeMillis() + acquireTimeout;
+
+        while (System.currentTimeMillis() < endTime) {
+            // 尝试获取锁:SET NX PX
+            Boolean success = stringRedisTemplate.opsForValue()
+                    .setIfAbsent(lockKeyWithPrefix, identifier, expireTime, TimeUnit.MILLISECONDS);
+
+            if (Boolean.TRUE.equals(success)) {
+                // 获取锁成功
+                return identifier;
+            }
+
+            try {
+                // 短暂休眠,避免频繁尝试
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                return null;
+            }
+        }
+
+        // 获取锁超时
+        return null;
+    }
+
+    /**
+     * 释放分布式锁
+     * @param lockKey 锁的标识
+     * @param identifier 锁的value值,用于验证
+     * @return 是否释放成功
+     */
+    public boolean unlock(String lockKey, String identifier) {
+        if (identifier == null) {
+            return false;
+        }
+
+        String lockKeyWithPrefix = "inventory:lock:" + lockKey;
+
+        // 使用Lua脚本确保释放锁的原子性
+        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
+                "return redis.call('del', KEYS[1]) " +
+                "else " +
+                "return 0 " +
+                "end";
+
+        RedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
+        Long result = stringRedisTemplate.execute(script,
+                Collections.singletonList(lockKeyWithPrefix),
+                identifier);
+
+        return result != null && result > 0;
+    }
+
+}

+ 69 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/config/DruidConfig.java

@@ -0,0 +1,69 @@
+package shop.alien.lawyer.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.support.http.StatViewServlet;
+import com.alibaba.druid.support.http.WebStatFilter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.boot.web.servlet.ServletRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.sql.DataSource;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Druid
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/12/4 9:13
+ */
+@Configuration
+public class DruidConfig {
+
+    @ConfigurationProperties(prefix = "spring.datasource.druid")
+    @Bean
+    public DataSource druid() {
+        return new DruidDataSource();
+    }
+
+    //配置Druid的监控
+    //1、配置一个管理后台的Servlet
+    @Bean
+    public ServletRegistrationBean statViewServlet() {
+        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
+        Map<String, String> initParams = new HashMap<>();
+        initParams.put("loginUsername", "admin");
+        initParams.put("loginPassword", "alien1234");
+        initParams.put("allow", "");//默认就是允许所有访问
+        //initParams.put("deny","127.0.0.1");
+        bean.setInitParameters(initParams);
+        return bean;
+    }
+
+    //2、配置一个web监控的filter
+    @Bean
+    public FilterRegistrationBean webStatFilter() {
+        FilterRegistrationBean bean = new FilterRegistrationBean();
+        bean.setFilter(new WebStatFilter());
+        Map<String, String> initParams = new HashMap<>();
+        initParams.put("exclusions", "*.js,*.css,/druid/*");
+        bean.setInitParameters(initParams);
+        bean.setUrlPatterns(Arrays.asList("/*"));
+        return bean;
+    }
+
+//    @Bean
+//    public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
+//        ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
+//        registrationBean.addInitParameter("allow", "");// IP白名单 (没有配置或者为空,则允许所有访问)
+//        registrationBean.addInitParameter("deny", "");// IP黑名单 (存在共同时,deny优先于allow)
+//        registrationBean.addInitParameter("loginUsername", "root");
+//        registrationBean.addInitParameter("loginPassword", "alien1234");
+//        registrationBean.addInitParameter("resetEnable", "false");
+//        return registrationBean;
+//    }
+}

+ 60 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/config/MyBatisFieldHandler.java

@@ -0,0 +1,60 @@
+package shop.alien.lawyer.config;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+import shop.alien.util.common.JwtUtil;
+
+import java.util.Date;
+import java.util.Objects;
+
+/**
+ * Mybatis日期填充
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/12/6 10:19
+ */
+@Component
+@Slf4j
+public class MyBatisFieldHandler implements MetaObjectHandler {
+
+    /**
+     * insert操作时填充方法
+     *
+     * @param metaObject 元对象
+     */
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        log.info("=================================insertFill=========================================");
+        System.out.println(metaObject.getOriginalObject());
+        //字段为实体类名, 不是表字段名
+        this.setFieldValByName("createdTime", new Date(), metaObject);
+        this.setFieldValByName("updatedTime", new Date(), metaObject);
+        if (JwtUtil.hasToken()) {
+            this.setFieldValByName("createdUserId", Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getInteger("userId"), metaObject);
+            this.setFieldValByName("updatedUserId", Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getInteger("userId"), metaObject);
+        } else {
+            this.setFieldValByName("createdUserId", 0, metaObject);
+            this.setFieldValByName("updatedUserId", 0, metaObject);
+        }
+    }
+
+    /**
+     * update操作时填充方法
+     *
+     * @param metaObject 元对象
+     */
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        log.info("=================================updateFill=========================================");
+        //字段为实体类名, 不是表字段名
+        this.setFieldValByName("updatedTime", new Date(), metaObject);
+        if (JwtUtil.hasToken()) {
+            this.setFieldValByName("updatedUserId", Objects.requireNonNull(JwtUtil.getCurrentUserInfo()).getInteger("userId"), metaObject);
+        } else {
+            this.setFieldValByName("updatedUserId", 0, metaObject);
+        }
+    }
+}

+ 13 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/config/MyBatisPlusPageConfig.java

@@ -0,0 +1,13 @@
+package shop.alien.lawyer.config;
+
+import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MyBatisPlusPageConfig {
+    @Bean
+    public PaginationInterceptor paginationInterceptor() {
+        return new PaginationInterceptor();
+    }
+}

+ 57 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/config/SwaggerConfig.java

@@ -0,0 +1,57 @@
+package shop.alien.lawyer.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+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;
+
+/**
+ * swagger配置类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/12/4 9:17
+ */
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+
+    /**
+     * controller扫描
+     *
+     * @return controller扫描
+     */
+    @Bean
+    public Docket docket() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(apiInfo())
+                .groupName("爱丽恩2.0Api服务")
+                .select()
+                .apis(RequestHandlerSelectors.any())
+                .paths(PathSelectors.any())
+                .build();
+    }
+
+    /**
+     * Api标题信息
+     *
+     * @return api标题Api标题信息
+     */
+    public ApiInfo apiInfo() {
+        Contact contact = new Contact("爱丽恩", "https://www.alien.shop", "");
+        return new ApiInfoBuilder()
+                .title("爱丽恩2.0Api服务")
+                .description("爱丽恩2.0Api服务")
+                .license("爱丽恩")
+                .contact(contact)
+                .termsOfServiceUrl("https://www.alien.shop")
+                .version("1.0.0")
+                .build();
+    }
+}

+ 49 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/config/TokenInfoArgumentResolver.java

@@ -0,0 +1,49 @@
+package shop.alien.lawyer.config;
+
+import com.alibaba.fastjson.JSONObject;
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+import shop.alien.entity.store.UserLoginInfo;
+import shop.alien.util.common.JwtUtil;
+import shop.alien.util.common.TokenInfo;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class TokenInfoArgumentResolver implements HandlerMethodArgumentResolver {
+
+    @Override
+    public boolean supportsParameter(MethodParameter parameter) {
+        return parameter.hasParameterAnnotation(TokenInfo.class);
+    }
+
+    @Override
+    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
+                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
+        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
+        String token = request.getHeader("Authorization");
+        if (token != null) {
+            try {
+                JSONObject tokenInfo = JwtUtil.getTokenInfo(token);
+//                DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
+                UserLoginInfo userLoginInfo = new UserLoginInfo();
+                String phone = tokenInfo.getString("phone");
+                String userName = tokenInfo.getString("userName");
+                String userId = tokenInfo.getString("userId");
+                String type = tokenInfo.getString("userType");
+                userLoginInfo.setUserPhone(phone);
+                userLoginInfo.setUserId(Integer.parseInt(userId));
+                userLoginInfo.setUserName(userName);
+                userLoginInfo.setToken(token);
+                userLoginInfo.setType(type);
+                // 这里返回用户信息,可根据需求调整续加
+                return userLoginInfo;
+            } catch (Exception e) {
+                return null;
+            }
+        }
+        return null;
+    }
+}

+ 16 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/config/WebConfig.java

@@ -0,0 +1,16 @@
+package shop.alien.lawyer.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.List;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
+        resolvers.add(new TokenInfoArgumentResolver());
+    }
+}

+ 23 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/config/WebSocketConfig.java

@@ -0,0 +1,23 @@
+package shop.alien.lawyer.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * WebSocketConfig
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2024/2/29 14:40
+ */
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig {
+
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter() {
+        return new ServerEndpointExporter();
+    }
+}

+ 241 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/config/WebSocketProcess.java

@@ -0,0 +1,241 @@
+package shop.alien.lawyer.config;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+import shop.alien.entity.store.LifeBlacklist;
+import shop.alien.entity.store.LifeMessage;
+import shop.alien.entity.store.vo.WebSocketVo;
+import shop.alien.mapper.LifeBlacklistMapper;
+import shop.alien.mapper.LifeMessageMapper;
+import shop.alien.util.common.safe.ImageReviewServiceEnum;
+import shop.alien.util.common.safe.TextModerationResultVO;
+import shop.alien.util.common.safe.TextModerationUtil;
+import shop.alien.util.common.safe.TextReviewServiceEnum;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2024/2/29 14:42
+ */
+@Slf4j
+@Component
+@ServerEndpoint(value = "/socket/{sendId}")
+public class WebSocketProcess implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+    private static LifeBlacklistMapper lifeBlacklistMapper;
+    private static LifeMessageMapper lifeMessageMapper;
+    private static BaseRedisService baseRedisService;
+    private static TextModerationUtil textModerationUtil;
+    /*
+     * 持有每个webSocket对象,以key-value存储到线程安全ConcurrentHashMap,
+     */
+    private static final ConcurrentHashMap<String, WebSocketProcess> concurrentHashMap = new ConcurrentHashMap<>(12);
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) {
+        WebSocketProcess.applicationContext = context;
+        WebSocketProcess.lifeBlacklistMapper = context.getBean(LifeBlacklistMapper.class);
+        WebSocketProcess.lifeMessageMapper = context.getBean(LifeMessageMapper.class);
+        WebSocketProcess.baseRedisService = context.getBean(BaseRedisService.class);
+        WebSocketProcess.textModerationUtil = context.getBean(TextModerationUtil.class);
+    }
+
+    /**
+     * 会话对象
+     **/
+    private Session session;
+
+    /**
+     * 接收到客户端消息时触发
+     */
+    @OnMessage
+    public void onMessage(@PathParam("sendId") String id, String message) throws Exception {
+        try {
+            // 解析消息
+            WebSocketVo webSocketVo = JSONObject.parseObject(message, WebSocketVo.class);
+
+            // 过滤心跳
+            if (null == webSocketVo || "heartbeat".equals(webSocketVo.getCategory())) return;
+
+            // 记录已读消息id
+            if ("receipt".equals(webSocketVo.getCategory())) {
+                baseRedisService.setListRight("readMessageIdKey", String.valueOf(webSocketVo.getMessageId()));
+                return;
+            }
+
+            // 检查消息合规性
+            if (!checkCompliance(webSocketVo)) {
+                webSocketVo.setType("7");
+                webSocketVo.setText("发送内容存在违规行为");
+                sendMessage(webSocketVo.getSenderId(), JSONObject.from(webSocketVo).toJSONString());
+                return;
+            }
+
+            log.info("webSocketVo----------------{}", JSONObject.from(webSocketVo).toJSONString());
+            log.info("concurrentHashMap----------{}", concurrentHashMap.keySet());
+
+            // 保存消息记录
+            LifeMessage lifeMessage = new LifeMessage();
+            lifeMessage.setSenderId(webSocketVo.getSenderId());
+            lifeMessage.setReceiverId(webSocketVo.getReceiverId());
+            lifeMessage.setContent(webSocketVo.getText());
+            lifeMessage.setType(webSocketVo.getType());
+            lifeMessage.setBusinessId(webSocketVo.getBusinessId());
+            // 查询自己是否在对方的黑名单中
+            if (baseRedisService.hasKey("blackList_" + webSocketVo.getSenderId())) {
+                List<String> blackList = baseRedisService.getList("blackList_" + webSocketVo.getSenderId());
+                if (blackList.contains(webSocketVo.getReceiverId())) {
+                    lifeMessage.setDeletePhoneId(webSocketVo.getReceiverId());
+                    lifeMessageMapper.insert(lifeMessage);
+                    // 发送消息
+                    webSocketVo.setMessageId(lifeMessage.getId());
+                    webSocketVo.setCategory("message");
+                    webSocketVo.setCreatedTime(lifeMessage.getCreatedTime());
+                    sendMessage(webSocketVo.getSenderId(), JSONObject.from(webSocketVo).toJSONString());
+                    return;
+                }
+            }
+
+            lifeMessageMapper.insert(lifeMessage);
+            // 发送消息
+            webSocketVo.setMessageId(lifeMessage.getId());
+            webSocketVo.setCategory("message");
+            webSocketVo.setCreatedTime(lifeMessage.getCreatedTime());
+            sendMessage(webSocketVo.getSenderId(), JSONObject.from(webSocketVo).toJSONString());
+            sendMessage(webSocketVo.getReceiverId(), JSONObject.from(webSocketVo).toJSONString());
+        } catch (Exception e) {
+            log.error("WebSocketProcess.onMessage()----Exception----Message={}", e.getMessage());
+        }
+    }
+
+    /**
+     * 发送消息到指定客户端
+     */
+    public void sendMessage(String id, String message) throws Exception {
+        try {
+            log.info("WebSocketProcess.sendMessage()--readySend----id={},message={}", id, message);
+            // 根据id,从map中获取存储的webSocket对象
+            WebSocketProcess webSocketProcess = concurrentHashMap.get(id);
+            if (!ObjectUtils.isEmpty(webSocketProcess)) {
+                // 当客户端是Open状态时,才能发送消息
+                if (webSocketProcess.session.isOpen()) {
+                    webSocketProcess.session.getBasicRemote().sendText(message);
+                    log.info("WebSocketProcess.sendMessage()---sendSuccess---id={},message={}", id, message);
+                } else {
+                    log.error("WebSocketProcess.sendMessage()---sendError----websocket session:{} is closed ", id);
+                }
+            } else {
+                log.error("WebSocketProcess.sendMessage()---sendError----websocket session:{} is not exit ", id);
+            }
+        } catch (Exception e) {
+            log.error("WebSocketProcess.sendMessage()---Exception----Message={}", e.getMessage());
+        }
+    }
+
+    /**
+     * 客户端创建连接时触发
+     * */
+    @OnOpen
+    public void onOpen(@PathParam("sendId") String id, Session session) {
+        try {
+            //每新建立一个连接,就把当前客户id为key,this为value存储到map中
+            this.session = session;
+            concurrentHashMap.put(id, this);
+            log.info("WebSocketProcess.onOpen() Open a websocket. id={}", id);
+
+            // 获取拉黑自己的用户信息
+            LambdaQueryWrapper<LifeBlacklist> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LifeBlacklist::getBlockedPhoneId, id);
+            List<String> blackList = lifeBlacklistMapper.selectList(wrapper).stream().map(LifeBlacklist::getBlockerPhoneId).collect(Collectors.toList());
+            if (CollectionUtil.isNotEmpty(blackList)) baseRedisService.setSaveOrOverwriteScriptList("blackList_" + id, blackList);
+        } catch (Exception e) {
+            log.error("WebSocketProcess.onOpen()----Exception----Message={}", e.getMessage());
+        }
+    }
+
+
+    /**
+     * 客户端连接关闭时触发
+     **/
+    @OnClose
+    public void onClose(Session session, @PathParam("sendId") String id) {
+        try {
+            //客户端连接关闭时,移除map中存储的键值对
+            concurrentHashMap.remove(id);
+            log.info("WebSocketProcess.onClose() close a websocket, concurrentHashMap remove sessionId= {}", id);
+            if (baseRedisService.hasKey("blackList_" + id)) baseRedisService.delete("blackList_" + id);
+        } catch (Exception e) {
+            log.error("WebSocketProcess.onClose()----Exception----Message={}", e.getMessage());
+        }
+    }
+
+    /**
+     * 连接发生异常时候触发
+     */
+    @OnError
+    public void onError(@PathParam("sendId") String id, Throwable error) {
+        try {
+            log.error("WebSocketProcess.onError() Error,id={}, Msg=", id, error);
+        } catch (Exception e) {
+            log.error("WebSocketProcess.onError()----Exception----Message={}", e.getMessage());
+        }
+    }
+
+    /**
+     * 验证消息合规性
+     */
+    private boolean checkCompliance(WebSocketVo websocketVo) throws Exception {
+        try {
+            List<String> servicesList = Lists.newArrayList();
+            TextModerationResultVO textModerationResultVO = null;
+            if ("1".equals(websocketVo.getType())) {
+                servicesList.add(TextReviewServiceEnum.AD_COMPLIANCE_DETECTION_PRO.getService());
+                servicesList.add(TextReviewServiceEnum.CHAT_DETECTION_PRO.getService());
+                textModerationResultVO = textModerationUtil.invokeFunction(websocketVo.getText(), servicesList);
+
+            } else if ("2".equals(websocketVo.getType())) {
+                servicesList.add(ImageReviewServiceEnum.TONALITY_IMPROVE.getService());
+                textModerationResultVO = textModerationUtil.invokeFunction(websocketVo.getText(), servicesList);
+            }
+            return !(null != textModerationResultVO && "high".equals(textModerationResultVO.getRiskLevel()));
+        } catch (Exception e) {
+            log.error("WebSocketProcess.checkCompliance()----Exception----Message={}", e.getMessage());
+            return true;
+        }
+    }
+
+    /**
+     * 发送消息到所有客户端
+     */
+    public void sendAllMessage(String msg) throws Exception {
+        Set<Map.Entry<String, WebSocketProcess>> entries = concurrentHashMap.entrySet();
+        for (Map.Entry<String, WebSocketProcess> entry : entries) {
+            String cid = entry.getKey();
+            WebSocketProcess webSocketProcess = entry.getValue();
+            boolean sessionOpen = webSocketProcess.session.isOpen();
+            if (sessionOpen) {
+                webSocketProcess.session.getBasicRemote().sendText(msg);
+            } else {
+                log.info("cid={} is closed,ignore send text", cid);
+            }
+        }
+    }
+
+}

+ 43 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/controller/TestController.java

@@ -0,0 +1,43 @@
+package shop.alien.lawyer.controller;
+
+import io.swagger.annotations.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+import shop.alien.entity.result.R;
+import shop.alien.lawyer.service.TestService;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2025/11/10 14:09
+ */
+@Slf4j
+@Api(tags = {"测试接口"})
+@ApiSort(1)
+@CrossOrigin
+@RestController
+@RequestMapping("/test")
+@RequiredArgsConstructor
+public class TestController {
+
+    private final TestService testService;
+
+    @ApiOperation("测试")
+    @ApiOperationSupport(order = 1)
+    @ApiImplicitParams({
+            @ApiImplicitParam(
+                    name = "name",
+                    value = "姓名",
+                    dataType = "String",
+                    paramType = "query",
+                    required = true,
+                    defaultValue = "张三")
+    })
+    @GetMapping("/test")
+    public R getIdInfo(@RequestParam("name") String name) {
+        log.info("AliController.getIdInfo?name={}", name);
+        return R.data(testService.test(name));
+    }
+
+}

+ 12 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/feign/AlienSecondFeign.java

@@ -0,0 +1,12 @@
+package shop.alien.lawyer.feign;
+
+import org.springframework.cloud.openfeign.FeignClient;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2025/11/10 14:08
+ */
+@FeignClient(name = "alienSecondFeign", url = "${feign.alienSecond.url}")
+public interface AlienSecondFeign {
+}

+ 12 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/feign/AlienStoreFeign.java

@@ -0,0 +1,12 @@
+package shop.alien.lawyer.feign;
+
+import org.springframework.cloud.openfeign.FeignClient;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2025/11/10 14:08
+ */
+@FeignClient(name = "alienStoreFeign", url = "${feign.alienStore.url}")
+public interface AlienStoreFeign {
+}

+ 11 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/TestService.java

@@ -0,0 +1,11 @@
+package shop.alien.lawyer.service;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2025/11/10 14:31
+ */
+public interface TestService {
+
+    String test( String name);
+}

+ 17 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/service/impl/TestServiceImpl.java

@@ -0,0 +1,17 @@
+package shop.alien.lawyer.service.impl;
+
+import org.springframework.stereotype.Service;
+import shop.alien.lawyer.service.TestService;
+
+/**
+ * @author ssk
+ * @version 1.0
+ * @date 2025/11/10 14:31
+ */
+@Service
+public class TestServiceImpl implements TestService {
+    @Override
+    public String test(String name) {
+        return name;
+    }
+}

+ 100 - 0
alien-lawyer/src/main/java/shop/alien/lawyer/util/DateUtils.java

@@ -0,0 +1,100 @@
+package shop.alien.lawyer.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 日期计算工具类
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2023/5/24 16:03
+ */
+public class DateUtils {
+
+    public static void main(String[] args) throws ParseException {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+//        Date date = calcHours(new Date(), 1);
+//        System.out.println(sdf.format(date));
+//
+//        boolean b = dateCompare(new Date(), date);
+//        System.out.println(b);
+
+
+        Date date1 = sdf.parse("2024-05-10 19:10:00");
+        Date date2 = sdf.parse("2024-05-10 19:11:00");
+        System.out.println(calcMinute(date1, 1));
+        System.out.println(dateCompare(date1, calcMinute(date1, 1)));
+
+    }
+
+    /**
+     * 天数加减
+     *
+     * @param date 日期
+     * @param days 加减天数
+     * @return 处理后的日期
+     */
+    public static Date calcDays(Date date, Integer days) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(Calendar.DAY_OF_MONTH, days);
+        return c.getTime();
+    }
+
+    /**
+     * 小时加减
+     *
+     * @param date  日期
+     * @param hours 加减时间
+     * @return 处理后的日期
+     */
+    public static Date calcHours(Date date, Integer hours) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(Calendar.HOUR_OF_DAY, hours);
+        return c.getTime();
+    }
+
+    /**
+     * 分钟加减
+     *
+     * @param date   日期
+     * @param minute 加减时间
+     * @return 处理后的日期
+     */
+    public static Date calcMinute(Date date, Integer minute) {
+        Calendar c = Calendar.getInstance();
+        c.setTime(date);
+        c.add(Calendar.MINUTE, minute);
+        return c.getTime();
+    }
+
+    /**
+     * 日期比较
+     *
+     * @param dateOne 日期1
+     * @param dateTwo 日期2
+     * @return 是否大于
+     */
+    public static boolean dateCompare(Date dateOne, Date dateTwo) {
+        return dateOne.compareTo(dateTwo) > 0;
+    }
+
+    /**
+     * 今日剩余秒数
+     *
+     * @return int
+     */
+    public static Integer todayLastSecond() {
+        int daySecond = 86400;
+        LocalDateTime now = LocalDateTime.now();
+        int hour = now.getHour() * 60 * 60;
+        int minute = now.getMinute() * 60;
+        int second = now.getSecond();
+        return daySecond - hour - minute - second;
+    }
+}

+ 20 - 0
alien-lawyer/src/main/resources/bootstrap-dev.yml

@@ -0,0 +1,20 @@
+spring:
+  application:
+    name: alien-lawyer
+
+  cloud:
+    nacos:
+      #注册中心
+      discovery:
+        server-addr: 192.168.2.252:8848
+        username: nacos
+        password: ngfriend198092
+
+      #配置中心
+      config:
+        enabled: true
+        server-addr: 192.168.2.252:8848
+        username: nacos
+        password: ngfriend198092
+        group: DEFAULT_GROUP
+        file-extension: yml

+ 22 - 0
alien-lawyer/src/main/resources/bootstrap-prod.yml

@@ -0,0 +1,22 @@
+spring:
+  application:
+    name: alien-lawyer
+
+  cloud:
+    nacos:
+      #注册中心
+      discovery:
+        server-addr: localhost:8848
+        username: nacos
+        password: ngfriend198092
+        namespace: 3cbb802a-b56e-47f7-b658-b5012ecafb1f
+
+      #配置中心
+      config:
+        enabled: true
+        server-addr: localhost:8848
+        username: nacos
+        password: ngfriend198092
+        group: DEFAULT_GROUP
+        file-extension: yml
+        namespace: 3cbb802a-b56e-47f7-b658-b5012ecafb1f

+ 22 - 0
alien-lawyer/src/main/resources/bootstrap-test.yml

@@ -0,0 +1,22 @@
+spring:
+  application:
+    name: alien-lawyer
+
+  cloud:
+    nacos:
+      #注册中心
+      discovery:
+        server-addr: 192.168.2.252:8848
+        username: nacos
+        password: ngfriend198092
+        namespace: 0e1e2d77-56e8-422c-8317-6f71d7285e59
+
+      #配置中心
+      config:
+        enabled: true
+        server-addr: 192.168.2.252:8848
+        username: nacos
+        password: ngfriend198092
+        group: DEFAULT_GROUP
+        file-extension: yml
+        namespace: 0e1e2d77-56e8-422c-8317-6f71d7285e59

+ 22 - 0
alien-lawyer/src/main/resources/bootstrap-uat.yml

@@ -0,0 +1,22 @@
+spring:
+  application:
+    name: alien-lawyer
+
+  cloud:
+    nacos:
+      #注册中心
+      discovery:
+        server-addr: localhost:8848
+        username: nacos
+        password: ngfriend198092
+        namespace: 79060c39-10ad-4098-9022-5e8a47796f8f
+
+      #配置中心
+      config:
+        enabled: true
+        server-addr: localhost:8848
+        username: nacos
+        password: ngfriend198092
+        group: DEFAULT_GROUP
+        file-extension: yml
+        namespace: 79060c39-10ad-4098-9022-5e8a47796f8f

+ 3 - 0
alien-lawyer/src/main/resources/bootstrap.yml

@@ -0,0 +1,3 @@
+spring:
+  profiles:
+    active: dev

+ 173 - 0
alien-lawyer/src/main/resources/logback-spring.xml

@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
+<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
+<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
+<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
+<!-- 该信息是由于设置了当配置文件变化时重新加载,所以每当达到扫描时间的时候就会检查配置文件是否错误。但是由于一般配置文件都放在了JAR包中,
+    而扫描的时候无法扫描JAR包内,因此会提示没有可以检查的文件,所以每隔一段时间就输出一次-->
+<configuration scan="false" scanPeriod="60 seconds" debug="true">
+<!--    <contextName>logback-spring</contextName>-->
+
+    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
+    <!-- 定义全局参数常量 -->
+    <property name="log.level" value="debug"/>
+    <property name="log.maxHistory" value="30"/><!-- 30表示30个 -->
+    <springProperty scope="context" name="logging.path" source="logging.path"/>
+    <!--输出文件前缀-->
+    <property name="FILENAME" value="alien"/>
+
+    <!--0. 日志格式和颜色渲染 -->
+    <!-- 彩色日志依赖的渲染类 -->
+    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+
+    <!-- 文件输出格式 -->
+    <property name="FILE_LOG_PATTERN" value="[%d{MM/dd HH:mm:ss.SSS}][%-10.10thread][%-5level][%-40.40c{1}:%5line]:[%15method] || %m%n"/>
+    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+
+    <!--1. 输出到控制台-->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>${log.level}</level>
+        </filter>
+        <encoder>
+            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
+            <!-- 设置字符集 -->
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!--2. 输出到文档-->
+    <!-- DEBUG 日志 -->
+    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 当前的日志文件存放路径 -->
+        <file>${logging.path}/DEBUG.log</file>
+        <!-- 日志滚动策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 历史日志文件的存放路径和名称 -->
+            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_DEBUG.log.gz</fileNamePattern>
+            <!-- 日志文件最大的保存历史 数量-->
+            <maxHistory>${log.maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+        <!--日志文件最大的大小-->
+        <!--        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
+        <!--            <MaxFileSize>10MB</MaxFileSize>-->
+        <!--        </triggeringPolicy>-->
+        <!-- 此日志文档只记录debug级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>DEBUG</level>
+            <onMatch>ACCEPT</onMatch>  <!-- 用过滤器,只接受DEBUG级别的日志信息,其余全部过滤掉 -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- INFO 日志 -->
+    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logging.path}/INFO.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_INFO.log.gz</fileNamePattern>
+            <maxHistory>${log.maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- WARN 日志 -->
+    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logging.path}/WARN.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_WARN.log.gz</fileNamePattern>
+            <maxHistory>${log.maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>WARN</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${logging.path}/ERROR.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${logging.path}/%d{yyyy-MM-dd}_${FILENAME}_ERROR.log.gz</fileNamePattern>
+            <maxHistory>${log.maxHistory}</maxHistory>
+        </rollingPolicy>
+        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+            <pattern>${FILE_LOG_PATTERN}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!--
+      <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
+      以及指定<appender>。<logger>仅有一个name属性,
+      一个可选的level和一个可选的addtivity属性。
+      name:用来指定受此logger约束的某一个包或者具体的某一个类。
+      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+         还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
+         如果未设置此属性,那么当前logger将会继承上级的级别。
+      addtivity:是否向上级logger传递打印信息。默认是true。
+      <logger name="org.springframework.web" level="info"/>
+      <logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
+    -->
+
+    <!--
+      使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
+      第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
+      第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
+      【logging.level.org.mybatis=debug logging.level.dao=debug】
+     -->
+    <!-- mybatis显示sql,修改此处扫描包名 -->
+
+
+    <!--
+      root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
+      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+      不能设置为INHERITED或者同义词NULL。默认是DEBUG
+      可以包含零个或多个元素,标识这个appender将会添加到这个logger。
+    -->
+
+    <!-- 4. 最终的策略 -->
+    <!-- 4.1 开发环境:打印控制台-->
+    <!--打印sql-->
+    <!--    <logger name="com.veryhappy.music.dao" level="debug"/>-->
+
+    <!--打印log-->
+    <root level="info">
+        <appender-ref ref="CONSOLE"/>
+        <appender-ref ref="DEBUG_FILE"/>
+        <appender-ref ref="INFO_FILE"/>
+        <appender-ref ref="WARN_FILE"/>
+        <appender-ref ref="ERROR_FILE"/>
+    </root>
+
+    <!--   4.2 生产环境:输出到文档-->
+    <springProfile name="pro">
+        <root level="info">
+            <appender-ref ref="CONSOLE"/>
+            <appender-ref ref="DEBUG_FILE"/>
+            <appender-ref ref="INFO_FILE"/>
+            <appender-ref ref="ERROR_FILE"/>
+            <appender-ref ref="WARN_FILE"/>
+        </root>
+    </springProfile>
+</configuration>

+ 0 - 15
alien-store/pom.xml

@@ -323,19 +323,4 @@
             </plugin>
         </plugins>
     </build>
-    <profiles>
-        <profile>
-            <id>test</id>
-            <properties>
-                <profiles.active>test</profiles.active>
-                <nacos.server-addr>192.168.2.252:8848</nacos.server-addr>
-                <nacos.namespace>0e1e2d77-56e8-422c-8317-6f71d7285e59</nacos.namespace>
-                <nacos.username>nacos</nacos.username>
-                <nacos.password>ngfriend198092</nacos.password>
-            </properties>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-        </profile>
-    </profiles>
 </project>

+ 1 - 0
pom.xml

@@ -18,6 +18,7 @@
         <module>alien-job</module>
         <module>alien-second</module>
         <module>alien-store-platform</module>
+        <module>alien-lawyer</module>
     </modules>
     <!--pom工程作用:1、管理版本;2、聚合工程-->
     <packaging>pom</packaging>