diff --git a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java index 9115e806..c81677e9 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java @@ -6,12 +6,14 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Component; import jakarta.annotation.Resource; +import xiaozhi.common.utils.ResourcesUtils; /** * Redis工具类 @@ -23,6 +25,9 @@ public class RedisUtils { @Resource private RedisTemplate redisTemplate; + @Autowired + private ResourcesUtils resourceUtils; + /** * 默认过期时长为24小时,单位:秒 */ @@ -40,6 +45,24 @@ public class RedisUtils { */ public final static long NOT_EXPIRE = -1L; + public Long increment(String key, long expire) { + Long increment = redisTemplate.opsForValue().increment(key, 1L); + if (expire != NOT_EXPIRE) { + expire(key, expire); + } + return increment; + } + + public Long increment(String key) { + return redisTemplate.opsForValue().increment(key, 1L); + } + + public Long decrement(String key) { + return redisTemplate.opsForValue().decrement(key, 1L); + } + + + public void set(String key, Object value, long expire) { redisTemplate.opsForValue().set(key, value); if (expire != NOT_EXPIRE) { @@ -134,7 +157,7 @@ public class RedisUtils { */ public void emptyAll() { // Lua 脚本 FLUSHALL是redis清空所有库的命令 - String luaScript ="redis.call('FLUSHALL')"; + String luaScript =resourceUtils.loadString("lua/emptyAll.lua"); // 创建 DefaultRedisScript 对象 DefaultRedisScript redisScript = new DefaultRedisScript<>(); @@ -147,4 +170,26 @@ public class RedisUtils { } + /** + * 获取在redis指定key的值,如果值为空,着设置key的默认值 + * @param key redis的key + * @param defaultValue 默认值 + * @param expiresInSecond 过期时间 + * @return 返回key的值 + */ + public String getKeyOrCreate(String key, String defaultValue,Long expiresInSecond) { + // Lua 脚本 + String luaScript = resourceUtils.loadString("lua/getKeyOrCreate.lua"); + + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(luaScript); + redisScript.setResultType(String.class); + + // 执行 Lua 脚本 + List keys = Collections.singletonList(key); + return redisTemplate.execute(redisScript, keys, defaultValue,expiresInSecond); + } + + + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java new file mode 100644 index 00000000..49d9a727 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/ResourcesUtils.java @@ -0,0 +1,44 @@ +package xiaozhi.common.utils; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; +import xiaozhi.common.exception.RenException; + + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * 资源处理工具 + */ +@AllArgsConstructor +@Slf4j +@Component +public class ResourcesUtils { + private ResourceLoader resourceLoader; + + /** + * 读取资源,返回字符串 + * @param fileName 资源路径:resources下开始 + * @return 字符串 + */ + public String loadString(String fileName) { + Resource resource = resourceLoader.getResource("classpath:" + fileName); + StringBuilder luaScriptBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(resource.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + luaScriptBuilder.append(line).append("\n"); + } + } catch (IOException e){ + log.error("方法:loadString()读取资源失败--{}",e.getMessage()); + throw new RenException("读取资源失败"); + } + return luaScriptBuilder.toString(); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java index 663adca0..c015240f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java @@ -84,6 +84,7 @@ public class ShiroConfig { filterMap.put("/user/login", "anon"); filterMap.put("/user/pub-config", "anon"); filterMap.put("/user/register", "anon"); + filterMap.put("/user/retrieve-password", "anon"); // 将config路径使用server服务过滤器 filterMap.put("/config/**", "server"); filterMap.put("/agent/chat-history/report", "server"); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java index d0d84e66..47fd7b1b 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java @@ -174,19 +174,19 @@ public class LoginController { // 验证用户是否是手机号码 boolean validPhone = ValidatorUtils.isValidPhone(dto.getPhone()); if (!validPhone) { - throw new RenException("用户名不是手机号码,无法提供找回密码服务"); + throw new RenException("输入的手机号码格式不正确"); } // 按照用户名获取用户 SysUserDTO userDTO = sysUserService.getByUsername(dto.getPhone()); if (userDTO == null) { - throw new RenException("用户名或验证码错误"); + throw new RenException("输入的手机号码未注册"); } // 验证短信验证码是否正常 boolean validate = captchaService.validateSMSValidateCode(dto.getPhone(), dto.getCode(), false); // 判断是否通过验证 if (!validate) { - throw new RenException("用户名或验证码错误"); + throw new RenException("输入的手机验证码错误"); } sysUserService.changePasswordDirectly(userDTO.getId(), dto.getPassword()); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java index 09c8a185..c6f8f490 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/impl/CaptchaServiceImpl.java @@ -79,7 +79,10 @@ public class CaptchaServiceImpl implements CaptchaService { public void sendSMSValidateCode(String phone) { // 检查发送间隔 String lastSendTimeKey = RedisKeys.getSMSLastSendTimeKey(phone); - String lastSendTime = (String) redisUtils.get(lastSendTimeKey); + // 获取是否发送过,如果没有设置最后发送时间(60秒) + String lastSendTime = redisUtils + .getKeyOrCreate(lastSendTimeKey, + String.valueOf(System.currentTimeMillis()), 60L); if (lastSendTime != null) { long lastSendTimeLong = Long.parseLong(lastSendTime); long currentTime = System.currentTimeMillis(); @@ -114,15 +117,11 @@ public class CaptchaServiceImpl implements CaptchaService { // 设置验证码 setCache(key, validateCodes); - // 设置最后发送时间(60秒) - redisUtils.set(lastSendTimeKey, String.valueOf(System.currentTimeMillis()), 60); - // 更新今日发送次数 if (todayCount == 0) { - // 如果是今天第一次发送,设置24小时过期 - redisUtils.set(todayCountKey, 1, RedisUtils.DEFAULT_EXPIRE); + redisUtils.increment(todayCountKey, RedisUtils.DEFAULT_EXPIRE); } else { - redisUtils.set(todayCountKey, todayCount + 1, RedisUtils.DEFAULT_EXPIRE); + redisUtils.increment(todayCountKey); } // 发送验证码短信 diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java index b39b24c8..e0ebb523 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java @@ -10,6 +10,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.RenException; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; import xiaozhi.modules.sms.service.SmsService; import xiaozhi.modules.sys.service.SysParamsService; @@ -18,6 +20,7 @@ import xiaozhi.modules.sys.service.SysParamsService; @Slf4j public class ALiYunSmsService implements SmsService { private final SysParamsService sysParamsService; + private final RedisUtils redisUtils; @Override public void sendVerificationCodeSms(String phone, String VerificationCode) { @@ -37,6 +40,9 @@ public class ALiYunSmsService implements SmsService { SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime); log.info("发送短信响应的requestID: {}", sendSmsResponse.getBody().getRequestId()); } catch (Exception e) { + // 如果发送失败了退还这次发送数 + String todayCountKey = RedisKeys.getSMSTodayCountKey(phone); + redisUtils.delete(todayCountKey); // 错误 message log.error(e.getMessage()); throw new RenException("短信发送失败"); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java index 2187a8b4..a72b94f4 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysParamsServiceImpl.java @@ -1,9 +1,6 @@ package xiaozhi.modules.sys.service.impl; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -82,7 +79,7 @@ public class SysParamsServiceImpl extends BaseServiceImpl list = new ArrayList(); + list.add(Constant.SysMSMParam.SYSTEM_SMS_MAX_SEND_COUNT.getValue()); + list.add(Constant.SysMSMParam.ALIYUN_SMS_ACCESS_KEY_ID.getValue()); + list.add(Constant.SysMSMParam.ALIYUN_SMS_ACCESS_KEY_SECRET.getValue()); + list.add(Constant.SysMSMParam.ALIYUN_SMS_SIGN_NAME.getValue()); + list.add(Constant.SysMSMParam.ALIYUN_SMS_SMS_CODE_TEMPLATE_CODE.getValue()); + StringBuilder str = new StringBuilder(); + list.forEach( item -> { + if (!StringUtils.isNoneBlank(item)) { + str.append(",").append(item); + } + }); + if (!str.isEmpty()) { + String promptStr = "%s这些参数不可以为空"; + String substring = str.substring(1, str.length()); + throw new RenException(promptStr.formatted(substring)); + } + return true; + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java index 54048fe0..9f35836f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java @@ -133,6 +133,10 @@ public class SysUserServiceImpl extends BaseServiceImpl 0 then + redis.call('EXPIRE', KEYS[1], expireTime) + end +end +return value \ No newline at end of file diff --git a/main/manager-web/src/apis/module/user.js b/main/manager-web/src/apis/module/user.js index 1ad28b2e..6c16c3e6 100755 --- a/main/manager-web/src/apis/module/user.js +++ b/main/manager-web/src/apis/module/user.js @@ -169,4 +169,28 @@ export default { }); }).send(); }, + // 找回用户密码 + retrievePassword(passwordData, callback, failCallback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/user/retrieve-password`) + .method('PUT') + .data({ + phone: passwordData.phone, + code: passwordData.code, + password: passwordData.password + }) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + RequestService.clearRequestTime(); + failCallback(err); + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.retrievePassword(passwordData, callback, failCallback); + }); + }).send() + } } diff --git a/main/manager-web/src/router/index.js b/main/manager-web/src/router/index.js index a19b4ec6..49ee7e4c 100644 --- a/main/manager-web/src/router/index.js +++ b/main/manager-web/src/router/index.js @@ -39,6 +39,13 @@ const routes = [ return import('../views/register.vue') } }, + { + path: '/retrieve-password', + name: 'RetrievePassword', + component: function () { + return import('../views/retrievePassword.vue') + } + }, // 设备管理页面路由 { path: '/device-management', diff --git a/main/manager-web/src/views/login.vue b/main/manager-web/src/views/login.vue index f082721e..e31d5180 100644 --- a/main/manager-web/src/views/login.vue +++ b/main/manager-web/src/views/login.vue @@ -56,12 +56,13 @@
新用户注册
+
忘记密码?
-