feat: 修改密码功能

This commit is contained in:
liulujian 2026-04-28 18:06:36 +08:00
parent 3a8b70495a
commit ee3ce43c8e
23 changed files with 944 additions and 46 deletions

View File

@ -0,0 +1,367 @@
# 修改密码功能实现计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为碳信网平台添加普通用户自助修改密码功能txw-sso后端 + txw-mhzc-web前端
**Architecture:**
- 后端SSO服务新增 `POST /sso/auth/changePassword` 接口,用户通过 authenticate 验证旧密码,通过 mhzc-service 的 Feign 接口更新用户密码
- 前端mhzc-web 新增修改密码页面(账号管理),工作台增加快捷入口,用户中心左侧菜单增加"账号管理"菜单项
**Tech Stack:** Java Spring Boot / Vue 2 / TDesign / Feign
---
## 涉及的变更文件
### 后端txw-sso
| 文件 | 改动 |
|------|------|
| `AuthService.java` | 新增 `changePassword` 接口声明 |
| `AuthServiceImpl.java` | 实现 `changePassword` 逻辑 |
| `AuthController.java` | 新增 `/auth/changePassword` 路由 |
| `ChangePasswordReqVO.java` | 新建请求 VO |
| `ChangePasswordRespVO.java` | 新建响应 VO |
| `YhxxApi.java` (txw-sso-api) | 新增 Feign 方法 `updatePassword` |
| `TxwMhzcYhxxbService.java` (txw-mhzc) | 新增 `updatePassword` 方法声明 |
| `TxwMhzcYhxxbServiceImpl.java` (txw-mhzc) | 实现 `updatePassword` |
| `TxwMhzcYhxxbMapper.xml` | 新增 updatePassword SQL |
| `ISsoApi.java` | FeignClient name 常量需确认mhzc服务名 |
### 前端txw-mhzc-web
| 文件 | 改动 |
|------|------|
| `views/gzt/index.vue` | 新增修改密码快捷入口按钮 |
| `views/yhzx/zhanghugl/index.vue` | 新建修改密码页面 |
| `router/routes.js` | 新增 `zhanghugl` 路由 |
| `views/glxtSy/config.js` | 菜单配置新增"账号管理" |
| `api/sso.js` | 新建,封装 changePassword API |
---
## 后端任务拆分
### Task 1: 后端 - 新建 ChangePasswordReqVO
**文件:** `txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/pojo/vo/ChangePasswordReqVO.java`
- [ ] **Step 1: 创建文件**
```java
package com.css.txw.sso.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
@Schema(description = "修改密码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ChangePasswordReqVO {
@Schema(description = "旧密码MD5加密", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "旧密码不能为空")
private String oldPassword;
@Schema(description = "新密码MD5加密", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "新密码不能为空")
private String newPassword;
@Schema(description = "确认密码MD5加密", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
}
```
---
### Task 2: 后端 - AuthService 新增 changePassword 接口声明
**文件:** `txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/service/auth/AuthService.java`
在接口末尾添加:
```java
/**
* 修改密码
*
* @param reqVO 修改密码请求
* @return 成功返回 null
*/
void changePassword(ChangePasswordReqVO reqVO);
```
引入 import
```java
import com.css.txw.sso.pojo.vo.ChangePasswordReqVO;
```
---
### Task 3: 后端 - AuthServiceImpl 实现 changePassword
**文件:** `txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/service/auth/impl/AuthServiceImpl.java`
在类末尾添加实现方法:
```java
@Override
public void changePassword(ChangePasswordReqVO reqVO) {
// 1. 校验新密码与确认密码一致性
if (!reqVO.getNewPassword().equals(reqVO.getConfirmPassword())) {
throw exception(AUTH_PASSWORD_CONFIRM_MISMATCH);
}
// 2. 密码复杂度校验6-20位字母+数字)
if (!isValidPasswordComplexity(reqVO.getNewPassword())) {
throw exception(AUTH_PASSWORD_COMPLEXITY_INVALID);
}
// 3. 获取当前登录用户
String userId = SecurityFrameworkUtils.getLoginUserId();
YhxxbDTO yhxx = yhxxService.getYhxx(userId);
// 4. 校验新旧密码不能相同
if (reqVO.getOldPassword().equals(reqVO.getNewPassword())) {
throw exception(AUTH_PASSWORD_SAME_AS_OLD);
}
// 5. 旧密码正确性校验
authenticate(yhxx.getDlzh(), reqVO.getOldPassword());
// 6. 更新密码
yhxxService.updatePassword(userId, reqVO.getNewPassword());
}
private boolean isValidPasswordComplexity(String password) {
if (password == null || password.length() < 6 || password.length() > 20) {
return false;
}
boolean hasLetter = password.matches(".*[A-Za-z]+.*");
boolean hasDigit = password.matches(".*[0-9]+.*");
return hasLetter && hasDigit;
}
```
新增引入:
```java
import com.css.txw.sso.pojo.vo.ChangePasswordReqVO;
import com.css.txw.sso.util.SecurityFrameworkUtils;
import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_PASSWORD_CONFIRM_MISMATCH;
import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_PASSWORD_COMPLEXITY_INVALID;
import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_PASSWORD_SAME_AS_OLD;
```
---
### Task 4: 后端 - ErrorCodeConstants 新增错误码
**文件:** `txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/constants/ErrorCodeConstants.java`
新增三个错误码:
```java
AUTH_PASSWORD_CONFIRM_MISMATCH(400, "两次输入的新密码不一致"),
AUTH_PASSWORD_COMPLEXITY_INVALID(400, "密码长度6-20位需包含字母和数字"),
AUTH_PASSWORD_SAME_AS_OLD(400, "新密码不能与原密码相同"),
```
---
### Task 5: 后端 - AuthController 新增路由
**文件:** `txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/controller/auth/AuthController.java`
在类末尾添加:
```java
@PostMapping("/changePassword")
@Operation(summary = "修改密码")
public CommonResult<Void> changePassword(@RequestBody @Valid ChangePasswordReqVO reqVO) {
authService.changePassword(reqVO);
return success(null);
}
```
新增引入:
```java
import com.css.txw.sso.pojo.vo.ChangePasswordReqVO;
```
---
### Task 6: 后端 - YhxxService 新增 updatePassword 方法
**文件:** `txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/service/yhxx/YhxxService.java`
末尾新增方法声明:
```java
void updatePassword(String yhUuid, String newPassword);
```
---
### Task 7: 后端 - YhxxServiceImpl 实现 updatePasswordFeign调用mhzc
**文件:** `txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/service/yhxx/impl/YhxxServiceImpl.java`
首先需要确认 mhzc 服务的 Feign 接口存在。如果 YhxxApi 不支持更新密码,则需要在 txw-sso-api 中新增 Feign 方法指向 mhzc 的更新密码接口。
步骤:
1. 在 `txw-sso-api``YhxxApi.java` 中新增 `updatePassword` Feign 方法
2. 在 `txw-mhzc``TxwMhzcYhxxbService` 新增 `updatePassword` 方法
3. 在 `TxwMhzcYhxxbServiceImpl` 实现该方法
4. 在 mapper XML 中新增 update SQL
5. 在 `YhxxServiceImpl` 中调用该 Feign 接口
**YhxxApi.java (txw-sso-api) 新增:**
```java
@PostMapping("/mhzc/user/updatePassword")
@Operation(summary = "更新用户密码")
CommonResult<String> updatePassword(@RequestParam("yhUuid") String yhUuid, @RequestParam("dlmm") String dlmm);
```
**TxwMhzcYhxxbService.java 新增:**
```java
String updatePassword(String yhUuid, String dlmm);
```
**TxwMhzcYhxxbServiceImpl.java 新增:**
```java
@Override
public String updatePassword(String yhUuid, String dlmm) {
TxwMhzcYhxxbDO yhxxbDO = new TxwMhzcYhxxbDO();
yhxxbDO.setYhUuid(yhUuid);
yhxxbDO.setDlmm(dlmm);
this.updateById(yhxxbDO);
return "success";
}
```
**TxwMhzcYhxxbMapper.xml 新增:**
```xml
<update id="updatePassword">
UPDATE txw_mhzc_yhxxb SET dlmm = #{dlmm} WHERE yh_uuid = #{yhUuid}
</update>
```
**YhxxServiceImpl.java 实现:**
```java
@Resource
private YhxxApi yhxxApi;
@Override
public void updatePassword(String yhUuid, String newPassword) {
yhxxApi.updatePassword(yhUuid, newPassword);
}
```
---
## 前端任务拆分txw-mhzc-web
### Task 8: 前端 - 新建 api/sso.js
**文件:** `txw-mhzc-web/src/pages/index/api/sso.js`
```js
import { fetchSso } from '@/core/request';
export const changePassword = (params) => {
return fetchSso({
url: '/sso/auth/changePassword',
data: JSON.stringify(params),
method: 'post',
loading: true,
});
};
```
---
### Task 9: 前端 - 新建修改密码页面
**文件:** `txw-mhzc-web/src/pages/index/views/yhzx/zhanghugl/index.vue`
页面元素:
- 三个密码输入框(旧密码、新密码、确认密码)
- 确认按钮
- 密码格式提示文字
校验逻辑:
1. 旧密码、新密码、确认密码不能为空
2. 新密码长度6-20位需包含字母和数字提示密码长度6-20位需包含字母和数字
3. 新密码与确认密码一致性(提示:两次输入的新密码不一致)
4. 提交后调用 changePassword API
5. 成功提示"密码修改成功",可跳转回工作台
样式参考现有的登录页面,使用 TDesign 组件库。
---
### Task 10: 前端 - 路由配置新增 zhanghugl
**文件:** `txw-mhzc-web/src/pages/index/router/routes.js`
`yhzx` 的 children 数组中新增:
```js
{ path: 'zhanghugl', component: zhanghugl, name: 'zhanghugl', meta: { title: '账号管理' } }
```
新增 zhanghugl 路由函数:
```js
function zhanghugl() {
return import('@/pages/index/views/yhzx/zhanghugl/index.vue');
}
```
---
### Task 11: 前端 - 菜单配置新增"账号管理"
**文件:** `txw-mhzc-web/src/pages/index/views/glxtSy/config.js`
`menuList` 数组中新增一项:
```js
{
value: 'zhanghugl',
title: '账号管理',
icon: 'password',
}
```
---
### Task 12: 前端 - 工作台新增修改密码快捷入口
**文件:** `txw-mhzc-web/src/pages/index/views/gzt/index.vue`
在快捷操作区域新增按钮:
```html
<t-button theme="primary" variant="outline" @click="$router.push('/yhzx/zhanghugl')">
修改密码
</t-button>
```
---
## 任务执行顺序
建议按以下顺序执行:
1. Task 1 - 新建 ChangePasswordReqVO
2. Task 4 - 新增错误码
3. Task 6 - YhxxService 新增 updatePassword
4. Task 7 - YhxxServiceImpl + Feign + MHZC 实现
5. Task 2 - AuthService 新增 changePassword
6. Task 3 - AuthServiceImpl 实现
7. Task 5 - AuthController 新增路由
8. Task 8 - 前端 API 新建
9. Task 9 - 前端页面新建
10. Task 10 - 路由配置
11. Task 11 - 菜单配置
12. Task 12 - 工作台入口

