浏览代码

添加权限验证

Lin Qilong 2 天之前
父节点
当前提交
abc16edebf
共有 32 个文件被更改,包括 1518 次插入28 次删除
  1. 48 10
      pom.xml
  2. 2 0
      src/main/java/cn/com/goldenwater/Application.java
  3. 52 0
      src/main/java/cn/com/goldenwater/auth/JwtTokenProvider.java
  4. 47 0
      src/main/java/cn/com/goldenwater/config/FastJson2JsonRedisSerializer.java
  5. 42 0
      src/main/java/cn/com/goldenwater/config/RedisConfig.java
  6. 1 1
      src/main/java/cn/com/goldenwater/constant/Constants.java
  7. 22 0
      src/main/java/cn/com/goldenwater/controller/AuthController.java
  8. 244 0
      src/main/java/cn/com/goldenwater/core/redis/RedisCache.java
  9. 44 0
      src/main/java/cn/com/goldenwater/domain/PtApp.java
  10. 43 13
      src/main/java/cn/com/goldenwater/domain/PtServiceLog.java
  11. 24 0
      src/main/java/cn/com/goldenwater/exception/CheckException.java
  12. 104 0
      src/main/java/cn/com/goldenwater/filter/ApiAuthFilter.java
  13. 82 0
      src/main/java/cn/com/goldenwater/filter/AuthFilter.java
  14. 70 0
      src/main/java/cn/com/goldenwater/filter/RateLimitFilter.java
  15. 53 3
      src/main/java/cn/com/goldenwater/filter/ResponseLogGlobalFilter.java
  16. 12 0
      src/main/java/cn/com/goldenwater/mapper/PermissionMapper.java
  17. 11 0
      src/main/java/cn/com/goldenwater/mapper/PtAppMapper.java
  18. 9 0
      src/main/java/cn/com/goldenwater/service/AuditService.java
  19. 11 0
      src/main/java/cn/com/goldenwater/service/AuthService.java
  20. 8 0
      src/main/java/cn/com/goldenwater/service/PermissionService.java
  21. 17 0
      src/main/java/cn/com/goldenwater/service/PtAppService.java
  22. 16 0
      src/main/java/cn/com/goldenwater/service/RateLimiterService.java
  23. 50 0
      src/main/java/cn/com/goldenwater/service/impl/AuditServiceImpl.java
  24. 53 0
      src/main/java/cn/com/goldenwater/service/impl/AuthServiceImpl.java
  25. 44 0
      src/main/java/cn/com/goldenwater/service/impl/PermissionServiceImpl.java
  26. 34 0
      src/main/java/cn/com/goldenwater/service/impl/PtAppServiceImpl.java
  27. 44 0
      src/main/java/cn/com/goldenwater/service/impl/RateLimiterServiceImpl.java
  28. 41 0
      src/main/java/cn/com/goldenwater/utils/CheckUtils.java
  29. 225 0
      src/main/java/cn/com/goldenwater/utils/RedisOperator.java
  30. 18 0
      src/main/java/cn/com/goldenwater/utils/TokenUtils.java
  31. 30 1
      src/main/resources/application.yml
  32. 17 0
      src/main/resources/mapper/PermissionMapper.xml

+ 48 - 10
pom.xml

@@ -13,16 +13,28 @@
         <java.version>1.8</java.version>
         <java.version>1.8</java.version>
         <mybatis-plus.version>3.5.1</mybatis-plus.version>
         <mybatis-plus.version>3.5.1</mybatis-plus.version>
         <mybatis-spring-boot.version>2.2.0</mybatis-spring-boot.version>
         <mybatis-spring-boot.version>2.2.0</mybatis-spring-boot.version>
-        <spring-boot.version>2.7.14</spring-boot.version>
-        <spring-cloud.version>2021.0.8</spring-cloud.version>
+        <spring-boot.version>2.7.18</spring-boot.version>
+        <spring-cloud.version>2021.0.9</spring-cloud.version>
+        <spring-cloud-alibaba.version>2021.0.6.1</spring-cloud-alibaba.version>
+        <spring-framework.version>5.3.39</spring-framework.version>
         <commons-lang3.version>3.12.0</commons-lang3.version>
         <commons-lang3.version>3.12.0</commons-lang3.version>
         <fastjson.version>2.0.53</fastjson.version>
         <fastjson.version>2.0.53</fastjson.version>
+        <jwt.version>0.9.1</jwt.version>
     </properties>
     </properties>
 
 
     <!--  查看是否引入SpringCloud 版本依赖  -->
     <!--  查看是否引入SpringCloud 版本依赖  -->
     <dependencyManagement>
     <dependencyManagement>
         <dependencies>
         <dependencies>
