Bladeren bron

add 增加 短信登录 与 小程序登录 示例

疯狂的狮子Li 3 jaren geleden
bovenliggende
commit
4a353896e3

+ 34 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@@ -7,6 +7,7 @@ import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.domain.entity.SysMenu;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.core.domain.model.LoginBody;
+import com.ruoyi.common.core.domain.model.SmsLoginBody;
 import com.ruoyi.common.helper.LoginHelper;
 import com.ruoyi.system.domain.vo.RouterVo;
 import com.ruoyi.system.service.ISysMenuService;
@@ -22,6 +23,7 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.validation.constraints.NotBlank;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -60,6 +62,38 @@ public class SysLoginController {
         return R.ok(ajax);
     }
 
+    /**
+     * 短信登录(示例)
+     *
+     * @param smsLoginBody 登录信息
+     * @return 结果
+     */
+    @ApiOperation("短信登录(示例)")
+    @PostMapping("/smsLogin")
+    public R<Map<String, Object>> smsLogin(@Validated @RequestBody SmsLoginBody smsLoginBody) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.smsLogin(smsLoginBody.getPhonenumber(), smsLoginBody.getSmsCode());
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
+    /**
+     * 小程序登录(示例)
+     *
+     * @param xcxCode 小程序code
+     * @return 结果
+     */
+    @ApiOperation("短信登录(示例)")
+    @PostMapping("/xcxLogin")
+    public R<Map<String, Object>> xcxLogin(@NotBlank(message = "{xcx.code.not.blank}") String xcxCode) {
+        Map<String, Object> ajax = new HashMap<>();
+        // 生成令牌
+        String token = loginService.xcxLogin(xcxCode);
+        ajax.put(Constants.TOKEN, token);
+        return R.ok(ajax);
+    }
+
     @ApiOperation("登出方法")
     @PostMapping("/logout")
     public R<Void> logout() {

+ 5 - 0
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
 user.password.not.valid=* 5-50个字符
 user.email.not.valid=邮箱格式错误
+user.phonenumber.not.blank=用户手机号不能为空
 user.mobile.phone.number.not.valid=手机号格式错误
 user.login.success=登录成功
 user.register.success=注册成功
@@ -38,3 +39,7 @@ no.export.permission=您没有导出数据的权限,请联系管理员添加
 no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
 repeat.submit.message=不允许重复提交,请稍候再试
 rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码错误次数过多,帐户锁定{0}分钟
+xcx.code.not.blank=小程序code不能为空

+ 5 - 0
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties

@@ -18,6 +18,7 @@ user.password.not.blank=Password cannot be empty
 user.password.length.valid=Password length must be between {min} and {max} characters
 user.password.not.valid=* 5-50 characters
 user.email.not.valid=Mailbox format error
+user.phonenumber.not.blank=Phone number cannot be blank
 user.mobile.phone.number.not.valid=Phone number format error
 user.login.success=Login successful
 user.register.success=Register successful
@@ -38,3 +39,7 @@ no.export.permission=You do not have permission to export data,please contact
 no.view.permission=You do not have permission to view data,please contact your administrator to add permissions [{0}]
 repeat.submit.message=Repeat submit is not allowed, please try again later
 rate.limiter.message=Visit too frequently, please try again later
+sms.code.not.blank=Sms code cannot be blank
+sms.code.retry.limit.count=Sms code input error {0} times
+sms.code.retry.limit.exceed=Too many sms code errors, account locked for {0} minutes
+xcx.code.not.blank=Mini program code cannot be blank

+ 5 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
 user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
 user.password.not.valid=* 5-50个字符
 user.email.not.valid=邮箱格式错误
+user.phonenumber.not.blank=用户手机号不能为空
 user.mobile.phone.number.not.valid=手机号格式错误
 user.login.success=登录成功
 user.register.success=注册成功
@@ -38,3 +39,7 @@ no.export.permission=您没有导出数据的权限,请联系管理员添加
 no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}]
 repeat.submit.message=不允许重复提交,请稍候再试
 rate.limiter.message=访问过于频繁,请稍候再试
