短信登录

This commit is contained in:
syruan 2023-12-12 17:31:15 +08:00 committed by syruan
parent 81da0279d0
commit e2695c05a2
19 changed files with 404 additions and 61 deletions

View File

@ -33,6 +33,18 @@ public interface RemoteUserService
@GetMapping("/user/info/{username}")
public R<LoginUser> getUserInfo(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 通过手机号查询用户信息
*
* @param phone 用户名
* @param source 请求来源
* @return 结果
*/
@GetMapping("/user/info/{phone}")
public R<LoginUser> getUserInfoByPhone(@PathVariable("phone") String phone, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 注册用户信息
*

View File

@ -28,20 +28,22 @@ public class RemoteUserFallbackFactory implements FallbackFactory<RemoteUserServ
return new RemoteUserService()
{
@Override
public R<LoginUser> getUserInfo(String username, String source)
{
public R<LoginUser> getUserInfo(String username, String source) {
return R.fail("获取用户失败:" + throwable.getMessage());
}
@Override
public R<Boolean> registerUserInfo(SysUser sysUser, String source)
{
public R<LoginUser> getUserInfoByPhone(String phone, String source) {
return R.fail("获取用户失败:" + throwable.getMessage());
}
@Override
public R<Boolean> registerUserInfo(SysUser sysUser, String source) {
return R.fail("注册用户失败:" + throwable.getMessage());
}
@Override
public R<List<SysUser>> getUserList(SysUser sysUser, String source)
{
public R<List<SysUser>> getUserList(SysUser sysUser, String source) {
return R.fail("获取用户失败:" + throwable.getMessage());
}
};

View File

@ -51,6 +51,16 @@
<groupId>com.bonus.sgzb</groupId>
<artifactId>sgzb-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.bonus.sgzb</groupId>
<artifactId>sgzb-modules-system</artifactId>
<version>3.6.3</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

@ -32,14 +32,21 @@ public class TokenController
private SysLoginService sysLoginService;
@PostMapping("login")
public R<?> login(@RequestBody LoginBody form)
{
public R<?> login(@RequestBody LoginBody form) {
// 用户登录
LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword());
// 获取登录token
return R.ok(tokenService.createToken(userInfo));
}
@PostMapping("loginCode")
public R<?> loginCode(@RequestBody LoginBody form) {
// 用户登录
LoginUser userInfo = sysLoginService.loginCode(form.getPhone(), form.getCode());
// 获取登录token
return R.ok(tokenService.createToken(userInfo));
}
@DeleteMapping("logout")
public R<?> logout(HttpServletRequest request)
{

View File

@ -1,12 +1,14 @@
package com.bonus.sgzb.auth.form;
import lombok.Data;
/**
* 用户登录对象
*
* @author ruoyi
*/
public class LoginBody
{
@Data
public class LoginBody {
/**
* 用户名
*/
@ -17,23 +19,14 @@ public class LoginBody
*/
private String password;
public String getUsername()
{
return username;
}
/**
* 验证码
*/
private String code;
public void setUsername(String username)
{
this.username = username;
}
/**
* 手机号码
*/
private String phone;
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}

View File

@ -1,5 +1,6 @@
package com.bonus.sgzb.auth.service;
import com.bonus.sgzb.system.service.ISysSmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.bonus.sgzb.common.core.constant.CacheConstants;
@ -18,6 +19,8 @@ import com.bonus.sgzb.system.api.RemoteUserService;
import com.bonus.sgzb.system.api.domain.SysUser;
import com.bonus.sgzb.system.api.model.LoginUser;
import javax.annotation.Resource;
/**
* 登录校验方法
*
@ -26,12 +29,15 @@ import com.bonus.sgzb.system.api.model.LoginUser;
@Component
public class SysLoginService
{
@Autowired
@Resource
private RemoteUserService remoteUserService;
@Autowired
private SysPasswordService passwordService;
@Resource
private ISysSmsService smsService;
@Autowired
private SysRecordLogService recordLogService;
@ -39,7 +45,7 @@ public class SysLoginService
private RedisService redisService;
/**
* 登录
* 用户名密码登录
*/
public LoginUser login(String username, String password)
{
@ -64,12 +70,7 @@ public class SysLoginService
throw new ServiceException("用户名不在指定范围");
}
// IP黑名单校验
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
recordLogService.recordLogininfor(username, Constants.LOGIN_FAIL, "很遗憾访问IP已被列入系统黑名单");
throw new ServiceException("很遗憾访问IP已被列入系统黑名单");
}
isBlackIp(username);
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
@ -101,6 +102,67 @@ public class SysLoginService
return userInfo;
}
/**
* 手机号验证码登录
*/
public LoginUser loginCode(String phone, String code) {
// 手机号或验证码为空 错误
if (StringUtils.isAnyBlank(phone, code)) {
recordLogService.recordLogininfor(phone, Constants.LOGIN_FAIL, "手机号/验证码必须填写");
throw new ServiceException("手机号/验证码必须填写");
}
// 验证码不在指定范围内 错误
if (code.length() != UserConstants.CODE_MIN_LENGTH_LOGIN) {
recordLogService.recordLogininfor(code, Constants.LOGIN_FAIL, "验证码长度不在指定范围");
throw new ServiceException("验证码长度不在指定范围");
}
// 手机号码不在指定范围内 错误
if (phone.length() != UserConstants.PHONE_DEFAULT_LENGTH_LOGIN) {
recordLogService.recordLogininfor(phone, Constants.LOGIN_FAIL, "手机号码长度不在指定范围");
throw new ServiceException("手机号码长度不在指定范围");
}
// IP黑名单校验
isBlackIp(phone);
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfoByPhone(phone, SecurityConstants.INNER);
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) {
recordLogService.recordLogininfor(phone, Constants.LOGIN_FAIL, "登录手机号不存在");
throw new ServiceException("登录手机号:" + phone + " 不存在");
}
if (R.FAIL == userResult.getCode()) {
throw new ServiceException(userResult.getMsg());
}
LoginUser userInfo = userResult.getData();
SysUser user = userResult.getData().getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
recordLogService.recordLogininfor(phone, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new ServiceException("对不起,您的账号:" + phone + " 已被删除");
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
recordLogService.recordLogininfor(phone, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new ServiceException("对不起,您的账号:" + phone + " 已停用");
}
if (!smsService.checkCode(phone, code)) {
throw new ServiceException("对不起,您输入的验证码:" + code + " 不存在");
} else {
recordLogService.recordLogininfor(phone, Constants.LOGIN_SUCCESS, "登录成功");
return userInfo;
}
}
private void isBlackIp(String phone) {
String blackStr = Convert.toStr(redisService.getCacheObject(CacheConstants.SYS_LOGIN_BLACKIPLIST));
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
recordLogService.recordLogininfor(phone, Constants.LOGIN_FAIL, "很遗憾访问IP已被列入系统黑名单");
throw new ServiceException("很遗憾访问IP已被列入系统黑名单");
}
}
public void logout(String loginName)
{
recordLogService.recordLogininfor(loginName, Constants.LOGOUT, "退出成功");

View File

@ -77,4 +77,14 @@ public class UserConstants
public static final int PASSWORD_MIN_LENGTH = 5;
public static final int PASSWORD_MAX_LENGTH = 20;
/**
* 验证码长度限制
*/
public static final int CODE_MIN_LENGTH_LOGIN = 6;
/**
* 手机号长度限制
*/
public static final int PHONE_DEFAULT_LENGTH_LOGIN = 11;
}

View File

@ -14,11 +14,11 @@ spring:
nacos:
discovery:
# 服务注册地址
server-addr: 10.40.92.153:8848
server-addr: 192.168.0.14:8848
namespace: sgzb_cloud_dev
config:
# 配置中心地址
server-addr: 10.40.92.153:8848
server-addr: 192.168.0.14:8848
namespace: sgzb_cloud_dev
# 配置文件格式
file-extension: yml

View File

@ -9,7 +9,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* 文件服务
* 结算服务
*
* @author ruoyi
*/
@ -22,7 +22,7 @@ public class SgzbSettlementApplication
public static void main(String[] args)
{
SpringApplication.run(SgzbSettlementApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 基础管理模块启动成功 ლ(´ڡ`ლ)゙ \n" +
System.out.println("(♥◠‿◠)ノ゙ 结算模块启动成功 ლ(´ڡ`ლ)゙ \n" +
" .-------. ____ __ \n" +
" | _ _ \\ \\ \\ / / \n" +
" | ( ' ) | \\ _. / ' \n" +

View File

@ -14,11 +14,11 @@ spring:
nacos:
discovery:
# 服务注册地址
server-addr: 10.40.92.153:8848
server-addr: 192.168.0.14:8848
namespace: sgzb_cloud_dev
config:
# 配置中心地址
server-addr: 10.40.92.153:8848
server-addr: 192.168.0.14:8848
namespace: sgzb_cloud_dev
# 配置文件格式
file-extension: yml

View File

@ -17,6 +17,13 @@
<dependencies>
<!-- 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
@ -82,6 +89,16 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
</dependency>
</dependencies>

View File

@ -0,0 +1,48 @@
package com.bonus.sgzb.system.controller;
import com.bonus.sgzb.common.core.web.controller.BaseController;
import com.bonus.sgzb.common.core.web.domain.AjaxResult;
import com.bonus.sgzb.system.service.ISysSmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* Description: 短信控制器
*
* @Author 阮世耀
* @Create 2023/12/11 15:46
* @Version 1.0
*/
@RestController
@RequestMapping("/sms")
public class SysSmsController extends BaseController {
@Resource
private ISysSmsService smsService;
@PostMapping("codeLogin")
public AjaxResult codeLogin(@RequestParam(value = "phone") String phone){
try {
return smsService.codeLogin(phone);
} catch (Exception e) {
return error(e.getMessage());
}
}
@PostMapping("send")
public AjaxResult send(@RequestParam(value = "phone") String phone, @RequestParam(value = "msg",required = false) String msg){
try {
return smsService.sendSms(phone, msg);
} catch (Exception e) {
return error(e.getMessage());
}
}
}

View File

@ -4,7 +4,14 @@ import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import com.bonus.sgzb.common.core.constant.Constants;
import com.bonus.sgzb.common.core.constant.SecurityConstants;
import com.bonus.sgzb.common.core.exception.ServiceException;
import com.bonus.sgzb.system.api.RemoteUserService;
import com.bonus.sgzb.system.service.*;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
@ -32,12 +39,6 @@ import com.bonus.sgzb.system.api.domain.SysDept;
import com.bonus.sgzb.system.api.domain.SysRole;
import com.bonus.sgzb.system.api.domain.SysUser;
import com.bonus.sgzb.system.api.model.LoginUser;
import com.bonus.sgzb.system.service.ISysConfigService;
import com.bonus.sgzb.system.service.ISysDeptService;
import com.bonus.sgzb.system.service.ISysPermissionService;
import com.bonus.sgzb.system.service.ISysPostService;
import com.bonus.sgzb.system.service.ISysRoleService;
import com.bonus.sgzb.system.service.ISysUserService;
/**
* 用户信息
@ -60,12 +61,18 @@ public class SysUserController extends BaseController
@Autowired
private ISysPostService postService;
@Autowired
private ISysSmsService smsService;
@Autowired
private ISysPermissionService permissionService;
@Autowired
private ISysConfigService configService;
@Resource
private RemoteUserService remoteUserService;
/**
* 获取用户列表
*/
@ -141,6 +148,7 @@ public class SysUserController extends BaseController
return R.ok(sysUserVo);
}
/**
* 注册用户信息
*/
@ -233,8 +241,7 @@ public class SysUserController extends BaseController
@RequiresPermissions("system:user:edit")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysUser user)
{
public AjaxResult edit(@Validated @RequestBody SysUser user) {
userService.checkUserAllowed(user);
userService.checkUserDataScope(user.getUserId());
if (!userService.checkUserNameUnique(user))
@ -274,8 +281,7 @@ public class SysUserController extends BaseController
@RequiresPermissions("system:user:edit")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@PutMapping("/resetPwd")
public AjaxResult resetPwd(@RequestBody SysUser user)
{
public AjaxResult resetPwd(@RequestBody SysUser user) {
userService.checkUserAllowed(user);
userService.checkUserDataScope(user.getUserId());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
@ -283,6 +289,35 @@ public class SysUserController extends BaseController
return toAjax(userService.resetPwd(user));
}
/**
* 根据手机验证码重制密码
*/
@RequiresPermissions("system:user:edit")
@Log(title = "用户管理", businessType = BusinessType.UPDATE)
@PutMapping("/resetPwdByCode")
public AjaxResult resetPwdByCode(String phone, String code, String password) {
if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code) || StringUtils.isEmpty(password)) {
return AjaxResult.error("参数错误");
}
if (smsService.checkCode(phone, code)) {
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfoByPhone(phone, SecurityConstants.INNER);
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData())) {
return AjaxResult.error("该手机号用户:" + phone + " 不存在");
}
if (R.FAIL == userResult.getCode()) {
throw new ServiceException(userResult.getMsg());
}
SysUser user = userResult.getData().getSysUser();
user.setPassword(SecurityUtils.encryptPassword(password));
user.setUpdateBy(SecurityUtils.getUsername());
return toAjax(userService.resetPwd(user));
} else {
return AjaxResult.error("验证码错误");
}
}
/**
* 状态修改
*/

View File

@ -36,7 +36,7 @@ public interface SysUserMapper
public List<SysUser> selectUnallocatedList(SysUser user);
/**
* 通过用户名查询用户
* 通过 用户名/手机号码 查询用户
*
* @param userName 用户名
* @return 用户对象信息

View File

@ -0,0 +1,32 @@
package com.bonus.sgzb.system.service;
import com.bonus.sgzb.common.core.web.domain.AjaxResult;
/**
* Description:
*
* @Author 阮世耀
* @Create 2023/12/11 15:50
* @Version 1.0
*/
public interface ISysSmsService {
/**
* 发送短信
*
* @param phone 手机号
* @param msg 内容
* @return 结果
*/
AjaxResult sendSms(String phone, String msg);
/**
* 发送短信
*
* @param phone 手机号
* @return 结果
*/
AjaxResult codeLogin(String phone);
boolean checkCode(String phone, String code);
}

View File

@ -3,6 +3,8 @@ package com.bonus.sgzb.system.service.impl;
import java.util.Collection;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bonus.sgzb.common.core.constant.CacheConstants;
@ -23,10 +25,10 @@ import com.bonus.sgzb.system.service.ISysConfigService;
@Service
public class SysConfigServiceImpl implements ISysConfigService
{
@Autowired
@Resource
private SysConfigMapper configMapper;
@Autowired
@Resource
private RedisService redisService;
/**

View File

@ -0,0 +1,112 @@
package com.bonus.sgzb.system.service.impl;
import cn.hutool.http.HttpRequest;
import com.alibaba.druid.util.StringUtils;
import com.bonus.sgzb.common.core.exception.ServiceException;
import com.bonus.sgzb.common.core.web.domain.AjaxResult;
import com.bonus.sgzb.common.redis.service.RedisService;
import com.bonus.sgzb.system.service.ISysSmsService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import static com.bonus.sgzb.common.core.web.domain.AjaxResult.success;
/**
* Description: 短信发送
*
* @Author 阮世耀
* @Create 2023/12/11 15:49
* @Version 1.0
*/
@Service
public class SysSmsServiceImpl implements ISysSmsService {
@Resource
private RedisService redisService;
@Override
public AjaxResult sendSms(String phone, String msg) {
return sendCodeByPhone(phone, msg);
}
@Override
public AjaxResult codeLogin(String phone) {
// 校验手机号码
return sendCodeByPhone(phone, null);
}
/**
* 发送验证码到手机
* @param phone 手机号码
* @return AjaxResult对象
*/
private AjaxResult sendCodeByPhone(String phone, String msg) {
// 校验手机号码
if (phone == null || phone.length() != 11) {
return AjaxResult.error("手机号码不正确");
}
String code = getSixBitCode();
// 校验验证码
if (code.length() != 6) {
return AjaxResult.error("验证码格式不正确");
}
// 发送短信
try {
String url = "http://api.ktsms.cn/sms_token?ddtkey=bonus&secretkey=KtyBns@Admin2023!";
String content = url + "&mobile=" + phone + "&content=【智慧仓储】您正在进行短信验证,验证码:" + code + "请在5分钟内完成验证。";
String body = HttpRequest.post(content).execute(false).body();
System.out.println("发送短信:" + phone + ",验证码:" + code + ",返回结果:" + body);
if (body == null || !body.contains("success")) {
return AjaxResult.error("发送失败");
}
// 存储验证码至Redis中键值为code_15588886157 , 有效期5时间颗粒度为MINUTES:分钟
redisService.setCacheObject("code_" + phone, code, 5L, TimeUnit.MINUTES);
return success("手机号:" + phone + ",用户登录验证码:" + code + ",返回结果:" + body);
} catch (Exception e) {
return AjaxResult.error("发送失败:" + e.getMessage());
}
}
/**
* 判断验证码是否存在Redis中
*
* @param phone 手机号码
* @param code 用户填入的验证码
* @return 校验结果
* @throws ServiceException 异常信息
*/
@Override
public boolean checkCode(String phone, String code) {
String redisCode = redisService.getCacheObject("code_" + phone);
if (StringUtils.isEmpty(redisCode)) {
throw new ServiceException("验证码失效", 403);
}
if (!StringUtils.equals(redisCode.split("_")[0], code)) {
throw new ServiceException("验证码错误", 401);
} else {
redisService.deleteObject("code_" + phone);
return true;
}
}
/**
* 随机生成6位验证码
*/
private String getSixBitCode() {
//随机数
StringBuilder sb = new StringBuilder();
Random rand = new Random();
for (int i = 0; i < 6; i++) {
sb.append(rand.nextInt(10));
}
return sb.toString();
}
}

View File

@ -107,8 +107,7 @@ public class SysUserServiceImpl implements ISysUserService
* @return 用户对象信息
*/
@Override
public SysUser selectUserByUserName(String userName)
{
public SysUser selectUserByUserName(String userName) {
return userMapper.selectUserByUserName(userName);
}

View File

@ -122,7 +122,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
<include refid="selectUserVo"/>
where u.user_name = #{userName} and u.del_flag = '0'
where u.user_name = #{userName} or u.phonenumber = #{userName}
and u.del_flag = '0'
LIMIT 1
</select>
<select id="selectUserById" parameterType="Long" resultMap="SysUserResult">