全网首例全栈实践(七)Spring Boot 用户登录功能

TOKEN#token维持的时间{@OverrideprotectedIntegerinitialValue{try{IntegerdbInde

全网首例全栈实践(七)Spring Boot 用户登录功能

登录功能我们使用了Redis的缓存功能,以下为登录相关的目录结构。

全网首例全栈实践(七)Spring Boot 用户登录功能

其中config目录下的RedisConfig为Redis的配置,其中

@ConfigurationProperties(prefix = "redis")

加载application-dev.yml配置文件中的Redis连接配置,如下:

#redis配置
redis:
 #数据库索引(默认为0)
 database: 0
 #服务器地址
 hostName: localhost
 #端口
 port: 6379
 #密码(默认为空)
 password: xxxx
 #编码格式
 encode: utf-8
 #最大连接数
 pool:
 max-active: 100
 max-wait: -1
 timeout: 20000
 #登录成功后的token对应的key
 tokenKey: TOKEN
 #token维持的时间(秒)
 tokenTimeout: 600

utils->Redis目录下的RedisConstants为Redis的数据库配置,Redis默认有16个库,默认连接的是 index=0 的库,具体参看如下,可以分别定义不同的库:

public class RedisConstants {
 /**
 * redis库1 保存登录信息
 */
 public static final Integer datebase1=1;
}

一、重写RedisTemplate

为了增加选库的功能,首先我们需要重写RedisTemplate,使其支持选库插入。

public class RedisTemplate extends org.springframework.data.redis.core.RedisTemplate {
 public static ThreadLocal<Integer> indexdb = new ThreadLocal<Integer>(){
 @Override protected Integer initialValue() { return 0; }
 };
 @Override
 protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
 try {
 Integer dbIndex = indexdb.get();
 //如果设置了dbIndex
 if (dbIndex != null) {
 if (connection instanceof JedisConnection) {
 if (((JedisConnection) connection).getNativeConnection().getDB().intValue() != dbIndex) {
 connection.select(dbIndex);
 }
 } else {
 connection.select(dbIndex);
 }
 } else {
 connection.select(0);
 }
 } finally {
 indexdb.remove();
 }
 return super.preProcessConnection(connection, existingConnection);
 }
}

二、创建RedisUtil工具类

@Lazy
@Component
public class RedisUtil{
 @Autowired
 private RedisTemplate redisTemplate;
 public void setRedisTemplate(RedisTemplate redisTemplate) {
 this.redisTemplate = redisTemplate;
 }
 /**
 * 普通缓存获取
 * @param key 键
 * @return 值
 */
 public Object get(String key, int indexdb){
 redisTemplate.indexdb.set(indexdb);
 return key==null?null:redisTemplate.opsForValue().get(key);
 }
 /**
 * 普通缓存放入并设置时间
 * @param key 键
 * @param value 值
 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
 * @return true成功 false 失败
 */
 public boolean set(String key,Object value,int indexdb,long time){
 try {
 redisTemplate.indexdb.set(indexdb);
 if(time>0){
 redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
 }else{
 redisTemplate.opsForValue().set(key, value);
 }
 return true;
 } catch (Exception e) {
 e.printStackTrace();
 return false;
 }
 }
}

三、创建RedisConfig配置类

@ConfigurationProperties(prefix = "redis")
@Configuration
public class RedisConfig {
 @Autowired
 RedisProperties redisProperties;
 /**
 * @Description: Jedis配置
 */
 @Bean
 public JedisConnectionFactory JedisConnectionFactory(){
 RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration ();
 redisStandaloneConfiguration.setHostName(redisProperties.getHostName());
 redisStandaloneConfiguration.setPort(redisProperties.getPort());
 //由于我们使用了动态配置库,所以此处省略
 //redisStandaloneConfiguration.setDatabase(database);
 redisStandaloneConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
 JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder();
 jedisClientConfiguration.connectTimeout(Duration.ofMillis(redisProperties.getTimeout()));
 JedisConnectionFactory factory = new JedisConnectionFactory(redisStandaloneConfiguration,
 jedisClientConfiguration.build());
 return factory;
 }
 /**
 * @Description: 实例化 RedisTemplate 对象
 */
 @Bean
 public RedisTemplate functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
 LOGGER.info("RedisTemplate实例化成功!");
 RedisTemplate redisTemplate = new RedisTemplate();
 initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
 return redisTemplate;
 }
 /**
 * @Description: 引入自定义序列化
 */
 @Bean
 public RedisSerializer fastJson2JsonRedisSerializer() {
 return new FastJson2JsonRedisSerializer<Object>(Object.class);
 }
 /**
 * @Description: 设置数据存入 redis 的序列化方式,并开启事务
 */
 private void initDomainRedisTemplate(RedisTemplate redisTemplate, RedisConnectionFactory factory) {
 //如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!
 redisTemplate.setKeySerializer(new StringRedisSerializer());
 redisTemplate.setHashKeySerializer(new StringRedisSerializer());
 redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
 redisTemplate.setValueSerializer(fastJson2JsonRedisSerializer());
 // 开启事务
 redisTemplate.setEnableTransactionSupport(true);
 redisTemplate.setConnectionFactory(factory);
 }
 /**
 * @Description: 注入封装RedisTemplate
 */
 @Bean(name = "redisUtil")
 public RedisUtil redisUtil(RedisTemplate redisTemplate) {
 LOGGER.info("RedisUtil注入成功!");
 RedisUtil redisUtil = new RedisUtil();
 redisUtil.setRedisTemplate(redisTemplate);
 return redisUtil;
 }
}