+sms.code.not.blank=短信验证码不能为空
+sms.code.retry.limit.count=短信验证码输入错误{0}次
+sms.code.retry.limit.exceed=短信验证码错误次数过多,帐户锁定{0}分钟
+xcx.code.not.blank=小程序code不能为空

+ 33 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java

@@ -0,0 +1,33 @@
+package com.ruoyi.common.core.domain.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * 短信登录对象
+ *
+ * @author Lion Li
+ */
+
+@Data
+@ApiModel("短信登录对象")
+public class SmsLoginBody {
+
+    /**
+     * 用户名
+     */
+    @NotBlank(message = "{user.phonenumber.not.blank}")
+    @ApiModelProperty(value = "用户手机号")
+    private String phonenumber;
+
+    /**
+     * 用户密码
+     */
+    @NotBlank(message = "{sms.code.not.blank}")
+    @ApiModelProperty(value = "短信验证码")
+    private String smsCode;
+
+}

+ 6 - 1
ruoyi-common/src/main/java/com/ruoyi/common/enums/DeviceType.java

@@ -21,7 +21,12 @@ public enum DeviceType {
     /**
      * app端
      */
-    APP("app");
+    APP("app"),
+
+    /**
+     * 小程序端
+     */
+    XCX("xcx");
 
     private final String device;
 }

+ 8 - 0
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java

@@ -68,6 +68,14 @@ public interface SysUserMapper extends BaseMapperPlus<SysUserMapper, SysUser, Sy
      */
     SysUser selectUserByUserName(String userName);
 
+    /**
+     * 通过手机号查询用户
+     *
+     * @param phonenumber 手机号
+     * @return 用户对象信息
+     */
+    SysUser selectUserByPhonenumber(String phonenumber);
+
     /**
      * 通过用户ID查询用户
      *

+ 8 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysUserService.java

@@ -48,6 +48,14 @@ public interface ISysUserService {
      */
     SysUser selectUserByUserName(String userName);
 
