忘记密码

Uniboot 模板项目中,提供了完整的忘记密码与自助重置密码功能。该功能在 Simple 模板中通过本地组件与安全 API 的配合来实现。

本文将以 Simple 模板中的 src/views/account/forget.vue 文件为核心,详细介绍忘记密码页面的实现细节、三阶段式安全流交互以及相关的 API 接口设计。

页面架构设计

忘记密码页面采用了来自 uniboot-ui 组件库的内置面板组件 ForgetPage。在前端页面中,它将以卡片式引导的方式引导用户分步完成密码找回。

其核心结构如下:

html
<template>
  <forget-page
    :card-title="$t('forgetPassword')"
    :steps="steps"
    :step1-panel-title="$t('forgetPassword.step.sendEmail')"
    :step2-panel-title="$t('forgetPassword.step.reset')"
    :email-label="$t('forgetPassword.step1.email')"
    :email-placeholder="$t('enter')"
    :captcha-label="$t('user.changeEmail.emailCode')"
    :captcha-placeholder="$t('enter')"
    :send-code-button-text="$t('forgetPassword.step1.getCode')"
    :email-code-label="$t('forgetPassword.step2.code')"
    :new-password-label="$t('user.changePwd.newPwd')"
    :confirm-password-label="$t('user.changePwd.confirmPwd')"
    :reget-code-text="$t('forgetPassword.step2.getCode')"
    :cancel-text="$t('cancel')"
    :submit-password-text="$t('ok')"
    :password-tips="passwordTips"
    :success-title="$t('forgetPassword.step3.tip1')"
    :success-sub-title="$t('forgetPassword.step3.tip2')"
    :back-to-login-text="$t('forgetPassword.step3.btn')"
    :get-captcha="getCaptcha"
    :on-send-code="handleSendCode"
    :on-reset-password="handleResetPassword"
    :on-back-to-login="handleBackToLogin"
    :languages="languages"
    :default-language="currentLocale"
    :on-language-change="handleLanguageChange"
    :enter-message="$t('enter')"
    :email-format-message="$t('emailFormatError')"
    :password-format-message="$t('wrong_format')"
    :confirm-mismatch-message="$t('twice_inconsistent')"
    @back="router.back()"
  />
</template>

该组件内部整合了国际化提示配置、表单输入规则校验、多语言刷新加载机制,并提供四种关键的生命周期/行为回调方法以连接本地业务接口。


核心业务交互流

忘记密码的交互流共分为三个安全阶段:发送邮箱验证码校验验证码并重置密码 以及 重置成功处理

1. 图形验证码加载机制

在用户发送邮箱验证码前,页面需要进行图形验证码校验,以防范恶意接口刷单行为。

  • 实现机制: 在 getCaptcha 方法中,调用本地 src/api/user.tscaptcha 接口,要求后端返回指定尺寸的图形验证码数据(图片 Blob 二进制流)。
  • 图片流处理: 若成功获取到图片 Blob,使用 URL.createObjectURL(blob) 在本地生成一个临时 URL,由组件库渲染在界面上。
typescript
const getCaptcha = async (): Promise<LoginFormCaptchaResult> => {
  // 请求图形验证码接口,要求宽高为 100x36
  const res = await captcha({ width: 100, height: 36 })
  const blob = (res as { data?: Blob }).data
  if (!blob) {
    return { img: '' }
  }
  // 转换二进制流为临时的前端图片 URL
  return { img: URL.createObjectURL(blob) }
}

2. 交互第一阶段:发送邮箱验证码

在第一步中,用户输入邮箱和对应的图形验证码后,点击获取邮箱验证码。

  • 业务逻辑: 组件库在完成本地基本校验后,触发 handleSendCode
  • API 通信: 调用 getAccountEmailCode(email, captcha) 发送请求。成功后,后端会返回一个包含 codeToken 的响应对象。该 Token 作为这一步验证的安全关联凭证,需返回给组件以传递到第二步。
typescript
const handleSendCode: ForgetOnSendCode = async (values) => {
  // 调用接口向指定邮箱发送验证码,传入邮箱与图形验证码答案
  const { codeToken } = await getAccountEmailCode(values.email, values.captcha)
  if (codeToken) {
    // 返回 codeToken 供组件内部状态机存储,作为下一步的安全令牌
    return { codeToken }
  }
  return false
}

3. 交互第二阶段:密码重置执行

用户从收到的邮箱中获取动态验证码,并输入新的符合规范的密码。

  • 业务逻辑: 组件确认用户两次输入的新密码一致后,触发 handleResetPassword 回调。
  • API 通信: 调用 passwordRecover 接口。参数中必须携带 codeToken(第一阶段获得的会话令牌)、code(邮箱收到的验证码)、account(用户邮箱地址)、findType: 'email'(表示邮箱找回),以及用户设定的新密码明文。
typescript
const handleResetPassword: ForgetOnResetPassword = async (values) => {
  // 调用安全恢复接口以提交新密码修改
  await passwordRecover({
    account: values.email,
    code: values.code,
    codeToken: values.codeToken,
    findType: 'email',
    password: values.password,
  })
}

4. 交互第三阶段:状态重置与重新登录

密码成功重置后,组件内部会切换到成功提示阶段。用户点击“返回登录”按钮。

  • 业务逻辑: 触发 handleBackToLogin
  • 状态登出: 调用 Pinia 状态管理器的 userStore.logout() 动作。该动作将会彻底清除当前本地未失效的令牌和用户信息等本地持久化状态,并执行路由跳转至 /login 页,确保后续可使用新密码安全地重新登录。
typescript
const handleBackToLogin = async () => {
  // 清理 Pinia 状态与缓存并强制导航回登录页
  await userStore.logout()
}

辅助协作逻辑

多语言刷新机制

为了确保语言切换后,整个页面的全部占位语、状态和全局 UI 资源均同步加载对应的多语言文件,页面在切换语言时采用强刷机制:

typescript
const handleLanguageChange = async (value: string) => {
  // 将所选的语言代码写入全局 i18n
  setI18nLanguage(value)
  // 强制重新加载页面以拉取新翻译包并重载页面资源
  window.location.reload()
}