java开发常用的工具以及配置类

java开发常用的工具以及配置类

今天接着上篇博文 java开发常用的工具以及配置类进行总结。

9 分布式开发登录工具类
登录业务开发,传统的基于Session的登录方案,在用户登录成功后,服务器会为用户创建一个会话(session),并生成一个唯一的session ID。服务器将session ID 存储在内存或数据库中,并将其发送给客户端,通常是通过设置一个名为"JSESSIONID"的Cookie。客户端在后续的请求中将该session ID带上,服务器通过session ID来查找对应的会话,验证用户的身份和权限。这种方案需要在服务器端维护session的状态,因此在分布式环境下不太适用。使用基于Token的登录方案使用无状态的方式进行身份验证和授权。当用户登录成功后,服务器会生成一个Token,并将其发送给客户端。客户端将Token存储在本地,通常是在本地存储(如LocalStorage)或Cookie中。客户端在后续的请求中将Token发送给服务器进行验证。服务器验证Token的合法性、有效性和授权信息,而无需在服务器端存储任何会话状态。常见的Token技术包括JWT(JSON Web Token)

使用JWT,第一步,pom.xml引入依赖

<dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.7.0version>
 dependency>

第二步 创建JWT的工具类

/**
 * JWT用法:
 *  client接收服务端返回的jwt,将其存储到Cookie或者localStorage中.
 *  此后,client在于服务器交互都会携带jwt,存储在Cookie中,可以自动发送,但不会跨域。
 *  因此一般是将它放入到HTTP请求头或者POST请求的数据主体中。
 */
public class JwtUtils {

    private static long tokenExpiration = 24*60*60*1000;  // 过期时间
    private static String tokenSignKey = "A1t2g3uigu123456"; // token签名参数;