+    /**
+     * 通过手机号查询用户
+     *
+     * @param phonenumber 手机号
+     * @return 用户对象信息
+     */
+    SysUser selectUserByPhonenumber(String phonenumber);
+
     /**
      * 通过用户ID查询用户
      *

+ 104 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java

@@ -87,6 +87,8 @@ public class SysLoginService {
 
         // 登录成功 清空错误次数
         RedisUtils.deleteObject(Constants.LOGIN_ERROR + username);
+
+        // 此处可根据登录用户的数据不同 自行创建 loginUser
         LoginUser loginUser = buildLoginUser(user);
         // 生成token
         LoginHelper.loginByDevice(loginUser, DeviceType.PC);
@@ -96,10 +98,80 @@ public class SysLoginService {
         return StpUtil.getTokenValue();
     }
 
+    public String smsLogin(String phonenumber, String smsCode) {
+        // 通过手机号查找用户
+        SysUser user = loadUserByPhonenumber(phonenumber);
+
+        HttpServletRequest request = ServletUtils.getRequest();
+        // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
+        Integer errorNumber = RedisUtils.getCacheObject(Constants.LOGIN_ERROR + user.getUserName());
+        // 锁定时间内登录 则踢出
+        if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(Constants.LOGIN_ERROR_NUMBER)) {
+            asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_FAIL, MessageUtils.message("sms.code.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME), request);
+            throw new UserException("sms.code.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME);
+        }
+
+        if (!validateSmsCode(phonenumber, smsCode)) {
+            // 是否第一次
+            errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
+            // 达到规定错误次数 则锁定登录
+            if (errorNumber.equals(Constants.LOGIN_ERROR_NUMBER)) {
+                RedisUtils.setCacheObject(Constants.LOGIN_ERROR + user.getUserName(), errorNumber, Constants.LOGIN_ERROR_LIMIT_TIME, TimeUnit.MINUTES);
+                asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_FAIL, MessageUtils.message("sms.code.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME), request);
+                throw new UserException("sms.code.retry.limit.exceed", Constants.LOGIN_ERROR_LIMIT_TIME);
+            } else {
+                // 未达到规定错误次数 则递增
+                RedisUtils.setCacheObject(Constants.LOGIN_ERROR + user.getUserName(), errorNumber);
+                asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_FAIL, MessageUtils.message("sms.code.retry.limit.count", errorNumber), request);
+                throw new UserException("sms.code.retry.limit.count", errorNumber);
+            }
+        }
+
+        // 登录成功 清空错误次数
+        RedisUtils.deleteObject(Constants.LOGIN_ERROR + user.getUserName());
+
+        // 此处可根据登录用户的数据不同 自行创建 loginUser
+        LoginUser loginUser = buildLoginUser(user);
+        // 生成token
+        LoginHelper.loginByDevice(loginUser, DeviceType.APP);
+
+        asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
+        recordLoginInfo(user.getUserId(), user.getUserName());
+        return StpUtil.getTokenValue();
+    }
+
+
+    public String xcxLogin(String xcxCode) {
+        HttpServletRequest request = ServletUtils.getRequest();
+        // xcxCode 为 小程序调用 wx.login 授权后获取
+        // todo 以下自行实现
+        // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
+        String openid = "";
+        SysUser user = loadUserByOpenid(openid);
+
+        // 此处可根据登录用户的数据不同 自行创建 loginUser
+        LoginUser loginUser = buildLoginUser(user);
+        // 生成token
+        LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
+
+        asyncService.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"), request);
+        recordLoginInfo(user.getUserId(), user.getUserName());
+        return StpUtil.getTokenValue();
+    }
+
+
     public void logout(String loginName) {
         asyncService.recordLogininfor(loginName, Constants.LOGOUT, MessageUtils.message("user.logout.success"), ServletUtils.getRequest());
     }
 
+    /**
+     * 校验短信验证码
+     */
+    private boolean validateSmsCode(String phonenumber, String smsCode) {
+        // todo 此处使用手机号查询redis验证码与参数验证码是否一致 用户自行实现
+        return true;
+    }
+
     /**
      * 校验验证码
      *
@@ -136,6 +208,38 @@ public class SysLoginService {
         return user;
     }
 
+    private SysUser loadUserByPhonenumber(String phonenumber) {
+        SysUser user = userService.selectUserByPhonenumber(phonenumber);
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", phonenumber);
+            throw new UserException("user.not.exists", phonenumber);
+        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
+            log.info("登录用户:{} 已被删除.", phonenumber);
+            throw new UserException("user.password.delete", phonenumber);
+        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", phonenumber);
+            throw new UserException("user.blocked", phonenumber);
+        }
+        return user;
+    }
+
+    private SysUser loadUserByOpenid(String openid) {
+        // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
+        // todo 自行实现 userService.selectUserByOpenid(openid);
+        SysUser user = new SysUser();
+        if (ObjectUtil.isNull(user)) {
+            log.info("登录用户:{} 不存在.", openid);
+            // todo 用户不存在 业务逻辑自行实现
+        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
+            log.info("登录用户:{} 已被删除.", openid);
+            // todo 用户已被删除 业务逻辑自行实现
+        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            log.info("登录用户:{} 已被停用.", openid);
+            // todo 用户已被停用 业务逻辑自行实现
+        }
+        return user;
+    }
+
     /**
      * 构建登录用户
      */

+ 11 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java

@@ -137,6 +137,17 @@ public class SysUserServiceImpl implements ISysUserService {
         return baseMapper.selectUserByUserName(userName);
     }
 
+    /**
+     * 通过手机号查询用户
+     *
+     * @param phonenumber 手机号
+     * @return 用户对象信息
+     */
+    @Override
+    public SysUser selectUserByPhonenumber(String phonenumber) {
+        return baseMapper.selectUserByPhonenumber(phonenumber);
+    }
+
     /**
      * 通过用户ID查询用户
      *

+ 5 - 0
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml

@@ -121,6 +121,11 @@
         where u.del_flag = '0' and u.user_name = #{userName}
     </select>
 
+    <select id="selectUserByPhonenumber" parameterType="String" resultMap="SysUserResult">
+        <include refid="selectUserVo"/>
+        where u.del_flag = '0' and u.phonenumber = #{phonenumber}
+    </select>
+
     <select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
         <include refid="selectUserVo"/>
         where u.del_flag = '0' and u.user_id = #{userId}