View File

@ -0,0 +1,147 @@
# 修改密码功能设计
## 1. 概述
为碳信网平台添加普通用户自助修改密码功能,采用前后端分离架构。
- **普通用户修改密码**:位于 `txw-mhzc`(用户中心),入口在个人中心左侧菜单
- **管理员重置密码**:已存在于 `txw-yygl`,无需改动
---
## 2. 后端接口
### 2.1 新增接口
**POST `/sso/auth/changePassword`**
请求体:
```json
{
"oldPassword": "MD5加密字符串",
"newPassword": "MD5加密字符串",
"confirmPassword": "MD5加密字符串"
}
```
响应(统一 `CommonResult`
```json
{
"code": 1,
"data": null,
"msg": "密码修改成功"
}
```
### 2.2 密码复杂度规则
**中等级别**
- 长度6-20 位
- 必须包含:字母 + 数字
### 2.3 校验流程
1. **格式校验**新密码长度6-20位需同时包含字母和数字
2. **一致性校验**newPassword === confirmPassword
3. **身份校验**authenticate(username, oldPassword) 验证旧密码正确性
4. **防重名校验**newPassword !== oldPassword
### 2.4 错误码
| 场景 | msg |
|------|-----|
| 密码格式不符 | 密码长度6-20位需包含字母和数字 |
| 两次密码不一致 | 两次输入的新密码不一致 |
| 旧密码错误 | 原密码输入错误 |
| 新旧密码相同 | 新密码不能与原密码相同 |
### 2.5 涉及文件
| 文件 | 改动 |
|------|------|
| `AuthService.java` | 新增 `changePassword` 接口声明 |
| `AuthServiceImpl.java` | 实现 `changePassword` 逻辑 |
| `AuthController.java` | 新增 `/auth/changePassword` 路由 |
| `AuthLoginReqVO.java` | 建议复用或新建 `ChangePasswordReqVO` |
---
## 3. 前端页面txw-mhzc-web
### 3.1 路由
| 路由路径 | 组件 | 菜单标题 |
|---------|------|---------|
| `/yhzx/zhanghugl` | `ChangePassword.vue` | 账号管理 |
### 3.2 工作台入口
`txw-mhzc-web/src/pages/index/views/gzt/index.vue` 新增快捷操作按钮"修改密码",点击跳转 `/yhzx/zhanghugl`
### 3.3 用户中心左侧菜单
`yhzx` 路由的 children 中新增:
```js
{ path: 'zhanghugl', component: zhanghugl, name: 'zhanghugl', meta: { title: '账号管理' } }
```
### 3.4 修改密码页面 `ChangePassword.vue`
**页面元素**
- 旧密码输入框(密码类型)
- 新密码输入框(密码类型)
- 确认密码输入框(密码类型)
- 确认按钮
**交互逻辑**
- 输入框下方实时显示密码格式提示
- 提交前统一校验
- 成功后给出提示并可跳转其他页面
- 失败回显错误信息
### 3.5 API 调用
```js
// txw-mhzc-web/src/pages/index/api/sso.js
export const changePassword = (params) => {
return fetchSso({
url: '/sso/auth/changePassword',
data: JSON.stringify(params),
method: 'post',
loading: true,
});
};
```
### 3.6 涉及文件
| 文件 | 改动 |
|------|------|
| `router/routes.js` | 新增 `zhanghugl` 路由 |
| `views/gzt/index.vue` | 新增修改密码快捷入口 |
| `views/yhzx/zhanghugl/index.vue` | 新建修改密码页面 |
| `api/sso.js` | 新建,封装 changePassword API |
---
## 4. yygl 管理员重置密码(已有,无需改动)
| 文件 | 说明 |
|------|------|
| `txw-yygl-web/.../yhgl/index.vue` | "重置密码"按钮,调用 `resetPassword(yhUuid)` |
| `txw-yygl-web/.../api/htgl.js` | `resetPassword` 接口已实现 |
| `txw-mhzc/.../UserController.java` | `resetPassword` 接口已实现,重置为配置默认密码 |
---
## 5. 数据库字段
用户表 `dlmm` 字段存储 MD5 加密后的密码。
---
## 6. 备注
- 旧密码传输前已在前端做 MD5 加密(与现有登录流程一致)
- 新密码传输前在前端做 MD5 加密
- 此设计与现有 `authenticate``isPasswordMatch` 逻辑保持一致

View File

@ -107,6 +107,16 @@ export function mhLogout() {
}); });
} }
// 修改密码(用户自助)
export function changePassword(params) {
return fetchSso({
headers,
url: `${basurl}/sso/auth/changePassword`,
data: JSON.stringify(params),
method: 'post',
});
}
// 获取重定向地址 // 获取重定向地址
export function getRedirectUri() { export function getRedirectUri() {
return fetchSso({ return fetchSso({

View File

@ -101,6 +101,11 @@ function search() {
return import(/* webpackChunkName: "search" */ '@/pages/index/views/search/index.vue'); return import(/* webpackChunkName: "search" */ '@/pages/index/views/search/index.vue');
} }
// 账号管理
function zhanghugl() {
return import(/* webpackChunkName: "zhanghugl" */ '@/pages/index/views/yhzx/zhanghugl/index.vue');
}
@ -177,6 +182,7 @@ export default [
{ path: 'tfwxq', component: tfwxq, name: 'tfwxq', meta: { title: '碳服务需求' } }, { path: 'tfwxq', component: tfwxq, name: 'tfwxq', meta: { title: '碳服务需求' } },
{ path: 'ggwhgl', component: newsCenter, name: 'ggwhgl', meta: { title: '消息中心' } }, { path: 'ggwhgl', component: newsCenter, name: 'ggwhgl', meta: { title: '消息中心' } },
{ path: 'lsjy', component: lsjy, name: 'lsjy', meta: { title: '绿色交易' } }, { path: 'lsjy', component: lsjy, name: 'lsjy', meta: { title: '绿色交易' } },
{ path: 'zhanghugl', component: zhanghugl, name: 'zhanghugl', meta: { title: '账号管理' } },
] ]
}, },
{ {

View File

@ -24,4 +24,9 @@ export const menuList = [
icon: 'circle', icon: 'circle',
children: [], children: [],
}, },
{
value: 'zhanghugl',
title: '账号管理',
icon: 'password',
},
]; ];

View File

@ -45,7 +45,7 @@ export default {
{ label: '消息中心', icon: 'mail', to: '/yhzx/ggwhgl', bgColor: '#FCE4EC', color: '#E91E63' }, { label: '消息中心', icon: 'mail', to: '/yhzx/ggwhgl', bgColor: '#FCE4EC', color: '#E91E63' },
{ label: '我的认证', icon: 'user', to: '/yhzx/qyrenzheng', bgColor: '#E3F2FD', color: '#2196F3' }, { label: '我的认证', icon: 'user', to: '/yhzx/qyrenzheng', bgColor: '#E3F2FD', color: '#2196F3' },
{ label: '账号编辑', icon: 'edit', to: '/yhzx/qyrenzheng', bgColor: '#FFF8E1', color: '#FF9800' }, { label: '账号编辑', icon: 'edit', to: '/yhzx/qyrenzheng', bgColor: '#FFF8E1', color: '#FF9800' },
{ label: '修改密码', icon: 'lock-on', to: '/yhzx/qyrenzheng', bgColor: '#E8F5E9', color: '#4CAF50' }, { label: '修改密码', icon: 'lock-on', to: '/yhzx/zhanghugl', bgColor: '#E8F5E9', color: '#4CAF50' },
], ],
}; };
}, },