-            <!--  SpringCloud 版本依赖  -->
+            <!-- 覆盖SpringFramework的依赖配置-->
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-framework-bom</artifactId>
+                <version>${spring-framework.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- SpringCloud 微服务 -->
             <dependency>
             <dependency>
                 <groupId>org.springframework.cloud</groupId>
                 <groupId>org.springframework.cloud</groupId>
                 <artifactId>spring-cloud-dependencies</artifactId>
                 <artifactId>spring-cloud-dependencies</artifactId>
@@ -30,6 +42,24 @@
                 <type>pom</type>
                 <type>pom</type>
                 <scope>import</scope>
                 <scope>import</scope>
             </dependency>
             </dependency>
+
+            <!-- SpringCloud Alibaba 微服务 -->
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>${spring-cloud-alibaba.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+
+            <!-- SpringBoot 依赖配置 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
         </dependencies>
         </dependencies>
     </dependencyManagement>
     </dependencyManagement>
 
 
@@ -39,10 +69,15 @@
         <dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
             <artifactId>spring-boot-starter-web</artifactId>
-            <version>${spring-boot.version}</version>
         </dependency>
         </dependency>
 
 
-        <!--spring cloud gateway-->
+        <!-- redis 缓存操作 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <!-- spring cloud gateway -->
         <dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-gateway</artifactId>
             <artifactId>spring-cloud-starter-gateway</artifactId>
@@ -51,7 +86,6 @@
         <dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-jdbc</artifactId>
             <artifactId>spring-boot-starter-jdbc</artifactId>
-            <version>${spring-boot.version}</version>
         </dependency>
         </dependency>
 
 
         <!-- Mysql驱动包 -->
         <!-- Mysql驱动包 -->
@@ -72,7 +106,6 @@
         <dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
             <artifactId>commons-lang3</artifactId>
-            <version>${commons-lang3.version}</version>
         </dependency>
         </dependency>
 
 
         <!-- 阿里JSON解析器 -->
         <!-- 阿里JSON解析器 -->
@@ -85,13 +118,11 @@
         <dependency>
         <dependency>
             <groupId>org.projectlombok</groupId>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <artifactId>lombok</artifactId>
-            <version>1.18.30</version>
         </dependency>
         </dependency>
 
 
         <dependency>
         <dependency>
             <groupId>junit</groupId>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <artifactId>junit</artifactId>
-            <version>3.8.1</version>
             <scope>test</scope>
             <scope>test</scope>
         </dependency>
         </dependency>
 
 
@@ -101,8 +132,15 @@
             <version>2.13.1</version>
             <version>2.13.1</version>
         </dependency>
         </dependency>
 
 
+        <!-- Token生成与解析-->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>${jwt.version}</version>
+        </dependency>
+
     </dependencies>
     </dependencies>
     <build>
     <build>
         <finalName>sh-model-gateway</finalName>
         <finalName>sh-model-gateway</finalName>
     </build>
     </build>
-</project>
+</project>

+ 2 - 0
src/main/java/cn/com/goldenwater/Application.java

@@ -1,6 +1,7 @@
 package cn.com.goldenwater;
 package cn.com.goldenwater;
 
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
 import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
 import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
 import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
@@ -11,6 +12,7 @@ import org.springframework.context.annotation.Bean;
  *
  *
  * @author ruoyi
  * @author ruoyi
  */
  */
+@AutoConfiguration
 @SpringBootApplication
 @SpringBootApplication
 public class Application {
 public class Application {
 
 

+ 52 - 0
src/main/java/cn/com/goldenwater/auth/JwtTokenProvider.java

@@ -0,0 +1,52 @@
+package cn.com.goldenwater.auth;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class JwtTokenProvider {
+
+    private static final String idName = "thirdPartyId";
+
+    @Value("${jwt.secret}")
+    private String secret;
+
+    @Value("${jwt.expiration}")
+    private long expiration;
+
+    public String createToken(String appId, Collection<String> authorities) {
+        Date now = new Date();
+        Date expiryDate = new Date(now.getTime() + expiration);
+
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(idName, appId);
+
+        return Jwts.builder()
+                .setSubject(appId)
+                .setIssuedAt(now)
+                .setExpiration(expiryDate)
+                .addClaims(claims)
+                .signWith(SignatureAlgorithm.HS512, secret)
+                .compact();
+    }
+
+    public Claims parseToken(String token) {
+        return Jwts.parser()
+                .setSigningKey(secret)
+                .parseClaimsJws(token)
+                .getBody();
+    }
+
+    public String getAppId(String token) {
+        return parseToken(token).get(idName).toString();
+    }
+
+}

+ 47 - 0
src/main/java/cn/com/goldenwater/config/FastJson2JsonRedisSerializer.java

@@ -0,0 +1,47 @@
+package cn.com.goldenwater.config;
+
+import cn.com.goldenwater.constant.Constants;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONReader;
+import com.alibaba.fastjson2.JSONWriter;
+import com.alibaba.fastjson2.filter.Filter;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.SerializationException;
+
+import java.nio.charset.Charset;
+
+/**
+ * Redis使用FastJson序列化
+ *
+ * @author ruoyi
+ */
+public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
+    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+    static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR);
+
+    private Class<T> clazz;
+
+    public FastJson2JsonRedisSerializer(Class<T> clazz) {
+        super();
+        this.clazz = clazz;
+    }
+
+    @Override
+    public byte[] serialize(T t) throws SerializationException {
+        if (t == null) {
+            return new byte[0];
+        }
+        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
+    }
+
+    @Override
+    public T deserialize(byte[] bytes) throws SerializationException {
+        if (bytes == null || bytes.length <= 0) {
+            return null;
+        }
+        String str = new String(bytes, DEFAULT_CHARSET);
+
+        return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER);
+    }
+}

+ 42 - 0
src/main/java/cn/com/goldenwater/config/RedisConfig.java

@@ -0,0 +1,42 @@
+package cn.com.goldenwater.config;
+
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+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.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+/**
+ * redis配置
+ *
+ * @author ruoyi
+ */
+@Configuration
+@EnableCaching
+public class RedisConfig extends CachingConfigurerSupport
+{
+    @Bean
+    @SuppressWarnings(value = { "unchecked", "rawtypes" })
+    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
+    {
+        RedisTemplate<Object, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(connectionFactory);
+
+        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
+
+        // 使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setValueSerializer(serializer);
+
+        // Hash的key也采用StringRedisSerializer的序列化方式
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+
+        template.afterPropertiesSet();
+        return template;
+    }
+}

+ 1 - 1
src/main/java/cn/com/goldenwater/constant/Constants.java

@@ -149,7 +149,7 @@ public class Constants {
     /**
     /**
      * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
      * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
      */
      */
-    public static final String[] JSON_WHITELIST_STR = {"org.springframework", "com.ruoyi"};
+    public static final String[] JSON_WHITELIST_STR = {"org.springframework", "cn.com.goldenwater"};
 
 
     /**
     /**
      * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
      * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)

+ 22 - 0
src/main/java/cn/com/goldenwater/controller/AuthController.java

@@ -0,0 +1,22 @@
+package cn.com.goldenwater.controller;
+
+import cn.com.goldenwater.auth.JwtTokenProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+    @Autowired
+    private JwtTokenProvider jwtTokenProvider;
+
+//    @GetMapping("/token")
+//    public void refreshRoutes() {
+//        eventPublisher.publishEvent(new RefreshRoutesEvent(this));
+//    }
+}

+ 244 - 0
src/main/java/cn/com/goldenwater/core/redis/RedisCache.java

@@ -0,0 +1,244 @@
+package cn.com.goldenwater.core.redis;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.BoundSetOperations;
+import org.springframework.data.redis.core.HashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * spring redis 工具类
+ *
+ * @author ruoyi
+ **/
+@SuppressWarnings(value = {"unchecked", "rawtypes"})
+@Component
+public class RedisCache {
+
+    @Autowired
+    public RedisTemplate redisTemplate;
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key   缓存的键值
+     * @param value 缓存的值
+     */
+    public <T> void setCacheObject(final String key, final T value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 缓存基本的对象,Integer、String、实体类等
+     *
+     * @param key      缓存的键值
+     * @param value    缓存的值
+     * @param timeout  时间
+     * @param timeUnit 时间颗粒度
+     */
+    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
+        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key     Redis键
+     * @param timeout 超时时间
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout) {
+        return expire(key, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 设置有效时间
+     *
+     * @param key     Redis键
+     * @param timeout 超时时间
+     * @param unit    时间单位
+     * @return true=设置成功;false=设置失败
+     */
+    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
+        return redisTemplate.expire(key, timeout, unit);
+    }
+
+    /**
+     * 获取有效时间
+     *
+     * @param key Redis键
+     * @return 有效时间
+     */
+    public long getExpire(final String key) {
+        return redisTemplate.getExpire(key);
+    }
+
+    /**
+     * 判断 key是否存在
+     *
+     * @param key 键
+     * @return true 存在 false不存在
+     */
+    public Boolean hasKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
+
+    /**
+     * 获得缓存的基本对象。
+     *
+     * @param key 缓存键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> T getCacheObject(final String key) {
+        ValueOperations<String, T> operation = redisTemplate.opsForValue();
+        return operation.get(key);
+    }
+
+    /**
+     * 删除单个对象
+     *
+     * @param key
+     */
+    public boolean deleteObject(final String key) {
+        return redisTemplate.delete(key);
+    }
+
+    /**
+     * 删除集合对象
+     *
+     * @param collection 多个对象
+     * @return
+     */
+    public boolean deleteObject(final Collection collection) {
+        return redisTemplate.delete(collection) > 0;
+    }
+
+    /**
+     * 缓存List数据
+     *
+     * @param key      缓存的键值
+     * @param dataList 待缓存的List数据
+     * @return 缓存的对象
+     */
+    public <T> long setCacheList(final String key, final List<T> dataList) {
+        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
+        return count == null ? 0 : count;
+    }
+
+    /**
+     * 获得缓存的list对象
+     *
+     * @param key 缓存的键值
+     * @return 缓存键值对应的数据
+     */
+    public <T> List<T> getCacheList(final String key) {
+        return redisTemplate.opsForList().range(key, 0, -1);
+    }
+
+    /**
+     * 缓存Set
+     *
+     * @param key     缓存键值
+     * @param dataSet 缓存的数据
+     * @return 缓存数据的对象
+     */
+    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
+        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
+        Iterator<T> it = dataSet.iterator();
+        while (it.hasNext()) {
+            setOperation.add(it.next());
+        }
+        return setOperation;
+    }
+
+    /**
+     * 获得缓存的set
+     *
+     * @param key
+     * @return
+     */
+    public <T> Set<T> getCacheSet(final String key) {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    /**
+     * 缓存Map
+     *
+     * @param key
+     * @param dataMap
+     */
+    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
+        if (dataMap != null) {
+            redisTemplate.opsForHash().putAll(key, dataMap);
+        }
+    }
+
+    /**
+     * 获得缓存的Map
+     *
+     * @param key
+     * @return
+     */
+    public <T> Map<String, T> getCacheMap(final String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    /**
+     * 往Hash中存入数据
+     *
+     * @param key   Redis键
+     * @param hKey  Hash键
+     * @param value 值
+     */
+    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
+        redisTemplate.opsForHash().put(key, hKey, value);
+    }
+
+    /**
+     * 获取Hash中的数据
+     *
+     * @param key  Redis键
+     * @param hKey Hash键
+     * @return Hash中的对象
+     */
+    public <T> T getCacheMapValue(final String key, final String hKey) {
+        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
+        return opsForHash.get(key, hKey);
+    }
+
+    /**
+     * 获取多个Hash中的数据
+     *
+     * @param key   Redis键
+     * @param hKeys Hash键集合
+     * @return Hash对象集合
+     */
+    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
+        return redisTemplate.opsForHash().multiGet(key, hKeys);
+    }
+
+    /**
+     * 删除Hash中的某条数据
+     *
+     * @param key  Redis键
+     * @param hKey Hash键
+     * @return 是否成功
+     */
+    public boolean deleteCacheMapValue(final String key, final String hKey) {
+        return redisTemplate.opsForHash().delete(key, hKey) > 0;
+    }
+
+    /**
+     * 获得缓存的基本对象列表
+     *
+     * @param pattern 字符串前缀
+     * @return 对象列表
+     */
+    public Collection<String> keys(final String pattern) {
+        return redisTemplate.keys(pattern);
+    }
+}

+ 44 - 0
src/main/java/cn/com/goldenwater/domain/PtApp.java

@@ -0,0 +1,44 @@
+package cn.com.goldenwater.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+public class PtApp implements Serializable {
+
+    /** appId */
+    @TableId
+    private String appId;
+    /** 应用名称 */
+    private String appName;
+    /** 应用secret */
+    private String appSecret;
+    /** 应用地址 */
+    private String appUrl;
+    /** 应用开发商 */
+    private String appDeveloper;
+    /** 应用备注 */
+    private String appNote;
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date rqTime;
+    /** 应用发布时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date appPublish;
+    /** 应用状态 */
+    private String appState;
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    private Date auditTime;
+    private String appCate;
+    /** token */
+    private String appToken;
+    /** 用户ID */
+    private String userId;
+    /** 应用类型 */
+    private String appType;
+    /** 应用领域 */
+    private String appField;
+}

+ 43 - 13
src/main/java/cn/com/goldenwater/domain/PtServiceLog.java

@@ -1,7 +1,5 @@
 package cn.com.goldenwater.domain;
 package cn.com.goldenwater.domain;
 
 
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.AllArgsConstructor;
 import lombok.AllArgsConstructor;
@@ -25,38 +23,70 @@ public class PtServiceLog implements Serializable {
 
 
     private Long logId;
     private Long logId;
 
 
-    /** 服务id */
+    /**
+     * 服务id
+     */
     private String serId;
     private String serId;
 
 
-    /** 模型id */
+    /**
+     * 模型id
+     */
     private String mdId;
     private String mdId;
 
 
-    /** 请求时间戳 */
+    /**
+     * 请求时间戳
+     */
     @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", timezone = "GMT+8")
     @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", timezone = "GMT+8")
     private Date tm;
     private Date tm;
 
 
-    /** 请求信息 */
+    /**
+     * 请求信息
+     */
+    private String url;
+
+    /**
+     * 请求信息
+     */
     private String senText;
     private String senText;
 
 
-    /** 返回信息 */
+    /**
+     * 返回信息
+      */
     private String returnText;
     private String returnText;
 
 
-    /** 请求耗时(毫秒) */
+    /**
+     * 请求耗时(毫秒)
+     */
     private Long execTm;
     private Long execTm;
 
 
-    /** 状态码 */
+    /**
+     * 状态码
+     */
     private Long statusCode;
     private Long statusCode;
 
 
-    /** 客户端ip */
+    /**
+     * 客户端ip
+     */
     private String clientIp;
     private String clientIp;
 
 
-    /** 用户id */
+    /**
+     * 用户id
+     */
     private String userId;
     private String userId;
 
 
-    /** 地理位置 */
+    /**
+     * 地理位置
+     */
     private String geoCity;
     private String geoCity;
 
 
-    /** 服务名称 */
+    /**
+     * 服务名称
+     */
     private String serviceName;
     private String serviceName;
 
 
+    /** 应用ID */
+    private String appId;
+
+    /** 应用名称 */
+    private String appName;
 }
 }

