用户微服务代码改造

改造成微服务架构后,一般会在网关层进行鉴权操作,所以这部分代码会进行一些调整。不过,改造的只是代码层面和实现细节,其底层的原理并没有太多改动,依然可以根据笔者提供的登录流程来理解和学习。

本节的源代码是在 newbee-mall-cloud-dev-step03 工程的基础上改造的,将工程命名为 newbee-mall-cloud-dev-step04。改造思路是将原来存储在商城管理员 token 表中的数据改为存储到 Redis 中,这样在网关层也可以通过读取 Redis 中的数据实现用户鉴权操作。

引入Redis进行鉴权改造

引入与 Redis 相关的依赖并配置好相关的连接参数。

第一步,引入依赖。

打开 newbee-mall-cloud-user-web 模块下的 pom.xml 文件,在 dependencies 标签下引入与 Redis 相关的依赖,新增配置代码如下:

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

第二步,新增连接 Redis 数据库的配置项。

newbee-mall-cloud-user-web 模块下的 application.properties 配置文件中增加连接 Redis 的配置项,代码如下:

# Redis 配置
# Redis 数据库索引(默认为 0)
spring.redis.database=13
# Redis 服务器地址
spring.redis.host=127.0.0.1
# Redis 服务器连接端口
spring.redis.port=6379
# Redis 服务器连接密码
spring.redis.password=123456
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=5000

连接 Redis 的配置项主要包括 Redis 服务器地址、数据库索引、账号密码、连接超时时间等。

第三步,新增 Redis 配置类。

newbee-mall-cloud-user-web 模块下的 ltd.user.cloud.newbee.config 包中新增自定义的 Redis 配置类,代码如下:

package ltd.user.cloud.newbee.config;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import serializer.StringRedisSerializer;

import javax.annotation.Resource;
import java.io.Serializable;

import java.lang.reflect.Method;
import java.util.HashSet;

import java.util.Set;

@Configuration
@EnableCaching

@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport {

    @Resource
    private LettuceConnectionFactory lettuceConnectionFactory;

    public RedisConfig() {
    }

    @Bean

    public RedisTemplate<String, Serializable> redisCacheTemplate
            (LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        template.setConnectionFactory(redisConnectionFactory);

        return template;

    }

    @Bean
    public CacheManager cacheManager() {

        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory);
        @SuppressWarnings("serial")
        Set<String> cacheNames = new HashSet<String>() {
            {
                add("codeNameCache");
            }
        };
        builder.initialCacheNames(cacheNames);
        return builder.build();
    }

    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuffer stringBuffer = new StringBuffer();
                stringBuffer.append(target.getClass().getName());
                stringBuffer.append(method.getName());
                for (Object obj : params) {
                    stringBuffer.append(obj.toString());
                }
                return stringBuffer.toString();
            }
        };
    }
}

以上代码主要配置 RedisTemplate 对象,并自定义一些序列化方式。以上代码是 Redis 配置类常见的配置方式,可以直接在代码中使用。

另外,在项目开发中使用 Redis 时,一般会自定义一个静态工具类,把一些常见的缓存操作方法抽取出来方便调用。在本项目中对缓存的操作并不复杂,因此并未单独抽取 Redis 工具类,而是直接使用 RedisTemplate 类中提供的底层方法。

用户微服务中登录代码及鉴权代码修改

原来的登录逻辑中会生成 AdminUserToken 的相关数据并保存到 MySQL 数据库中,现在引入了 Redis 数据库,把这部分数据保存到 Redis 数据库中即可,修改后的登录方法代码如下:

@Autowired
private RedisTemplate redisTemplate;

@Override
public String login(String userName, String password) {
    AdminUser loginAdminUser = adminUserMapper.login(userName, password);
    if (loginAdminUser != null) {
        //登录后执行修改 token 值的操作
        String token = getNewToken(System.currentTimeMillis() + "",
                loginAdminUser.getAdminUserId());
        AdminUserToken adminUserToken = new AdminUserToken();
        adminUserToken.setAdminUserId(loginAdminUser.getAdminUserId());
        adminUserToken.setToken(token);
        ValueOperations<String, AdminUserToken> setToken =
                redisTemplate.opsForValue();
        setToken.set(token, adminUserToken, 2 * 24 * 60 * 60, TimeUnit.SECONDS);
        //过期时间为 48 小时
        return token;
    }
    return "登录失败";
}

修改的类是 newbee-mall-cloud-user-web 模块下的 ltd.user.cloud.newbee.service.impl.AdminUserServiceImpl 类。

删除原有保存和修改 MySQL 数据库中 AdminUserToken 数据的逻辑代码,之后引入 RedisTemplate 对象,并且添加将 AdminUserToken 数据存储到 Redis 数据库中的代码。简单理解就是改变数据的存储方式,不过登录方法中生成 token 值并保存等待后续鉴权的底层思路没有太多变化。

登录方法的逻辑修改完成后,相应地修改鉴权逻辑。原来的逻辑是获取请求头中的 token 变量,并根据这个变量查询 MySQL 数据库是否存在和是否过期。现在引入了 Redis 数据库,就不需要查询 MySQL 数据库了,直接读取 Redis 数据库中是否有对应的 token 值即可。修改后的鉴权代码如下:

@Autowired
private RedisTemplate redisTemplate;

public Object resolveArgument(MethodParameter parameter,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
                              WebDataBinderFactory binderFactory) {
    if (parameter.getParameterAnnotation(TokenToAdminUser.class) instanceof TokenToAdminUser) {
        String token = webRequest.getHeader("token");
        if (null != token && !"".equals(token) && token.length() == 32) {
            ValueOperations<String, AdminUserToken> opsForAdminUserToken = redisTemplate.opsForValue();
            AdminUserToken adminUserToken = opsForAdminUserToken.get(token);
            if (adminUserToken == null) {
                NewBeeMallException.fail("ADMIN_NOT_LOGIN_ERROR");
            }
            return adminUserToken;
        } else {
            NewBeeMallException.fail("ADMIN_NOT_LOGIN_ERROR");
        }
    }
    return null;
}

修改的类是 newbee-mall-cloud-user-web 模块下的 ltd.user.cloud.newbee.service.config.handler.TokenToAdminUserMethodArgumentResolver 类。

删除原有查询 MySQL 数据库中 AdminUserToken 数据的逻辑代码,之后引入 RedisTemplate 对象,并且添加从 Redis 数据库中读取 AdminUserToken 数据的代码,后续逻辑并没有更改。

至此,对 tb_newbee_mall_admin_user_token 表的相关操作就可以全部删除了,直接从数据库中删除这张表及工程中对这张表操作的相关代码。这一步就比较简单了,删除 Mapper 文件及 dao 包中的接口即可。