Sure enough fresh e-commerce project (25) - member's only login

introduction

In the previous section E-commerce project (24) - log printing , mainly explain the basic usage of slf4j log framework.

This article briefly explains how to realize unique login for member services.

1. What is a unique login?

We often use QQ, wechat, nailing and other social applications. They all support logging in on the PC, Android or IOS. These applications ensure that a user can log in only once at a certain end. This is the "unique login" to be talked about in this article. Take member services as an example

2. Implementation idea of member unique login

Login code flow chart:

Flow chart for obtaining user information:

3. Function realization

3.1 database design

Create a table in the member database (guoranxinxian member). The script is as follows:

CREATE TABLE `user_token` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `token` varchar(255) DEFAULT NULL,
  `login_type` varchar(255) CHARACTER SET utf8 DEFAULT NULL,
  `device_infor` varchar(255) DEFAULT NULL,
  `is_availability` int(2) DEFAULT NULL,
  `user_id` int(11) DEFAULT NULL,
  `create_time` date DEFAULT NULL,
  `update_time` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2

Created successfully:

Corresponding UserTokenMapper:

package com.guoranxinxian.entity;

import lombok.Data;

@Data
public class UserTokenDo {
    /**
     * id
     */
    private Long id;
    /**
     * User token
     */
    private String token;
    /**
     * Login type
     */
    private String loginType;

    /**
     * Equipment information
     */
    private String deviceInfor;
    /**
     * User userId
     */
    private Long userId;

}


package com.guoranxinxian.mapper;

import com.guoranxinxian.entity.UserTokenDo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

/**
 * description: User TokenMapper
 */
public interface UserTokenMapper {

    /**
     * According to userid + logintype + is_ Query with availability = 0
     *
     * @param userId
     * @param loginType
     * @return
     */
    @Select("SELECT id as id ,token as token ,login_type as LoginType, device_infor as deviceInfor ,is_availability as isAvailability,user_id as userId"
            + "" + ""
            + " , create_time as createTime,update_time as updateTime   FROM user_token WHERE user_id=#{userId} AND login_type=#{loginType} and is_availability ='0'; ")
    UserTokenDo selectByUserIdAndLoginType(@Param("userId") Long userId, @Param("loginType") String loginType);

    /**
     * Modify the status of userId+loginType token to unavailable
     *
     * @param token
     * @return
     */
    @Update(" update user_token set is_availability  ='1', update_time=now() where token=#{token}")
    int updateTokenAvailability(@Param("token") String token);


    /**
     * token Insert a record into the record table
     *
     * @param userTokenDo
     * @return
     */
    @Insert("INSERT INTO `user_token` VALUES (null, #{token},#{loginType}, #{deviceInfor}, 0, #{userId} ,now(),null ); ")
    int insertUserToken(UserTokenDo userTokenDo);
}

3.2 code implementation

3.2.1 user login

1. Define the token generation tool class:

package com.guoranxinxian.util;

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

import java.util.UUID;

/**
 * description: Token Build tool class
 */
@Component
public class GenerateToken {
    @Autowired
    private RedisUtil redisUtil;

    /**
     * Generate token
     *
     * @param keyPrefix  Token key prefix
     * @param redisValue redis Stored value
     * @return Return token
     */
    public String createToken(String keyPrefix, String redisValue) {
        return createToken(keyPrefix, redisValue, null);
    }

    /**
     * Generate token
     *
     * @param keyPrefix  Token key prefix
     * @param redisValue redis Stored value
     * @param time       term of validity
     * @return Return token
     */
    public String createToken(String keyPrefix, String redisValue, Long time) {
        if (StringUtils.isEmpty(redisValue)) {
            new Exception("redisValue Not nul");
        }
        String token = keyPrefix + UUID.randomUUID().toString().replace("-", "");
        redisUtil.setString(token, redisValue, time);
        return token;
    }

    /**
     * Get the value in redis according to the token
     *
     * @param token
     * @return
     */
    public String getToken(String token) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        String value = redisUtil.getString(token);
        return value;
    }

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

}


2. New constant definitions (in fact, they should not only be written to general Constants, but should correspond to one constant for each micro service. In addition, they will not be put into Apollo because these Constants do not change frequently):

    // token
    String MEMBER_TOKEN_KEYPREFIX = "guoranxinxian.member.login";

    // Android login type
    String MEMBER_LOGIN_TYPE_ANDROID = "Android";
    // Login type of IOS
    String MEMBER_LOGIN_TYPE_IOS = "IOS";
    // Login type of PC
    String MEMBER_LOGIN_TYPE_PC = "PC";

    // Login timeout valid for 90 days
    Long MEMBRE_LOGIN_TOKEN_TIME = 77776000L;