+ 24 - 0
src/main/java/cn/com/goldenwater/exception/CheckException.java

@@ -0,0 +1,24 @@
+package cn.com.goldenwater.exception;
+
+public class CheckException extends RuntimeException {
+    private static final long serialVersionUID = 1L;
+
+    public CheckException() {
+    }
+
+    public CheckException(String message) {
+        super(message);
+    }
+
+    public CheckException(Throwable cause) {
+        super(cause);
+    }
+
+    public CheckException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public CheckException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 104 - 0
src/main/java/cn/com/goldenwater/filter/ApiAuthFilter.java

@@ -0,0 +1,104 @@
+package cn.com.goldenwater.filter;
+
+import cn.com.goldenwater.auth.JwtTokenProvider;
+import cn.com.goldenwater.service.AuditService;
+import cn.com.goldenwater.service.PermissionService;
+import cn.com.goldenwater.service.PtAppService;
+import cn.com.goldenwater.utils.JsonUtils;
+import cn.com.goldenwater.utils.TokenUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class ApiAuthFilter implements GlobalFilter, Ordered {
+
+    @Autowired
+    private PermissionService permissionService;
+
+    @Autowired
+    private AuditService auditService;
+
+    @Autowired
+    private JwtTokenProvider jwtTokenProvider;
+
+    @Autowired
+    private PtAppService ptAppService;
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        String path = request.getPath().value();
+        String method = request.getMethodValue();
+
+        // 从exchange属性中获取已经在AuthFilter中解析的token
+        String token = (String) exchange.getAttribute("AUTH_TOKEN");
+        if (token == null) {
+            // 如果没有获取到token,则从请求头中获取
+            token = TokenUtils.getTokenFromRequest(request);
+        }
+        
+        // 获取第三方服务ID
+//        String thirdPartyId = jwtTokenProvider.getAppId(token);
+        String thirdPartyId = ptAppService.getAppIdByAppToken(token);
+
+        if (thirdPartyId == null) {
+            return forbidden(exchange, "Invalid third-party identity");
+        }
+
+        // 将 thirdPartyId 存储到exchange属性中
+        exchange.getAttributes().put("THIRD_PARTY_ID", thirdPartyId);
+
+        // 检查权限
+        return permissionService.checkPermission(thirdPartyId, path, method)
+                .flatMap(hasPermission -> {
+                    if (!hasPermission) {
+                        return forbidden(exchange, "Insufficient permissions");
+                    }
+
+                    // 记录写操作审计日志
+                    if (isWriteOperation(method)) {
+                        auditService.logWriteOperation(thirdPartyId, path, method, request);
+                    }
+
+                    return chain.filter(exchange);
+                });
+    }
+
+    private boolean isWriteOperation(String method) {
+        return "POST".equalsIgnoreCase(method) ||
+                "PUT".equalsIgnoreCase(method) ||
+                "DELETE".equalsIgnoreCase(method) ||
+                "PATCH".equalsIgnoreCase(method);
+    }
+
+    private Mono<Void> forbidden(ServerWebExchange exchange, String message) {
+        ServerHttpResponse response = exchange.getResponse();
+        response.setStatusCode(HttpStatus.FORBIDDEN);
+        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+
+        Map<String, Object> body = new HashMap<>();
+        body.put("code", 403);
+        body.put("message", message);
+        body.put("path", exchange.getRequest().getPath().value());
+
+        return response.writeWith(Mono.just(response.bufferFactory()
+                .wrap(JsonUtils.objectToJson(body).getBytes())));
+    }
+
+    @Override
+    public int getOrder() {
+        return -90;
+    }
+}

