开放平台项目2.0版

本文档详细介绍了开放平台项目2.0的各个组件和技术,包括配置管理、日志处理、注册中心、统一配置中心、缓存服务、运营管理平台、网关功能(动态路由、参数校验、服务降级、时间戳校验、签名校验、幂等性校验、限流、计费、二级缓存动态更新)、登陆鉴权(JWT介绍与实现)、订单服务和仓储服务的整合、日志系统(使用ES存储和MQ异步处理)以及监控系统(使用Elastic-Job和Zookeeper实现分布式任务)。
摘要由CSDN通过智能技术生成

开放平台 logo

文章目录

零、 写在前面


这里主要是写一些我们的约定

约定名 约定值
工程groupid com.qianfeng
工程artifactId openplatform
module模块名 openapi-xxx xxx代表模块名
包名 com.qianfeng.openplatform.模块名.具体细分

一、 工程pom文件


为了授课编写代码的便捷性,我们的项目采用一个Project,多个module的方式,所以我们会在项目的最外层添加我们的Springboot和Spring Cloud配置

<packaging>pom</packaging>

<!--
SpringBoot 父依赖
-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>


<!--
SpringCloud工具集
-->
    <dependencyManagement>

        <dependencies>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR5</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>

        </dependencies>

    </dependencyManagement>

二 、日志文件(logback-spring.xml)


我们的日志使用的是Springboot自带的logback,下面为logback的配置文件,我们按照模块区分日志,主要就是保存位置的区别

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="6000000" debug="false">

    <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} %-5p [%t:%c{1}:%L] - %msg%n"/>
<!--    
将日志文件的保存位置修改为自定义的路径,当前的配置是在项目的目录中新建一个logs目录,在logs中创建具体的模块的日志目录.
-->
    <property name="LOG_PATH" value="./logs/config/"/>
    <!-- 系统级配置文件 开始 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>${LOG_PATTERN}</Pattern>
        </layout>
    </appender>

    <!-- stdout -->
    <appender name="rootstdout"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}rootstdout.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <FileNamePattern>${LOG_PATH}rootstdout.%i.log.zip</FileNamePattern>
            <MinIndex>1</MinIndex>
            <MaxIndex>20</MaxIndex>
        </rollingPolicy>
        <triggeringPolicy
                class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>${LOG_PATTERN}</Pattern>
        </layout>
    </appender>

    <!-- debug -->
    <appender name="rootDebug" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}root-debug.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <FileNamePattern>${LOG_PATH}root-debug.%i.log.zip</FileNamePattern>
            <MinIndex>1</MinIndex>
            <MaxIndex>10</MaxIndex>
        </rollingPolicy>
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>${LOG_PATTERN}</Pattern>
        </layout>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- info -->
    <appender name="rootInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}root-info.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <FileNamePattern>${LOG_PATH}root-info.%i.log.zip</FileNamePattern>
            <MinIndex>1</MinIndex>
            <MaxIndex>10</MaxIndex>
        </rollingPolicy>
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>${LOG_PATTERN}</Pattern>
        </layout>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- warn -->
    <appender name="rootWarn" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}root-warn.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <FileNamePattern>${LOG_PATH}root-warn.%i.log.zip</FileNamePattern>
            <MinIndex>1</MinIndex>
            <MaxIndex>10</MaxIndex>
        </rollingPolicy>
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>${LOG_PATTERN}</Pattern>
        </layout>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- error -->
    <appender name="rootError" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_PATH}root-error.log</File>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}root-error.%d{yyyy-MM-dd}.log</FileNamePattern>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>${LOG_PATTERN}</Pattern>
        </layout>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>Error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <springProfile name="local">
        <root level="info">
            <!-- 本地测试时使用,将日志打印到控制台,实际部署时请注释掉 -->
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="rootstdout"/>
            <appender-ref ref="rootDebug"/>
            <appender-ref ref="rootInfo"/>
            <appender-ref ref="rootWarn"/>
            <appender-ref ref="rootError"/>
        </root>
    </springProfile>

    <springProfile name="dev">
        <root level="info">
            <!-- 本地测试时使用,将日志打印到控制台,实际部署时请注释掉 -->
            <appender-ref ref="rootstdout"/>
            <appender-ref ref="rootDebug"/>
            <appender-ref ref="rootInfo"/>
            <appender-ref ref="rootWarn"/>
            <appender-ref ref="rootError"/>
        </root>
    </springProfile>

    <springProfile name="prod">
        <root level="info">
            <!-- 本地测试时使用,将日志打印到控制台,实际部署时请注释掉 -->
            <appender-ref ref="rootstdout"/>
            <appender-ref ref="rootDebug"/>
            <appender-ref ref="rootInfo"/>
            <appender-ref ref="rootWarn"/>
            <appender-ref ref="rootError"/>
        </root>
    </springProfile>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <jmxConfigurator/>