    private static Key getKeyInstance(){
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] bytes = DatatypeConverter.parseBase64Binary(tokenSignKey);
        return new SecretKeySpec(bytes,signatureAlgorithm.getJcaName());
    }

	// 创建jwt; 
    public static String createToken(Long userId, String userName) {
        String token = Jwts.builder()
                .setSubject("SRB-USER")  // 设置JWT的主题(Subject),表示该JWT所代表的实体或用户。
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)) // 过期时间
                .claim("userId", userId) // 设置JWT的自定义声明(Claims),可以在该方法中添加自定义的键值对作为JWT的附加信息。
                .claim("userName", userName)
                .signWith(SignatureAlgorithm.HS512, getKeyInstance()) // 设置JWT的签名算法和密钥,用于对JWT进行签名。
                .compressWith(CompressionCodecs.GZIP)
                .compact(); // 将JWT的参数和签名生成最终的JWT字符串表示。
        return token;  // 返回token字符串;
    }

    /**
     * 判断token是否有效
     * @param token
     * @return
     */
    public static boolean checkToken(String token) {
        if(StringUtils.isEmpty(token)) {
            return false;
        }
        try {
            Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
	// 可以根据创建token字符串 得到用户Id 因为生成token的主体 我们加入了用户Id;
    public static Long getUserId(String token) {
        Claims claims = getClaims(token);
        Integer userId = (Integer)claims.get("userId");
        return userId.longValue();
    }

	// 可以根据创建token字符串 得到用户名 因为生成token的主体 我们加入了用户名;
    public static String getUserName(String token) {
        Claims claims = getClaims(token);
        return (String)claims.get("userName");
    }

    public static void removeToken(String token) {
        //jwttoken无需删除,客户端扔掉即可。
    }

    /**
     * 校验token并返回Claims
     * @param token
     * @return
     */
    private static Claims getClaims(String token) {
        if(StringUtils.isEmpty(token)) {
            // 这里可以加入一个消息,提示未登录
            throw new BusinessException(ResponseEnum.LOGIN_AUTH_ERROR);
        }
        try {
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            return claims;
        } catch (Exception e) {
            throw new BusinessException(ResponseEnum.LOGIN_AUTH_ERROR);
        }
    }
}

该工具类常用于判断用户登录,以及需要登录的场景,根据token取出用户信息。因此,还是很重要的。下面用jwt测试一下 创建token字符串 和根据token字符串取出token载荷数据部分。

/**
 * 测试jwt创建和解析;
 */
public class JwtTests {

    //过期时间,毫秒,24小时
    private static long tokenExpiration = 24*60*60*1000; // 过期时间1天;
    //秘钥
    private static String tokenSignKey = "baidu123";

    /**
     * 测试创建jwt;
     */
    @Test
    public void testCreateToken(){
        String token = Jwts.builder()
                .setHeaderParam("typ", "JWT")   // 令牌类型,目前就是JWT;
                .setHeaderParam("alg", "HS256")  // 签名算法,这里是 HS256

                .setSubject("baidu-user")        // 令牌主题
                .setIssuer("baidu-admin")           // 签发者
                .setAudience("baidu")          // 接收者
                .setIssuedAt(new Date())         // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))  //过期时间(当前时间+时间戳)
                .setNotBefore(new Date(System.currentTimeMillis() + 20*1000))   //20秒后可用
                .setId(UUID.randomUUID().toString())  // 每次生成jwt都是唯一标识,避免重放攻击;
            /**
             载 荷,自定义信息;
            */
                .claim("nickname", "boger").claim("avatar", "1.jpg")
                .signWith(SignatureAlgorithm.HS256, tokenSignKey)   //签名哈希
                .compact();     //把以上信息组装 转换成字符串
        System.out.println(token);
        /**
         * 生成的token字符串
         * eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiYWlkdS11c2VyIiwiaXNzIjoiYmFpZHUtYWRtaW4iLCJhdWQiOiJiYWlkdSIsImlhdCI6MTY4NjM2NDQwOSwiZXhwIjoxNjg2NDUwODA5LCJuYmYiOjE2ODYzNjQ0MjksImp0aSI6ImE3MmRlMWFhLWUyN2UtNDFhNi05MzQ5LWZhMzM0OGM2MmY2OCIsIm5pY2tuYW1lIjoiYm9nZXIiLCJhdmF0YXIiOiIxLmpwZyJ9.d_Kz8EX6SqjHWF17KilboLhmKrdoC1Pnf1Kltg2OYHo
         */
    }

    /**
     * 解析jwt字符串;
     */
    @Test
    public void testGetUserInfo(){
        // 传入上面生成的jwt字符串;
        /**
         * 三部分组成 jwt头   载荷   签名哈希  用 . 隔开
         */
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiYWlkdS11c2VyIiwiaXNzIjoiYmFpZHUtYWRtaW4iLCJhdWQiOiJiYWlkdSIsImlhdCI6MTY4NjM2NDQwOSwiZXhwIjoxNjg2NDUwODA5LCJuYmYiOjE2ODYzNjQ0MjksImp0aSI6ImE3MmRlMWFhLWUyN2UtNDFhNi05MzQ5LWZhMzM0OGM2MmY2OCIsIm5pY2tuYW1lIjoiYm9nZXIiLCJhdmF0YXIiOiIxLmpwZyJ9.d_Kz8EX6SqjHWF17KilboLhmKrdoC1Pnf1Kltg2OYHo";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody(); // 获取有效载荷;

        /**
         * 从载荷获取信息;
         */
        String subject = claims.getSubject();
        String issuer = claims.getIssuer();
        String audience = claims.getAudience();
        Date issuedAt = claims.getIssuedAt();
        Date expiration = claims.getExpiration();
        Date notBefore = claims.getNotBefore();
        String id = claims.getId();

        System.out.println(subject);
        System.out.println(issuer);
        System.out.println(audience);
        System.out.println(issuedAt);
        System.out.println(expiration);
        System.out.println(notBefore);
        System.out.println(id);

        String nickname = (String)claims.get("nickname");
        String avatar = (String)claims.get("avatar");
        System.out.println(nickname);
        System.out.println(avatar);
    }
}

下图是根据token取出数据的截图
java开发常用的工具以及配置类_第1张图片
可以看出 取出了数据部分。测试时候,过期时间不要设置太短

10 Redis使用及其工具类
在开发中,将经常读取的数据存储在Redis中,以减少对后端数据库的访问压力。例如,可以将数据库查询结果、API响应结果或计算结果存储在Redis缓存中,提高系统的读取性能和响应速度。

要使用Redis第一步,引入依赖pom.xml


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-pool2artifactId>
        dependency>

        
        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-databindartifactId>
        dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatypegroupId>
            <artifactId>jackson-datatype-jsr310artifactId>
        dependency>

引入后两个依赖,主要是解决redis存储键值对的json序列化。便于阅读和读取,以便于后续业务开发。

第二步 项目配置文件配置redis。这里以yml格式文件为例。 yml文件配置 注意层级

spring:
	redis:
	    host: 127.0.0.1  #  redis的主机,这里是本地主机;
	    port: 6379 # redis端口默认 6379
	    database: 0 # 使用的数据库;
	    password:   #默认为空
	    timeout: 3000ms #最大等待时间,超时则抛出异常,否则请求一直等待
	    lettuce:
	      pool:
	        max-active: 20  #最大连接数,负值表示没有限制,默认8
	        max-wait: -1    #最大阻塞等待时间,负值表示没限制,默认-1
	        max-idle: 8     #最大空闲连接,默认8
	        min-idle: 0     #最小空闲连接,默认0

第三步 添加java配置类 RedisConfig

/**
 * redis配置;
 */
@Configuration
public class RedisConfig {


    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
        /**
         *   设置redis连接池;
         */
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        /**
         * 首先解决key的序列化方式  不使用默认的jdk序列化方式;
         */
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);

        /**
         *  解决value的序列化方式  不使用默认的jdk序列化方式;
         */
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        /**
         * 序列化时将类的数据类型存入json,以便反序列化的时候转换成正确的类型
         */
        ObjectMapper objectMapper = new ObjectMapper();
        //objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        /**
         * 将当前对象的数据类型也存入序列化的结果字符串中;
          */
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);

        /**
         * 解决jackson2无法反序列化LocalDateTime的问题
          */
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }
}