View File

@ -0,0 +1,192 @@
<template>
<div class="change-password-page">
<div class="page-header">
<div class="page-title">账号管理</div>
</div>
<div class="page-content">
<t-form
ref="formRef"
:data="formData"
:rules="rules"
labelWidth="120"
@submit="onSubmit"
class="password-form"
>
<t-form-item label="旧密码" name="oldPassword">
<t-input
v-model="formData.oldPassword"
type="password"
placeholder="请输入旧密码"
clearable
showPassword
/>
</t-form-item>
<t-form-item label="新密码" name="newPassword">
<t-input
v-model="formData.newPassword"
type="password"
placeholder="请输入新密码"
clearable
showPassword
/>
<div class="password-tip">密码长度6-20需包含字母和数字</div>
</t-form-item>
<t-form-item label="确认密码" name="confirmPassword">
<t-input
v-model="formData.confirmPassword"
type="password"
placeholder="请再次输入新密码"
clearable
showPassword
/>
</t-form-item>
<t-form-item class="submit-item">
<t-button theme="primary" type="submit" :loading="submitLoading">确认修改</t-button>
<t-button theme="default" variant="outline" @click="onReset" style="margin-left: 12px">重置</t-button>
</t-form-item>
</t-form>
</div>
</div>
</template>
<script>
import { changePassword } from '@/pages/index/api/login';
import { MessagePlugin } from 'tdesign-vue';
export default {
name: 'ChangePassword',
data() {
//
const validatePasswordComplexity = (value) => {
if (!value || value.length < 6 || value.length > 20) {
return { valid: false, message: '密码长度6-20位' };
}
const hasLetter = /[A-Za-z]/.test(value);
const hasDigit = /[0-9]/.test(value);
if (!hasLetter || !hasDigit) {
return { valid: false, message: '需包含字母和数字' };
}
return true;
};
//
const validateConfirmPassword = (value) => {
if (value !== this.formData.newPassword) {
return { valid: false, message: '两次输入的新密码不一致' };
}
return true;
};
return {
formData: {
oldPassword: '',
newPassword: '',
confirmPassword: '',
},
rules: {
oldPassword: [
{ required: true, message: '旧密码不能为空', trigger: 'blur' },
],
newPassword: [
{ required: true, message: '新密码不能为空', trigger: 'blur' },
{
validator: validatePasswordComplexity,
message: '密码长度6-20位需包含字母和数字',
trigger: 'blur',
},
],
confirmPassword: [
{ required: true, message: '确认密码不能为空', trigger: 'blur' },
{
validator: validateConfirmPassword,
message: '两次输入的新密码不一致',
trigger: 'blur',
},
],
},
submitLoading: false,
};
},
methods: {
onSubmit({ validateResult }) {
if (validateResult !== true) {
return;
}
this.submitLoading = true;
const params = {
oldPassword: this.formData.oldPassword,
newPassword: this.formData.newPassword,
confirmPassword: this.formData.confirmPassword,
};
changePassword(params)
.then((res) => {
if (res.code === 1) {
MessagePlugin.success('密码修改成功');
this.onReset();
setTimeout(() => {
this.$router.push('/yhzx/gzt');
}, 1500);
} else {
MessagePlugin.error(res.msg || '密码修改失败');
}
})
.catch((err) => {
MessagePlugin.error(err.msg || '密码修改失败');
})
.finally(() => {
this.submitLoading = false;
});
},
onReset() {
this.formData = {
oldPassword: '',
newPassword: '',
confirmPassword: '',
};
},
},
};
</script>
<style lang="less" scoped>
.change-password-page {
padding: 24px;
background: #fff;
min-height: 100%;
}
.page-header {
margin-bottom: 32px;
border-bottom: 1px solid #eee;
padding-bottom: 16px;
.page-title {
font-size: 20px;
font-weight: 600;
color: #1a1b24;
}
}
.page-content {
max-width: 480px;
}
.password-form {
.password-tip {
font-size: 12px;
color: #999;
margin-top: 4px;
line-height: 1.5;
}
.submit-item {
margin-top: 24px;
}
}
</style>