</configuration>

三、 多配置文件


实际开发中,为了方便测试,我们会有多个不同的配置文件,比如本地的,测试服务器的等,为了减少修改,我们一般会将配置文件按照具体场景单独写,在运行的时候通过指定加载配置文件的方式来执行

比如我们假设我们的项目有 local和prod两个不同的配置,则我们会创建application.yml主文件, application-local.yml和application-prod.yml 三个文件,其中local和prod代表的就是我们的前面的配置

我们有两种方式可以选择,我们在项目中可能会采用两种方式混用的情况

3.1 方式1

此方式是通过在主文件中指定加载文件后缀的方式来进行加载对应的配置文件

3.1.1 application.yml主文件
server:
  port: 12000
spring:
  profiles:
    active: local #通过这个属性指定文件的后缀,则程序会自动加载application-local.yml文件,如果需要prod只需要修改为prod然后启动即可
3.1.2 local文件

此文件仅仅为演示

spring:
  application:
    name: test-profile
eureka:
  client:
    service-url:
      defaultZone: http://localhost:20000/eureka
  instance:
    prefer-ip-address: true #显示 ip

3.1.3 prod文件

此文件仅仅为演示

spring:
  application:
    name: test-profile
eureka:
  client:
    service-url:
      defaultZone: http://test.qfjava.cn:20000/eureka
  instance:
    prefer-ip-address: true #显示 ip

3.2 方式2

此方式是通过在对应的项目的pom文件中指定属性名,然后在application.yml中通过变量名引入,在启动时候通过maven属性指定的方式来选择,假设我们的配置文件仍然是local和prod

3.2.1 pom文件
<!--
指定属性,可以让我们的application.yml引入
-->
    <profiles>

        <profile>
            <!--
				当前属性的id,唯一
			-->
            <id>local</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <!--
				当前id对应的具体的参数的值,当我们通过选中上面id对应的值时候,就会使用这个值,这两个值可以一样,可以不一样
当使用这个值的时候会加载application-local.yml文件
			-->
            <properties>
                <profileActive>local</profileActive>
            </properties>
        </profile>

        <profile>
            <id>prod</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <properties>
                <profileActive>prod</profileActive>
            </properties>
        </profile>


    </profiles>
3.2.2 application.yml 主文件
server:
  port: 12000
spring:
  profiles:
    active: '@profileActive@' #设置我们的加载的maven配置文件的后缀为这个属性的值,通过指定maven的启动参数来选中
3.2.3 启动

在启动程序前,在maven的选项中选择要使用的属性的id,然后启动程序即可

选择使用的配置文件
profiles

四、 注册中心(openapi-eureka)


我们的项目使用的是eureka作为注册中心,其使用相对简单,配置方便

4.1 pom中的依赖

下面的内容主要是依赖相关的内容

<!--
eureka server 的依赖,注意是server
-->

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

<!--
和安全相关的依赖包,我们访问 eureka的需要密码,此处为了操作方便,不使用密码
-->

<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-security</artifactId>-->
<!--        </dependency>-->
    </dependencies>
4.2 application.yml

程序启动的主要配置

server:
  port: 20000 #程序运行的端口,可以自定义修改
spring:
  application:
    name: openapi-eureka #程序的名字
    #配置 eureka 页面的登陆的账号和密码,需要配合security依赖使用,为了操作方便,就不配置了