+ 82 - 0
src/main/java/cn/com/goldenwater/filter/AuthFilter.java

@@ -0,0 +1,82 @@
+package cn.com.goldenwater.filter;
+
+import cn.com.goldenwater.service.AuthService;
+import cn.com.goldenwater.utils.JsonUtils;
+import cn.com.goldenwater.utils.TokenUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class AuthFilter implements GlobalFilter, Ordered {
+
+    @Autowired
+    private AuthService authService;
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        String path = request.getPath().value();
+
+        // 跳过不需要认证的路径
+        if (authService.isPublicPath(path)) {
+            return chain.filter(exchange);
+        }
+
+        // 获取访问令牌
+        String token = TokenUtils.getTokenFromRequest(request);
+        if (token == null) {
+            return unauthorized(exchange, "Missing access token");
+        }
+        
+        // 将token存储到exchange属性中
+        exchange.getAttributes().put("AUTH_TOKEN", token);
+
+        // 验证令牌
+        return authService.validateToken(token)
+                .flatMap(valid -> {
+                    if (!(Boolean) valid) {
+                        return unauthorized(exchange, "Invalid access token");
+                    }
+                    return chain.filter(exchange);
+                })
+                .onErrorResume(e -> {
+                    log.error("Token validation error", e);
+                    return unauthorized(exchange, "Token validation error");
+                });
+    }
+
+
+    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
+        ServerHttpResponse response = exchange.getResponse();
+        response.setStatusCode(HttpStatus.UNAUTHORIZED);
+        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+
+        Map<String, Object> body = new HashMap<>();
+        body.put("code", 401);
+        body.put("message", message);
+        body.put("path", exchange.getRequest().getPath().value());
+
+        return response.writeWith(Mono.just(response.bufferFactory()
+                .wrap(JsonUtils.objectToJson(body).getBytes())));
+    }
+
+    @Override
+    public int getOrder() {
+        return -100;
+    }
+
+}