3. User login interface:

UserLoginInDTO

package com.guoranxinxian.member.dto.input;


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

/**
 * description: User login request parameters
 */
@Data
@ApiModel(value = "User login parameters")
public class UserLoginInDTO {
    /**
     * phone number
     */
    @ApiModelProperty(value = "phone number")
    private String mobile;
    /**
     * password
     */
    @ApiModelProperty(value = "password")
    private String password;

    /**
     * Login type: PC, Android, IOS
     */
    @ApiModelProperty(value = "Login type")
    private String loginType;
    /**
     * Equipment information
     */
    @ApiModelProperty(value = "Equipment information")
    private String deviceInfor;

}

package com.guoranxinxian.service;

import com.alibaba.fastjson.JSONObject;
import com.guoranxinxian.api.BaseResponse;
import com.guoranxinxian.member.dto.input.UserLoginInDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * description: User login interface service
 * create by: YangLinWei
 * create time: 2020/3/3 4:35 afternoon
 */
@Api(tags = "User login service interface")
public interface MemberLoginService {
    /**
     * User login interface
     *
     * @param userLoginInDTO
     * @return
     */
    @PostMapping("/login")
    @ApiOperation(value = "Member user login information interface")
    BaseResponse<JSONObject> login(@RequestBody UserLoginInDTO userLoginInDTO);

}


4. User login interface implementation:

package com.guoranxinxian.mapper;

import com.guoranxinxian.entity.UserDo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

/**
 * description: User mapper
 */
public interface UserMapper {

    @Insert("INSERT INTO `user` VALUES (null,#{mobile}, #{email}, #{password}, #{userName}, null, null, null, '1', null, null, null);")
    int register(UserDo userEntity);

    @Select("SELECT * FROM user WHERE MOBILE=#{mobile};")
    UserDo existMobile(@Param("mobile") String mobile);

    @Select("SELECT USER_ID AS USERID ,MOBILE AS MOBILE,EMAIL AS EMAIL,PASSWORD AS PASSWORD, USER_NAME AS USER_NAME ,SEX AS SEX ,AGE AS AGE ,CREATE_TIME AS CREATETIME,IS_AVALIBLE AS ISAVALIBLE,PIC_IMG AS PICIMG,QQ_OPENID AS QQOPENID,WX_OPENID AS WXOPENID "
            + "  FROM user  WHERE MOBILE=#{mobile} and password=#{password};")
    UserDo login(@Param("mobile") String mobile, @Param("password") String password);

    @Select("SELECT USER_ID AS USERID ,MOBILE AS MOBILE,EMAIL AS EMAIL,PASSWORD AS PASSWORD, USER_NAME AS userName ,SEX AS SEX ,AGE AS AGE ,CREATE_TIME AS CREATETIME,IS_AVALIBLE AS ISAVALIBLE,PIC_IMG AS PICIMG,QQ_OPENID AS QQOPENID,WX_OPENID AS WXOPENID"
            + " FROM user WHERE user_Id=#{userId}")
    UserDo findByUserId(@Param("userId") Long userId);
}


package com.guoranxinxian.impl;