#  security:
#    user:
#      name: admin
#      password: admin
eureka:
  client:
    service-url:
      defaultZone: http://localhost:20000/eureka #我们的注册中心的地址
    #单机版的配置,集群只需要启动多个eureka并相互作为客户端配置其他eureka地址即可,为了方便演示,我们使用单机版
    fetch-registry: false
    register-with-eureka: false

4.3 安全配置类(可选)

本配置文件取决于是否启用了security密码操作,因为开启了csrf验证,我们使用了端口,所以我们的eureka客户端会无法注册到当前注册中心,本配置是为了让客户端可以注册到eureka,如果没有使用security 可以忽略

@EnableWebSecurity
public class EurekaConfig  extends WebSecurityConfigurerAdapter
{
   

    @Override
    protected void configure(HttpSecurity http) throws Exception {
   
        http.csrf().disable()//禁用掉 csrf 跨域攻击,以免我们的服务无法注册到 eureka
        .authorizeRequests()//需要认证所有的请求
        .mvcMatchers("/eureka/**").permitAll()//符合以上路径规则的放行
        .mvcMatchers("/actuator/**").permitAll()//放行
        .anyRequest().authenticated().and().httpBasic();//剩余的所有的请求都需要验证
    }
}

4.4 SpringBoot主程序
@SpringBootApplication
//启用eureka 注册中心的注解,必须添加
@EnableEurekaServer
public class EurekaStartApp {
   
    public static void main (String[] args){
   
        SpringApplication.run(EurekaStartApp.class,args);
    }
}

五、 统一配置中心(openapi-configserver)


在我们的项目中,我们的一些配置并不是一成不变的,我们可能会经常发生变化, 比如我的redis服务器地址,我们的mq服务器地址等,当我们发生变化的时候,我们需要将所有引入这些内容的服务器都重新替换配置文件并部署,但是我们的服务器是集群,可能会有数量不明确的机器在使用,这样的情况下,维护就变得非常的繁琐,我们通过一个统一的配置中心,让我们所有需要的服务器都从配置中间下载配置文件,这样我们只需要将配置中心对应的配置文件修改即可,这样我们就需要搭建一个统一的配置中心,

我们使用的是SpringCloud ConfigServer来搭建,并将文件保存到git中,通过使用mq来进行批量更新

5.1 pom中的依赖

    <dependencies>
        <!--

        注意 config 的server 没有 starter,个人猜测应该是个 bug
        -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

    </dependencies>

5.2 application.yml主配置文件

application.yml文件内容一样

server:
  port: 12000
spring:
  application:
    name: openapi-configserver #我们的程序在eureka中的名字
  #配置我们保存配置文件的位置
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/chenzetao666/{
   application} #我们的配置文件保存的位置,在码云上,{application}是个通配符,代表的是从当前配置中心找配置文件的应用程序的名字
       #   username:
       #   password:
eureka:
  client:
    service-url:
      #这是如果注册中心是带密码的
     # defaultZone: http://admin:admin@localhost:20000/eureka
      defaultZone: http://localhost:20000/eureka
  instance:
    prefer-ip-address: true #显示 ip

5.4 SpringBoot主程序
@SpringBootApplication
//开启配置中心服务端
@EnableConfigServer
//开启服务注册,会(ˇ?ˇ) 想向eureka中注册当前服务,可以忽略编写,在使用eureka的情况下效果等于@EnableEurekaClient
@EnableDiscoveryClient
public class ConfigServerStartApp {
   

    public static void main (String[] args){
   
        SpringApplication.run(ConfigServerStartApp.class,args);
    }
}

六、 缓存服务(openapi-cache)


我们的缓存模块,当前使用的是 redis,缓存模块我们设计为是一个独立的 web 项目
原因是因为如果我们设置为依赖包,如果缓存模块发生变化,需要重新打包发布然后让所有依赖
缓存的功能都需要重新更新依赖以及打包上线,这样就不符合我们的项目的拆分的目的,所以
我们的缓存设计为 web 程序,这样我们的缓存发生变化的时候只要返回结果不变,内部如何变化只需要重启缓存功能就可以

6.1 pom中的依赖
  <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!--
        操作 redis 的依赖包
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--
        统一配置中心的客户端,注意没有 client
        -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <!--

        用于做降级的依赖包,当我们的程序内部发生问题的时候快速返回降级数据给调用者,防止我们的程序因等待导致出现问题,最终导致调用者出现级联失败
        -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
     
    </dependencies>