View File

@ -64,4 +64,12 @@ public interface IMhzcApi {
@Operation(summary = "更新用户did信息") @Operation(summary = "更新用户did信息")
CommonResult<YhxxbDTO> updateDid(@RequestBody YhxxbDTO yhxx); CommonResult<YhxxbDTO> updateDid(@RequestBody YhxxbDTO yhxx);
@PostMapping(PREFIX+"/updatePassword")
@Operation(summary = "更新用户密码")
CommonResult<String> updatePassword(@RequestBody YhxxReqDTO reqDTO);
@PostMapping(PREFIX+"/resetPassword")
@Operation(summary = "重置用户密码")
CommonResult<String> resetPassword(@RequestBody YhxxReqDTO reqDTO);
} }

View File

@ -18,4 +18,6 @@ public class YhxxReqDTO implements Serializable {
private String scxzmx; private String scxzmx;
private String did; private String did;
private String sfzjhm; private String sfzjhm;
private String dlmm;
private String oldPassword;
} }

View File

@ -113,10 +113,16 @@ public class UserController {
return CommonResult.success(yhxxbService.getAllUserId()); return CommonResult.success(yhxxbService.getAllUserId());
} }
@GetMapping("/resetPassword") @PostMapping("/resetPassword")
@Operation(summary = "用户列表") @Operation(summary = "重置密码")
CommonResult<String> resetPassword(@RequestParam("yhUuid") String yhuuid){ CommonResult<String> resetPassword(@RequestBody YhxxReqDTO reqDTO){
return CommonResult.success(yhxxbService.resetPassword(yhuuid)); return CommonResult.success(yhxxbService.resetPassword(reqDTO.getYhuuid(), reqDTO.getDlmm()));
}
@PostMapping("/updatePassword")
@Operation(summary = "更新密码")
CommonResult<String> updatePassword(@RequestBody YhxxReqDTO reqDTO){
return CommonResult.success(yhxxbService.updatePassword(reqDTO.getYhuuid(), reqDTO.getDlmm(), reqDTO.getOldPassword()));
} }
} }