+ 70 - 0
src/main/java/cn/com/goldenwater/filter/RateLimitFilter.java

@@ -0,0 +1,70 @@
+package cn.com.goldenwater.filter;
+
+import cn.com.goldenwater.auth.JwtTokenProvider;
+import cn.com.goldenwater.service.PtAppService;
+import cn.com.goldenwater.service.RateLimiterService;
+import cn.com.goldenwater.utils.JsonUtils;
+import cn.com.goldenwater.utils.TokenUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class RateLimitFilter implements GlobalFilter, Ordered {
+
+    @Autowired
+    private JwtTokenProvider jwtTokenProvider;
+
+    @Autowired
+    private PtAppService ptAppService;
+
+    @Autowired
+    private RateLimiterService rateLimiterService;
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        String path = request.getPath().value();
+
+        // 获取第三方服务ID
+        String thirdPartyId = (String) exchange.getAttribute("THIRD_PARTY_ID");
+
+        return rateLimiterService.isAllowed(thirdPartyId, path)
+                .flatMap(allowed -> {
+                    if (!allowed) {
+                        return tooManyRequests(exchange);
+                    }
+                    return chain.filter(exchange);
+                });
+    }
+
+    private Mono<Void> tooManyRequests(ServerWebExchange exchange) {
+        ServerHttpResponse response = exchange.getResponse();
+        response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
+        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+
+        Map<String, Object> body = new HashMap<>();
+        body.put("code", 429);
+        body.put("message", "Too many requests");
+        body.put("path", exchange.getRequest().getPath().value());
+
+        return response.writeWith(Mono.just(response.bufferFactory()
+                .wrap(JsonUtils.objectToJson(body).getBytes())));
+    }
+
+    @Override
+    public int getOrder() {
+        return -80;
+    }
+}

+ 53 - 3
src/main/java/cn/com/goldenwater/filter/ResponseLogGlobalFilter.java

@@ -1,11 +1,14 @@
 package cn.com.goldenwater.filter;
 package cn.com.goldenwater.filter;
 
 
+import cn.com.goldenwater.domain.PtApp;
 import cn.com.goldenwater.domain.PtService;
 import cn.com.goldenwater.domain.PtService;
 import cn.com.goldenwater.domain.PtServiceLog;
 import cn.com.goldenwater.domain.PtServiceLog;
+import cn.com.goldenwater.service.PtAppService;
 import cn.com.goldenwater.service.PtServiceLogService;
 import cn.com.goldenwater.service.PtServiceLogService;
 import cn.com.goldenwater.service.PtServiceService;
 import cn.com.goldenwater.service.PtServiceService;
 import cn.com.goldenwater.utils.GeoipUtils;
 import cn.com.goldenwater.utils.GeoipUtils;
 import cn.com.goldenwater.utils.JsonUtils;
 import cn.com.goldenwater.utils.JsonUtils;
+import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
 import com.alibaba.fastjson2.JSONObject;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.reactivestreams.Publisher;
 import org.reactivestreams.Publisher;
@@ -22,6 +25,7 @@ import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.MediaType;
 import org.springframework.http.server.reactive.ServerHttpResponse;
 import org.springframework.http.server.reactive.ServerHttpResponse;
 import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
 import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
+import org.springframework.util.MultiValueMap;
 import org.springframework.web.server.ServerWebExchange;
 import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import reactor.core.publisher.Mono;
@@ -41,6 +45,9 @@ public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {
     @Autowired
     @Autowired
     private PtServiceService ptServiceService;
     private PtServiceService ptServiceService;
 
 
+    @Autowired
+    private PtAppService ptAppService;
+
     @Override
     @Override
     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
         try {
         try {
@@ -94,7 +101,8 @@ public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {
                             return bufferFactory.wrap(result.getBytes());
                             return bufferFactory.wrap(result.getBytes());
                         }));
                         }));
                     } else {
                     } else {
-                        log.error("<-- {} 响应code异常", getStatusCode());
+                        // 对于非Flux响应体,仍然记录日志但不处理内容
+                        saveServiceLog(exchange, "", System.currentTimeMillis() - startTime, originalResponse.getStatusCode().value());
                     }
                     }
                     return super.writeWith(body);
                     return super.writeWith(body);
                 }
                 }
@@ -126,7 +134,40 @@ public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {
             PtServiceLog serviceLog = new PtServiceLog();
             PtServiceLog serviceLog = new PtServiceLog();
             // 设置日志字段
             // 设置日志字段
             serviceLog.setTm(new Date());
             serviceLog.setTm(new Date());
-            serviceLog.setSenText(exchange.getRequest().getURI().toString());
+
+            // 构建包含查询参数和请求体的JSON对象
+            JSONObject requestParams = new JSONObject();
+
+            // 添加查询参数
+            MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
+            if (queryParams != null && !queryParams.isEmpty()) {
+                for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
+                    List<String> values = entry.getValue();
+                    if (values != null && !values.isEmpty()) {
+                        if (values.size() == 1) {
+                            requestParams.put(entry.getKey(), values.get(0));
+                        } else {
+                            requestParams.put(entry.getKey(), values);
+                        }
+                    }
+                }
+            }
+
+            // 添加请求体参数(如果存在)
+            String requestBody = exchange.getAttribute("POST_BODY");
+            if (requestBody != null && !requestBody.isEmpty()) {
+                try {
+                    JSONObject bodyJson = JSON.parseObject(requestBody);
+                    requestParams.putAll(bodyJson);
+                } catch (Exception e) {
+                    // 如果请求体不是JSON格式,将其作为普通字符串添加
+                    requestParams.put("body", requestBody);
+                }
+            }
+
+            // 将请求参数转换为JSON字符串存储
+            serviceLog.setSenText(requestParams.toJSONString());
+            serviceLog.setUrl(exchange.getRequest().getURI().getPath());
             serviceLog.setReturnText(responseContent);
             serviceLog.setReturnText(responseContent);
             serviceLog.setExecTm(execTime);
             serviceLog.setExecTm(execTime);
             serviceLog.setStatusCode((long) statusCode);
             serviceLog.setStatusCode((long) statusCode);
@@ -155,10 +196,19 @@ public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {
                 serviceLog.setServiceName("未找到服务信息");
                 serviceLog.setServiceName("未找到服务信息");
             }
             }
 
 