<!--
bootstrap.yml 如果无法使用 '@profileActive@'来获取我们的值,添加以下配置来让它可以访问

-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>

    </build>

    <profiles>

        <profile>
            <id>local</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <profileActive>local</profileActive>
            </properties>
        </profile>

        <profile>
            <id>prod</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <properties>
                <profileActive>prod</profileActive>
            </properties>
        </profile>


    </profiles>
6.2 bootstrap.yml

因为我们需要从config-server中获取配置,所以我们的一些配置需要放到bootstrap.yml中, application.yml放端口等其他配置

#因为我们的 redis 的服务器放在 git中,但是我们程序启动起来初始化对象的时候必须有服务器地址了
#当前配置文件的优先级是最高的,在 spring 初始化对象之前就会先加载这个配置文件,所以我们把从git 上面加载配置文件的过程写入到这里面
#来保证我们的程序在初始化对象之前就把服务器信息加载回来了,这样才能保证后续的初始化不会出现错误
spring:
  application:
    name: openapi-cache
  #需要告诉我们的程序,config-server 在 eureka 中叫什么,以及告诉 configserver 我要加载个配置文件
  cloud:
    config:
      discovery:
        enabled: true #开始通过服务发现来找 config server
        service-id: OPENAPI-CONFIGSERVER #设置 config server 的服务的名字
      label: master #设置我们的配置文件在 git 中属于什么分支,属于 master 分支
      profile: '@profileActive@' #因为我们一个仓库里面可以写好多个不同的配置文件,那么这个属性告诉 config server 我们要加载哪个后缀的配置文件
    #  name:  如果不设置值,则使用 spring.application.name 的值
  profiles:
    active: '@profileActive@'

eureka:
  client:
    service-url:
      #这是如果注册中心是带密码的
      # defaultZone: http://admin:admin@localhost:20000/eureka
      defaultZone: http://localhost:20000/eureka
  instance:
    prefer-ip-address: true #显示 ip
6.3 application.yml
server:
  port: 21000

在gitee中创建openapi-cache仓库

6.4 Redis配置类

用来配置我们redis数据的缓存方式

@Configuration
public class RedisConfig extends CachingConfigurerSupport {
   

    //这个是 key 的生成方式,指的是我们放的 key 可以做具体的区分,不重要
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
   


        return (target,method,params)->{
   

            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append(target.getClass().getName());
            stringBuilder.append(method.getName());
            for (Object param : params) {
   
                stringBuilder.append(param.toString());
            }
            return stringBuilder.toString();

        };
    }

    //,不重要,指的是我们缓存的写入方式
    @Bean
    public CacheManager cacheManager(LettuceConnectionFactory connectionFactory) {
   
        //以锁写入的方式创建我们的写入对象
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(connectionFactory);
        //创建默认的缓存配置对象
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        //根据我们的默认配置和写入方式创建缓存的管理器
        RedisCacheManager manager = new RedisCacheManager(writer, cacheConfiguration);

        return manager;
    }


    @Bean//创建我们的模板对象
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) {
   

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);//设置要连接的 redis

        //设置 key 的序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();//创建一个字符串的序列化方式,所有的数据会被序列为字符串
        redisTemplate.setKeySerializer(stringRedisSerializer);//设置 key 的序列化方式为字符串
        redisTemplate.setHashKeySerializer(stringRedisSerializer);//设置 hash 的 key 序列化方式为 string


        //我们的 value 使用什么方式来进行序列化,因为 value是任意的对象,所以我们使用 json
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//创建一个解析为 json 的序列化对象
        ObjectMapper objectMapper = new ObjectMapper();//因为 jackson 中是使用ObjectMapper来进行序列化的,所以我们需要设置给ObjectMapper
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);//设置所有非 final 修饰的变量都可以被序列化
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);//指定我们的 objectmapper

        //设置 value 的序列化方式
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);//设置值的序列化方式为 json
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

6.5 核心操作

redis中的一些核心操作

public interface CacheService {
   