import com.alibaba.fastjson.JSONObject;
import com.guoranxinxian.api.BaseResponse;
import com.guoranxinxian.constants.Constants;
import com.guoranxinxian.entity.BaseApiService;
import com.guoranxinxian.entity.UserDo;
import com.guoranxinxian.entity.UserTokenDo;
import com.guoranxinxian.mapper.UserMapper;
import com.guoranxinxian.mapper.UserTokenMapper;
import com.guoranxinxian.member.dto.input.UserLoginInDTO;
import com.guoranxinxian.service.MemberLoginService;
import com.guoranxinxian.util.GenerateToken;
import com.guoranxinxian.util.MD5Util;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MemberLoginServiceImpl extends BaseApiService<JSONObject> implements MemberLoginService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private GenerateToken generateToken;

    @Autowired
    private UserTokenMapper userTokenMapper;

    @Override
    public BaseResponse<JSONObject> login(@RequestBody UserLoginInDTO userLoginInpDTO) {
        // 1. Validation parameters
        String mobile = userLoginInpDTO.getMobile();
        if (StringUtils.isEmpty(mobile)) {
            return setResultError("Mobile phone number cannot be empty!");
        }
        String password = userLoginInpDTO.getPassword();
        if (StringUtils.isEmpty(password)) {
            return setResultError("Password cannot be empty!");
        }
        // Determine login type
        String loginType = userLoginInpDTO.getLoginType();
        if (StringUtils.isEmpty(loginType)) {
            return setResultError("Login type cannot be empty!");
        }
        // The purpose is to limit the scope
        if (!(loginType.equals(Constants.MEMBER_LOGIN_TYPE_ANDROID) || loginType.equals(Constants.MEMBER_LOGIN_TYPE_IOS)
                || loginType.equals(Constants.MEMBER_LOGIN_TYPE_PC))) {
            return setResultError("Error in login type!");
        }

        // Equipment information
        String deviceInfor = userLoginInpDTO.getDeviceInfor();
        if (StringUtils.isEmpty(deviceInfor)) {
            return setResultError("Device information cannot be empty!");
        }

        // 2. Encrypt the login password
        String newPassWord = MD5Util.MD5(password);
        // 3. Query the database with mobile phone number + password to judge whether the user exists
        UserDo userDo = userMapper.login(mobile, newPassWord);
        if (userDo == null) {
            return setResultError("Wrong user name or password!");
        }
        // User login Token Session difference
        // After each end of the user logs in successfully, a token token (temporary and unique) will be generated and stored in redis as rediskey value userid
        // 4. Get userid
        Long userId = userDo.getUserId();
        // 5. Query whether the current login type account has logged in before according to userId+loginType. If it has logged in, clear the previous redistoken
        UserTokenDo userTokenDo = userTokenMapper.selectByUserIdAndLoginType(userId, loginType);
        if (userTokenDo != null) {
            // If you have logged in and cleared before redistoken
            String token = userTokenDo.getToken();
            Boolean isremoveToken = generateToken.removeToken(token);
            if (isremoveToken) {
                // Change the status of the token to 1
                userTokenMapper.updateTokenAvailability(token);
            }

        }

        // . generate the corresponding user token and store it in redis
        String keyPrefix = Constants.MEMBER_TOKEN_KEYPREFIX + loginType;
        String newToken = generateToken.createToken(keyPrefix, userId + "");

        // 1. Insert a new token
        UserTokenDo userToken = new UserTokenDo();
        userToken.setUserId(userId);
        userToken.setLoginType(userLoginInpDTO.getLoginType());
        userToken.setToken(newToken);
        userToken.setDeviceInfor(deviceInfor);
        userTokenMapper.insertUserToken(userToken);
        JSONObject data = new JSONObject();
        data.put("token", newToken);

        return setResultSuccess(data);
    }

}


3.2.2 obtaining user information

1. Add an interface for obtaining user information:

 /**
     * Query user information according to token
     *
     * @param token
     * @return
     */
    @GetMapping("/getUserInfo")
    @ApiOperation(value = "/getUserInfo")
    BaseResponse<UserOutDTO> getInfo(@RequestParam("token") String token);

2. Implementation interface:

  @Select("SELECT id as id ,token as token ,login_type as LoginType, device_infor as deviceInfor ,is_availability as isAvailability,user_id as userId"
          + "" + ""
          + " , create_time as createTime,update_time as updateTime   FROM user_token WHERE token=#{token} and is_availability ='0'; ")
  UserTokenDo selectByToken(String token);
@Override
public BaseResponse<UserOutDTO> getInfo(String token) {
    UserTokenDo userTokenDo = userTokenMapper.selectByToken(token);
    if(userTokenDo == null){
        return setResultError("The user is not logged in!");
    }

    UserDo userDo = userMapper.findByUserId(userTokenDo.getUserId());
    if (userDo == null) {
        return setResultError("user does not exist!");
    }
    // In the next lesson, put the conversion code into BaseApiService
    return setResultSuccess(BeanUtils.doToDto(userDo, UserOutDTO.class));
}

4. Test

4.1 three terminal unique login test

Currently, there are two users in the database:

Now test login. After starting the member project, use swagger to access the login interface:

Request content

To view redis and database:

Access the interface again. Let's look at redis and database:


After several more visits, you can see that only one piece of data is available in the database:

Of course, a Redis user can only have three pieces of data at most:

4.2 obtain user information according to token

Use Swagger to obtain user information according to the token (click the picture to zoom in on the result)

Android

IOS


PC

5. Summary

This article mainly explains that members use Android, IOS and PC to achieve unique login, and obtain user information through token.

Keywords: Java Eclipse Hibernate Spring Tomcat

Added by gOloVasTicK on Tue, 11 Jan 2022 12:14:24 +0200