From 5f98bf24b31d4af588e4f87aafa8b9ca821800eb Mon Sep 17 00:00:00 2001 From: nicheng_he Date: Thu, 24 Jul 2025 11:21:44 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=8F=91=E9=80=81=E9=82=AE=E4=BB=B6=E6=97=B6?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A8=A1=E7=89=88=202.=E9=87=8D=E7=BD=AE?= =?UTF-8?q?=E6=88=90=E5=8A=9F=E6=97=B6=E6=B8=85=E9=99=A4=E7=99=BB=E9=99=86?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=AC=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/framework/forgot-password/index.vue | 24 +++++-- .../src/views/framework/login/sms-code.vue | 2 + .../views/framework/register/email-code.vue | 2 + .../src/controller/basic/code-controller.ts | 17 ++++- .../user/login/forgot-password-controller.ts | 8 ++- .../src/modules/basic/service/code-service.ts | 70 ++++++++++++++----- .../sys/authority/service/user-service.ts | 1 + 7 files changed, 102 insertions(+), 22 deletions(-) diff --git a/packages/ui/certd-client/src/views/framework/forgot-password/index.vue b/packages/ui/certd-client/src/views/framework/forgot-password/index.vue index 9e94db2a..1acee359 100644 --- a/packages/ui/certd-client/src/views/framework/forgot-password/index.vue +++ b/packages/ui/certd-client/src/views/framework/forgot-password/index.vue @@ -21,7 +21,13 @@ - + @@ -33,7 +39,14 @@ - + @@ -60,7 +73,7 @@ 找回密码
- 管理员无绑定邮箱或MFA丢失找回 + 管理员无绑定通信方式或MFA丢失找回
@@ -83,7 +96,10 @@ const rules = { input: [{ required: true }], validateCode: [{ required: true }], imgCode: [{ required: true }, { min: 4, max: 4, message: "请输入4位图片验证码" }], - password: [{ required: true, trigger: "change", message: "请输入密码" }], + password: [ + { required: true, trigger: "change", message: "请输入密码" }, + { len: 6, message: "至少输入6位密码" }, + ], confirmPassword: [ { required: true, trigger: "change", message: "请确认密码" }, { diff --git a/packages/ui/certd-client/src/views/framework/login/sms-code.vue b/packages/ui/certd-client/src/views/framework/login/sms-code.vue index b9f6e1ac..efbf6ab3 100644 --- a/packages/ui/certd-client/src/views/framework/login/sms-code.vue +++ b/packages/ui/certd-client/src/views/framework/login/sms-code.vue @@ -23,6 +23,7 @@ const props = defineProps<{ phoneCode?: string; imgCode?: string; randomStr?: string; + verificationType?: string; }>(); const emit = defineEmits(["update:value", "change"]); @@ -58,6 +59,7 @@ async function sendSmsCode() { mobile: props.mobile, imgCode: props.imgCode, randomStr: props.randomStr, + verificationType: props.verificationType, }); } finally { loading.value = false; diff --git a/packages/ui/certd-client/src/views/framework/register/email-code.vue b/packages/ui/certd-client/src/views/framework/register/email-code.vue index 51e6cd15..6ee92e1f 100644 --- a/packages/ui/certd-client/src/views/framework/register/email-code.vue +++ b/packages/ui/certd-client/src/views/framework/register/email-code.vue @@ -22,6 +22,7 @@ const props = defineProps<{ email?: string; imgCode?: string; randomStr?: string; + verificationType?: string; }>(); const emit = defineEmits(["update:value", "change"]); @@ -53,6 +54,7 @@ async function sendSmsCode() { email: props.email, imgCode: props.imgCode, randomStr: props.randomStr, + verificationType: props.verificationType, }); } finally { loading.value = false; diff --git a/packages/ui/certd-server/src/controller/basic/code-controller.ts b/packages/ui/certd-server/src/controller/basic/code-controller.ts index 09a90009..fb0eedc2 100644 --- a/packages/ui/certd-server/src/controller/basic/code-controller.ts +++ b/packages/ui/certd-server/src/controller/basic/code-controller.ts @@ -27,6 +27,9 @@ export class EmailCodeReq { @Rule(RuleType.string().required().max(4)) imgCode: string; + + @Rule(RuleType.string()) + verificationType: string; } /** @@ -55,8 +58,20 @@ export class BasicController extends BaseController { @Body(ALL) body: EmailCodeReq ) { + const opts = { + verificationType: body.verificationType, + title: undefined, + content: undefined, + duration: undefined, + }; + if(body?.verificationType === 'forgotPassword') { + opts.title = '找回密码'; + opts.content = '验证码:${code}。您正在找回密码,请输入验证码并完成操作。如非本人操作请忽略'; + opts.duration = 3; + } + await this.codeService.checkCaptcha(body.randomStr, body.imgCode); - await this.codeService.sendEmailCode(body.email, body.randomStr); + await this.codeService.sendEmailCode(body.email, body.randomStr, opts); // 设置缓存内容 return this.ok(null); } diff --git a/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts b/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts index 62ff9a46..382bdcb2 100644 --- a/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts +++ b/packages/ui/certd-server/src/controller/user/login/forgot-password-controller.ts @@ -2,12 +2,15 @@ import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core'; import { BaseController, CommonException, Constants, SysSettingsService } from "@certd/lib-server"; import { CodeService } from '../../../modules/basic/service/code-service.js'; import { UserService } from '../../../modules/sys/authority/service/user-service.js'; +import { LoginService } from "../../../modules/login/service/login-service.js"; /** */ @Provide() @Controller('/api') export class LoginController extends BaseController { + @Inject() + loginService: LoginService; @Inject() userService: UserService; @Inject() @@ -23,6 +26,7 @@ export class LoginController extends BaseController { ) { if(body.type === 'email') { this.codeService.checkEmailCode({ + verificationType: 'forgotPassword', email: body.input, randomStr: body.randomStr, validateCode: body.validateCode, @@ -30,6 +34,7 @@ export class LoginController extends BaseController { }); } else if(body.type === 'mobile') { await this.codeService.checkSmsCode({ + verificationType: 'forgotPassword', mobile: body.input, randomStr: body.randomStr, phoneCode: body.phoneCode, @@ -39,7 +44,8 @@ export class LoginController extends BaseController { } else { throw new CommonException('暂不支持的找回类型,请联系管理员找回'); } - await this.userService.forgotPassword(body); + const username = await this.userService.forgotPassword(body); + username && this.loginService.clearCacheOnSuccess(username) return this.ok(); } } diff --git a/packages/ui/certd-server/src/modules/basic/service/code-service.ts b/packages/ui/certd-server/src/modules/basic/service/code-service.ts index a7f5122a..62d108ca 100644 --- a/packages/ui/certd-server/src/modules/basic/service/code-service.ts +++ b/packages/ui/certd-server/src/modules/basic/service/code-service.ts @@ -57,7 +57,15 @@ export class CodeService { } /** */ - async sendSmsCode(phoneCode = '86', mobile: string, randomStr: string) { + async sendSmsCode( + phoneCode = '86', + mobile: string, + randomStr: string, + opts?: { + duration?: number, + verificationType?: string + }, + ) { if (!mobile) { throw new Error('手机号不能为空'); } @@ -65,6 +73,8 @@ export class CodeService { throw new Error('randomStr不能为空'); } + const duration = Math.max(Math.floor(Math.min(opts?.duration || 5, 15)), 1); + const sysSettings = await this.sysSettingsService.getPrivateSettings(); if (!sysSettings.sms?.config?.accessId) { throw new Error('当前站点还未配置短信'); @@ -84,16 +94,29 @@ export class CodeService { phoneCode, }); - const key = this.buildSmsCodeKey(phoneCode, mobile, randomStr); + const key = this.buildSmsCodeKey(phoneCode, mobile, randomStr, opts?.verificationType); cache.set(key, smsCode, { - ttl: 5 * 60 * 1000, //5分钟 + ttl: duration * 60 * 1000, //5分钟 }); return smsCode; } /** + * + * @param email 收件邮箱 + * @param randomStr + * @param opts title标题 content内容模版 duration有效时间单位分钟 verificationType验证类型 */ - async sendEmailCode(email: string, randomStr: string) { + async sendEmailCode( + email: string, + randomStr: string, + opts?: { + title?: string, + content?: string, + duration?: number, + verificationType?: string + }, + ) { if (!email) { throw new Error('Email不能为空'); } @@ -110,15 +133,20 @@ export class CodeService { } const code = randomNumber(4); + const duration = Math.max(Math.floor(Math.min(opts?.duration || 5, 15)), 1); + + const title = `【${siteTitle}】${!!opts?.title ? opts.title : '验证码'}`; + const content = !!opts.content ? this.compile(opts.content)({code, duration}) : `您的验证码是${code},请勿泄露`; + await this.emailService.send({ - subject: `【${siteTitle}】验证码`, - content: `您的验证码是${code},请勿泄露`, + subject: title, + content: content, receivers: [email], }); - const key = this.buildEmailCodeKey(email, randomStr); + const key = this.buildEmailCodeKey(email, randomStr, opts?.verificationType); cache.set(key, code, { - ttl: 5 * 60 * 1000, //5分钟 + ttl: duration * 60 * 1000, //5分钟 }); return code; } @@ -126,20 +154,20 @@ export class CodeService { /** * checkSms */ - async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; throwError: boolean }) { - const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.randomStr); + async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; verificationType?: string; throwError: boolean }) { + const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.randomStr, opts.verificationType); if (isDev()) { return true; } return this.checkValidateCode(key, opts.smsCode, opts.throwError); } - buildSmsCodeKey(phoneCode: string, mobile: string, randomStr: string) { - return `sms:${phoneCode}${mobile}:${randomStr}`; + buildSmsCodeKey(phoneCode: string, mobile: string, randomStr: string, verificationType?: string) { + return ['sms', verificationType, phoneCode, mobile, randomStr].filter(item => !!item).join(':'); } - buildEmailCodeKey(email: string, randomStr: string) { - return `email:${email}:${randomStr}`; + buildEmailCodeKey(email: string, randomStr: string, verificationType?: string) { + return ['email', verificationType, email, randomStr].filter(item => !!item).join(':'); } checkValidateCode(key: string, userCode: string, throwError = true) { //验证图片验证码 @@ -154,8 +182,18 @@ export class CodeService { return true; } - checkEmailCode(opts: { randomStr: string; validateCode: string; email: string; throwError: boolean }) { - const key = this.buildEmailCodeKey(opts.email, opts.randomStr); + checkEmailCode(opts: { randomStr: string; validateCode: string; email: string; verificationType?: string; throwError: boolean }) { + const key = this.buildEmailCodeKey(opts.email, opts.randomStr, opts.verificationType); return this.checkValidateCode(key, opts.validateCode, opts.throwError); } + + compile(templateString: string) { + return new Function( + "data", + ` with(data || {}) { + return \`${templateString}\`; + } + ` + ); + } } diff --git a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts index 94359243..3298d477 100644 --- a/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts +++ b/packages/ui/certd-server/src/modules/sys/authority/service/user-service.ts @@ -250,6 +250,7 @@ export class UserService extends BaseService { // return; } await this.resetPassword(user.id, data.password) + return user.username; } async changePassword(userId: any, form: any) {