+            // 获取第三方服务ID
+            String thirdPartyId = (String) exchange.getAttribute("THIRD_PARTY_ID");
+            PtApp ptApp = ptAppService.getById(thirdPartyId);
+            if (ptApp != null) {
+                serviceLog.setUserId(ptApp.getUserId());
+                serviceLog.setAppId(ptApp.getAppId());
+                serviceLog.setAppName(ptApp.getAppName());
+            }
+
             // 异步保存日志
             // 异步保存日志
             ptServiceLogService.save(serviceLog);
             ptServiceLogService.save(serviceLog);
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("保存服务日志异常: ", e);
             log.error("保存服务日志异常: ", e);
         }
         }
     }
     }
-}
+}

+ 12 - 0
src/main/java/cn/com/goldenwater/mapper/PermissionMapper.java

@@ -0,0 +1,12 @@
+package cn.com.goldenwater.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import reactor.core.publisher.Mono;
+
+public interface PermissionMapper {
+
+    Mono<Boolean> findPermission(@Param("thirdPartyId") String thirdPartyId,
+                                 @Param("path") String path,
+                                 @Param("method") String method);
+
+}

+ 11 - 0
src/main/java/cn/com/goldenwater/mapper/PtAppMapper.java

@@ -0,0 +1,11 @@
+package cn.com.goldenwater.mapper;
+
+
+import cn.com.goldenwater.domain.PtApp;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface PtAppMapper extends BaseMapper<PtApp> {
+
+}

+ 9 - 0
src/main/java/cn/com/goldenwater/service/AuditService.java

@@ -0,0 +1,9 @@
+package cn.com.goldenwater.service;
+
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+public interface AuditService {
+
+    void logWriteOperation(String thirdPartyId, String path, String method, ServerHttpRequest request);
+
+}

+ 11 - 0
src/main/java/cn/com/goldenwater/service/AuthService.java

@@ -0,0 +1,11 @@
+package cn.com.goldenwater.service;
+
+import reactor.core.publisher.Mono;
+
+public interface AuthService {
+
+    boolean isPublicPath(String path);
+
+    Mono<Object> validateToken(String token);
+
+}

+ 8 - 0
src/main/java/cn/com/goldenwater/service/PermissionService.java

@@ -0,0 +1,8 @@
+package cn.com.goldenwater.service;
+
+import reactor.core.publisher.Mono;
+
+public interface PermissionService {
+
+    Mono<Boolean> checkPermission(String thirdPartyId, String path, String method);
+}

+ 17 - 0
src/main/java/cn/com/goldenwater/service/PtAppService.java

@@ -0,0 +1,17 @@
+package cn.com.goldenwater.service;
+
+
+import cn.com.goldenwater.domain.PtApp;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * @author LinQiLong
+ * @date 2022/1/7 10:20
+ */
+public interface PtAppService extends IService<PtApp> {
+
+    String getAppIdByAppToken(String appToken);
+
+
+
+}

+ 16 - 0
src/main/java/cn/com/goldenwater/service/RateLimiterService.java

@@ -0,0 +1,16 @@
+package cn.com.goldenwater.service;
+
+import reactor.core.publisher.Mono;
+
+public interface RateLimiterService {
+
+    /**
+     * 检查第三方服务在特定路径上是否被允许访问(限流检查)
+     *
+     * @param thirdPartyId 第三方服务ID
+     * @param path 访问路径
+     * @return 是否允许访问
+     */
+    Mono<Boolean> isAllowed(String thirdPartyId, String path);
+
+}

+ 50 - 0
src/main/java/cn/com/goldenwater/service/impl/AuditServiceImpl.java

@@ -0,0 +1,50 @@
+package cn.com.goldenwater.service.impl;
+
+import cn.com.goldenwater.service.AuditService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.CompletableFuture;
+
+@Service
+public class AuditServiceImpl implements AuditService {
+
+//    @Autowired
+//    private AuditLogRepository auditLogRepository;
+
+//    @Autowired
+//    private KafkaTemplate<String, AuditMessage> kafkaTemplate;
+
+    @Override
+    public void logWriteOperation(String thirdPartyId, String path, String method, ServerHttpRequest request) {
+        // 异步记录审计日志
+        CompletableFuture.runAsync(() -> {
+//            AuditLog log = new AuditLog();
+//            log.setThirdPartyId(thirdPartyId);
+//            log.setRequestUrl(path);
+//            log.setHttpMethod(method);
+//            log.setRequestParams(extractParams(request));
+//            log.setAction("WRITE");
+//            log.setCreatedAt(new Date());
+//
+//            // 保存到数据库
+//            auditLogRepository.save(log);
+//
+//            // 发送到Kafka
+//            AuditMessage message = new AuditMessage(log);
+//            kafkaTemplate.send("audit-log-topic", message);
+        });
+    }
+
+    private String extractParams(ServerHttpRequest request) {
+        // 提取请求参数
+        if (request.getMethod() == HttpMethod.GET) {
+            return request.getQueryParams().toString();
+        } else {
+            // 对于POST/PUT等,记录部分关键信息
+            return "Body content length: " + request.getHeaders().getContentLength();
+        }
+    }
+}

+ 53 - 0
src/main/java/cn/com/goldenwater/service/impl/AuthServiceImpl.java

@@ -0,0 +1,53 @@
+package cn.com.goldenwater.service.impl;
+
+import cn.com.goldenwater.auth.JwtTokenProvider;
+import cn.com.goldenwater.domain.PtApp;
+import cn.com.goldenwater.service.AuthService;
+import cn.com.goldenwater.service.PtAppService;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import io.jsonwebtoken.JwtException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+@Service
+public class AuthServiceImpl implements AuthService {
+
+    @Autowired
+    private JwtTokenProvider jwtTokenProvider;
+
+    @Autowired
+    private PtAppService ptAppService;
+
+
+    @Override
+    public boolean isPublicPath(String path) {
+        return false;
+    }
+
+    @Override
+    public Mono<Object> validateToken(String token) {
+        try {
+            // 使用 JwtTokenProvider 解析token
+//            Claims claims = jwtTokenProvider.parseToken(token);
+
+            // 检查token是否过期
+//            if (claims.getExpiration().before(new Date())) {
+//                return Mono.just(false);
+//            }
+
+            QueryWrapper<PtApp> queryWrapper = new QueryWrapper<>();
+            queryWrapper.eq("app_secret", token);
+            PtApp ptApp = ptAppService.getOne(queryWrapper);
+            if (ptApp == null) {
+                return Mono.just(false);
+            }
+
+            // token有效
+            return Mono.just(true);
+        } catch (JwtException | IllegalArgumentException e) {
+            // token无效或解析出错
+            return Mono.just(false);
+        }
+    }
+}