配置了解决存入的键值序列化方式。便于阅读。 如果不配置的话,那么在redis存入的键值阅读性差。比如在之前开发中,将前端经常访问的数据字典存入到redis,如下,进行单元测试。

 @Test
    public void saveDict(){
        Dict dict = dictMapper.selectById(1);  // 根据数据id为1的查询字典
        
        /**
         * 测试结果
         * 打开redis可视化连接工具
         *  输入  keys *  
         * 得到: "\xac\xed\x00\x05t\x00\x04dict"
         * 获取对应的值
         *  输入 get "\xac\xed\x00\x05t\x00\x04dict"   得到的是 二进制字符串;
         *  RedisTemplate默认使用了JDK的序列化方式存储了key和value,我们使用json序列化; 因此需要配置序列化
         */
        redisTemplate.opsForValue().set("dict",dict,5, TimeUnit.MINUTES);  // 将查询到的数据存入redis,过期时间5分钟;
    }

11 日志文件德输出格式工具类
创建Springboot项目,如果使用自定义的控制台日志,可读性不太好。而且,如果需要根据日志文件,针对输出信息分析,那么需要将日志输出到文件。

在项目resources文件夹下面创建logback-spring.xml。名字不要改


<configuration>

    <contextName>随便取名,一般是项目名,不要中文contextName>  

    
    <property name="log.path" value="文件目录自定义" />

    
    
    
    
    
    
    
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) %highlight([%-5level]) %green(%logger) %msg%n"/>

    
    <property name="FILE_LOG_PATTERN"
              value="%date{yyyy-MM-dd HH:mm:ss} [%-5level] %thread %file:%line %logger %msg%n" />

    
    <property name="ENCODING"
              value="UTF-8" />

    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}pattern>
            <charset>${ENCODING}charset>
        encoder>
    appender>

    
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${log.path}/log.logfile>  
        <append>trueappend>  
        <encoder>
            <pattern>${FILE_LOG_PATTERN}pattern>
            <charset>${ENCODING}charset>
        encoder>
    appender>

    
    <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${log.path}/log-rolling.logfile> 
        <encoder>
            <pattern>${FILE_LOG_PATTERN}pattern>
            <charset>${ENCODING}charset>
        encoder>

        
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            
            <fileNamePattern>${log.path}/info/log-rolling-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            
            <maxHistory>15maxHistory>

            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>1KBmaxFileSize>
            timeBasedFileNamingAndTriggeringPolicy>
        rollingPolicy>
    appender>

    
    <springProfile name="dev,test">
        <logger name="自定义" level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="ROLLING_FILE" />
        logger>
    springProfile>

    
    <springProfile name="prod">
        <logger name="自定义" level="ERROR">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="ROLLING_FILE" />
        logger>
    springProfile>
