feat(用户管理): 添加重置用户密码功能 (#53)

在用户管理页面中新增重置用户密码的功能,包括前端对话框、表单验证、API调用以及后端服务层的密码重置逻辑。同时,更新了用户管理页面的操作列,增加了重置密码按钮,并调整了相关UI样式。
This commit is contained in:
zstar 2025-04-25 17:20:49 +08:00 committed by GitHub
parent e832029258
commit 63c6838701
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 214 additions and 11 deletions

View File

@ -79,6 +79,9 @@ RAGFLOW_IMAGE=zstar1003/ragflowplus:v0.2.0
# The local time zone.
TIMEZONE='Asia/Shanghai'
# 后端允许上传的最大文件大小
MAX_CONTENT_LENGTH=10737418240 # 10GB
# 管理系统用户名和密码
MANAGEMENT_ADMIN_USERNAME=admin
MANAGEMENT_ADMIN_PASSWORD=12345678

View File

@ -1,5 +1,5 @@
from flask import jsonify, request
from services.users.service import get_users_with_pagination, delete_user, create_user, update_user
from services.users.service import get_users_with_pagination, delete_user, create_user, update_user, reset_user_password
from .. import users_bp
@users_bp.route('', methods=['GET'])
@ -71,4 +71,39 @@ def get_current_user():
"roles": ["admin"]
},
"message": "获取用户信息成功"
})
})
@users_bp.route('/<string:user_id>/reset-password', methods=['PUT'])
def reset_password_route(user_id):
"""
重置用户密码的API端点
Args:
user_id (str): 需要重置密码的用户ID
Returns:
Response: JSON响应
"""
try:
data = request.json
new_password = data.get('password')
# 校验密码是否存在
if not new_password:
return jsonify({"code": 400, "message": "缺少新密码参数 'password'"}), 400
# 调用 service 函数重置密码
success = reset_user_password(user_id=user_id, new_password=new_password)
if success:
return jsonify({
"code": 0,
"message": f"用户密码重置成功"
})
else:
# service 层可能因为用户不存在或其他原因返回 False
return jsonify({"code": 404, "message": f"用户未找到或密码重置失败"}), 404
except Exception as e:
# 统一处理异常
return jsonify({
"code": 500,
"message": f"重置密码失败: {str(e)}"
}), 500

View File