    /**
     * 保存数据到 redis
     * @param key
     * @param value
     * @param expireTime
     * @return
     * @throws Exception
     */
    boolean save2Redis(String key, String value, long expireTime) throws Exception;

    /**
     * 从 redis 中获取数据
     * @param key
     * @return
     * @throws Exception
     */
    String getFromRedis(String key) throws Exception;

    /**
     * 删除一个 key
     * @param key
     * @return
     * @throws Exception
     */
    boolean deleteKey(String key) throws Exception;

    /**
     * 给指定的 key 设置过期时间
     * @param key
     * @param expireTime
     * @return
     * @throws Exception
     */
    boolean expire(String key, long expireTime) throws Exception;

    /**
     * 获取自增 id
     * @param key
     * @return
     * @throws Exception
     */
    Long getAutoIncrementId(String key) throws Exception;

    /**
     * 获取指定 key的 set 集合
     * @param key
     * @return
     * @throws Exception
     */
    Set<Object> sMembers(String key) throws Exception;

    /**
     * 向 set 中添加数据
     * @param key
     * @param value
     * @return
     * @throws Exception
     */
    Long sAdd(String key, String value) throws Exception;

    /**
     * 批量向 set 中添加数据
     * @param key
     * @param value
     * @return
     * @throws Exception
     */
    Long sAdd(String key, String[] value) throws Exception;

    /**
     * 删除指定数据
     * @param key
     * @param value
     * @return
     * @throws Exception
     */
    Long sRemove(String key, String value) throws Exception ;

    /**
     * 向hash中存放某个数据
     * @param key
     * @param field
     * @param value
     * @throws Exception
     */

    void hSet(String key, String field, String value) throws Exception;

    /**
     * 从hash中获取指定field的数据
     * @param key
     * @param field
     * @return
     * @throws Exception
     */
    String hGet(String key, String field) throws Exception;

    /**
     * 获取 hash 中所有的数据
     * @param key
     * @return
     * @throws Exception
     */
    Map<Object, Object> hGetAll(String key) throws Exception;


    /**
     * setnx操作
     * @param key
     * @param value
     * @param expireSecond
     * @return
     * @throws Exception
     */
    boolean setNX(String key, String value, long expireSecond) throws Exception;

    /**
     * 批量添加
     * @param key
     * @param map
     * @return
     * @throws Exception
     */
    boolean hMset(String key, Map<String,Object> map) throws Exception;

    /**
     * 查找符合表达式的 key   keys*
     * @param partten
     * @return
     * @throws Exception
     */
    Set<String> findKeyByPartten(String partten) throws Exception;

    /**
     * redis中hash数据的自增操作
     * @param key
     * @param field
     * @param delta 自增步长
     * @return
     * @throws Exception
     */
    Long hIncrement(String key, String field, long delta) throws Exception;
}

6.6 Service实现类
package com.alan.openplatform.cache.service.impl;

import com.alan.openplatform.cache.service.CacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @Author Alan Ture
 * @Description
 */

@Service
public class CacheServiceImpl implements CacheService {
   

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Override
    public boolean save2Redis(String key, String value, long expireTime) throws Exception {
   
        if(expireTime==0){
    //按我们自己的要求,如果是0,那就设置为-1
            expireTime = -1;
        }else if(expireTime<-1){
   
            expireTime = Math.abs(expireTime);//小于-1,则取绝对值
        }
        redisTemplate.opsForValue().set(key,value);
        if(expireTime>0){
   
            redisTemplate.expire(key,expireTime, TimeUnit.SECONDS);//时间为秒
        }
        return true;
    }

    @Override
    public String getFromRedis(String key) throws Exception {
   
        return (String) redisTemplate.opsForValue().get(key);
    }

    @Override
    public boolean deleteKey(String key) throws Exception {
   
        return redisTemplate.delete(key);
    }

    @Override
    public boolean expire(String key,long expireTime) throws Exception {
   
        if(expireTime==0){
    //按我们自己的要求,如果是0,那就设置为-1
            expireTime = -1;
        }else if(expireTime<-1){
   
            expireTime = Math.abs(expireTime);//小于-1,则取绝对值
        }
        if(expireTime>0){
   
            return  redisTemplate.expire(key,expireTime, TimeUnit.SECONDS);//时间为秒
        }else{
   
            //设置为-1的情况
            return  redisTemplate.persist(key);
        }
    }

