微服务电商实战(十)登陆接口

一、简介

本篇博客实现登陆接口,登陆成功会返回一个token,每种登陆方式只能拥有一个有效token,即唯一登陆。

流程如下:

1、校验账号密码是否正确

2、查询数据库中用户同种登陆方式的有效token,若存在则设置为失效,并从redis中移除。其中需要开启redis和db事务

3、生成token,存入数据库和redis中

4、返回token

 

 

 

二、实战

 

2.1创建用户令牌表

CREATE TABLE `u_user_token` (
  `user_token_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
  `token` varchar(255) DEFAULT NULL COMMENT 'token',
  `login_type` TINYINT (1) DEFAULT NULL COMMENT '登陆方式 1=pc,2=android,3=ios',
  `device_infor` varchar(255) DEFAULT NULL COMMENT '设备信息',
  `status` TINYINT (1) DEFAULT NULL COMMENT 'token状态 0=失效 1=有效',
  `user_id` int(11) DEFAULT NULL COMMENT '用户id',
  `create_time` TIMESTAMP NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` TIMESTAMP NULL DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`user_token_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2

 

 

2.2创建DO和DAO

package com.liazhan.member.dao.entity;

import lombok.Data;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.util.Date;

/**
 * @version:V1.0
 * @Description: 用户令牌表实体类
 * @author: Liazhan
 * @date 2020/4/29 9:25
 */
@Data
@Entity(name = "u_user_token")
@DynamicInsert
@DynamicUpdate
@EntityListeners(AuditingEntityListener.class)
public class UserTokenDO {
    /*
     * 主键
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer userTokenId;
    /*
     * 令牌
     */
    private String token;
    /*
     * 登陆方式 1=pc,2=android,3=ios
     */
    private Integer loginType;
    /*
     * 设备信息
     */
    private String deviceInfor;
    /*
     * token状态 0=失效 1=有效
     */
    private Integer status;
    /*
     * 用户id
     */
    private Integer userId;
    /**
     * 创建时间
     */
    @CreatedDate
    private Date createTime;
    /**
     * 修改时间
     */
    @LastModifiedDate
    private Date updateTime;
}
package com.liazhan.member.dao;

import com.liazhan.member.dao.entity.UserTokenDO;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @version:V1.0
 * @Description: 用户令牌dao层
 * @author: Liazhan
 * @date 2020/4/29 9:43
 */
public interface UserTokenDao extends JpaRepository<UserTokenDO,Integer> {

    /*
     * 根据用户id、登陆类型、token状态获取用户token记录
     */
    UserTokenDO findByUserIdAndLoginTypeAndStatus(Integer userId,Integer loginType,Integer status);
}

 

 

2.3 修改redis工具类,添加事务功能

package com.liazhan.core.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @version:V1.0
 * @Description: Redis工具类
 * @author: Liazhan
 * @date 2020/4/22 15:56
 */
@Component
public class RedisUtil {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 存放String类型,有过期时间
     * @param key
     * @param data
     * @param timeout 过期时间,单位为秒
     */
    public void setString(String key,String data,Long timeout){
        stringRedisTemplate.opsForValue().set(key,data);
        if(timeout!=null){
            stringRedisTemplate.expire(key,timeout,TimeUnit.SECONDS);
        }
    }

    /**
     * 存放String类型
     * @param key
     * @param data
     */
    public void setString(String key,String data){
        stringRedisTemplate.opsForValue().set(key,data);
    }

    /**
     * 根据key获取String类型数据
     * @param key
     * @return String
     */
    public String getString(String key){
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 根据key删除
     * @param key
     * @return Boolean
     */
    public Boolean delKey(String key){
        return stringRedisTemplate.delete(key);
    }

    /**
     * 开启redis事务
     */
    public void begin(){
        //开启redis事务权限
        stringRedisTemplate.setEnableTransactionSupport(true);
        //开启事务
        stringRedisTemplate.multi();
    }

    /**
     * 提交事务
     */
    public void exec(){
        stringRedisTemplate.exec();
    }

    /**
     * 回滚redis事务
     */
    public void discard(){
        stringRedisTemplate.discard();
    }
}

 

2.4 创建redis和db的事务工具类

package com.liazhan.core.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;

/**
 * @version:V1.0
 * @Description: redis与数据库事务工具类
 * @author: Liazhan
 * @date 2020/4/29 11:09
 */
@Component
//多例
@Scope(ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)
public class RedisAndDBTransactionUtil {
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private PlatformTransactionManager transactionManager;

    /**
     * 开启事务  采用默认传播行为
     * @return
     */
    public TransactionStatus begin(){
        //手动开启数据库事务
        TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionAttribute());
        redisUtil.begin();
        return transaction;
    }

    /**
     * 提交事务
     * @param transactionStatus
     * @throws Exception
     */
    public void commit(TransactionStatus transactionStatus) throws Exception {
        if(transactionStatus==null){
            throw new Exception("transactionStatus is null");
        }
        //redis与数据库事务会同时提交
        transactionManager.commit(transactionStatus);
    }

    /**
     * 回滚事务
     * @param transactionStatus
     * @throws Exception
     */
    public void rollback(TransactionStatus transactionStatus) throws Exception {
        if(transactionStatus==null){
            throw new Exception("transactionStatus is null");
        }
        //redis与数据库事务会同时回滚
        transactionManager.rollback(transactionStatus);
    }
}

 

2.5 创建token工具类

package com.liazhan.core.utils;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * @version:V1.0
 * @Description: token工具类
 * @author: Liazhan
 * @date 2020/4/28 10:08
 */
@Component
public class TokenUtil {
    @Autowired
    private RedisUtil redisUtil;

    /**
     * 生成令牌
     *
     * @param prefix
     *            令牌key前缀
     * @param redisValue
     *            redis存放的值
     * @return 返回token
     */
    public String createToken(String keyPrefix, String redisValue) {
        return createToken(keyPrefix, redisValue, null);
    }

    /**
     * 生成令牌
     *
     * @param prefix
     *            令牌key前缀
     * @param redisValue
     *            redis存放的值
     * @param time
     *            有效期
     * @return 返回token
     */
    public String createToken(String keyPrefix, String redisValue, Long time) {
        if (StringUtils.isEmpty(redisValue)) {
            new Exception("redisValue Not null");
        }
        String token = keyPrefix + UUID.randomUUID().toString().replace("-", "");
        redisUtil.setString(token, redisValue, time);
        return token;
    }

    /**
     * 根据token获取redis中的value值
     *
     * @param token
     * @return
     */
    public String getToken(String token) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        String value = redisUtil.getString(token);
        return value;
    }

    /**
     * 移除token
     *
     * @param token
     * @return
     */
    public Boolean removeToken(String token) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        return redisUtil.delKey(token);

    }
}

 

2.6 会员服务创建常量类

package com.liazhan.member.consts;

/**
 * @version:V1.0
 * @Description: 会员服务常量类
 * @author: Liazhan
 * @date 2020/4/28 10:11
 */
public interface MemberConst {
    //登录token在redis的key前缀
    String MEMBER_LOGIN_TOKEN_PREFIX = "login.token";
    //登录token过期时间  1小时
    Long MEMBER_LOGIN_TOKEN_TIMEOUT = 3600L;
    //登陆token失效状态
    Integer MEMBER_LOGIN_TOKEN_INVALID = 0;
    //登陆token有效状态
    Integer MEMBER_LOGIN_TOKEN_VALID = 1;
}

 

2.7 github上的会员服务配置文件member-dev.yml添加redis和登陆类型相关配置

#服务端口号
server:
  port: 8300

spring:
  application:
    name: liazhan-member
  datasource:
    druid:
      # 数据库访问配置, 使用druid数据源
      db-type: com.alibaba.druid.pool.DruidDataSource
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://47.98.183.103:3306/shop-member?serverTimezone=GMT%2b8&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: 123456
      # 连接池配置
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 连接等待超时时间
      max-wait: 30000
     # 配置检测可以关闭的空闲连接间隔时间
      time-between-eviction-runs-millis: 60000
  ##Jpa配置
  jpa:
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
  #redis配置
  redis:
    host: 47.98.183.103
    password: 123456
    port: 6379
    pool:
      max-idle: 100
      min-idle: 1
      max-active: 1000
      max-wait: -1



####swagger相关配置
swagger:
  base-package: com.liazhan.member.service
  title: 微服务电商项目-会员服务接口
  description: 会员服务
  version: 1.1
  terms-of-service-url: www.baidu.com
  contact:
    name: liazhan
    email: 33421352+liazhan@users.noreply.github.com


####会员登陆类型相关配置
login:
  type:
    max: 3
    value: pc,android,ios

配置文件github地址https://github.com/liazhan/shop-project-config/tree/b96d7e2a4b603df4f186d14394ec3fd23aae522b

 

2.8 创建登陆接口的输入dto类

package com.liazhan.member.input.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

/**
 * @version:V1.0
 * @Description:
 * @author: Liazhan
 * @date 2020/4/27 15:37
 */
@Data
@ApiModel(value = "用户登录输入实体类")
public class UserLoginInpDTO {

    @NotBlank(message = "请输入手机号!")
    @Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$",message = "手机号格式错误!")
    @ApiModelProperty(value = "手机号码")
    private String phone;

    @NotBlank(message = "请输入密码!")
    @ApiModelProperty(value = "密码")
    private String password;

    @NotNull(message = "登陆类型为空!")
    @ApiModelProperty(value = "登录类型 1=pc,2=android,3=ios")
    private Integer loginType;

    @NotBlank(message = "请输入设备信息!")
    @ApiModelProperty(value = "设备信息")
    private String deviceInfor;
}

 

2.9 UserDao添加根据手机号和密码查询用户的方法

package com.liazhan.member.dao;

import com.liazhan.member.dao.entity.UserDO;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @version:V1.0
 * @Description: 用户dao层
 * @author: Liazhan
 * @date 2020/4/21 10:58
 */
public interface UserDao extends JpaRepository<UserDO,Integer> {
    /**
     * 根据手机号查询用户是否存在
     */
    boolean existsByPhone(String phone);

    /*
     * 根据手机号和密码查询用户
     */
    UserDO findByPhoneAndPassword(String phone,String password);
}

 

2.10 创建登陆相关接口

package com.liazhan.member.service;

import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import com.liazhan.member.input.dto.UserLoginInpDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import javax.validation.Valid;

/**
 * @version:V1.0
 * @Description: 会员登录相关接口
 * @author: Liazhan
 * @date 2020/4/27 16:33
 */
@Api(tags = "会员登录相关接口")
public interface MemberLoginService {

    @PostMapping("/login")
    @ApiOperation(value = "登录接口")
    BaseResponse<JSONObject> login(@RequestBody @Valid UserLoginInpDTO userLoginInpDTO) throws Exception;
}
package com.liazhan.member.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.liazhan.base.BaseResponse;
import com.liazhan.base.BaseServiceImpl;
import com.liazhan.core.utils.DtoUtil;
import com.liazhan.core.utils.RedisAndDBTransactionUtil;
import com.liazhan.core.utils.TokenUtil;
import com.liazhan.member.consts.MemberConst;
import com.liazhan.member.dao.UserDao;
import com.liazhan.member.dao.UserTokenDao;
import com.liazhan.member.dao.entity.UserDO;
import com.liazhan.member.dao.entity.UserTokenDO;
import com.liazhan.member.input.dto.UserLoginInpDTO;
import com.liazhan.member.service.MemberLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/**
 * @version:V1.0
 * @Description: 会员登录相关接口实现类
 * @author: Liazhan
 * @date 2020/4/28 9:24
 */
@RestController
@RefreshScope
public class MemberLoginServiceImpl extends BaseServiceImpl<JSONObject> implements MemberLoginService {

    @Value("${login.type.max}")
    private Integer loginTypeMax;
    @Value("${login.type.value}")
    private String loginTypeValue;
    @Autowired
    private UserDao userDao;
    @Autowired
    private UserTokenDao userTokenDao;
    @Autowired
    private TokenUtil tokenUtil;
    @Autowired
    private RedisAndDBTransactionUtil redisAndDBTransactionUtil;

    @Transactional
    @Override
    public BaseResponse<JSONObject> login(@Valid UserLoginInpDTO userLoginInpDTO) throws Exception {
        //1.校验登陆类型
        Integer loginType = userLoginInpDTO.getLoginType();
        if(loginType<1 || loginType>loginTypeMax){
            return getResultError("登陆类型错误!");
        }

        //2.校验账号密码是否正确
        String oldPassword = userLoginInpDTO.getPassword();
        String newPassword = DigestUtils.md5DigestAsHex(oldPassword.getBytes());
        UserDO userDo = userDao.findByPhoneAndPassword(userLoginInpDTO.getPhone(), newPassword);
        if(userDo==null){
           return getResultError("账号或密码错误!");
        }
        /*
         * 3.将同类型的token失效
         */
        //查询同类型的有效token记录
        UserTokenDO oldUserTokenDo = userTokenDao.findByUserIdAndLoginTypeAndStatus(userDo.getUserId(),
                userLoginInpDTO.getLoginType(), MemberConst.MEMBER_LOGIN_TOKEN_VALID);
        //开启事务 防止出现异常时redis和db数据不一致
        TransactionStatus transactionStatus = redisAndDBTransactionUtil.begin();
        try {
            if(oldUserTokenDo!=null) {
                //移除redis的旧token
                tokenUtil.removeToken(oldUserTokenDo.getToken());
                //将数据库的token失效
                oldUserTokenDo.setStatus(MemberConst.MEMBER_LOGIN_TOKEN_INVALID);
                userTokenDao.save(oldUserTokenDo);
            }
            //4.生成token
            String[] loginTypeArray = loginTypeValue.split(",");
            String loginTypeStr = loginTypeArray[loginType-1];
            String key = loginTypeStr+"."+MemberConst.MEMBER_LOGIN_TOKEN_PREFIX;
            String token = tokenUtil.createToken(
                    key, userDo.getUserId() + "", MemberConst.MEMBER_LOGIN_TOKEN_TIMEOUT);
            //5.dto转do
            UserTokenDO userTokenDO = DtoUtil.dtoToDo(userLoginInpDTO, UserTokenDO.class);
            userTokenDO.setToken(token);
            userTokenDO.setStatus(MemberConst.MEMBER_LOGIN_TOKEN_VALID);
            userTokenDO.setUserId(userDo.getUserId());
            //6.保存token记录
            userTokenDao.save(userTokenDO);

            redisAndDBTransactionUtil.commit(transactionStatus);
            //7.返回数据
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("token",token);
            return getResultSuccess(jsonObject);
        }catch (Exception e){
            redisAndDBTransactionUtil.rollback(transactionStatus);
        }
        return getResultError("登陆失败!");
    }
}

 

 

ok,如此便大工告成。

依次启动config、eureka、member服务。

访问http://localhost:8300/swagger-ui.html 进行测试

 

 

github项目地址https://github.com/liazhan/shop-project/tree/58aeb838b392792f0df5a36b6b07277a75affa7a

版本号为58aeb838b392792f0df5a36b6b07277a75affa7a

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值