@ -229,7 +229,7 @@ class KnowledgebaseService:
# 设置默认值
default_parser_config = json.dumps({
"layout_recognize": "DeepDOC",
"layout_recognize": "MinerU",
"chunk_token_num": 512,
"delimiter": "\n!?;。;!?",
"auto_keywords": 0,
@ -557,7 +557,7 @@ class KnowledgebaseService:
# 设置默认值
default_parser_id = "naive"
default_parser_config = json.dumps({
"layout_recognize": "DeepDOC",
"layout_recognize": "MinerU",
"chunk_token_num": 512,
"delimiter": "\n!?;。;!?",
"auto_keywords": 0,

View File

@ -287,4 +287,61 @@ def update_user(user_id, user_data):
return True
except mysql.connector.Error as err:
print(f"更新用户错误: {err}")
return False
def reset_user_password(user_id, new_password):
"""
重置指定用户的密码
Args:
user_id (str): 用户ID
new_password (str): 新的明文密码
Returns:
bool: 操作是否成功
"""
try:
conn = mysql.connector.connect(**DB_CONFIG)
cursor = conn.cursor()
# 加密新密码
encrypted_password = encrypt_password(new_password) # 使用与创建用户时相同的加密方法
update_time = int(datetime.now().timestamp() * 1000)
update_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 更新用户密码
update_query = """
UPDATE user
SET password = %s, update_time = %s, update_date = %s
WHERE id = %s
"""
cursor.execute(update_query, (encrypted_password, update_time, update_date, user_id))
# 检查是否有行被更新
if cursor.rowcount == 0:
conn.rollback() # 如果没有更新,回滚
cursor.close()
conn.close()
print(f"用户 {user_id} 未找到,密码未更新。")
return False # 用户不存在
conn.commit() # 提交事务
cursor.close()
conn.close()
print(f"用户 {user_id} 密码已成功重置。")
return True
except mysql.connector.Error as err:
print(f"重置密码时数据库错误: {err}")
# 可以在这里添加更详细的日志记录
# 如果 conn 存在且打开,尝试回滚
if 'conn' in locals() and conn.is_connected():
conn.rollback()
cursor.close()
conn.close()
return False
except Exception as e:
print(f"重置密码时发生未知错误: {e}")
if 'conn' in locals() and conn.is_connected():
conn.rollback()
cursor.close()
conn.close()
return False

View File

@ -35,3 +35,17 @@ export function getTableDataApi(params: Tables.TableRequestData) {
params
})
}
/**
*
* @param userId ID
* @param password
* @returns BaseResponse
*/
export function resetPasswordApi(userId: number, password: string) {
return request({
url: `api/v1/users/${userId}/reset-password`,
method: "put",
data: { password } // 发送新密码
})
}

View File

@ -1,9 +1,9 @@
<script lang="ts" setup>
import type { CreateOrUpdateTableRequestData, TableData } from "@@/apis/tables/type"
import type { FormInstance, FormRules } from "element-plus"
import { createTableDataApi, deleteTableDataApi, getTableDataApi, updateTableDataApi } from "@@/apis/tables"
import { createTableDataApi, deleteTableDataApi, getTableDataApi, resetPasswordApi, updateTableDataApi } from "@@/apis/tables"
import { usePagination } from "@@/composables/usePagination"
import { CirclePlus, Delete, Refresh, RefreshRight, Search } from "@element-plus/icons-vue"
import { CirclePlus, Delete, Edit, Key, Refresh, RefreshRight, Search } from "@element-plus/icons-vue"
import { cloneDeep } from "lodash-es"
defineOptions({
@ -36,10 +36,23 @@ const formRules: FormRules<CreateOrUpdateTableRequestData> = {
],
password: [{ required: true, trigger: "blur", message: "请输入密码" }]
}
// #region
const resetPasswordDialogVisible = ref<boolean>(false)
const resetPasswordFormRef = ref<FormInstance | null>(null)
const currentUserId = ref<number | undefined>(undefined) // ID
const resetPasswordFormData = reactive({
password: ""
})
const resetPasswordFormRules: FormRules = {
password: [
{ required: true, message: "请输入新密码", trigger: "blur" }
]
}
// #endregion
function handleCreateOrUpdate() {
formRef.value?.validate((valid) => {
if (!valid) {
ElMessage.error("表单校验不通过")
ElMessage.error("登录校验不通过")
return
}
loading.value = true
@ -59,6 +72,60 @@ function resetForm() {
}
// #endregion
// #region
/**
* 打开重置密码对话框
* @param {TableData} row - 当前行用户数据
*/
function handleResetPassword(row: TableData) {
currentUserId.value = row.id
resetPasswordFormData.password = "" //
resetPasswordDialogVisible.value = true
//
nextTick(() => {
resetPasswordFormRef.value?.clearValidate()
})
}
/**
* 提交重置密码表单
*/
function submitResetPassword() {
resetPasswordFormRef.value?.validate((valid) => {
if (!valid) {
ElMessage.error("表单校验不通过")
return
}
if (currentUserId.value === undefined) {
ElMessage.error("用户ID丢失无法重置密码")
return
}
loading.value = true
// API
resetPasswordApi(currentUserId.value, resetPasswordFormData.password)
.then(() => {
ElMessage.success("密码重置成功")
resetPasswordDialogVisible.value = false
})
.catch((error: any) => {
console.error("重置密码失败:", error)
ElMessage.error("密码重置失败")
})
.finally(() => {
loading.value = false
})
})
}
/**
* 关闭重置密码对话框时重置状态
*/
function resetPasswordDialogClosed() {
currentUserId.value = undefined
resetPasswordFormRef.value?.resetFields() //
}
// #endregion
// #region
function handleDelete(row: TableData) {
ElMessageBox.confirm(`正在删除用户:${row.username},确认删除?`, "提示", {
@ -198,12 +265,15 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
<el-table-column prop="email" label="邮箱" align="center" />
<el-table-column prop="createTime" label="创建时间" align="center" />
<el-table-column prop="updateTime" label="更新时间" align="center" />
<el-table-column fixed="right" label="操作" width="150" align="center">
<el-table-column fixed="right" label="操作" width="300" align="center">
<template #default="scope">
<el-button type="primary" text bg size="small" @click="handleUpdate(scope.row)">
修改
<el-button type="primary" text bg size="small" :icon="Edit" @click="handleUpdate(scope.row)">
修改用户名
</el-button>
<el-button type="danger" text bg size="small" @click="handleDelete(scope.row)">
<el-button type="warning" text bg size="small" :icon="Key" @click="handleResetPassword(scope.row)">
重置密码
</el-button>
<el-button type="danger" text bg size="small" :icon="Delete" @click="handleDelete(scope.row)">
删除
</el-button>
</template>
@ -250,6 +320,28 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
</el-button>
</template>
</el-dialog>
<!-- 重置密码对话框 -->
<el-dialog
v-model="resetPasswordDialogVisible"
title="重置密码"
width="30%"
@closed="resetPasswordDialogClosed"
>
<el-form ref="resetPasswordFormRef" :model="resetPasswordFormData" :rules="resetPasswordFormRules" label-width="100px" label-position="left">
<el-form-item prop="password" label="新密码">
<el-input v-model="resetPasswordFormData.password" type="password" show-password placeholder="请输入新密码" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="resetPasswordDialogVisible = false">
取消
</el-button>
<el-button type="primary" :loading="loading" @click="submitResetPassword">
确认重置
</el-button>
</template>
</el-dialog>
</div>
</template>

View File

@ -38,6 +38,8 @@ declare module 'vue' {
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']