    @Override
    public Long getAutoIncrementId(String key) throws Exception {
   
        return redisTemplate.opsForValue().increment(key);
    }

    @Override
    public Set<Object> sMembers(String key) throws Exception {
   

        return redisTemplate.opsForSet().members(key);
    }

    @Override
    public Long sAdd(String key, String value) throws Exception {
   
        return redisTemplate.opsForSet().add(key,value);
    }

    @Override
    public Long sAdd(String key, String[] value) throws Exception {
   
        return redisTemplate.opsForSet().add(key,value);
    }

    @Override
    public Long sRemove(String key, String value) throws Exception {
   
        return redisTemplate.opsForSet().remove(key,value);
    }

    @Override
    public void hSet(String key, String field, String value) throws Exception {
   
        redisTemplate.opsForHash().put(key,field,value);
    }

    @Override
    public String hGet(String key, String field) throws Exception {
   
        return (String) redisTemplate.opsForHash().get(key,field);
    }

    @Override
    public Map<Object, Object> hGetAll(String key) throws Exception {
   
        return redisTemplate.opsForHash().entries(key);
    }

    @Override
    public boolean setNX(String key, String value, long expireTime) throws Exception {
   
        if(expireTime==0){
    //按我们自己的要求,如果是0,那就设置为-1
            expireTime = -1;
        }else if(expireTime<-1){
   
            expireTime = Math.abs(expireTime);//小于-1,则取绝对值
        }
        redisTemplate.opsForValue().setIfAbsent(key,value);
        if(expireTime>0){
   
            redisTemplate.expire(key,expireTime, TimeUnit.SECONDS);//时间为毫秒
        }
        return true;
    }

    @Override
    public boolean hMset(String key, Map<String, Object> map) throws Exception {
   
        redisTemplate.opsForHash().putAll(key,map);
        return true;
    }

    @Override
    public Set<String> findKeyByPartten(String pattern) throws Exception {
   
        return redisTemplate.keys(pattern);
    }

    @Override
    public Long hIncrement(String key, String field, long delta) throws Exception {
   
        return redisTemplate.opsForHash().increment(key,field,delta);
    }
}

6.7 CacheController的实现
@RestController
@RequestMapping("/cache")
public class CacheController {
   

    @Autowired
    private CacheService cacheService;

    @PostMapping("/set/{key}/{value}/{expireTime}")
    public boolean set2redis(@PathVariable String key, @PathVariable String value, @PathVariable long expireTime) throws Exception {
   
        return cacheService.save2Redis(key,value,expireTime);
    }

    @GetMapping("/get/{key}")
    public String getFromRedis(@PathVariable String key) throws Exception {
   
        return cacheService.getFromRedis(key);
    }

    @PostMapping("/delete/{key}")
    public boolean deleteKey(@PathVariable String key) throws Exception {
   
        return cacheService.deleteKey(key);
    }

    @PostMapping("/expire/{key}/{expireTime}")
    public boolean expire(@PathVariable String key,@PathVariable long expireTime) throws Exception {
   
        return cacheService.expire(key,expireTime);
    }

    @GetMapping("/getIncrement/{key}")
    public Long getIncrementId(@PathVariable String key) throws Exception {
   
        return cacheService.getAutoIncrementId(key);
    }

    @PostMapping("/smembers/{key}")
    public Set<Object> sMembers(@PathVariable String key) throws Exception {
   
        return cacheService.sMembers(key);
    }

    @PostMapping("/sadd/{key}/{value}")
    public Long sAdd(@PathVariable String key,@PathVariable String value) throws Exception {
   
        return cacheService.sAdd(key,value);
    }

    @PostMapping("/sadds/{key}")
    public Long sAdd(@PathVariable String key, String[] value) throws Exception {
   
        return cacheService.sAdd(key,value);
    }

    @PostMapping("/sremove/{key}/{value}")
    public long sRemove(@PathVariable String key, @PathVariable String value) throws Exception {
   
        return cacheService.sRemove(key,value);
    }

    @PostMapping("/hset/{key}/{field}/{value}")
    public void hSet(@PathVariable String key,@PathVariable String field,@PathVariable String value) throws Exception {
   
        cacheService.hSet(key,field,value);
    }