View File

@ -27,8 +27,6 @@ public interface TxwMhzcYhxxbService extends IService<TxwMhzcYhxxbDO> {
Page<UserVO> getAllUser(UserReqVO reqVO); Page<UserVO> getAllUser(UserReqVO reqVO);
String resetPassword(String yhuuid);
List<String> getAllUserId(); List<String> getAllUserId();
Integer lockUser(UserLockVO infoVO); Integer lockUser(UserLockVO infoVO);
@ -42,4 +40,8 @@ public interface TxwMhzcYhxxbService extends IService<TxwMhzcYhxxbDO> {
YhxxbDTO saveYhxxByDid(YhxxbDTO yhxx); YhxxbDTO saveYhxxByDid(YhxxbDTO yhxx);
YhxxbDTO updateDid(YhxxbDTO yhxx); YhxxbDTO updateDid(YhxxbDTO yhxx);
String updatePassword(String yhUuid, String dlmm, String oldPassword);
String resetPassword(String yhUuid, String dlmm);
} }

View File

@ -243,14 +243,28 @@ public class TxwMhzcYhxxbServiceImpl extends ServiceImpl<TxwMhzcYhxxbMapper, Txw
} }
@Override @Override
public String resetPassword(String yhuuid) { public String resetPassword(String yhuuid, String dlmm) {
TxwMhzcYhxxbDO yhxxbDO = new TxwMhzcYhxxbDO(); TxwMhzcYhxxbDO yhxxbDO = new TxwMhzcYhxxbDO();
yhxxbDO.setYhUuid(yhuuid); yhxxbDO.setYhUuid(yhuuid);
yhxxbDO.setDlmm(CacheUtils.dm2mc("cs_ggzc_xtcs", "dlmm")); yhxxbDO.setDlmm(MD5.create().digestHex(dlmm));
this.updateById(yhxxbDO); this.updateById(yhxxbDO);
return "success"; return "success";
} }
@Override
public String updatePassword(String yhUuid, String dlmm, String oldPassword) {
TxwMhzcYhxxbDO yhxxbDO = this.getById(yhUuid);
if (yhxxbDO == null) {
throw new RuntimeException("用户不存在");
}
// oldPassword已在SSO层校验过dlmm是新密码明文需MD5加密存储
TxwMhzcYhxxbDO updateDO = new TxwMhzcYhxxbDO();
updateDO.setYhUuid(yhUuid);
updateDO.setDlmm(MD5.create().digestHex(dlmm));
this.updateById(updateDO);
return "success";
}
@Override @Override
public List<String> getAllUserId() { public List<String> getAllUserId() {
List<TxwMhzcYhxxbDO> allUserId = yhxxbMapper.getAllUserId(); List<TxwMhzcYhxxbDO> allUserId = yhxxbMapper.getAllUserId();

View File

@ -1,18 +0,0 @@
package com.css.txw.sso.api.yhxx;
import com.css.ggzc.framework.common.pojo.CommonResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "sso-service")
@Tag(name = "用户信息接口")
public interface YhxxApi {
@PostMapping("/sso/yhxx/getYhxx")
@Operation(summary = "获取用户信息")
CommonResult<String> getYhxx(@RequestParam("djxh") String djxh);
}

View File

@ -41,5 +41,10 @@ public interface ErrorCodeConstants {
ErrorCode OAUTH2_SJHM_LOCK = new ErrorCode(1004019, "距离上次发送验证码不超过1分钟"); ErrorCode OAUTH2_SJHM_LOCK = new ErrorCode(1004019, "距离上次发送验证码不超过1分钟");
ErrorCode OAUTH2_LOGIN_SMS_NOT_EXISTS = new ErrorCode(1004020, "验证码无效或已过期"); ErrorCode OAUTH2_LOGIN_SMS_NOT_EXISTS = new ErrorCode(1004020, "验证码无效或已过期");
// ========== 修改密码 1-002-030-000 ==========
ErrorCode AUTH_PASSWORD_CONFIRM_MISMATCH = new ErrorCode(1004021, "两次输入的新密码不一致");
ErrorCode AUTH_PASSWORD_COMPLEXITY_INVALID = new ErrorCode(1004022, "密码长度6-20位需包含字母和数字");
ErrorCode AUTH_PASSWORD_SAME_AS_OLD = new ErrorCode(1004023, "新密码不能与原密码相同");
} }

