diff --git a/main/manager-api/pom.xml b/main/manager-api/pom.xml index d08e32f2..883295bf 100644 --- a/main/manager-api/pom.xml +++ b/main/manager-api/pom.xml @@ -28,6 +28,8 @@ 1.6.2 33.0.0-jre 4.20.0 + 4.1.0 + 3.4.0 @@ -37,6 +39,7 @@ jakarta ${shiro.version} + org.apache.shiro shiro-spring @@ -201,6 +204,18 @@ com.fasterxml.jackson.datatype jackson-datatype-jsr310 + + + com.aliyun + dysmsapi20170525 + ${aliyun-sms-version} + + + + com.squareup.okio + okio + ${okio-version} + diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java index b197c506..c6ec6761 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java +++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java @@ -153,6 +153,46 @@ public interface Constant { } } + /** + * 系统短信 + */ + enum SysMSMParam { + /** + * 阿里云授权keyID + */ + ALIYUN_SMS_ACCESS_KEY_ID("aliyun.sms.access_key_id"), + /** + * 阿里云授权密钥 + */ + ALIYUN_SMS_ACCESS_KEY_SECRET("aliyun.sms.access_key_secret"), + /** + * 阿里云短信签名 + */ + ALIYUN_SMS_SIGN_NAME("aliyun.sms.sign_name"), + /** + * 阿里云短信模板 + */ + ALIYUN_SMS_SMS_CODE_TEMPLATE_CODE("aliyun.sms.sms_code_template_code"), + /** + * 单号码最大短信发送条数 + */ + SYSTEM_SMS_MAX_SEND_COUNT("system.sms.max_send_count"), + /** + * 是否开启手机注册 + */ + SYSTEM_ENABLE_MOBILE_REGISTER("system.enable_mobile_register"); + + private String value; + + SysMSMParam(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + /** * 数据状态 */ diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java index c59a6a14..6a9af011 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/ErrorCode.java @@ -20,7 +20,7 @@ public interface ErrorCode { int ACCOUNT_DISABLE = 10005; int IDENTIFIER_NOT_NULL = 10006; int CAPTCHA_ERROR = 10007; - int SUB_MENU_EXIST = 10008; + int PHONE_NOT_NULL = 10008; int PASSWORD_ERROR = 10009; int SUPERIOR_DEPT_ERROR = 10011; diff --git a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java index 62487456..6daa3b2d 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java +++ b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java @@ -117,4 +117,26 @@ public class RedisKeys { public static String getAgentAudioIdKey(String uuid) { return "agent:audio:id:" + uuid; } + + /** + * 获取短信验证码的缓存key + */ + public static String getSMSValidateCodeKey(String phone) { + return "sms:Validate:Code:" + phone; + } + + /** + * 获取短信验证码最后发送时间的缓存key + */ + public static String getSMSLastSendTimeKey(String phone) { + return "sms:Validate:Code:" + phone + ":last_send_time"; + } + + /** + * 获取短信验证码今日发送次数的缓存key + */ + public static String getSMSTodayCountKey(String phone) { + return "sms:Validate:Code:" + phone + ":today_count"; + } + } diff --git a/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java b/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java index 9e31c964..364da0d9 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/validator/ValidatorUtils.java @@ -2,6 +2,7 @@ package xiaozhi.common.validator; import java.util.Locale; import java.util.Set; +import java.util.regex.Pattern; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; import org.springframework.context.i18n.LocaleContextHolder; @@ -45,4 +46,32 @@ public class ValidatorUtils { throw new RenException(constraint.getMessage()); } } + + /** + * 国际手机号正则表达式 + * 要求必须带国际区号,格式:+[国家代码][手机号] + * 例如: + * - +8613800138000 + * - +12345678900 + * - +447123456789 + */ + private static final String INTERNATIONAL_PHONE_REGEX = "^\\+[1-9]\\d{0,3}[1-9]\\d{4,14}$"; + + /** + * 校验手机号是否有效 + * 要求必须带国际区号,格式:+[国家代码][手机号] + * 例如:+8613800138000 + * + * @param phone 手机号 + * @return boolean + */ + public static boolean isValidPhone(String phone) { + if (phone == null || phone.isEmpty()) { + return false; + } + + // 验证必须带国际区号的手机号格式 + Pattern pattern = Pattern.compile(INTERNATIONAL_PHONE_REGEX); + return pattern.matcher(phone).matches(); + } } \ No newline at end of file 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 7875fa86..663adca0 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 @@ -80,6 +80,7 @@ public class ShiroConfig { filterMap.put("/doc.html", "anon"); filterMap.put("/favicon.ico", "anon"); filterMap.put("/user/captcha", "anon"); + filterMap.put("/user/smsVerification", "anon"); filterMap.put("/user/login", "anon"); filterMap.put("/user/pub-config", "anon"); filterMap.put("/user/register", "anon"); 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 7cbaa8af..b8186423 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 @@ -2,6 +2,7 @@ package xiaozhi.modules.security.controller; import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.springframework.web.bind.annotation.GetMapping; @@ -24,13 +25,18 @@ import xiaozhi.common.utils.Result; import xiaozhi.common.validator.AssertUtils; import xiaozhi.common.validator.ValidatorUtils; import xiaozhi.modules.security.dto.LoginDTO; +import xiaozhi.modules.security.dto.SmsVerificationDTO; import xiaozhi.modules.security.password.PasswordUtils; import xiaozhi.modules.security.service.CaptchaService; import xiaozhi.modules.security.service.SysUserTokenService; import xiaozhi.modules.security.user.SecurityUser; import xiaozhi.modules.sys.dto.PasswordDTO; +import xiaozhi.modules.sys.dto.RetrievePasswordDTO; import xiaozhi.modules.sys.dto.SysUserDTO; +import xiaozhi.modules.sys.service.SysDictDataService; +import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.service.SysUserService; +import xiaozhi.modules.sys.vo.SysDictDataItem; /** * 登录控制层 @@ -43,24 +49,43 @@ public class LoginController { private final SysUserService sysUserService; private final SysUserTokenService sysUserTokenService; private final CaptchaService captchaService; + private final SysParamsService sysParamsService; + private final SysDictDataService sysDictDataService; @GetMapping("/captcha") @Operation(summary = "验证码") public void captcha(HttpServletResponse response, String uuid) throws IOException { // uuid不能为空 AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL); - // 生成验证码 captchaService.create(response, uuid); } + @PostMapping("/smsVerification") + @Operation(summary = "短信验证码") + public Result smsVerification(@RequestBody SmsVerificationDTO dto) { + // 验证图形验证码 + boolean validate = captchaService.validate(dto.getCaptchaId(), dto.getCaptcha(), true); + if (!validate) { + throw new RenException("图形验证码错误"); + } + Boolean isMobileRegister = sysParamsService + .getValueObject(Constant.SysMSMParam.SYSTEM_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); + if (!isMobileRegister) { + throw new RenException("没有开启手机注册,没法使用短信验证码功能"); + } + // 发送短信验证码 + captchaService.sendSMSValidateCode(dto.getPhone()); + return new Result<>(); + } + @PostMapping("/login") @Operation(summary = "登录") public Result login(@RequestBody LoginDTO login) { // 验证是否正确输入验证码 - boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha()); + boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true); if (!validate) { - throw new RenException("验证码错误,请重新获取"); + throw new RenException("图形验证码错误,请重新获取"); } // 按照用户名获取用户 SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername()); @@ -81,11 +106,29 @@ public class LoginController { if (!sysUserService.getAllowUserRegister()) { throw new RenException("当前不允许普通用户注册"); } - // 验证是否正确输入验证码 - boolean validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha()); - if (!validate) { - throw new RenException("验证码错误,请重新获取"); + // 是否开启手机注册 + Boolean isMobileRegister = sysParamsService + .getValueObject(Constant.SysMSMParam.SYSTEM_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); + boolean validate; + if (isMobileRegister) { + // 验证用户是否是手机号码 + boolean validPhone = ValidatorUtils.isValidPhone(login.getUsername()); + if (!validPhone) { + throw new RenException("用户名不是手机号码,请重新输入"); + } + // 验证短信验证码是否正常 + validate = captchaService.validateSMSValidateCode(login.getUsername(), login.getMobileCaptcha(), false); + if (!validate) { + throw new RenException("手机验证码错误,请重新获取"); + } + } else { + // 验证是否正确输入验证码 + validate = captchaService.validate(login.getCaptchaId(), login.getCaptcha(), true); + if (!validate) { + throw new RenException("图形验证码错误,请重新获取"); + } } + // 按照用户名获取用户 SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername()); if (userDTO != null) { @@ -96,7 +139,6 @@ public class LoginController { userDTO.setPassword(login.getPassword()); sysUserService.save(userDTO); return new Result<>(); - } @GetMapping("/info") @@ -118,12 +160,50 @@ public class LoginController { return new Result<>(); } + @PutMapping("/retrieve-password") + @Operation(summary = "找回密码") + public Result retrievePassword(@RequestBody RetrievePasswordDTO dto) { + // 是否开启手机注册 + Boolean isMobileRegister = sysParamsService + .getValueObject(Constant.SysMSMParam.SYSTEM_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); + if (!isMobileRegister) { + throw new RenException("没有开启手机注册,没法使用找回密码功能"); + } + // 判断非空 + ValidatorUtils.validateEntity(dto); + // 验证用户是否是手机号码 + boolean validPhone = ValidatorUtils.isValidPhone(dto.getPhone()); + if (!validPhone) { + throw new RenException("用户名不是手机号码,无法提供找回密码服务"); + } + + // 按照用户名获取用户 + SysUserDTO userDTO = sysUserService.getByUsername(dto.getPhone()); + if (userDTO == null) { + throw new RenException("用户名或验证码错误"); + } + // 验证短信验证码是否正常 + boolean validate = captchaService.validateSMSValidateCode(dto.getPhone(), dto.getCode()); + // 判断是否通过验证 + if (!validate) { + throw new RenException("用户名或验证码错误"); + } + + sysUserService.changePasswordDirectly(userDTO.getId(), dto.getPassword()); + return new Result<>(); + } + @GetMapping("/pub-config") @Operation(summary = "公共配置") public Result> pubConfig() { Map config = new HashMap<>(); + config.put("enableMobileRegister", sysParamsService + .getValueObject(Constant.SysMSMParam.SYSTEM_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class)); config.put("version", Constant.VERSION); config.put("allowUserRegister", sysUserService.getAllowUserRegister()); + List list = sysDictDataService.getDictDataByType("MOBILE_AREA"); + config.put("mobileAreaList", list); + return new Result>().ok(config); } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java index cab143be..12b99d32 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/LoginDTO.java @@ -25,6 +25,9 @@ public class LoginDTO implements Serializable { @NotBlank(message = "{sysuser.captcha.require}") private String captcha; + @Schema(description = "手机验证码") + private String mobileCaptcha; + @Schema(description = "唯一标识") @NotBlank(message = "{sysuser.uuid.require}") private String captchaId; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/dto/SmsVerificationDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/SmsVerificationDTO.java new file mode 100644 index 00000000..425ef059 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/dto/SmsVerificationDTO.java @@ -0,0 +1,28 @@ +package xiaozhi.modules.security.dto; + +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 短信验证码请求DTO + */ +@Data +@Schema(description = "短信验证码请求") +public class SmsVerificationDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "手机号码") + @NotBlank(message = "{sysuser.username.require}") + private String phone; + + @Schema(description = "验证码") + @NotBlank(message = "{sysuser.captcha.require}") + private String captcha; + + @Schema(description = "唯一标识") + @NotBlank(message = "{sysuser.uuid.require}") + private String captchaId; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java b/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java index 9e5c153f..0db9d296 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/service/CaptchaService.java @@ -18,10 +18,28 @@ public interface CaptchaService { /** * 验证码效验 - * - * @param uuid uuid - * @param code 验证码 + * + * @param uuid uuid + * @param code 验证码 + * @param delete 是否删除验证码 * @return true:成功 false:失败 */ - boolean validate(String uuid, String code); + boolean validate(String uuid, String code, Boolean delete); + + /** + * 发送短信验证码 + * + * @param phone 手机 + */ + void sendSMSValidateCode(String phone); + + /** + * 验证短信验证码 + * + * @param phone 手机 + * @param code 验证码 + * @param delete 是否删除验证码 + * @return true:成功 false:失败 + */ + boolean validateSMSValidateCode(String phone, String code, Boolean delete); } 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 012b4d3e..09c8a185 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 @@ -1,6 +1,7 @@ package xiaozhi.modules.security.service.impl; import java.io.IOException; +import java.util.Random; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; @@ -14,9 +15,13 @@ import com.wf.captcha.base.Captcha; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.RenException; import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.modules.security.service.CaptchaService; +import xiaozhi.modules.sms.service.SmsService; +import xiaozhi.modules.sys.service.SysParamsService; /** * 验证码 @@ -25,6 +30,10 @@ import xiaozhi.modules.security.service.CaptchaService; public class CaptchaServiceImpl implements CaptchaService { @Resource private RedisUtils redisUtils; + @Resource + private SmsService smsService; + @Resource + private SysParamsService sysParamsService; @Value("${renren.redis.open}") private boolean open; /** @@ -51,12 +60,12 @@ public class CaptchaServiceImpl implements CaptchaService { } @Override - public boolean validate(String uuid, String code) { + public boolean validate(String uuid, String code, Boolean delete) { if (StringUtils.isBlank(code)) { return false; } // 获取验证码 - String captcha = getCache(uuid); + String captcha = getCache(uuid, delete); // 效验成功 if (code.equalsIgnoreCase(captcha)) { @@ -66,21 +75,98 @@ public class CaptchaServiceImpl implements CaptchaService { return false; } + @Override + public void sendSMSValidateCode(String phone) { + // 检查发送间隔 + String lastSendTimeKey = RedisKeys.getSMSLastSendTimeKey(phone); + String lastSendTime = (String) redisUtils.get(lastSendTimeKey); + if (lastSendTime != null) { + long lastSendTimeLong = Long.parseLong(lastSendTime); + long currentTime = System.currentTimeMillis(); + long timeDiff = currentTime - lastSendTimeLong; + if (timeDiff < 60000) { + throw new RenException("发送太频繁,请" + (60000 - timeDiff) / 1000 + "秒后再试"); + } + } + + // 检查今日发送次数 + String todayCountKey = RedisKeys.getSMSTodayCountKey(phone); + Integer todayCount = (Integer) redisUtils.get(todayCountKey); + if (todayCount == null) { + todayCount = 0; + } + + // 获取最大发送次数限制 + Integer maxSendCount = sysParamsService.getValueObject( + Constant.SysMSMParam.SYSTEM_SMS_MAX_SEND_COUNT.getValue(), + Integer.class); + if (maxSendCount == null) { + maxSendCount = 5; // 默认值 + } + + if (todayCount >= maxSendCount) { + throw new RenException("今日发送次数已达上限"); + } + + String key = RedisKeys.getSMSValidateCodeKey(phone); + String validateCodes = generateValidateCode(6); + + // 设置验证码 + setCache(key, validateCodes); + + // 设置最后发送时间(60秒) + redisUtils.set(lastSendTimeKey, String.valueOf(System.currentTimeMillis()), 60); + + // 更新今日发送次数 + if (todayCount == 0) { + // 如果是今天第一次发送,设置24小时过期 + redisUtils.set(todayCountKey, 1, RedisUtils.DEFAULT_EXPIRE); + } else { + redisUtils.set(todayCountKey, todayCount + 1, RedisUtils.DEFAULT_EXPIRE); + } + + // 发送验证码短信 + smsService.sendVerificationCodeSms(phone, validateCodes); + } + + @Override + public boolean validateSMSValidateCode(String phone, String code, Boolean delete) { + String key = RedisKeys.getSMSValidateCodeKey(phone); + return validate(key, code, delete); + } + + /** + * 生成指定数量的随机数验证码 + * + * @param length 数量 + * @return 随机码 + */ + private String generateValidateCode(Integer length) { + String chars = "0123456789"; // 字符范围可以自定义:数字 + Random random = new Random(); + StringBuilder code = new StringBuilder(); + for (int i = 0; i < length; i++) { + code.append(chars.charAt(random.nextInt(chars.length()))); + } + return code.toString(); + } + private void setCache(String key, String value) { if (open) { key = RedisKeys.getCaptchaKey(key); + // 设置5分钟过期 redisUtils.set(key, value, 300); } else { localCache.put(key, value); } } - private String getCache(String key) { + private String getCache(String key, Boolean delete) { if (open) { key = RedisKeys.getCaptchaKey(key); String captcha = (String) redisUtils.get(key); // 删除验证码 - if (captcha != null) { + if (captcha != null && delete) { redisUtils.delete(key); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sms/service/SmsService.java b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/SmsService.java new file mode 100644 index 00000000..c33ad945 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/SmsService.java @@ -0,0 +1,17 @@ +package xiaozhi.modules.sms.service; + +/** + * 短信服务的方法定义接口 + * + * @author zjy + * @since 2025-05-12 + */ +public interface SmsService { + + /** + * 发送验证码短信 + * @param phone 手机号码 + * @param VerificationCode 验证码 + */ + void sendVerificationCodeSms(String phone, String VerificationCode) ; +} 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 new file mode 100644 index 00000000..b39b24c8 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sms/service/imp/ALiYunSmsService.java @@ -0,0 +1,70 @@ +package xiaozhi.modules.sms.service.imp; + +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.models.RuntimeOptions; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.exception.RenException; +import xiaozhi.modules.sms.service.SmsService; +import xiaozhi.modules.sys.service.SysParamsService; + +@Service +@AllArgsConstructor +@Slf4j +public class ALiYunSmsService implements SmsService { + private final SysParamsService sysParamsService; + + @Override + public void sendVerificationCodeSms(String phone, String VerificationCode) { + Client client = createClient(); + String SignName = sysParamsService.getValue(Constant.SysMSMParam + .ALIYUN_SMS_SIGN_NAME.getValue(),true); + String TemplateCode = sysParamsService.getValue(Constant.SysMSMParam + .ALIYUN_SMS_SMS_CODE_TEMPLATE_CODE.getValue(),true); + try { + SendSmsRequest sendSmsRequest = new SendSmsRequest() + .setSignName(SignName) + .setTemplateCode(TemplateCode) + .setPhoneNumbers(phone) + .setTemplateParam(String.format("{\"code\":\"%s\"}", VerificationCode)); + RuntimeOptions runtime = new RuntimeOptions(); + // 复制代码运行请自行打印 API 的返回值 + SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime); + log.info("发送短信响应的requestID: {}", sendSmsResponse.getBody().getRequestId()); + } catch (Exception e) { + // 错误 message + log.error(e.getMessage()); + throw new RenException("短信发送失败"); + } + + } + + + /** + * 创建阿里云连接 + * @return 返回连接对象 + */ + private Client createClient(){ + String ACCESS_KEY_ID = sysParamsService.getValue(Constant.SysMSMParam + .ALIYUN_SMS_ACCESS_KEY_ID.getValue(),true); + String ACCESS_KEY_SECRET = sysParamsService.getValue(Constant.SysMSMParam + .ALIYUN_SMS_ACCESS_KEY_SECRET.getValue(),true); + try { + Config config = new Config() + .setAccessKeyId(ACCESS_KEY_ID) + .setAccessKeySecret(ACCESS_KEY_SECRET); + // 配置 Endpoint。中国站请使用dysmsapi.aliyuncs.com + config.endpoint = "dysmsapi.aliyuncs.com"; + return new Client(config); + }catch (Exception e){ + // 错误 message + log.error(e.getMessage()); + throw new RenException("短信连接建立失败"); + } + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/RetrievePasswordDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/RetrievePasswordDTO.java new file mode 100644 index 00000000..efac8099 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/RetrievePasswordDTO.java @@ -0,0 +1,30 @@ +package xiaozhi.modules.sys.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.io.Serializable; + +/** + * 找回密码DTO + */ +@Data +@Schema(description = "找回密码") +public class RetrievePasswordDTO implements Serializable { + + @Schema(description = "手机号码") + @NotBlank(message = "{sysuser.password.require}") + private String phone; + + @Schema(description = "验证码") + @NotBlank(message = "{sysuser.password.require}") + private String code; + + @Schema(description = "新密码") + @NotBlank(message = "{sysuser.password.require}") + private String password; + + + +} \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202505141132.sql b/main/manager-api/src/main/resources/db/changelog/202505141132.sql new file mode 100644 index 00000000..4e60a350 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505141132.sql @@ -0,0 +1,59 @@ +-- 添加手机短信注册功能的需要的参数 +delete from sys_params where id in (601, 602, 610, 611, 612, 613); +INSERT INTO sys_params +(id, param_code, param_value, value_type, param_type, remark, creator, create_date, updater, update_date) + VALUES +(601, 'system.enable_mobile_register', 'false', 'boolean', 1, '是否开启手机注册', NULL, NULL, NULL, NULL), +(602, 'system.sms.max_send_count', '10', 'number', 1, '单号码单日最大短信发送条数', NULL, NULL, NULL, NULL), +(610, 'aliyun.sms.access_key_id', '', 'string', 1, '阿里云平台access_key', NULL, NULL, NULL, NULL), +(611, 'aliyun.sms.access_key_secret', '', 'string', 1, '阿里云平台access_key_secret', NULL, NULL, NULL, NULL), +(612, 'aliyun.sms.sign_name', '', 'string', 1, '阿里云短信签名', NULL, NULL, NULL, NULL), +(613, 'aliyun.sms.sms_code_template_code', '', 'string', 1, '阿里云短信模板', NULL, NULL, NULL, NULL); + +-- 增加手机区域字典 +-- 插入固件类型字典类型 +delete from `sys_dict_type` where `id` = 102; +INSERT INTO `sys_dict_type` (`id`, `dict_type`, `dict_name`, `remark`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +(102, 'MOBILE_AREA', '手机区域', '手机区域字典', 0, 1, NOW(), 1, NOW()); + +-- 插入固件类型字典数据 +delete from `sys_dict_data` where `dict_type_id` = 102; +INSERT INTO `sys_dict_data` (`id`, `dict_type_id`, `dict_label`, `dict_value`, `remark`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES +(102001, 102, '中国大陆', '+86', '中国大陆', 1, 1, NOW(), 1, NOW()), +(102002, 102, '中国香港', '+852', '中国香港', 2, 1, NOW(), 1, NOW()), +(102003, 102, '中国澳门', '+853', '中国澳门', 3, 1, NOW(), 1, NOW()), +(102004, 102, '中国台湾', '+886', '中国台湾', 4, 1, NOW(), 1, NOW()), +(102005, 102, '美国/加拿大', '+1', '美国/加拿大', 5, 1, NOW(), 1, NOW()), +(102006, 102, '英国', '+44', '英国', 6, 1, NOW(), 1, NOW()), +(102007, 102, '法国', '+33', '法国', 7, 1, NOW(), 1, NOW()), +(102008, 102, '意大利', '+39', '意大利', 8, 1, NOW(), 1, NOW()), +(102009, 102, '德国', '+49', '德国', 9, 1, NOW(), 1, NOW()), +(102010, 102, '波兰', '+48', '波兰', 10, 1, NOW(), 1, NOW()), +(102011, 102, '瑞士', '+41', '瑞士', 11, 1, NOW(), 1, NOW()), +(102012, 102, '西班牙', '+34', '西班牙', 12, 1, NOW(), 1, NOW()), +(102013, 102, '丹麦', '+45', '丹麦', 13, 1, NOW(), 1, NOW()), +(102014, 102, '马来西亚', '+60', '马来西亚', 14, 1, NOW(), 1, NOW()), +(102015, 102, '澳大利亚', '+61', '澳大利亚', 15, 1, NOW(), 1, NOW()), +(102016, 102, '印度尼西亚', '+62', '印度尼西亚', 16, 1, NOW(), 1, NOW()), +(102017, 102, '菲律宾', '+63', '菲律宾', 17, 1, NOW(), 1, NOW()), +(102018, 102, '新西兰', '+64', '新西兰', 18, 1, NOW(), 1, NOW()), +(102019, 102, '新加坡', '+65', '新加坡', 19, 1, NOW(), 1, NOW()), +(102020, 102, '泰国', '+66', '泰国', 20, 1, NOW(), 1, NOW()), +(102021, 102, '日本', '+81', '日本', 21, 1, NOW(), 1, NOW()), +(102022, 102, '韩国', '+82', '韩国', 22, 1, NOW(), 1, NOW()), +(102023, 102, '越南', '+84', '越南', 23, 1, NOW(), 1, NOW()), +(102024, 102, '印度', '+91', '印度', 24, 1, NOW(), 1, NOW()), +(102025, 102, '巴基斯坦', '+92', '巴基斯坦', 25, 1, NOW(), 1, NOW()), +(102026, 102, '尼日利亚', '+234', '尼日利亚', 26, 1, NOW(), 1, NOW()), +(102027, 102, '孟加拉国', '+880', '孟加拉国', 27, 1, NOW(), 1, NOW()), +(102028, 102, '沙特阿拉伯', '+966', '沙特阿拉伯', 28, 1, NOW(), 1, NOW()), +(102029, 102, '阿联酋', '+971', '阿联酋', 29, 1, NOW(), 1, NOW()), +(102030, 102, '巴西', '+55', '巴西', 30, 1, NOW(), 1, NOW()), +(102031, 102, '墨西哥', '+52', '墨西哥', 31, 1, NOW(), 1, NOW()), +(102032, 102, '智利', '+56', '智利', 32, 1, NOW(), 1, NOW()), +(102033, 102, '阿根廷', '+54', '阿根廷', 33, 1, NOW(), 1, NOW()), +(102034, 102, '埃及', '+20', '埃及', 34, 1, NOW(), 1, NOW()), +(102035, 102, '南非', '+27', '南非', 35, 1, NOW(), 1, NOW()), +(102036, 102, '肯尼亚', '+254', '肯尼亚', 36, 1, NOW(), 1, NOW()), +(102037, 102, '坦桑尼亚', '+255', '坦桑尼亚', 37, 1, NOW(), 1, NOW()), +(102038, 102, '哈萨克斯坦', '+7', '哈萨克斯坦', 38, 1, NOW(), 1, NOW()); diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index ae81fd14..30dbd888 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -114,6 +114,13 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202505091409.sql + - changeSet: + id: 202505141132 + author: zjy + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505141132.sql - changeSet: id: 202505091555 author: whosmyqueen diff --git a/main/manager-api/src/main/resources/i18n/messages.properties b/main/manager-api/src/main/resources/i18n/messages.properties index 5126feb2..8d04c5a0 100644 --- a/main/manager-api/src/main/resources/i18n/messages.properties +++ b/main/manager-api/src/main/resources/i18n/messages.properties @@ -9,6 +9,7 @@ 10005=\u8D26\u53F7\u5DF2\u88AB\u505C\u7528 10006=\u552F\u4E00\u6807\u8BC6\u4E0D\u80FD\u4E3A\u7A7A 10007=\u9A8C\u8BC1\u7801\u4E0D\u6B63\u786E +10008=\u624B\u673A\u53F7\u7801\u4E0D\u53EF\u4EE5\u4E3A\u7A7A 10009=\u539F\u5BC6\u7801\u4E0D\u6B63\u786E 10010=\u8D26\u53F7\u6216\u5BC6\u7801\u4E0D\u6B63\u786E,\u60A8\u8FD8\u6709\u53EF\u4EE5\u5C1D\u8BD5{0}\u6B21 10011=\u4E0A\u7EA7\u90E8\u95E8\u9009\u62E9\u9519\u8BEF diff --git a/main/manager-api/src/test/java/xiaozhi/modules/sys/loginControllerTest.java b/main/manager-api/src/test/java/xiaozhi/modules/sys/loginControllerTest.java new file mode 100644 index 00000000..777abbf9 --- /dev/null +++ b/main/manager-api/src/test/java/xiaozhi/modules/sys/loginControllerTest.java @@ -0,0 +1,53 @@ +package xiaozhi.modules.sys; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import xiaozhi.modules.security.controller.LoginController; +import xiaozhi.modules.security.dto.LoginDTO; +import xiaozhi.modules.sys.dto.RetrievePasswordDTO; + + +@Slf4j +@SpringBootTest +@ActiveProfiles("dev") +class loginControllerTest { + + @Autowired + LoginController loginController; + + @Test + public void testRegister() { + LoginDTO loginDTO = new LoginDTO(); + loginDTO.setUsername("手机号码"); + loginDTO.setPassword("密码"); + loginDTO.setCaptcha("123456"); + loginController.register(loginDTO); + } + + @Test + public void testSmsVerification(){ + try { + loginController.smsVerification("手机号码"); + }catch (Exception e){ + System.out.println(e.getMessage()); + } + } + + @Test + public void testRetrievePassword(){ + try { + RetrievePasswordDTO retrievePasswordDTO = new RetrievePasswordDTO(); + retrievePasswordDTO.setCode("123456"); + retrievePasswordDTO.setPhone("手机号码"); + retrievePasswordDTO.setPassword("密码"); + loginController.retrievePassword(retrievePasswordDTO); + }catch (Exception e){ + System.out.println(e.getMessage()); + } + + } + +} \ No newline at end of file diff --git a/main/manager-web/src/apis/httpRequest.js b/main/manager-web/src/apis/httpRequest.js index 43cdf92d..245bf6cc 100755 --- a/main/manager-web/src/apis/httpRequest.js +++ b/main/manager-web/src/apis/httpRequest.js @@ -20,6 +20,7 @@ function sendRequest() { return { _sucCallback: null, _failCallback: null, + _networkFailCallback: null, _method: 'GET', _data: {}, _header: { 'content-type': 'application/json; charset=utf-8' }, @@ -36,7 +37,7 @@ function sendRequest() { headers: this._header, responseType: this._responseType }).then((res) => { - const error = httpHandlerError(res, this._failCallback); + const error = httpHandlerError(res, this._failCallback, this._networkFailCallback); if (error) { return } @@ -47,7 +48,7 @@ function sendRequest() { }).catch((res) => { // 打印失败响应 console.log('catch', res) - httpHandlerError(res, this._failCallback) + httpHandlerError(res, this._failCallback, this._networkFailCallback) }) return this }, @@ -59,6 +60,10 @@ function sendRequest() { this._failCallback = callback return this }, + 'networkFail'(callback) { + this._networkFailCallback = callback + return this + }, 'url'(url) { if (url) { url = url.replaceAll('$', '/') @@ -95,11 +100,11 @@ function sendRequest() { /** * Info 请求完成后返回信息 - * callBack 回调函数 - * errTip 自定义错误信息 + * failCallback 回调函数 + * networkFailCallback 回调函数 */ // 在错误处理函数中添加日志 -function httpHandlerError(info, callBack) { +function httpHandlerError(info, failCallback, networkFailCallback) { /** 请求成功,退出该函数 可以根据项目需求来判断是否请求成功。这里判断的是status为200的时候是成功 */ let networkError = false @@ -111,12 +116,16 @@ function httpHandlerError(info, callBack) { goToPage(Constant.PAGE.LOGIN, true); return true } else { - showDanger(info.data.msg) + if (failCallback) { + failCallback(info) + } else { + showDanger(info.data.msg) + } return true } } - if (callBack) { - callBack(info) + if (networkFailCallback) { + networkFailCallback(info) } else { showDanger(`网络请求出现了错误【${info.status}】`) } diff --git a/main/manager-web/src/apis/module/admin.js b/main/manager-web/src/apis/module/admin.js index fecc5877..92c344e2 100644 --- a/main/manager-web/src/apis/module/admin.js +++ b/main/manager-web/src/apis/module/admin.js @@ -18,7 +18,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('请求失败:', err) RequestService.reAjaxFun(() => { this.getUserList(callback) @@ -34,7 +34,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('删除失败:', err) RequestService.reAjaxFun(() => { this.deleteUser(id, callback) @@ -50,7 +50,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('重置密码失败:', err) RequestService.reAjaxFun(() => { this.resetUserPassword(id, callback) @@ -72,7 +72,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取参数列表失败:', err) RequestService.reAjaxFun(() => { this.getParamsList(params, callback) @@ -89,7 +89,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('添加参数失败:', err) RequestService.reAjaxFun(() => { this.addParam(data, callback) @@ -106,7 +106,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('更新参数失败:', err) RequestService.reAjaxFun(() => { this.updateParam(data, callback) @@ -123,7 +123,7 @@ export default { RequestService.clearRequestTime() callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('删除参数失败:', err) RequestService.reAjaxFun(() => { this.deleteParam(ids, callback) diff --git a/main/manager-web/src/apis/module/agent.js b/main/manager-web/src/apis/module/agent.js index 756e1bef..7f33d320 100644 --- a/main/manager-web/src/apis/module/agent.js +++ b/main/manager-web/src/apis/module/agent.js @@ -12,7 +12,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.getAgentList(callback); }); @@ -28,7 +28,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.addAgent(agentName, callback); }); @@ -43,7 +43,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.deleteAgent(agentId, callback); }); @@ -58,7 +58,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取配置失败:', err); RequestService.reAjaxFun(() => { this.getDeviceConfig(agentId, callback); @@ -75,7 +75,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.updateAgentConfig(agentId, configData, callback); }); @@ -90,7 +90,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取模板失败:', err); RequestService.reAjaxFun(() => { this.getAgentTemplate(callback); @@ -107,7 +107,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.getAgentSessions(agentId, params, callback); }); @@ -122,7 +122,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.getAgentChatHistory(agentId, sessionId, callback); }); @@ -137,7 +137,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.getAudioId(audioId, callback); }); diff --git a/main/manager-web/src/apis/module/device.js b/main/manager-web/src/apis/module/device.js index 53738bc6..2f20425d 100644 --- a/main/manager-web/src/apis/module/device.js +++ b/main/manager-web/src/apis/module/device.js @@ -11,7 +11,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取设备列表失败:', err); RequestService.reAjaxFun(() => { this.getAgentBindDevices(agentId, callback); @@ -28,7 +28,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('解绑设备失败:', err); RequestService.reAjaxFun(() => { this.unbindDevice(device_id, callback); @@ -44,7 +44,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('绑定设备失败:', err); RequestService.reAjaxFun(() => { this.bindDevice(agentId, deviceCode, callback); @@ -59,7 +59,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('更新OTA状态失败:', err) this.$message.error(err.msg || '更新OTA状态失败') RequestService.reAjaxFun(() => { diff --git a/main/manager-web/src/apis/module/dict.js b/main/manager-web/src/apis/module/dict.js index caf78279..8919c46b 100644 --- a/main/manager-web/src/apis/module/dict.js +++ b/main/manager-web/src/apis/module/dict.js @@ -18,7 +18,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取字典类型列表失败:', err) this.$message.error(err.msg || '获取字典类型列表失败') RequestService.reAjaxFun(() => { @@ -36,7 +36,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取字典类型详情失败:', err) this.$message.error(err.msg || '获取字典类型详情失败') RequestService.reAjaxFun(() => { @@ -55,7 +55,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('新增字典类型失败:', err) this.$message.error(err.msg || '新增字典类型失败') RequestService.reAjaxFun(() => { @@ -74,7 +74,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('更新字典类型失败:', err) this.$message.error(err.msg || '更新字典类型失败') RequestService.reAjaxFun(() => { @@ -93,7 +93,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('删除字典类型失败:', err) this.$message.error(err.msg || '删除字典类型失败') RequestService.reAjaxFun(() => { @@ -119,7 +119,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取字典数据列表失败:', err) this.$message.error(err.msg || '获取字典数据列表失败') RequestService.reAjaxFun(() => { @@ -137,7 +137,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取字典数据详情失败:', err) this.$message.error(err.msg || '获取字典数据详情失败') RequestService.reAjaxFun(() => { @@ -156,7 +156,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('新增字典数据失败:', err) this.$message.error(err.msg || '新增字典数据失败') RequestService.reAjaxFun(() => { @@ -175,7 +175,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('更新字典数据失败:', err) this.$message.error(err.msg || '更新字典数据失败') RequestService.reAjaxFun(() => { @@ -194,7 +194,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('删除字典数据失败:', err) this.$message.error(err.msg || '删除字典数据失败') RequestService.reAjaxFun(() => { @@ -217,7 +217,7 @@ export default { reject(new Error(res.data?.msg || '获取字典数据列表失败')) } }) - .fail((err) => { + .networkFail((err) => { console.error('获取字典数据列表失败:', err) reject(err) }).send() diff --git a/main/manager-web/src/apis/module/model.js b/main/manager-web/src/apis/module/model.js index 08615693..badf4174 100644 --- a/main/manager-web/src/apis/module/model.js +++ b/main/manager-web/src/apis/module/model.js @@ -18,7 +18,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取模型列表失败:', err) RequestService.reAjaxFun(() => { this.getModelList(params, callback) @@ -34,7 +34,7 @@ export default { RequestService.clearRequestTime() callback(res.data?.data || []) }) - .fail((err) => { + .networkFail((err) => { console.error('获取供应器列表失败:', err) this.$message.error('获取供应器列表失败') RequestService.reAjaxFun(() => { @@ -65,7 +65,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('新增模型失败:', err) this.$message.error(err.msg || '新增模型失败') RequestService.reAjaxFun(() => { @@ -82,7 +82,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('删除模型失败:', err) this.$message.error(err.msg || '删除模型失败') RequestService.reAjaxFun(() => { @@ -100,7 +100,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.getModelNames(modelType, modelName, callback); }); @@ -118,7 +118,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail(() => { + .networkFail(() => { RequestService.reAjaxFun(() => { this.getModelVoices(modelId, voiceName, callback); }); @@ -133,7 +133,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('获取模型配置失败:', err) this.$message.error(err.msg || '获取模型配置失败') RequestService.reAjaxFun(() => { @@ -150,7 +150,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('更新模型状态失败:', err) this.$message.error(err.msg || '更新模型状态失败') RequestService.reAjaxFun(() => { @@ -173,7 +173,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('更新模型失败:', err); this.$message.error(err.msg || '更新模型失败'); RequestService.reAjaxFun(() => { @@ -190,7 +190,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('设置默认模型失败:', err) this.$message.error(err.msg || '设置默认模型失败') RequestService.reAjaxFun(() => { @@ -219,7 +219,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { this.$message.error(err.msg || '获取供应器列表失败'); RequestService.reAjaxFun(() => { this.getModelProviders(params, callback); @@ -249,7 +249,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('新增模型供应器失败:', err) this.$message.error(err.msg || '新增模型供应器失败') RequestService.reAjaxFun(() => { @@ -281,7 +281,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { this.$message.error(err.msg || '更新模型供应器失败') RequestService.reAjaxFun(() => { this.updateModelProvider(params, callback); @@ -298,7 +298,7 @@ export default { RequestService.clearRequestTime() callback(res); }) - .fail((err) => { + .networkFail((err) => { this.$message.error(err.msg || '删除模型供应器失败') RequestService.reAjaxFun(() => { this.deleteModelProviderByIds(ids, callback) diff --git a/main/manager-web/src/apis/module/ota.js b/main/manager-web/src/apis/module/ota.js index 438aa3d8..4db86ad7 100644 --- a/main/manager-web/src/apis/module/ota.js +++ b/main/manager-web/src/apis/module/ota.js @@ -12,7 +12,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取OTA固件列表失败:', err); RequestService.reAjaxFun(() => { this.getOtaList(params, callback); @@ -28,7 +28,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取OTA固件信息失败:', err); RequestService.reAjaxFun(() => { this.getOtaInfo(id, callback); @@ -45,7 +45,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('保存OTA固件信息失败:', err); RequestService.reAjaxFun(() => { this.saveOta(entity, callback); @@ -62,7 +62,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('更新OTA固件信息失败:', err); RequestService.reAjaxFun(() => { this.updateOta(id, entity, callback); @@ -78,7 +78,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('删除OTA固件失败:', err); RequestService.reAjaxFun(() => { this.deleteOta(id, callback); @@ -97,7 +97,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('上传固件文件失败:', err); RequestService.reAjaxFun(() => { this.uploadFirmware(file, callback); @@ -113,7 +113,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取下载链接失败:', err); RequestService.reAjaxFun(() => { this.getDownloadUrl(id, callback); diff --git a/main/manager-web/src/apis/module/timbre.js b/main/manager-web/src/apis/module/timbre.js index 91920522..7aa6a711 100644 --- a/main/manager-web/src/apis/module/timbre.js +++ b/main/manager-web/src/apis/module/timbre.js @@ -1,4 +1,4 @@ -import {getServiceUrl} from '../api'; +import { getServiceUrl } from '../api'; import RequestService from '../httpRequest'; export default { @@ -18,7 +18,7 @@ export default { RequestService.clearRequestTime(); callback(res.data || []); }) - .fail((err) => { + .networkFail((err) => { console.error('获取音色列表失败:', err); RequestService.reAjaxFun(() => { this.getVoiceList(params, callback); @@ -42,7 +42,7 @@ export default { .success((res) => { callback(res.data); }) - .fail((err) => { + .networkFail((err) => { console.error('保存音色失败:', err); RequestService.reAjaxFun(() => { this.saveVoice(params, callback); @@ -59,7 +59,7 @@ export default { RequestService.clearRequestTime() callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('删除音色失败:', err); RequestService.reAjaxFun(() => { this.deleteVoice(ids, callback); @@ -82,7 +82,7 @@ export default { .success((res) => { callback(res.data); }) - .fail((err) => { + .networkFail((err) => { console.error('修改音色失败:', err); RequestService.reAjaxFun(() => { this.updateVoice(params, callback); diff --git a/main/manager-web/src/apis/module/user.js b/main/manager-web/src/apis/module/user.js index 84d5b7f9..1ad28b2e 100755 --- a/main/manager-web/src/apis/module/user.js +++ b/main/manager-web/src/apis/module/user.js @@ -4,7 +4,7 @@ import RequestService from '../httpRequest' export default { // 登录 - login(loginForm, callback) { + login(loginForm, callback, failCallback) { RequestService.sendRequest() .url(`${getServiceUrl()}/user/login`) .method('POST') @@ -13,7 +13,11 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail(() => { + .fail((err) => { + RequestService.clearRequestTime() + failCallback(err) + }) + .networkFail(() => { RequestService.reAjaxFun(() => { this.login(loginForm, callback) }) @@ -34,12 +38,32 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { // 添加错误参数 + .networkFail((err) => { // 添加错误参数 }).send() }, + // 发送短信验证码 + sendSmsVerification(data, callback, failCallback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/user/smsVerification`) + .method('POST') + .data(data) + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .fail((err) => { + RequestService.clearRequestTime() + failCallback(err) + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.sendSmsVerification(data, callback, failCallback) + }) + }).send() + }, // 注册账号 - register(registerForm, callback) { + register(registerForm, callback, failCallback) { RequestService.sendRequest() .url(`${getServiceUrl()}/user/register`) .method('POST') @@ -48,7 +72,14 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail(() => { + .fail((err) => { + RequestService.clearRequestTime() + failCallback(err) + }) + .networkFail(() => { + RequestService.reAjaxFun(() => { + this.register(registerForm, callback, failCallback) + }) }).send() }, // 保存设备配置 @@ -61,7 +92,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('保存配置失败:', err); RequestService.reAjaxFun(() => { this.saveDeviceConfig(device_id, configData, callback); @@ -77,7 +108,7 @@ export default { RequestService.clearRequestTime() callback(res) }) - .fail((err) => { + .networkFail((err) => { console.error('接口请求失败:', err) RequestService.reAjaxFun(() => { this.getUserInfo(callback) @@ -97,7 +128,7 @@ export default { RequestService.clearRequestTime(); successCallback(res); }) - .fail((error) => { + .networkFail((error) => { RequestService.reAjaxFun(() => { this.changePassword(oldPassword, newPassword, successCallback, errorCallback); }); @@ -115,7 +146,7 @@ export default { RequestService.clearRequestTime() successCallback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('修改用户状态失败:', err) RequestService.reAjaxFun(() => { this.changeUserStatus(status, userIds) @@ -131,7 +162,7 @@ export default { RequestService.clearRequestTime(); callback(res); }) - .fail((err) => { + .networkFail((err) => { console.error('获取公共配置失败:', err); RequestService.reAjaxFun(() => { this.getPubConfig(callback); diff --git a/main/manager-web/src/utils/index.js b/main/manager-web/src/utils/index.js index 8a5a960e..aef8de0a 100755 --- a/main/manager-web/src/utils/index.js +++ b/main/manager-web/src/utils/index.js @@ -136,3 +136,67 @@ export function getUUID() { }) } +/** + * 验证手机号格式 + * @param {string} mobile 手机号 + * @param {string} areaCode 区号 + * @returns {boolean} + */ +export function validateMobile(mobile, areaCode) { + // 移除所有非数字字符 + const cleanMobile = mobile.replace(/\D/g, ''); + + // 根据不同区号使用不同的验证规则 + switch (areaCode) { + case '+86': // 中国大陆 + return /^1[3-9]\d{9}$/.test(cleanMobile); + case '+852': // 中国香港 + return /^[569]\d{7}$/.test(cleanMobile); + case '+853': // 中国澳门 + return /^6\d{7}$/.test(cleanMobile); + case '+886': // 中国台湾 + return /^9\d{8}$/.test(cleanMobile); + case '+1': // 美国/加拿大 + return /^[2-9]\d{9}$/.test(cleanMobile); + case '+44': // 英国 + return /^7[1-9]\d{8}$/.test(cleanMobile); + case '+81': // 日本 + return /^[7890]\d{8}$/.test(cleanMobile); + case '+82': // 韩国 + return /^1[0-9]\d{7}$/.test(cleanMobile); + case '+65': // 新加坡 + return /^[89]\d{7}$/.test(cleanMobile); + case '+61': // 澳大利亚 + return /^[4578]\d{8}$/.test(cleanMobile); + case '+49': // 德国 + return /^1[5-7]\d{8}$/.test(cleanMobile); + case '+33': // 法国 + return /^[67]\d{8}$/.test(cleanMobile); + case '+39': // 意大利 + return /^3[0-9]\d{8}$/.test(cleanMobile); + case '+34': // 西班牙 + return /^[6-9]\d{8}$/.test(cleanMobile); + case '+55': // 巴西 + return /^[1-9]\d{10}$/.test(cleanMobile); + case '+91': // 印度 + return /^[6-9]\d{9}$/.test(cleanMobile); + case '+971': // 阿联酋 + return /^[5]\d{8}$/.test(cleanMobile); + case '+966': // 沙特阿拉伯 + return /^[5]\d{8}$/.test(cleanMobile); + case '+880': // 孟加拉国 + return /^1[3-9]\d{8}$/.test(cleanMobile); + case '+234': // 尼日利亚 + return /^[789]\d{9}$/.test(cleanMobile); + case '+254': // 肯尼亚 + return /^[17]\d{8}$/.test(cleanMobile); + case '+255': // 坦桑尼亚 + return /^[67]\d{8}$/.test(cleanMobile); + case '+7': // 哈萨克斯坦 + return /^[67]\d{9}$/.test(cleanMobile); + default: + // 其他国际号码:至少5位,最多15位 + return /^\d{5,15}$/.test(cleanMobile); + } +} + diff --git a/main/manager-web/src/views/DictManagement.vue b/main/manager-web/src/views/DictManagement.vue index 195abf1e..f4de8350 100644 --- a/main/manager-web/src/views/DictManagement.vue +++ b/main/manager-web/src/views/DictManagement.vue @@ -48,8 +48,7 @@