到此为止,我们已经将Redis的工具类封装好,便于登录成功后使用。

四、用户Service的设计

首先,我们在UserMapper中新增用户查询和更新的dao,如下:

/**
 * 根据手机号,查询用户
 *
 * @param phone 手机号
 */
User findByPhone(@Param("phone") String phone);
/**
 * 根据手机号码,更新用户登录时间
 *
 * @param user 用户
 */
int updateUserLoginTime(@Param("user") User user);

其次我们在UserService中增加相应的服务,如下:

/**
 * 根据手机号,查询用户
 *
 * @param phone 手机号
 */
User findByPhone(String phone);
/**
 * 根据手机号码,更新用户登录时间
 *
 * @param user 用户
 */
int updateUserLoginTime(User user);

然后在UserServiceImpl中实现UserService:

@Override
public User findByPhone(String phone) {
 return userMapper.findByPhone(phone);
}
@Override
public int updateUserLoginTime(User user) {
 return userMapper.updateUserLoginTime(user);
}

最后,在UserMapper.xml中编写相应的sql:

<select id="findByPhone" resultMap="BaseResultMap" parameterType="java.lang.String">
 select * from user where phone = #{phone}
</select>
<!-- 对应userMapper中的updateUserLoginTime方法, -->
<insert id="updateUserLoginTime" >
 <!-- mysql插入数据后,获取id -->
 <selectKey keyProperty="id" resultType="int" order="AFTER" >
 SELECT LAST_INSERT_ID() as id
 </selectKey>
 update user set login_time = #{user.loginTime, jdbcType=TIMESTAMP} where phone = #{user.phone}
</insert>

五、登录Api的实现

@RestController
public class LoginController {
 @Autowired
 private UserService userService;
 @Autowired
 RedisUtil redisUtil;
 @Autowired
 RedisProperties redisProperties;
 
 /*
 * 登录接口,参数为json,请求参数: {"phone":1,"password":1}
 */
 @RequestMapping(value = "/api/login", method = RequestMethod.POST)
 public BaseEntity login(@RequestBody User user) {
 BaseEntity result = new BaseEntity();
 if (null == user) {
 result.setFailMsg("2-00-001");
 return result;
 }
 if (StringUtils.isEmpty(user.getPhone()) || StringUtils.isEmpty(user.getPassword())) {
 result.setFailMsg("2-00-001");
 return result;
 }
 //获取用户信息
 User userInfo = userService.findByPhone(user.getPhone());
 if (null == userInfo) {
 result.setFailMsg("2-00-006");
 return result;
 }
 //判断密码是否正确
 if (!user.getPassword().equals(userInfo.getPassword())) {
 result.setFailMsg("2-00-003");
 return result;
 }
 //设置登录时间
 userInfo.setLoginTime(new Date());
 userService.updateUserLoginTime(userInfo);
 //保存登录token,key的格式为TOKEN-XXX
 String token = UUID.randomUUID().toString().replaceAll("-", "");
 String key = redisProperties.getTokenKey() + "-" + token;
 //根据需要保存token对应的用户信息的字段
 User sessionUser = new User();
 sessionUser.setPhone(userInfo.getPhone());
 sessionUser.setLoginTime(userInfo.getLoginTime());
 sessionUser.setName(userInfo.getName());
 sessionUser.setToken(token);
 // 插入缓存,默认token有效期为
 redisUtil.set(key, sessionUser, RedisConstants.datebase1, redisProperties.getTokenTimeout());
 //返回登录状态,包括token
 sessionUser.setSuccessMsg("2-00-005");
 return sessionUser;
 }
}

实现的思路如下:

1. 首先校验参数是否为空,为空给出提示;

2. 然后取出参数中的手机号码,在数据库中查找该号码是否存在,如果存在则比对用户密码是否一致,实际项目中一般的做法是密码参数进行md5等加密;

3. 密码校验通过后,更新用户的登录时间;

4. 生成token,并将用户的信息对象(包括token)保存到Redis中;

5. 返回用户登录信息(包括token)。

其中

redisUtil.set(key, sessionUser, RedisConstants.datebase1, redisProperties.getTokenTimeout());

这里RedisConstants.datebase1,我们默认将token保存到Redis的库1中。保存token的目的是在后续项目开发过程中在需要校验用户登录状态的接口中,对用户身份进行校验,这也是商业项目通常的做法。

六、总结

用户登录功能的实现,主要涉及到用户身份的校验,以及登录会话的保持,安全性验证等细节,业界有相对成熟的标准,结合Redis等非关系型数据库,效率更高。本章涉及的代码,部分在前几章有讲解,后续我们会将所有代码开源。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/9932.html

(0)
上一篇 2023年 4月 22日 下午11:54
下一篇 2023年 4月 22日 下午11:54

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信