View File

@ -10,7 +10,7 @@ public class SsoConstants {
public static final String CLIENT_DEFAULT = "default"; public static final String CLIENT_DEFAULT = "default";
public static final String SMS_CLIENT_SIGNNAME = "智贸链"; public static final String SMS_CLIENT_SIGNNAME = "可信碳信息网";
public static final String SMS_TEMPLATE_CODE = "SMS_474450289"; public static final String SMS_TEMPLATE_CODE = "SMS_474450289";

View File

@ -28,6 +28,7 @@ import com.css.txw.sso.constants.SsoConstants;
import com.css.txw.sso.enums.LoginLogTypeEnum; import com.css.txw.sso.enums.LoginLogTypeEnum;
import com.css.txw.sso.pojo.vo.AuthLoginReqVO; import com.css.txw.sso.pojo.vo.AuthLoginReqVO;
import com.css.txw.sso.pojo.vo.AuthLoginRespVO; import com.css.txw.sso.pojo.vo.AuthLoginRespVO;
import com.css.txw.sso.pojo.vo.ChangePasswordReqVO;
import com.css.txw.sso.pojo.vo.DidBindPhoneReqVO; import com.css.txw.sso.pojo.vo.DidBindPhoneReqVO;
import com.css.txw.sso.pojo.vo.SMSLoginReqVO; import com.css.txw.sso.pojo.vo.SMSLoginReqVO;
import com.css.txw.sso.pojo.vo.SendMsgReqVO; import com.css.txw.sso.pojo.vo.SendMsgReqVO;
@ -144,4 +145,19 @@ public class AuthController {
response.addCookie(cookie); response.addCookie(cookie);
return success(login); return success(login);
} }
@PostMapping("/changePassword")
@Operation(summary = "修改密码")
public CommonResult<Void> changePassword(HttpServletRequest request, @RequestBody @Valid ChangePasswordReqVO reqVO) {
String token = SecurityFrameworkUtils.obtainAuthorization(SsoConstants.COOKIE_TOKEN_KEY, request);
authService.changePassword(token, reqVO);
return success(null);
}
@PostMapping("/resetPassword")
@Operation(summary = "重置密码")
public CommonResult<Void> resetPassword(@RequestParam("yhUuid") String yhUuid) {
authService.resetPassword(yhUuid);
return success(null);
}
} }

