From 48d693936e2c96e9d6ef4175ec78756a967f6824 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Sat, 28 Feb 2026 17:28:16 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=BF=E9=87=8C=E7=9F=AD=E4=BF=A1=EF=BC=8C?= =?UTF-8?q?=E5=BF=98=E8=AE=B0=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/constant/SecurityConstants.java | 3 +- .../feign/RemoterThirdPartySmsService.java | 4 + ...mework.cloud.openfeign.FeignClient.imports | 1 + .../pjcloud-thirdparty-biz/pom.xml | 7 ++ .../thirdparty/controller/SmsController.java | 13 ++- .../thirdparty/utils/sms/AliyunSMS.java | 110 ++++++++++++++++++ .../src/main/resources/application.yml | 8 ++ .../admin/PjcloudAdminApplication.java | 2 + .../admin/controller/SysUserController.java | 20 ++++ .../pjcloud/admin/service/SysUserService.java | 9 ++ .../service/impl/SysMessageServiceImpl.java | 20 +++- .../service/impl/SysUserServiceImpl.java | 70 +++++++++++ 12 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/utils/sms/AliyunSMS.java diff --git a/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/SecurityConstants.java b/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/SecurityConstants.java index dcf350489..c0d17766e 100644 --- a/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/SecurityConstants.java +++ b/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/constant/SecurityConstants.java @@ -37,8 +37,9 @@ public interface SecurityConstants { /** * 验证码有效期 + * 更改为300秒 */ - int CODE_TIME = 60; + int CODE_TIME = 300; /** * 验证码长度 diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/api/feign/RemoterThirdPartySmsService.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/api/feign/RemoterThirdPartySmsService.java index 5e2dd59ff..8d02a53bd 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/api/feign/RemoterThirdPartySmsService.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/api/feign/RemoterThirdPartySmsService.java @@ -45,4 +45,8 @@ public interface RemoterThirdPartySmsService { public void sendParamsMsg( @RequestParam("mobile") String mobile,@RequestParam("templateId") String templateId, @RequestBody Map params,@RequestParam("clientIp") String clientIp); + + // 发送阿里云验证码 + @PostMapping("/sms/sendVerificationCode") + void sendVerificationCode(@RequestParam("mobile") String mobile, @RequestParam("code") String code); } diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports index 09e073709..37e0bfcf9 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports @@ -4,6 +4,7 @@ com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdLbApiService com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdPartyAuthService com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdZyService com.bsd.pjcloud.thirdparty.api.feign.RemoterVisitorService +com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdPartySmsService diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml index b89bfa465..40b4575d6 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml @@ -13,6 +13,13 @@ pjcloud-thirdparty-biz + + + com.aliyun + aliyun-java-sdk-core + 4.5.0 + + com.hikvision.ga artemis-http-client diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/SmsController.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/SmsController.java index 6b52a7a17..403772bd4 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/SmsController.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/SmsController.java @@ -2,7 +2,7 @@ package com.bsd.pjcloud.thirdparty.controller; import com.bsd.pjcloud.common.security.annotation.Inner; import com.bsd.pjcloud.thirdparty.service.SmsService; -import jakarta.servlet.http.HttpServletRequest; +import com.bsd.pjcloud.thirdparty.utils.sms.AliyunSMS; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -15,6 +15,7 @@ public class SmsController { private final SmsService smsService; + private final AliyunSMS aliyunSMS; // 简单发送 @PostMapping("/sendMsg") @@ -31,4 +32,14 @@ public class SmsController { smsService.sendSms(mobile, templateId, params,clientIp); } + // 阿里云发送验证码 + @PostMapping("/sendVerificationCode") + @Inner(value = false) + public void sendALiVerificationCode(@RequestParam("mobile") String mobile, @RequestParam("code") String code) { + aliyunSMS.sendVerificationCode(mobile, code); + } + + + + } \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/utils/sms/AliyunSMS.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/utils/sms/AliyunSMS.java new file mode 100644 index 000000000..d973dc955 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/utils/sms/AliyunSMS.java @@ -0,0 +1,110 @@ +package com.bsd.pjcloud.thirdparty.utils.sms; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.aliyuncs.CommonRequest; +import com.aliyuncs.CommonResponse; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.http.MethodType; +import com.aliyuncs.profile.DefaultProfile; +import com.bsd.pjcloud.thirdparty.exception.SmsException; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Map; + +/** + * 阿里云短信服务 + */ +@Component +public class AliyunSMS { + + private static final Logger logger = LoggerFactory.getLogger(AliyunSMS.class); + + @Value("${aliyun.sms.accessKeyId}") + private String accessKeyId; + + @Value("${aliyun.sms.accessKeySecret}") + private String accessKeySecret; + + @Value("${aliyun.sms.signName}") + private String signName; + + private static final String DEFAULT_TEMPLATE_KEY = "verification"; + + private final Map templateCodes; + + public AliyunSMS(Environment environment) { + this.templateCodes = Binder.get(environment) + .bind("aliyun.sms.template-codes", Bindable.mapOf(String.class, String.class)) + .orElse(Collections.emptyMap()); + } + + /** + * 发送验证码短信 + * + * @param phoneNo 手机号 + * @param code 验证码 + */ + public void sendVerificationCode(String phoneNo, String code) { + sendSms(phoneNo, DEFAULT_TEMPLATE_KEY, Collections.singletonMap("code", code)); + } + + /** + * 按模板 key 发送短信 + * + * @param phoneNo 手机号 + * @param templateKey 模板 key(对应 yml 配置中的 aliyun.sms.template-codes.) + * @param templateParam 模板参数 + */ + public void sendSms(String phoneNo, String templateKey, Map templateParam) { + String templateCode = templateCodes.get(templateKey); + if (templateCode == null || templateCode.trim().isEmpty()) { + throw new SmsException("未配置阿里云短信模板: " + templateKey); + } + + // 地域固定为 cn-hangzhou + DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret); + IAcsClient client = new DefaultAcsClient(profile); + + CommonRequest request = new CommonRequest(); + request.setSysMethod(MethodType.POST); + request.setSysRegionId("cn-hangzhou"); + request.setSysDomain("dysmsapi.aliyuncs.com"); + request.setSysVersion("2017-05-25"); + request.setSysAction("SendSms"); + request.putQueryParameter("PhoneNumbers", phoneNo); + request.putQueryParameter("SignName", signName); + request.putQueryParameter("TemplateCode", templateCode); + request.putQueryParameter("TemplateParam", JSON.toJSONString(templateParam)); + + try { + CommonResponse response = client.getCommonResponse(request); + String data = response.getData(); + logger.info("阿里云短信发送请求: phone={}, signName={}, templateKey={}, templateCode={}, response={}", + phoneNo, signName, templateKey, templateCode, data); + + JSONObject json = JSON.parseObject(data); + String respCode = json.getString("Code"); + String message = json.getString("Message"); + + if (!"OK".equalsIgnoreCase(respCode)) { + String errMsg = message != null ? message : "阿里云短信发送失败"; + throw new SmsException(errMsg); + } + } catch (ClientException e) { + logger.error("阿里云短信发送异常", e); + throw new SmsException("阿里云短信发送异常: " + e.getErrMsg()); + } + } +} + + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml index 1d6192b0d..674897636 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml @@ -176,3 +176,11 @@ hikvision: cancel-appointment: /api/visitor/v1/appointment/cancel # 新增取消预约接口 register-appointment: /api/visitor/v1/order/register # 新增登记接口 visitor-checkout: /api/visitor/v1/visitor/out # 新增签离接口 + +aliyun: + sms: + accessKeyId: LTAI5tGUX2j2kqSCW1mcgm6i + accessKeySecret: p4RIWlleFMH60cjuoeKl7YIBLS18v8 + signName: 浙江资安信信息科技 + template-codes: + verification: SMS_500945185 \ No newline at end of file diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/PjcloudAdminApplication.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/PjcloudAdminApplication.java index 8013aea61..71273a752 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/PjcloudAdminApplication.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/PjcloudAdminApplication.java @@ -19,6 +19,7 @@ package com.bsd.pjcloud.admin; +import com.bsd.pjcloud.common.feign.annotation.EnablePjcloudFeignClients; import com.bsd.pjcloud.common.security.annotation.EnablePjcloudResourceServer; import com.bsd.pjcloud.common.swagger.annotation.EnableOpenApi; import org.springframework.boot.SpringApplication; @@ -33,6 +34,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; * 用户统一管理系统 */ @EnableOpenApi("admin") +@EnablePjcloudFeignClients @EnablePjcloudResourceServer @EnableDiscoveryClient @EnableScheduling diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java index 9d0939b9c..a48be06f1 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java @@ -25,6 +25,7 @@ import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.admin.api.dto.ForgotPasswordDTO; import com.bsd.pjcloud.admin.api.dto.UserDTO; import com.bsd.pjcloud.admin.api.dto.UserDeptSimpleInfo; import com.bsd.pjcloud.admin.api.dto.UserInfo; @@ -94,6 +95,7 @@ public class SysUserController { private final SysRoleService sysRoleService; private final SysUserRoleService sysUserRoleService; + private final SysMessageService sysMessageService; /* ************************************* start 回调 start ************************************* */ @Inner(value = false) @@ -325,6 +327,24 @@ public class SysUserController { return userService.changePassword(userDto); } + /** + * 忘记密码-发送短信验证码 + */ + @Inner(value = false) + @GetMapping("/forgot-password/sms-code") + public R sendForgotPasswordSmsCode(@RequestParam("mobile") String mobile) { + return sysMessageService.sendSmsCode(mobile); + } + + /** + * 忘记密码-短信验证码重置密码 + */ + @Inner(value = false) + @PostMapping("/forgot-password/reset") + public R resetPasswordBySms(@Valid @RequestBody ForgotPasswordDTO forgotPasswordDTO) { + return userService.resetPasswordBySms(forgotPasswordDTO); + } + /** * @param username 用户名称 * @return 上级部门用户列表 diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserService.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserService.java index 857d06bb0..f6ef0be3d 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserService.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserService.java @@ -23,6 +23,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; +import com.bsd.pjcloud.admin.api.dto.ForgotPasswordDTO; import com.bsd.pjcloud.admin.api.dto.UserDTO; import com.bsd.pjcloud.admin.api.dto.UserInfo; import com.bsd.pjcloud.admin.api.entity.SysUser; @@ -203,6 +204,14 @@ public interface SysUserService extends IService { */ R resetUserPassword(UserDTO userDto); + /** + * 忘记密码场景:短信验证码重置密码 + * + * @param forgotPasswordDTO 重置参数 + * @return 重置结果 + */ + R resetPasswordBySms(ForgotPasswordDTO forgotPasswordDTO); + List getUserDeptByIds(List userIds); /** diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysMessageServiceImpl.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysMessageServiceImpl.java index e973abb44..d94cbf334 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysMessageServiceImpl.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysMessageServiceImpl.java @@ -49,6 +49,7 @@ import com.bsd.pjcloud.common.log.util.JacksonSensitiveFieldUtil; import com.bsd.pjcloud.common.log.util.LogTypeEnum; import com.bsd.pjcloud.common.log.util.SysLogUtils; import com.bsd.pjcloud.common.security.util.SecurityUtils; +import com.bsd.pjcloud.thirdparty.api.feign.RemoterThirdPartySmsService; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -119,6 +120,8 @@ public class SysMessageServiceImpl extends ServiceImpl impl private final SysUserPostMapper sysUserPostMapper; private final CacheManager cacheManager; + private final StringRedisTemplate redisTemplate; private final RemoteWalletService remoteWalletService; @@ -875,6 +880,10 @@ public class SysUserServiceImpl extends ServiceImpl impl if (StrUtil.isEmpty(userDto.getNewpassword1())) { return R.failed("新密码不能为空"); } + String weakPasswordMsg = validatePasswordStrength(userDto.getNewpassword1()); + if (StrUtil.isNotBlank(weakPasswordMsg)) { + return R.failed(weakPasswordMsg); + } String password = ENCODER.encode(userDto.getNewpassword1()); this.update(Wrappers.lambdaUpdate().set(SysUser::getPassword, password).set(SysUser::getPasswordModifyTime, LocalDateTime.now()).eq(SysUser::getUserId, user.getUserId())); @@ -938,6 +947,10 @@ public class SysUserServiceImpl extends ServiceImpl impl if (StrUtil.equals(userDto.getPassword(), userDto.getNewpassword1())) { return R.failed("新旧密码不能相同"); } + String weakPasswordMsg = validatePasswordStrength(userDto.getNewpassword1()); + if (StrUtil.isNotBlank(weakPasswordMsg)) { + return R.failed(weakPasswordMsg); + } // 重置密码 String password = ENCODER.encode(userDto.getNewpassword1()); @@ -945,6 +958,63 @@ public class SysUserServiceImpl extends ServiceImpl impl return R.ok(); } + @Override + public R resetPasswordBySms(ForgotPasswordDTO forgotPasswordDTO) { + SysUser user = baseMapper.selectOne(Wrappers.lambdaQuery().eq(SysUser::getPhone, forgotPasswordDTO.getMobile())); + if (Objects.isNull(user)) { + return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_APP_PHONE_UNREGISTERED, forgotPasswordDTO.getMobile())); + } + + String cacheKey = CacheConstants.DEFAULT_CODE_KEY + LoginTypeEnum.SMS.getType() + StringPool.AT + forgotPasswordDTO.getMobile(); + String cacheCode = redisTemplate.opsForValue().get(cacheKey); + if (StrUtil.isBlank(cacheCode) || !StrUtil.equals(cacheCode, forgotPasswordDTO.getSmsCode())) { + return R.failed("短信验证码错误或已过期"); + } + String weakPasswordMsg = validatePasswordStrength(forgotPasswordDTO.getNewPassword()); + if (StrUtil.isNotBlank(weakPasswordMsg)) { + return R.failed(weakPasswordMsg); + } + + String encodedPassword = ENCODER.encode(forgotPasswordDTO.getNewPassword()); + this.update(Wrappers.lambdaUpdate() + .set(SysUser::getPassword, encodedPassword) + .set(SysUser::getPasswordModifyTime, LocalDateTime.now()) + .set(SysUser::getPasswordExpireFlag, CommonConstants.STATUS_NORMAL) + .eq(SysUser::getUserId, user.getUserId())); + + redisTemplate.delete(cacheKey); + Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS); + if (Objects.nonNull(cache)) { + cache.evict(user.getUsername()); + } + return R.ok(Boolean.TRUE); + } + + private String validatePasswordStrength(String password) { + if (StrUtil.length(password) < 8 || StrUtil.length(password) > 20) { + return "密码长度需为8-20位"; + } + boolean hasUpper = false; + boolean hasLower = false; + boolean hasDigit = false; + boolean hasSpecial = false; + for (char c : password.toCharArray()) { + if (Character.isUpperCase(c)) { + hasUpper = true; + } else if (Character.isLowerCase(c)) { + hasLower = true; + } else if (Character.isDigit(c)) { + hasDigit = true; + } else { + hasSpecial = true; + } + } + if (!(hasUpper && hasLower && hasDigit && hasSpecial)) { + return "密码需包含大写字母、小写字母、数字和特殊字符"; + } + return null; + } + @Override public List listUserIdByRoleIds(List roleIdList) { return sysUserRoleMapper.selectList(Wrappers.lambdaQuery().in(SysUserRole::getRoleId, roleIdList)).stream().map(SysUserRole::getUserId).collect(Collectors.toList());