会员服务唯一登陆
登陆唯一表设计
CREATE TABLE `meite_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,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2
public interface UserMapper {
@Insert("INSERT INTO `meite_user` VALUES (null,#{mobile}, #{email}, #{password}, #{userName}, null, null, null, '1', null, null, null);")
int register(UserDo userDo);
@Select("SELECT * FROM meite_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 meite_user WHERE MOBILE=#{0} and password=#{1};")
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 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 meite_user WHERE user_Id=#{userId}")
UserDo findByUserId(@Param("userId") Long userId);
}
public interface UserTokenMapper {
@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 meite_user_token WHERE user_id=#{userId} AND login_type=#{loginType} and is_availability ='0'; ")
UserTokenDo selectByUserIdAndLoginType(@Param("userId") Long userId, @Param("loginType") String loginType);
@Update(" update meite_user_token set is_availability ='1',update_time=now() where user_id=#{userId} and login_type =#{loginType} ")
int updateTokenAvailability(@Param("userId") Long userId, @Param("loginType") String loginType);
// INSERT INTO `meite_user_token` VALUES ('2', '1', 'PC', '苹果7p', '1', '1');
@Insert(" INSERT INTO `meite_user_token` VALUES (null, #{token},#{loginType}, #{deviceInfor}, 0, #{userId} ,now(),null ); ")
int insertUserToken(UserTokenDo userTokenDo);
}
@Data
public class UserTokenDo extends BaseDo {
/**
* id
*/
private Long id;
/**
* 用户token
*/
private String token;
/**
* 登陆类型
*/
private String loginType;
/**
* 设备信息
*/
private String deviceInfor;
/**
* 用户userId
*/
private Long userId;
/**
* 注册时间
*/
private Date createTime;
/**
* 修改时间
*
*/
private Date updateTime;
}
@Data
public class BaseDo {
/**
* 注册时间
*/
private Date createTime;
/**
* 修改时间
*
*/
private Date updateTime;
/**
* id
*/
private Long id;
/**
* 是否可用 0可用 1不可用
*/
private Long isAvailability;
}
GenerateToken(令牌生成工具)
@Component
public class GenerateToken {
@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 nul");
}
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);
}
}
新增常量信息
// token
String MEMBER_TOKEN_KEYPREFIX = "mayikt.member.login";
// 安卓的登陆类型
String MEMBER_LOGIN_TYPE_ANDROID = "Android";
// IOS的登陆类型
String MEMBER_LOGIN_TYPE_IOS = "IOS";
// PC的登陆类型
String MEMBER_LOGIN_TYPE_PC = "PC";
// 登陆超时时间 有效期 90天
Long MEMBRE_LOGIN_TOKEN_TIME = 77776000L;
唯一登陆接口实现
@Data
@ApiModel(value = "用户登陆参数")
public class UserLoginInpDTO {
/**
* 手机号码
*/
@ApiModelProperty(value = "手机号码")
private String mobile;
/**
* 密码
*/
@ApiModelProperty(value = "密码")
private String password;
/**
* 登陆类型 PC、Android 、IOS
*/
@ApiModelProperty(value = "登陆类型")
private String loginType;
/**
* 设备信息
*/
@ApiModelProperty(value = "设备信息")
private String deviceInfor;
}
@Api(tags = "用户登陆服务接口")
public interface MemberLoginService {
/**
* 用户登陆接口
*
* @param userEntity
* @return
*/
@PostMapping("/login")
@ApiOperation(value = "会员用户登陆信息接口")
BaseResponse<JSONObject> login(@RequestBody UserLoginInpDTO userLoginInpDTO);
}
package com.mayikt.member.service.impl;
@Autowired
private UserMapper userMapper;
@Autowired
private UserTokenMapper userTokenMapper;
@Autowired
private GenerateToken generateToken;
@Override
public BaseResponse<JSONObject> login(@RequestBody UserLoginInpDTO userLoginInpDTO) {
// 1.验证参数
String mobile = userLoginInpDTO.getMobile();
if (StringUtils.isEmpty(mobile)) {
return setResultError("手机号码不能为空!");
}
String password = userLoginInpDTO.getPassword();
if (StringUtils.isEmpty(password)) {
return setResultError("密码不能为空!");
}
String loginType = userLoginInpDTO.getLoginType();
if (StringUtils.isEmpty(loginType)) {
return setResultError("登陆类型不能为空!");
}
if (!(loginType.equals(Constants.MEMBER_LOGIN_TYPE_ANDROID) || loginType.equals(Constants.MEMBER_LOGIN_TYPE_IOS)
|| loginType.equals(Constants.MEMBER_LOGIN_TYPE_PC))) {
return setResultError("登陆类型出现错误!");
}
// 设备信息
String deviceInfor = userLoginInpDTO.getDeviceInfor();
if (StringUtils.isEmpty(deviceInfor)) {
return setResultError("设备信息不能为空!");
}
String newPassWord = MD5Util.MD5(password);
// 2.用户名称与密码登陆
UserDo userDo = userMapper.login(mobile, newPassWord);
if (userDo == null) {
return setResultError("用户名称与密码错误!");
}
// 3.查询之前是否有过登陆
Long userId = userDo.getUserId();
UserTokenDo userTokenDo = userTokenMapper.selectByUserIdAndLoginType(userId, loginType);
if (userTokenDo != null) {
// 4.清除之前的token
String token = userTokenDo.getToken();
Boolean removeToken = generateToken.removeToken(token);
if (removeToken) {
userTokenMapper.updateTokenAvailability(userId, loginType);
}
}
// 5. 生成新的token
String token = generateToken.createToken(Constants.MEMBER_TOKEN_KEYPREFIX, userId + "",
Constants.MEMBRE_LOGIN_TOKEN_TIME);
JSONObject tokenData = new JSONObject();
tokenData.put("token", token);
// 6.存入在数据库中
UserTokenDo userToken = new UserTokenDo();
userToken.setUserId(userId);
userToken.setLoginType(userLoginInpDTO.getLoginType());
userToken.setToken(token);
userToken.setDeviceInfor(deviceInfor);
userTokenMapper.insertUserToken(userToken);
return setResultSuccess(tokenData);
}
根据Token查询用户信息
/**
* 根据token查询用户信息
*
* @param userEntity
* @return
*/
@GetMapping("/getUserInfo")
@ApiOperation(value = "/getUserInfo")
BaseResponse<UserOutDTO> getInfo(@RequestParam("token") String token);
@Override
public BaseResponse<UserOutDTO> getInfo(String token) {
// 1.参数验证
if (StringUtils.isEmpty(token)) {
return setResultError("token不能为空!");
}
// 2.使用token向redis中查询userId
String redisValue = generateToken.getToken(token);
if (StringUtils.isEmpty(redisValue)) {
return setResultError("token已经失效或者不正确");
}
Long userId = TypeCastHelper.toLong(redisValue);
// 3.根据userId查询用户信息
UserDo userDo = userMapper.findByUserId(userId);
if (userDo == null) {
return setResultError("用户信息不存在!");
}
// 4.将Do转换为Dto
UserOutDTO doToDto = MeiteBeanUtils.doToDto(userDo, UserOutDTO.class);
return setResultSuccess(doToDto);
}
笔记
假设一个用户登陆可以同时在PC、IOS、Android 最终生成三个Token
mayikt.member.login8faa6de4327e4086a666ef1221284dfb –PC登陆
mayikt.member.login8faa6de4327e4086a666ef1221284dfb –redis存放PCtoken
会员登陆模块优化与门户平台整合
BaseApiService新增
// 调用数据库层判断
public Boolean toDaoResult(int result) {
return result > 0 ? true : false;
}
修改RedisUtil
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 存放string类型
*
* @param key
* key
* @param data
* 数据
* @param timeout
* 超时间
*/
public void setString(String key, String data, Long timeout) {
try {
stringRedisTemplate.opsForValue().set(key, data);
if (timeout != null) {
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
} catch (Exception e) {
}
}
/**
* 开启Redis 事务
*
* @param isTransaction
*/
public void begin() {
// 开启Redis 事务权限
stringRedisTemplate.setEnableTransactionSupport(true);
// 开启事务
stringRedisTemplate.multi();
}
/**
* 提交事务
*
* @param isTransaction
*/
public void exec() {
// 成功提交事务
stringRedisTemplate.exec();
}
/**
* 回滚Redis 事务
*/
public void discard() {
stringRedisTemplate.discard();
}
/**
* 存放string类型
*
* @param key
* key
* @param data
* 数据
*/
public void setString(String key, String data) {
setString(key, data, null);
}
/**
* 根据key查询string类型
*
* @param key
* @return
*/
public String getString(String key) {
String value = stringRedisTemplate.opsForValue().get(key);
return value;
}
/**
* 根据对应的key删除key
*
* @param key
*/
public Boolean delKey(String key) {
return stringRedisTemplate.delete(key);
}
}
RedisDataSoureceTransaction(事务管理工具保持redis和数据库一致)
@Component
@Scope(ConfigurableListableBeanFactory.SCOPE_PROTOTYPE)
public class RedisDataSoureceTransaction {
@Autowired
private RedisUtil redisUtil;
/**
* 数据源事务管理器
*/
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
/**
* 开始事务 采用默认传播行为
*
* @return
*/
public TransactionStatus begin() {
// 手动begin数据库事务
// 1.开启数据库的事务 事务传播行为
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
// 2.开启redis事务
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与数据库事务同时提交
dataSourceTransactionManager.commit(transactionStatus);
}
/**
* 回滚事务
*
* @param transactionStatus
* @throws Exception
*/
public void rollback(TransactionStatus transactionStatus) throws Exception {
if (transactionStatus == null) {
throw new Exception("transactionStatus is null");
}
// 1.回滚数据库事务 redis事务和数据库的事务同时回滚
dataSourceTransactionManager.rollback(transactionStatus);
// // 2.回滚redis事务
// redisUtil.discard();
}
// 如果redis的值与数据库的值保持不一致话
}
登陆接口业务逻辑
@RestController
public class MemberLoginServiceImpl extends BaseApiService<JSONObject> implements MemberLoginService {
@Autowired
private UserMapper userMapper;
@Autowired
private GenerateToken generateToken;
@Autowired
private UserTokenMapper userTokenMapper;
/**
* 手动事务工具类
*/
@Autowired
private RedisDataSoureceTransaction manualTransaction;
/**
* redis 工具类
*/
@Autowired
private RedisUtil redisUtil;
@Override
public BaseResponse<JSONObject> login(@RequestBody UserLoginInpDTO userLoginInpDTO) {
// 1.验证参数
String mobile = userLoginInpDTO.getMobile();
if (StringUtils.isEmpty(mobile)) {
return setResultError("手机号码不能为空!");
}
String password = userLoginInpDTO.getPassword();
if (StringUtils.isEmpty(password)) {
return setResultError("密码不能为空!");
}
// 判断登陆类型
String loginType = userLoginInpDTO.getLoginType();
if (StringUtils.isEmpty(loginType)) {
return setResultError("登陆类型不能为空!");
}
// 目的是限制范围
if (!(loginType.equals(Constants.MEMBER_LOGIN_TYPE_ANDROID) || loginType.equals(Constants.MEMBER_LOGIN_TYPE_IOS)
|| loginType.equals(Constants.MEMBER_LOGIN_TYPE_PC))) {
return setResultError("登陆类型出现错误!");
}
// 设备信息
String deviceInfor = userLoginInpDTO.getDeviceInfor();
if (StringUtils.isEmpty(deviceInfor)) {
return setResultError("设备信息不能为空!");
}
// 2.对登陆密码实现加密
String newPassWord = MD5Util.MD5(password);
// 3.使用手机号码+密码查询数据库 ,判断用户是否存在
UserDo userDo = userMapper.login(mobile, newPassWord);
if (userDo == null) {
return setResultError("用户名称或者密码错误!");
}
TransactionStatus transactionStatus = null;
try {
// 1.获取用户UserId
Long userId = userDo.getUserId();
// 2.生成用户令牌Key
String keyPrefix = Constants.MEMBER_TOKEN_KEYPREFIX + loginType;
// 5.根据userId+loginType 查询当前登陆类型账号之前是否有登陆过,如果登陆过 清除之前redistoken
UserTokenDo userTokenDo = userTokenMapper.selectByUserIdAndLoginType(userId, loginType);
transactionStatus = manualTransaction.begin();
// // ####开启手动事务
if (userTokenDo != null) {
// 如果登陆过 清除之前redistoken
String oriToken = userTokenDo.getToken();
// 移除Token
generateToken.removeToken(oriToken);
int updateTokenAvailability = userTokenMapper.updateTokenAvailability(oriToken);
if (updateTokenAvailability < 0) {
manualTransaction.rollback(transactionStatus);
return setResultError("系统错误");
}
}
// 4.将用户生成的令牌插入到Token记录表中
UserTokenDo userToken = new UserTokenDo();
userToken.setUserId(userId);
userToken.setLoginType(userLoginInpDTO.getLoginType());
String newToken = generateToken.createToken(keyPrefix, userId + "");
userToken.setToken(newToken);
userToken.setDeviceInfor(deviceInfor);
int result = userTokenMapper.insertUserToken(userToken);
if (!toDaoResult(result)) {
manualTransaction.rollback(transactionStatus);
return setResultError("系统错误!");
}
// #######提交事务
JSONObject data = new JSONObject();
data.put("token", newToken);
manualTransaction.commit(transactionStatus);
return setResultSuccess(data);
} catch (Exception e) {
try {
// 回滚事务
manualTransaction.rollback(transactionStatus);
} catch (Exception e1) {
}
return setResultError("系统错误!");
}
}
}