View File

@ -0,0 +1,29 @@
package com.css.txw.sso.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
@Schema(description = "修改密码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ChangePasswordReqVO {
@Schema(description = "旧密码MD5加密", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "旧密码不能为空")
private String oldPassword;
@Schema(description = "新密码MD5加密", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "新密码不能为空")
private String newPassword;
@Schema(description = "确认密码MD5加密", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
}

View File

@ -3,6 +3,7 @@ package com.css.txw.sso.service.auth;
import com.css.txw.mhzc.pojo.YhxxbDTO; import com.css.txw.mhzc.pojo.YhxxbDTO;
import com.css.txw.sso.pojo.vo.AuthLoginReqVO; import com.css.txw.sso.pojo.vo.AuthLoginReqVO;
import com.css.txw.sso.pojo.vo.AuthLoginRespVO; import com.css.txw.sso.pojo.vo.AuthLoginRespVO;
import com.css.txw.sso.pojo.vo.ChangePasswordReqVO;
import com.css.txw.sso.pojo.vo.DidBindPhoneReqVO; import com.css.txw.sso.pojo.vo.DidBindPhoneReqVO;
import com.css.txw.sso.pojo.vo.SMSLoginReqVO; import com.css.txw.sso.pojo.vo.SMSLoginReqVO;
import com.css.txw.sso.pojo.vo.SendMsgReqVO; import com.css.txw.sso.pojo.vo.SendMsgReqVO;
@ -52,4 +53,19 @@ public interface AuthService {
AuthLoginRespVO loginBySMS(SMSLoginReqVO reqVO); AuthLoginRespVO loginBySMS(SMSLoginReqVO reqVO);
AuthLoginRespVO didBindPhone(@Valid DidBindPhoneReqVO reqVO); AuthLoginRespVO didBindPhone(@Valid DidBindPhoneReqVO reqVO);
/**
* 修改密码
*
* @param token 当前用户的访问令牌
* @param reqVO 修改密码请求
*/
void changePassword(String token, ChangePasswordReqVO reqVO);
/**
* 重置密码管理员操作重置为默认密码
*
* @param yhUuid 用户UUID
*/
void resetPassword(String yhUuid);
} }

View File