+ 44 - 0
src/main/java/cn/com/goldenwater/service/impl/PermissionServiceImpl.java

@@ -0,0 +1,44 @@
+package cn.com.goldenwater.service.impl;
+
+import cn.com.goldenwater.core.redis.RedisCache;
+import cn.com.goldenwater.mapper.PermissionMapper;
+import cn.com.goldenwater.service.PermissionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+@Service
+public class PermissionServiceImpl implements PermissionService {
+
+    @Autowired
+    private PermissionMapper permissionMapper;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Override
+    public Mono<Boolean> checkPermission(String thirdPartyId, String path, String method) {
+        // 从缓存中获取权限
+        String cacheKey = "permission:" + thirdPartyId + ":" + path + ":" + method;
+        Boolean cachedResult = redisCache.getCacheObject(cacheKey);
+        if (cachedResult != null) {
+            return Mono.just(cachedResult);
+        }
+
+        // 查询数据库
+//        return permissionMapper.findPermission(thirdPartyId, path, method)
+//                .map(() -> {
+//                    // 缓存结果,有效期5分钟
+//                    redisTemplate.opsForValue().set(cacheKey, true, 5, TimeUnit.MINUTES);
+//                    return true;
+//                })
+//                .switchIfEmpty(Mono.defer(() -> {
+//                    // 缓存无权限结果,有效期1分钟
+//                    redisTemplate.opsForValue().set(cacheKey, false, 1, TimeUnit.MINUTES);
+//                    return Mono.just(false);
+//                }));
+
+        return Mono.just(true);
+    }
+}

+ 34 - 0
src/main/java/cn/com/goldenwater/service/impl/PtAppServiceImpl.java

@@ -0,0 +1,34 @@
+package cn.com.goldenwater.service.impl;
+
+import cn.com.goldenwater.domain.PtApp;
+import cn.com.goldenwater.mapper.PtAppMapper;
+import cn.com.goldenwater.service.PtAppService;
+import cn.com.goldenwater.utils.CheckUtils;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author LinQiLong
+ * @date 2022/1/7 10:20
+ */
+@Service
+public class PtAppServiceImpl extends ServiceImpl<PtAppMapper, PtApp> implements PtAppService {
+
+    @Override
+    public boolean updateById(PtApp entity) {
+        CheckUtils.notEmpty(entity.getAppId(), "appId.no");
+        return super.updateById(entity);
+    }
+
+    @Override
+    public String getAppIdByAppToken(String appToken) {
+        QueryWrapper<PtApp> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("app_secret", appToken);
+        PtApp ptApp = getOne(queryWrapper);
+        if (ptApp != null) {
+            return ptApp.getAppId();
+        }
+        return null;
+    }
+}

+ 44 - 0
src/main/java/cn/com/goldenwater/service/impl/RateLimiterServiceImpl.java

@@ -0,0 +1,44 @@
+package cn.com.goldenwater.service.impl;
+
+import cn.com.goldenwater.service.RateLimiterService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.ReactiveRedisTemplate;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+import java.time.Instant;
+
+@Service
+public class RateLimiterServiceImpl implements RateLimiterService {
+
+    @Autowired
+    private ReactiveRedisTemplate<String, String> reactiveRedisTemplate;
+
+    // 默认限制:每分钟最多1000次请求
+    private static final int DEFAULT_LIMIT = 1000;
+    private static final int DEFAULT_WINDOW_SECONDS = 60;
+
+    @Override
+    public Mono<Boolean> isAllowed(String thirdPartyId, String path) {
+        String key = "rate_limit:" + thirdPartyId + ":" + path;
+        long currentTime = Instant.now().getEpochSecond();
+        String currentTimeStr = String.valueOf(currentTime);
+
+        return reactiveRedisTemplate.opsForValue()
+                .setIfAbsent(key + ":window", currentTimeStr, Duration.ofSeconds(DEFAULT_WINDOW_SECONDS))
+                .flatMap(isFirstRequest -> {
+                    if (isFirstRequest) {
+                        // 第一次请求,设置计数器为1
+                        return reactiveRedisTemplate.opsForValue()
+                                .set(key + ":count", "1", Duration.ofSeconds(DEFAULT_WINDOW_SECONDS))
+                                .thenReturn(true);
+                    } else {
+                        // 非第一次请求,增加计数器
+                        return reactiveRedisTemplate.opsForValue().increment(key + ":count")
+                                .map(count -> count <= DEFAULT_LIMIT);
+                    }
+                })
+                .onErrorReturn(true); // 出错时默认允许访问,避免Redis故障导致服务不可用
+    }
+}

+ 41 - 0
src/main/java/cn/com/goldenwater/utils/CheckUtils.java

@@ -0,0 +1,41 @@
+package cn.com.goldenwater.utils;
+
+import cn.com.goldenwater.exception.CheckException;
+import org.springframework.context.MessageSource;
+
+import java.util.Arrays;
+
+
+/**
+ * @author HAIHUA2
+ */
+public class CheckUtils {
+    private static MessageSource resources;
+
+    public static void setResources(MessageSource resources) {
+        CheckUtils.resources = resources;
+    }
+
+    public static void check(boolean condition, String msgKey, Object... args) {
+        if (!condition) {
+            fail(msgKey, args);
+        }
+    }
+
+    public static void notEmpty(String str, String msgKey, Object... args) {
+        if (str == null || str.isEmpty()) {
+            fail(msgKey, args);
+        }
+    }
+
+    public static void notNull(Object obj, String msgKey, Object... args) {
+        if (obj == null) {
+            fail(msgKey, args);
+        }
+    }
+
+    private static void fail(String msgKey, Object... args) {
+        throw new CheckException(msgKey + ": " + Arrays.toString(args));
+        //    throw new CheckException(resources.getMessage(msgKey, args, Locale.CHINA));
+    }
+}

+ 225 - 0
src/main/java/cn/com/goldenwater/utils/RedisOperator.java