configuration>

配置了输出目录,控制台输出日志样式 生产环境 开发环境 滚动日志等。注意上面的目录自定义的,自己需要保持一致。

12 网关gate_way的使用
通过网关的统一入口和功能,可以实现请求的转发、路由、认证、授权、负载均衡、限流、熔断等,提高分布式系统的可靠性、可扩展性和安全性。网关还能够屏蔽后端服务的具体实现细节,提供统一的API接口,简化客户端的调用和维护工作。

项目中使用网关gate_way,一般是单独基于maven创建一个spring boot项目。
第一步 引入依赖。pom.xml


        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

第二步 网关项目主启动类 添加注解 @EnableDiscoveryClient // 注册中心发现;

第三步 配置文件配置路由 这里以yml文件为例

server:
  port: 81 # 服务端口  
spring:
  profiles:
    active: dev # 环境设置
  application:
    name: service-gateway # 服务名
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # nacos服务地址
    gateway:
      discovery:
        locator:
          enabled: true # gateway可以发现nacos中的微服务,并自动生成转发路由
      # 微服务路由配置; 简化路由访问;
      routes:
        - id: service-A # 对应service-A微服务;
          uri: lb://service-A 
          predicates:
            - Path=/*/A/** # 路由匹配;
        - id: service-B  #对应service-B微服务;
          uri: lb://service-B
          predicates:
            - Path=/*/B/**
        - id: service-C   #对应service-C  微服务;
          uri: lb://service-C
          predicates:
            - Path=/*/C/**

配置文件 配置了网关端口。 以及nacos地址(这里需要引入nacos的依赖,一般将微服务项目需要的公共依赖,会单独创建一个微服务,作为服务的公共依赖如service-base模块)。微服务路由配置,配置了微服务模块A B C三个模块,如果还有其他模块,后续还会添加到这里。并且路由保持一致。如微服务模块A,里面的路由带有 /A/。

同时,网关微服务还需要配置,跨域处理。 未搭建网关微服务时,解决跨域,我们采用的是在 Controller类添加@CrossOrgin注解。

/**
 * 利用gateway跨域配置;  Controller类就不需要@CrossOrgin注解
 */
@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true); //是否允许携带cookie
        config.addAllowedOrigin("*"); //可接受的域,是一个具体域名或者*(代表任意域名)
        config.addAllowedHeader("*"); //允许携带的头
        config.addAllowedMethod("*"); //允许访问的方式

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

今天就到这里吧 ,下期再见。

你可能感兴趣的:(java,java,spring,cloud,jwt,redis,gate_way)