登录实现 和 添加配说明文档
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package org.jiayunet.controller;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jiayunet.pojo.dto.SmsLoginDto;
|
||||
import org.jiayunet.pojo.vo.LoginVo;
|
||||
import org.jiayunet.server.LoginServer;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
/**
|
||||
* 登录控制类
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/public")
|
||||
@AllArgsConstructor
|
||||
@Validated
|
||||
public class LoginController {
|
||||
|
||||
private LoginServer loginServer;
|
||||
|
||||
@PostMapping("/sms/sendCode")
|
||||
public boolean sendCode(@RequestParam @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") String mobileNumber) {
|
||||
return loginServer.sendCode(mobileNumber);
|
||||
}
|
||||
|
||||
@PostMapping("/login/smsLogin")
|
||||
public LoginVo smsLogin(@Validated @RequestBody SmsLoginDto dto,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
return loginServer.smsLogin(dto.getMobileNumber(), dto.getCode(), request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.jiayunet.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 短信验证码登录入参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
public class SmsLoginDto {
|
||||
|
||||
@NotBlank(message = "手机号不能为空")
|
||||
private String mobileNumber;
|
||||
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String code;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.jiayunet.pojo.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 登录返回
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class LoginVo {
|
||||
|
||||
private Long userId;
|
||||
|
||||
private String nick;
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package org.jiayunet.server;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jiayunet.constant.PreRedisKeyName;
|
||||
import org.jiayunet.constant.SmsTemplateEnum;
|
||||
import org.jiayunet.constant.sms.UniversalSmsVariable;
|
||||
import org.jiayunet.exception.BusinessException;
|
||||
import org.jiayunet.exception.BusinessExpCodeEnum;
|
||||
import org.jiayunet.mapper.UserMapper;
|
||||
import org.jiayunet.pojo.login.RedisLoginTokenInfo;
|
||||
import org.jiayunet.pojo.po.User;
|
||||
import org.jiayunet.pojo.vo.LoginVo;
|
||||
import org.jiayunet.tool.HttpIpTool;
|
||||
import org.jiayunet.tool.server.RedisServerTool;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 登录服务
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class LoginServer {
|
||||
|
||||
@Value("${app.secret.token:youweiqingnian123}")
|
||||
private String secret;
|
||||
|
||||
@Value("${app.login.token.exceed_time:43200}")
|
||||
private int tokenExceedTime;
|
||||
|
||||
@Value("${app.login.device_online_quantity:5}")
|
||||
private int deviceOnlineQuantity;
|
||||
|
||||
@Autowired
|
||||
private SmsServer smsServer;
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisServerTool redisServerTool;
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
*/
|
||||
public boolean sendCode(String mobileNumber) {
|
||||
Assert.hasText(mobileNumber, "手机号不能为空");
|
||||
|
||||
UniversalSmsVariable variable = new UniversalSmsVariable();
|
||||
String code = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
|
||||
variable.setCode(code);
|
||||
|
||||
return smsServer.send(mobileNumber, SmsTemplateEnum.UNIVERSAL, variable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信验证码登录
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public LoginVo smsLogin(String mobileNumber, String code, HttpServletRequest request, HttpServletResponse response) {
|
||||
Assert.hasText(mobileNumber, "手机号不能为空");
|
||||
Assert.hasText(code, "验证码不能为空");
|
||||
|
||||
// 校验验证码
|
||||
boolean verified = smsServer.verify(mobileNumber, SmsTemplateEnum.UNIVERSAL, code);
|
||||
if (!verified) {
|
||||
throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR, "验证码错误或已过期");
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getMobileNumber, mobileNumber));
|
||||
|
||||
// 用户不存在则自动注册
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
user.setMobileNumber(mobileNumber);
|
||||
user.setNick("用户" + mobileNumber.substring(mobileNumber.length() - 4));
|
||||
user.setStatus(0);
|
||||
user.setIsDelete(0L);
|
||||
user.setCreateTime(Instant.now());
|
||||
userMapper.insert(user);
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (user.getStatus() != null && user.getStatus() == 1) {
|
||||
throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR, "账号已被禁用");
|
||||
}
|
||||
|
||||
// 生成JWT
|
||||
String uuId = UUID.randomUUID().toString().replace("-", "");
|
||||
Algorithm algorithm = Algorithm.HMAC256(secret);
|
||||
String token = JWT.create()
|
||||
.withClaim("userId", user.getId())
|
||||
.withClaim("uuId", uuId)
|
||||
.sign(algorithm);
|
||||
|
||||
// 构建Redis登录信息
|
||||
String redisKey = PreRedisKeyName.LOGIN_TOKEN + user.getId();
|
||||
RedisLoginTokenInfo info = redisServerTool.get(redisKey, RedisLoginTokenInfo.class);
|
||||
if (info == null) {
|
||||
info = new RedisLoginTokenInfo();
|
||||
info.setUserId(user.getId());
|
||||
info.setAuthority(new ArrayList<>());
|
||||
info.setRole(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 过滤过期设备
|
||||
List<RedisLoginTokenInfo.LoginDevice> devices = info.getLoginDevices();
|
||||
if (devices == null) {
|
||||
devices = new ArrayList<>();
|
||||
}
|
||||
long expireMillis = System.currentTimeMillis() - tokenExceedTime * 1000L;
|
||||
devices = devices.stream()
|
||||
.filter(d -> d.getLastLoginTime().isAfter(Instant.ofEpochMilli(expireMillis)))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 超过设备上限,移除最早的
|
||||
while (devices.size() >= deviceOnlineQuantity) {
|
||||
devices.stream().min(Comparator.comparing(RedisLoginTokenInfo.LoginDevice::getLastLoginTime))
|
||||
.ifPresent(devices::remove);
|
||||
}
|
||||
|
||||
// 添加当前设备
|
||||
RedisLoginTokenInfo.LoginDevice device = new RedisLoginTokenInfo.LoginDevice();
|
||||
device.setUuId(uuId);
|
||||
device.setLastLoginTime(Instant.now());
|
||||
device.setLoginIp(HttpIpTool.gteRealIP(request));
|
||||
devices.add(device);
|
||||
|
||||
info.setLoginDevices(devices);
|
||||
redisServerTool.set(redisKey, info, tokenExceedTime, TimeUnit.SECONDS);
|
||||
|
||||
// 清除验证码
|
||||
smsServer.clear(mobileNumber, SmsTemplateEnum.UNIVERSAL);
|
||||
|
||||
// 设置Cookie
|
||||
Cookie cookie = new Cookie("Token", token);
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(tokenExceedTime);
|
||||
response.addCookie(cookie);
|
||||
|
||||
return new LoginVo(user.getId(), user.getNick());
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ spring:
|
||||
# 电子邮箱
|
||||
email:
|
||||
status: close
|
||||
account: ${EMAIL_ACCOUNT:sim18502043706@163.com}
|
||||
authorization: ${EMAIL_AUTHORIZATION:CHBCVPYGFZCSUNCP}
|
||||
account: ${EMAIL_ACCOUNT:xxx@163.com}
|
||||
authorization: ${EMAIL_AUTHORIZATION:123456}
|
||||
|
||||
# 微信支付
|
||||
wx_pay:
|
||||
@@ -44,20 +44,20 @@ app:
|
||||
token:
|
||||
exceed_time: 129600
|
||||
#设备在线数量
|
||||
device_online_quantity: 10
|
||||
device_online_quantity: 2
|
||||
|
||||
# 短信
|
||||
sms:
|
||||
service_provider: aliyun
|
||||
aliyun:
|
||||
access_key_id: LTAI5tGRFuJmt6nrxRqZYC7z
|
||||
access_key_secret: xLC3kNSmCvEtsc9j4O8g3rOs70QjZQ
|
||||
access_key_id: LTAI5tJBVUUJhB7yp14UDzVf
|
||||
access_key_secret: Opf0iO5FKNrdwI63DPhXazW7utAGTj
|
||||
|
||||
oss:
|
||||
service_provider: aliyun
|
||||
aliyun:
|
||||
access_key_id: LTAI5tGRFuJmt6nrxRqZYC7z
|
||||
access_key_secret: xLC3kNSmCvEtsc9j4O8g3rOs70QjZQ
|
||||
access_key_id: LTAI5tEdLKKQUKhTyUpfH5Mk
|
||||
access_key_secret: RjUdTrq0V5qA4b3BUElNhXqs3ZLp5k
|
||||
|
||||
# 防刷配置 20秒 20次
|
||||
prevent_replay:
|
||||
|
||||
Reference in New Issue
Block a user