@ -6,6 +6,9 @@ import static com.css.ggzc.framework.common.exception.util.ServiceExceptionUtil.
import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS; import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS;
import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_LOGIN_CAPTCHA_CODE_ERROR; import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_LOGIN_CAPTCHA_CODE_ERROR;
import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_LOGIN_PASSWORD_ERROR_LOCK; import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_LOGIN_PASSWORD_ERROR_LOCK;
import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_PASSWORD_CONFIRM_MISMATCH;
import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_PASSWORD_COMPLEXITY_INVALID;
import static com.css.txw.sso.constants.ErrorCodeConstants.AUTH_PASSWORD_SAME_AS_OLD;
import static com.css.txw.sso.constants.ErrorCodeConstants.OAUTH2_LOGIN_SJHM_NOT_EXISTS; import static com.css.txw.sso.constants.ErrorCodeConstants.OAUTH2_LOGIN_SJHM_NOT_EXISTS;
import static com.css.txw.sso.constants.ErrorCodeConstants.OAUTH2_LOGIN_SMS_NOT_EXISTS; import static com.css.txw.sso.constants.ErrorCodeConstants.OAUTH2_LOGIN_SMS_NOT_EXISTS;
import static com.css.txw.sso.constants.ErrorCodeConstants.OAUTH2_SJHM_LOCK; import static com.css.txw.sso.constants.ErrorCodeConstants.OAUTH2_SJHM_LOCK;
@ -37,6 +40,7 @@ import com.css.ggzc.framework.common.util.json.JsonUtils;
import com.css.txw.common.pojo.dto.sms.SMSResDTO; import com.css.txw.common.pojo.dto.sms.SMSResDTO;
import com.css.txw.common.service.ISMService; import com.css.txw.common.service.ISMService;
import com.css.txw.mhzc.pojo.YhxxbDTO; import com.css.txw.mhzc.pojo.YhxxbDTO;
import com.css.txw.sso.pojo.dto.session.SessionInfo;
import com.css.txw.sso.configuration.SMSClient; import com.css.txw.sso.configuration.SMSClient;
import com.css.txw.sso.controller.thirdparty.DidController; import com.css.txw.sso.controller.thirdparty.DidController;
import com.css.txw.sso.convert.AuthConvert; import com.css.txw.sso.convert.AuthConvert;
@ -44,6 +48,7 @@ import com.css.txw.sso.pojo.domain.oauth2.OAuth2AccessTokenDO;
import com.css.txw.sso.pojo.domain.oauth2.OAuth2ClientDO; import com.css.txw.sso.pojo.domain.oauth2.OAuth2ClientDO;
import com.css.txw.sso.pojo.vo.AuthLoginReqVO; import com.css.txw.sso.pojo.vo.AuthLoginReqVO;
import com.css.txw.sso.pojo.vo.AuthLoginRespVO; import com.css.txw.sso.pojo.vo.AuthLoginRespVO;
import com.css.txw.sso.pojo.vo.ChangePasswordReqVO;
import com.css.txw.sso.pojo.vo.DidBindPhoneReqVO; import com.css.txw.sso.pojo.vo.DidBindPhoneReqVO;
import com.css.txw.sso.pojo.vo.SMSLoginReqVO; import com.css.txw.sso.pojo.vo.SMSLoginReqVO;
import com.css.txw.sso.pojo.vo.SendMsgReqVO; import com.css.txw.sso.pojo.vo.SendMsgReqVO;
@ -324,4 +329,69 @@ public class AuthServiceImpl implements AuthService {
return String.format(PHONE_LOCK, phone); return String.format(PHONE_LOCK, phone);
} }
@Override
public void changePassword(String token, ChangePasswordReqVO reqVO) {
// 1. 校验新密码与确认密码一致性
if (!reqVO.getNewPassword().equals(reqVO.getConfirmPassword())) {
throw exception(AUTH_PASSWORD_CONFIRM_MISMATCH);
}
// 2. 密码复杂度校验6-20位字母+数字
if (!isValidPasswordComplexity(reqVO.getNewPassword())) {
throw exception(AUTH_PASSWORD_COMPLEXITY_INVALID);
}
// 3. 通过token解析获取当前登录用户
log.info("changePassword token: {}", token);
SessionInfo sessionInfo = oauth2TokenService.checkAccessToken(token);
log.info("changePassword sessionInfo: {}", sessionInfo);
if (sessionInfo == null) {
log.error("changePassword token解析失败, token: {}", token);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
}
// 优先使用currentYhuuid如果为空则使用tokenInfo中的yhUuid
String yhUuid = sessionInfo.getCurrentYhuuid();
if (GyUtils.isNull(yhUuid)) {
yhUuid = sessionInfo.getTokenInfo().getYhUuid();
}
if (GyUtils.isNull(yhUuid)) {
yhUuid = sessionInfo.getYhxx().getYhuuid();
}
log.info("changePassword yhUuid from token: {}", yhUuid);
YhxxbDTO yhxx = yhxxService.getYhxx(yhUuid);
log.info("changePassword yhxx: {}", yhxx);
if (yhxx == null) {
log.error("changePassword 用户不存在, yhUuid: {}", yhUuid);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
}
// 4. 校验新旧密码不能相同
if (reqVO.getOldPassword().equals(reqVO.getNewPassword())) {
throw exception(AUTH_PASSWORD_SAME_AS_OLD);
}
// 5. 旧密码正确性校验
authenticate(yhxx.getDlzh(), reqVO.getOldPassword());
// 6. 更新密码SSO已验证旧密码mhzc再次验证后更新
yhxxService.updatePassword(yhxx.getYhUuid(), reqVO.getNewPassword(), reqVO.getOldPassword());
}
@Override
public void resetPassword(String yhUuid) {
// 获取默认密码从配置中读取
String defaultPassword = CacheUtils.dm2mc("cs_ggzc_xtcs", "dlmm");
// 调用 mhzc 更新密码
yhxxService.resetPassword(yhUuid, defaultPassword);
}
private boolean isValidPasswordComplexity(String password) {
if (password == null || password.length() < 6 || password.length() > 20) {
return false;
}
boolean hasLetter = password.matches(".*[A-Za-z]+.*");
boolean hasDigit = password.matches(".*[0-9]+.*");
return hasLetter && hasDigit;
}
} }

View File

@ -41,4 +41,8 @@ public interface YhxxService {
YhxxbDTO updateDid(YhxxbDTO yhxx); YhxxbDTO updateDid(YhxxbDTO yhxx);
void updatePassword(String yhUuid, String newPassword, String oldPassword);
void resetPassword(String yhUuid, String newPassword);
} }

View File

@ -243,5 +243,22 @@ public class YhxxServiceImpl implements YhxxService {
return data; return data;
} }
@Override
public void updatePassword(String yhUuid, String newPassword, String oldPassword) {
YhxxReqDTO reqDTO = new YhxxReqDTO();
reqDTO.setYhuuid(yhUuid);
reqDTO.setDlmm(newPassword);
reqDTO.setOldPassword(oldPassword);
mhzcApi.updatePassword(reqDTO);
}
@Override
public void resetPassword(String yhUuid, String newPassword) {
YhxxReqDTO reqDTO = new YhxxReqDTO();
reqDTO.setYhuuid(yhUuid);
reqDTO.setDlmm(newPassword);
mhzcApi.resetPassword(reqDTO);
}
} }

View File

@ -67,8 +67,8 @@ export const getAllUser = (params) => {
// 重置账号密码 // 重置账号密码
export const resetPassword = (params) => { export const resetPassword = (params) => {
return fetchSso({ return fetchSso({
url: `${basurl}/mhzc/user/resetPassword?yhUuid=${params}`, url: `${basurl}/sso/auth/resetPassword?yhUuid=${params}`,
method: 'get', method: 'post',
loading: true, loading: true,
headers, headers,
}); });