@@ -0,0 +1,225 @@
+package cn.com.goldenwater.utils;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.StringRedisConnection;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author 慕课网
+ * @Title: Redis 工具类
+ */
+@Component
+public class RedisOperator {
+
+//	@Autowired
+//    private RedisTemplate<String, Object> redisTemplate;
+
+    @Autowired
+    private StringRedisTemplate redisTemplate;
+
+    // Key(键),简单的key-value操作
+
+    /**
+     * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
+     *
+     * @param key
+     * @return
+     */
+    public long ttl(String key) {
+        return redisTemplate.getExpire(key);
+    }
+
+    /**
+     * 实现命令:expire 设置过期时间,单位秒
+     *
+     * @param key
+     * @return
+     */
+    public void expire(String key, long timeout) {
+        redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 实现命令:INCR key,增加key一次
+     *
+     * @param key
+     * @return
+     */
+    public long incr(String key, long delta) {
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    /**
+     * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
+     */
+    public Set<String> keys(String pattern) {
+        return redisTemplate.keys(pattern);
+    }
+
+    /**
+     * 实现命令:DEL key,删除一个key
+     *
+     * @param key
+     */
+    public void del(String key) {
+        redisTemplate.delete(key);
+    }
+
+    // String(字符串)
+
+    /**
+     * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
+     *
+     * @param key
+     * @param value
+     */
+    public void set(String key, String value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /**
+     * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
+     *
+     * @param key
+     * @param value
+     * @param timeout (以秒为单位)
+     */
+    public void set(String key, String value, long timeout) {
+        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
+    }
+
+    /**
+     * 实现命令:GET key,返回 key所关联的字符串值。
+     *
+     * @param key
+     * @return value
+     */
+    public String get(String key) {
+        return (String) redisTemplate.opsForValue().get(key);
+    }
+
+    /**
+     * 批量查询,对应mget
+     *
+     * @param keys
+     * @return
+     */
+    public List<String> mget(List<String> keys) {
+        return redisTemplate.opsForValue().multiGet(keys);
+    }
+
+    /**
+     * 批量查询,管道pipeline
+     *
+     * @param keys
+     * @return
+     */
+    public List<Object> batchGet(List<String> keys) {
+
+//		nginx -> keepalive
+//		redis -> pipeline
+
+        List<Object> result = redisTemplate.executePipelined(new RedisCallback<String>() {
+            @Override
+            public String doInRedis(RedisConnection connection) throws DataAccessException {
+                StringRedisConnection src = (StringRedisConnection) connection;
+
+                for (String k : keys) {
+                    src.get(k);
+                }
+                return null;
+            }
+        });
+
+        return result;
+    }
+
+
+    // Hash(哈希表)
+
+    /**
+     * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
+     *
+     * @param key
+     * @param field
+     * @param value
+     */
+    public void hset(String key, String field, Object value) {
+        redisTemplate.opsForHash().put(key, field, value);
+    }
+
+    /**
+     * 实现命令:HGET key field,返回哈希表 key中给定域 field的值
+     *
+     * @param key
+     * @param field
+     * @return
+     */
+    public String hget(String key, String field) {
+        return (String) redisTemplate.opsForHash().get(key, field);
+    }
+
+    /**
+     * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
+     *
+     * @param key
+     * @param fields
+     */
+    public void hdel(String key, Object... fields) {
+        redisTemplate.opsForHash().delete(key, fields);
+    }
+
+    /**
+     * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
+     *
+     * @param key
+     * @return
+     */
+    public Map<Object, Object> hgetall(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    // List(列表)
+
+    /**
+     * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
+     *
+     * @param key
+     * @param value
+     * @return 执行 LPUSH命令后,列表的长度。
+     */
+    public long lpush(String key, String value) {
+        return redisTemplate.opsForList().leftPush(key, value);
+    }
+
+    /**
+     * 实现命令:LPOP key,移除并返回列表 key的头元素。
+     *
+     * @param key
+     * @return 列表key的头元素。
+     */
+    public String lpop(String key) {
+        return (String) redisTemplate.opsForList().leftPop(key);
+    }
+
+    /**
+     * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
+     *
+     * @param key
+     * @param value
+     * @return 执行 LPUSH命令后,列表的长度。
+     */
+    public long rpush(String key, String value) {
+        return redisTemplate.opsForList().rightPush(key, value);
+    }
+
+}

+ 18 - 0
src/main/java/cn/com/goldenwater/utils/TokenUtils.java

@@ -0,0 +1,18 @@
+package cn.com.goldenwater.utils;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+public class TokenUtils {
+
+    public static String getTokenFromRequest(ServerHttpRequest request) {
+        String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+//        if (authHeader != null && authHeader.startsWith("Bearer ")) {
+//            return authHeader.substring(7);
+//        }
+//        return null;
+        return authHeader;
+    }
+
+
+}

+ 30 - 1
src/main/resources/application.yml

@@ -14,6 +14,28 @@ spring:
     init:
     init:
       mode: always
       mode: always
       continue-on-error: false
       continue-on-error: false
+  # redis 配置
+  redis:
+    # 地址
+    host: 39.98.38.2
+    # 端口,默认为6379
+    port: 16379
+    # 数据库索引
+    database: 5
+    # 密码
+    password: 123456
+    # 连接超时时间
+    timeout: 20s
+    lettuce:
+      pool:
+        # 连接池中的最小空闲连接
+        min-idle: 0
+        # 连接池中的最大空闲连接
+        max-idle: 8
+        # 连接池的最大数据库连接数
+        max-active: 8
+        # #连接池最大阻塞等待时间(使用负值表示没有限制)
+        max-wait: -1ms
 
 
 # mybatis-plus
 # mybatis-plus
 mybatis-plus:
 mybatis-plus:
@@ -27,4 +49,11 @@ mybatis-plus:
     db-config:
     db-config:
       logic-delete-field: dele  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
       logic-delete-field: dele  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
       logic-delete-value: 9 # 逻辑已删除值(默认为 1)
       logic-delete-value: 9 # 逻辑已删除值(默认为 1)
-      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+
+
+jwt:
+  # 令牌密钥
+  secret: abcdefghijklmnopqrstuvwxyz
+  # 令牌有效期(默认30分钟)
+  expiration: 1440

+ 17 - 0
src/main/resources/mapper/PermissionMapper.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.com.goldenwater.mapper.PermissionMapper">
+    <select id="findPermission" resultType="java.lang.Boolean">
+        SELECT EXISTS(
+            SELECT 1
+            FROM permissions p
+                     JOIN third_party_permissions tpp ON p.id = tpp.permission_id
+            WHERE tpp.third_party_id = #{thirdPartyId}
+              AND p.path = #{path}
+              AND p.method = #{method}
+              AND p.enabled = 1
+        )
+    </select>
+</mapper>