    @GetMapping("/hget/{key}/{field}")
    public String hget(@PathVariable String key,@PathVariable String field) throws Exception {
   
        return cacheService.hGet(key,field);
    }

    @GetMapping("/hgetall/{key}")
    public Map<Object, Object> hGetAll(@PathVariable String key) throws Exception {
   
        return cacheService.hGetAll(key);
    }

    @PostMapping("/setnx/{key}/{value}/{expireTime}")
    public boolean setNx(@PathVariable String key,@PathVariable String value,@PathVariable long expireTime) throws Exception {
   
        return cacheService.setNX(key,value,expireTime);
    }

    @PostMapping("/hmset/{key}")
    public boolean hMset(@PathVariable String key,@RequestBody Map<String, Object> map) throws Exception {
   
        return cacheService.hMset(key,map);
    }

    @GetMapping("/keys/{pattern}")
    public Set<String> selectByPattern(@PathVariable String pattern) throws Exception {
   
        return cacheService.findKeyByPartten(pattern);
    }

    @GetMapping("/hincrement/{key}/{field}/{delta}")
    public Long hIncrement(@PathVariable String key, @PathVariable String field,@PathVariable long delta) throws Exception {
   
        return hIncrement(key,field,delta);
    }
}
6.7 服务降级

我们的服务需要包含降级操作,当程序内部出现问题的时候快速失败,返回数据给调用者,因此注意,我们的controller代码中需要配置hystrix

private static Logger logger = LoggerFactory.getLogger(CacheController.class);    

@PostMapping("/set/{key}/{value}/{expireTime}")
    @HystrixCommand(fallbackMethod = "set2redisFallback")  //降级处理
    public boolean set2redis(@PathVariable String key, @PathVariable String value, @PathVariable long expireTime) throws Exception {
   
        RedisUtils.checkNull(key);
        RedisUtils.checkNull(value);
        return cacheService.set2redis(key,value,expireTime);
    }

    @GetMapping("/get/{key}")
    @HystrixCommand(fallbackMethod = "getFromRedisFallback")
    public String getFromRedis(@PathVariable String key) throws Exception {
   
        RedisUtils.checkNull(key);
        return cacheService.getFromRedis(key);
    }
    
    
      //set2redis方法的降级方法
    public boolean set2redisFallback(String key,String value,long expireTime) throws Exception {
   
        logger.error("set2redis方法出现出错了!{},{}",key,value);
        return false;
    }

    //getFromRedis方法的降级方法
    public String getFromRedisFallback(String key) throws Exception {
   
        logger.error("getFromRedis方法出现出错了!{}",key);
        return null;
    }
6.8 controller方法参数的非空判断

自定义异常

public class RedisException extends RuntimeException{
   

    RedisException(){
   };

    private String message;
    private String code;

    public RedisException(String message, String code) {
   
        this.message = message;
        this.code = code;
    }

    @Override
    public String getMessage() {
   
        return message;
    }

    public void setMessage(String message) {
   
        this.message = message;
    }

    public String getCode() {
   
        return code;
    }

    public void setCode(String code) {
   
        this.code = code;
    }
}

创建openapi-commons服务,并在服务中创建一个CommonConstant接口

public interface CommonConstant {
   

    //内容为null的异常错误码
    String CONSTANT_NULL_EXCEPTION = "10001";
}

写一个RedisUtils工具类来做非空判断,并抛出自定义异常

public class RedisUtils {
   

    public static void checkNull(String resource){
   
        if(StringUtils.isEmpty(resource)){
   
             throw new RedisException("参数值为NULL!", CommonConstant.CONSTANT_NULL_EXCEPTION);
        }
    }
}

CacheController中的使用

@PostMapping("/set/{key}/{value}/{expireTime}")
@HystrixCommand(fallbackMethod = "set2redisFallback")  //降级处理
public boolean set2redis(@PathVariable String key, @PathVariable String value, @PathVariable long expireTime) throws Exception {
   
        RedisUtils.checkNull(key);
        RedisUtils.checkNull(value);
        return cacheService.set2redis(key,value,expireTime
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值