Browse Source

用户登录、密码管理、个人资料处理完成

master
hejl 2 weeks ago
parent
commit
6130e9e7e2
  1. 339
      gofaster/app/dist/renderer/js/index.js
  2. 248
      gofaster/app/src/renderer/components/PasswordChangeModal.vue
  3. 111
      gofaster/backend/internal/auth/controller/password_controller.go
  4. 91
      gofaster/backend/internal/auth/module.go
  5. 108
      gofaster/backend/internal/auth/routes/auth_routes.go
  6. 430
      gofaster/backend/internal/auth/service/auth_service.go
  7. 491
      gofaster/backend/internal/auth/service/password_service.go
  8. 98
      gofaster/backend/internal/core/manager.go
  9. 197
      gofaster/backend/internal/shared/middleware/jwt_middleware.go
  10. 91
      gofaster/backend/internal/shared/middleware/logger_middleware.go
  11. 32
      gofaster/backend/internal/shared/middleware/permission_middleware.go
  12. 6
      gofaster/backend/main.go
  13. 2
      gofaster/backend/tmp/build-errors.log
  14. BIN
      gofaster/backend/tmp/main.exe

339
gofaster/app/dist/renderer/js/index.js vendored

@ -1143,6 +1143,19 @@ ___CSS_LOADER_EXPORT___.push([module.id, ` @@ -1143,6 +1143,19 @@ ___CSS_LOADER_EXPORT___.push([module.id, `
.form-group input.error[data-v-52d75f2c] {
border-color: #f44336;
}
/* 确保禁用状态下的输入框仍然可见 */
.form-group input[data-v-52d75f2c]:disabled {
opacity: 0.7;
background: var(--bg-secondary);
cursor: not-allowed;
}
/* 测试焦点样式 */
.form-group input[data-v-52d75f2c]:focus {
outline: 2px solid #ff9800;
outline-offset: 2px;
}
.error-message[data-v-52d75f2c] {
color: #f44336;
font-size: 12px;
@ -3718,21 +3731,14 @@ __webpack_require__.r(__webpack_exports__); @@ -3718,21 +3731,14 @@ __webpack_require__.r(__webpack_exports__);
// 方法
const loadPasswordPolicy = async () => {
try {
console.log('开始加载密码策略...')
const response = await _services_userService__WEBPACK_IMPORTED_MODULE_1__.userService.getPasswordPolicy()
console.log('密码策略API响应:', response)
console.log('响应类型:', typeof response)
console.log('response.data 类型:', typeof response.data)
console.log('response.data 内容:', response.data)
// 修复:直接检查response.data的结构
if (response.data && response.data.code === 200 && response.data.data) {
policy.value = response.data.data
console.log('密码策略加载成功:', policy.value)
} else if (response && response.code === 200 && response.data) {
// 备用检查:如果response.data不存在,直接检查response
policy.value = response.data
console.log('密码策略加载成功(备用路径):', policy.value)
} else {
console.warn('密码策略响应格式不正确:', response)
console.warn('尝试解析响应结构...')
@ -3784,9 +3790,7 @@ __webpack_require__.r(__webpack_exports__); @@ -3784,9 +3790,7 @@ __webpack_require__.r(__webpack_exports__);
}
try {
console.log('正在验证密码:', form.newPassword)
const response = await _services_userService__WEBPACK_IMPORTED_MODULE_1__.userService.validatePassword(form.newPassword)
console.log('后端验证响应:', response)
// 修复:检查多种可能的响应结构
let result = null
@ -3797,7 +3801,6 @@ __webpack_require__.r(__webpack_exports__); @@ -3797,7 +3801,6 @@ __webpack_require__.r(__webpack_exports__);
}
if (result) {
console.log('验证结果:', result)
if (!result.is_valid) {
// 确保错误信息是数组格式
@ -3813,8 +3816,6 @@ __webpack_require__.r(__webpack_exports__); @@ -3813,8 +3816,6 @@ __webpack_require__.r(__webpack_exports__);
passwordStrength.value = result.strength || 0
passwordLevel.value = result.level || 0
console.log('设置密码强度:', passwordStrength.value, '等级:', passwordLevel.value)
// 更新要求满足状态
updateRequirements(form.newPassword)
} else {
@ -3830,7 +3831,6 @@ __webpack_require__.r(__webpack_exports__); @@ -3830,7 +3831,6 @@ __webpack_require__.r(__webpack_exports__);
}
const updateRequirements = (password) => {
console.log('updateRequirements 被调用,密码:', password)
// 重置所有状态
requirements.length = false
@ -3865,14 +3865,6 @@ __webpack_require__.r(__webpack_exports__); @@ -3865,14 +3865,6 @@ __webpack_require__.r(__webpack_exports__);
}
}
console.log('字符类型检测结果:', {
hasUppercase: hasUppercase,
hasLowercase: hasLowercase,
hasNumbers: hasNumbers,
hasSpecial: hasSpecial,
length: password.length
})
// 更新要求状态
requirements.length = password.length >= 6 // 要求至少6位
requirements.uppercase = hasUppercase
@ -3891,15 +3883,6 @@ __webpack_require__.r(__webpack_exports__); @@ -3891,15 +3883,6 @@ __webpack_require__.r(__webpack_exports__);
const strength = calculatePasswordStrength(password, charTypes)
passwordStrength.value = strength
passwordLevel.value = strength
console.log('更新后的要求状态:', {
length: requirements.length,
uppercase: requirements.uppercase,
lowercase: requirements.lowercase,
numbers: requirements.numbers,
special: requirements.special
})
console.log('计算出的密码强度:', strength)
}
// 前端密码强度计算方法
@ -3953,6 +3936,11 @@ __webpack_require__.r(__webpack_exports__); @@ -3953,6 +3936,11 @@ __webpack_require__.r(__webpack_exports__);
loading.value = true
// 清除之前的错误信息
Object.keys(errors).forEach(key => {
errors[key] = ''
})
try {
const requestData = {
current_password: form.currentPassword,
@ -3960,12 +3948,9 @@ __webpack_require__.r(__webpack_exports__); @@ -3960,12 +3948,9 @@ __webpack_require__.r(__webpack_exports__);
confirm_password: form.confirmPassword
}
await _services_userService__WEBPACK_IMPORTED_MODULE_1__.userService.changePassword(requestData)
// 成功
emit('success')
handleClose()
const response = await _services_userService__WEBPACK_IMPORTED_MODULE_1__.userService.changePassword(requestData)
// 成功处理
// 显示成功消息
if (window.showToast) {
window.showToast({
@ -3973,21 +3958,62 @@ __webpack_require__.r(__webpack_exports__); @@ -3973,21 +3958,62 @@ __webpack_require__.r(__webpack_exports__);
title: '密码修改成功',
content: '您的密码已成功修改'
})
} else {
// 如果没有全局toast,使用alert作为后备
alert('密码修改成功!')
}
// 延迟关闭弹窗,让用户看到成功提示
setTimeout(() => {
emit('success')
handleClose()
}, 1500)
} catch (error) {
console.error('修改密码失败:', error)
// 显示错误消息
if (error.response?.data?.message) {
if (error.response.data.message.includes('当前密码不正确')) {
errors.currentPassword = '当前密码不正确'
} else if (error.response.data.message.includes('新密码不符合要求')) {
errors.newPassword = error.response.data.message
} else {
errors.newPassword = error.response.data.message
// 改进错误信息处理
let errorMessage = '修改密码失败,请重试'
let targetField = 'newPassword'
if (error.response?.data) {
const responseData = error.response.data
// 处理不同的错误情况
if (responseData.error) {
// 优先使用error字段,这通常包含具体的错误信息
errorMessage = responseData.error
// 根据错误信息内容判断应该显示在哪个字段
if (errorMessage.includes('当前密码') || errorMessage.includes('current password')) {
targetField = 'currentPassword'
} else if (errorMessage.includes('新密码') || errorMessage.includes('new password')) {
targetField = 'newPassword'
} else if (errorMessage.includes('确认密码') || errorMessage.includes('confirm password')) {
targetField = 'confirmPassword'
}
} else if (responseData.message) {
// 如果没有error字段,使用message字段
errorMessage = responseData.message
// 根据错误信息内容判断应该显示在哪个字段
if (errorMessage.includes('当前密码') || errorMessage.includes('current password')) {
targetField = 'currentPassword'
} else if (errorMessage.includes('新密码') || errorMessage.includes('new password')) {
targetField = 'newPassword'
} else if (errorMessage.includes('确认密码') || errorMessage.includes('confirm password')) {
targetField = 'confirmPassword'
}
}
} else {
errors.newPassword = '修改密码失败,请重试'
} else if (error.message) {
errorMessage = error.message
}
// 显示错误信息
errors[targetField] = errorMessage
// 如果是强制修改密码模式,错误信息也显示在newPassword字段
if (props.isForceChange && targetField === 'currentPassword') {
errors.newPassword = errorMessage
}
} finally {
loading.value = false
@ -3997,23 +4023,8 @@ __webpack_require__.r(__webpack_exports__); @@ -3997,23 +4023,8 @@ __webpack_require__.r(__webpack_exports__);
const handleClose = () => {
if (loading.value) return
// 重置表单
form.currentPassword = ''
form.newPassword = ''
form.confirmPassword = ''
// 清除错误
Object.keys(errors).forEach(key => {
errors[key] = ''
})
// 重置要求状态
Object.keys(requirements).forEach(key => {
requirements[key] = false
})
passwordStrength.value = 0
passwordLevel.value = 0
// 使用统一的重置函数
resetFormState()
emit('close')
}
@ -4024,19 +4035,98 @@ __webpack_require__.r(__webpack_exports__); @@ -4024,19 +4035,98 @@ __webpack_require__.r(__webpack_exports__);
// 只有通过明确的取消按钮或关闭按钮才能关闭
return false
}
const clearCurrentPasswordError = () => {
errors.currentPassword = ''
}
const clearNewPasswordError = () => {
errors.newPassword = ''
}
const clearConfirmPasswordError = () => {
errors.confirmPassword = ''
}
const handleNewPasswordInput = () => {
clearNewPasswordError()
validatePassword()
}
const handleConfirmPasswordInput = () => {
clearConfirmPasswordError()
validateConfirmPassword()
}
// 监听器
;(0,vue__WEBPACK_IMPORTED_MODULE_0__.watch)(() => props.visible, (newVal) => {
if (newVal) {
console.log('模态框显示,开始加载密码策略')
// 每次打开弹窗时,确保完全重置所有状态
resetFormState()
loadPasswordPolicy()
// 延迟测试输入框焦点和强制聚焦
;(0,vue__WEBPACK_IMPORTED_MODULE_0__.nextTick)(() => {
if (!props.isForceChange && form.currentPassword === '') {
// 尝试强制聚焦到第一个输入框
try {
const currentPasswordInput = document.querySelector('input[type="password"]')
if (currentPasswordInput) {
currentPasswordInput.focus()
}
} catch (error) {
console.error('聚焦失败:', error)
}
} else {
// 尝试强制聚焦到新密码输入框
try {
const newPasswordInput = document.querySelectorAll('input[type="password"]')[1]
if (newPasswordInput) {
newPasswordInput.focus()
}
} catch (error) {
console.error('聚焦失败:', error)
}
}
})
}
})
// 重置表单状态的函数
const resetFormState = () => {
// 重置表单数据
form.currentPassword = ''
form.newPassword = ''
form.confirmPassword = ''
// 清除所有错误信息
Object.keys(errors).forEach(key => {
errors[key] = ''
})
// 重置密码要求状态
Object.keys(requirements).forEach(key => {
requirements[key] = false
})
// 重置密码强度和等级
passwordStrength.value = 0
passwordLevel.value = 0
// 重置加载状态 - 这是关键!
loading.value = false
// 强制触发响应式更新
;(0,vue__WEBPACK_IMPORTED_MODULE_0__.nextTick)(() => {
})
}
// 监听密码变化,实时更新要求状态
;(0,vue__WEBPACK_IMPORTED_MODULE_0__.watch)(() => form.newPassword, (newPassword) => {
if (newPassword) {
console.log('密码变化,更新要求状态:', newPassword)
updateRequirements(newPassword)
} else {
// 密码为空时重置所有状态
@ -4051,7 +4141,6 @@ __webpack_require__.r(__webpack_exports__); @@ -4051,7 +4141,6 @@ __webpack_require__.r(__webpack_exports__);
// 生命周期
;(0,vue__WEBPACK_IMPORTED_MODULE_0__.onMounted)(() => {
console.log('组件挂载,检查是否需要加载密码策略')
if (props.visible) {
loadPasswordPolicy()
}
@ -4080,7 +4169,13 @@ __webpack_require__.r(__webpack_exports__); @@ -4080,7 +4169,13 @@ __webpack_require__.r(__webpack_exports__);
updateRequirements,
handleSubmit,
handleClose,
handleOverlayClick
handleOverlayClick,
clearCurrentPasswordError,
clearNewPasswordError,
clearConfirmPasswordError,
handleNewPasswordInput,
handleConfirmPasswordInput,
resetFormState // 暴露重置函数
}
}
});
@ -5413,32 +5508,35 @@ const _hoisted_3 = { @@ -5413,32 +5508,35 @@ const _hoisted_3 = {
key: 0,
class: "form-group"
}
const _hoisted_4 = {
const _hoisted_4 = ["disabled"]
const _hoisted_5 = {
key: 0,
class: "error-message"
}
const _hoisted_5 = { class: "form-group" }
const _hoisted_6 = {
const _hoisted_6 = { class: "form-group" }
const _hoisted_7 = ["disabled"]
const _hoisted_8 = {
key: 0,
class: "error-message"
}
const _hoisted_7 = { class: "password-strength" }
const _hoisted_8 = { class: "strength-bar" }
const _hoisted_9 = { class: "strength-text" }
const _hoisted_10 = { class: "password-requirements" }
const _hoisted_11 = { class: "form-group" }
const _hoisted_12 = {
const _hoisted_9 = { class: "password-strength" }
const _hoisted_10 = { class: "strength-bar" }
const _hoisted_11 = { class: "strength-text" }
const _hoisted_12 = { class: "password-requirements" }
const _hoisted_13 = { class: "form-group" }
const _hoisted_14 = ["disabled"]
const _hoisted_15 = {
key: 0,
class: "error-message"
}
const _hoisted_13 = { class: "form-actions" }
const _hoisted_14 = ["disabled"]
const _hoisted_15 = ["disabled"]
const _hoisted_16 = {
const _hoisted_16 = { class: "form-actions" }
const _hoisted_17 = ["disabled"]
const _hoisted_18 = ["disabled"]
const _hoisted_19 = {
key: 0,
class: "loading-spinner"
}
const _hoisted_17 = {
const _hoisted_20 = {
key: 1,
class: "force-change-notice"
}
@ -5448,11 +5546,11 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { @@ -5448,11 +5546,11 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", {
key: 0,
class: "password-modal-overlay",
onClick: _cache[10] || (_cache[10] = (...args) => ($setup.handleOverlayClick && $setup.handleOverlayClick(...args)))
onClick: _cache[11] || (_cache[11] = (...args) => ($setup.handleOverlayClick && $setup.handleOverlayClick(...args)))
}, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", {
class: "password-modal",
onClick: _cache[9] || (_cache[9] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)(() => {}, ["stop"]))
onClick: _cache[10] || (_cache[10] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)(() => {}, ["stop"]))
}, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_1, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.title), 1 /* TEXT */),
@ -5463,55 +5561,64 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { @@ -5463,55 +5561,64 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_2, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("form", {
onSubmit: _cache[8] || (_cache[8] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)((...args) => ($setup.handleSubmit && $setup.handleSubmit(...args)), ["prevent"]))
onSubmit: _cache[9] || (_cache[9] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)((...args) => ($setup.handleSubmit && $setup.handleSubmit(...args)), ["prevent"]))
}, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 当前密码 "),
(!$props.isForceChange)
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_3, [
_cache[11] || (_cache[11] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "当前密码 *", -1 /* CACHED */)),
_cache[12] || (_cache[12] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "当前密码 *", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[1] || (_cache[1] = $event => (($setup.form.currentPassword) = $event)),
type: "password",
required: "",
placeholder: "请输入当前密码",
class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({ 'error': $setup.errors.currentPassword })
}, null, 2 /* CLASS */), [
onInput: _cache[2] || (_cache[2] = (...args) => ($setup.clearCurrentPasswordError && $setup.clearCurrentPasswordError(...args))),
class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({ 'error': $setup.errors.currentPassword }),
disabled: $setup.loading,
ref: "currentPasswordInput",
tabindex: "1",
autocomplete: "current-password"
}, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_4), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.form.currentPassword]
]),
($setup.errors.currentPassword)
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_4, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.errors.currentPassword), 1 /* TEXT */))
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_5, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.errors.currentPassword), 1 /* TEXT */))
: (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true)
]))
: (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 新密码 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_5, [
_cache[12] || (_cache[12] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "新密码 *", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_6, [
_cache[13] || (_cache[13] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "新密码 *", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[2] || (_cache[2] = $event => (($setup.form.newPassword) = $event)),
"onUpdate:modelValue": _cache[3] || (_cache[3] = $event => (($setup.form.newPassword) = $event)),
type: "password",
required: "",
placeholder: "请输入新密码",
onInput: _cache[3] || (_cache[3] = (...args) => ($setup.validatePassword && $setup.validatePassword(...args))),
onKeyup: _cache[4] || (_cache[4] = $event => ($setup.updateRequirements($setup.form.newPassword))),
class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({ 'error': $setup.errors.newPassword })
}, null, 34 /* CLASS, NEED_HYDRATION */), [
onInput: _cache[4] || (_cache[4] = (...args) => ($setup.handleNewPasswordInput && $setup.handleNewPasswordInput(...args))),
onKeyup: _cache[5] || (_cache[5] = $event => ($setup.updateRequirements($setup.form.newPassword))),
class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({ 'error': $setup.errors.newPassword }),
disabled: $setup.loading,
ref: "newPasswordInput",
tabindex: "2",
autocomplete: "new-password"
}, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_7), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.form.newPassword]
]),
($setup.errors.newPassword)
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_6, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.errors.newPassword), 1 /* TEXT */))
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_8, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.errors.newPassword), 1 /* TEXT */))
: (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 密码强度指示器 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_7, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_8, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_9, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_10, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", {
class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["strength-fill", $setup.strengthClass]),
style: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeStyle)({ width: $setup.strengthPercentage + '%' })
}, null, 6 /* CLASS, STYLE */)
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_9, " 强度: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.strengthText) + " (" + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.passwordLevel) + "级) ", 1 /* TEXT */)
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_11, " 强度: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.strengthText) + " (" + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.passwordLevel) + "级) ", 1 /* TEXT */)
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 密码要求提示 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_10, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_12, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", {
class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["requirement", { 'met': $setup.requirements.length }])
}, " ✓ 密码长度" + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.form.newPassword.length) + "位 ", 3 /* TEXT, CLASS */),
@ -5530,44 +5637,48 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { @@ -5530,44 +5637,48 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 确认新密码 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_11, [
_cache[13] || (_cache[13] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "确认新密码 *", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_13, [
_cache[14] || (_cache[14] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "确认新密码 *", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[5] || (_cache[5] = $event => (($setup.form.confirmPassword) = $event)),
"onUpdate:modelValue": _cache[6] || (_cache[6] = $event => (($setup.form.confirmPassword) = $event)),
type: "password",
required: "",
placeholder: "请再次输入新密码",
onInput: _cache[6] || (_cache[6] = (...args) => ($setup.validateConfirmPassword && $setup.validateConfirmPassword(...args))),
class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({ 'error': $setup.errors.confirmPassword })
}, null, 34 /* CLASS, NEED_HYDRATION */), [
onInput: _cache[7] || (_cache[7] = (...args) => ($setup.handleConfirmPasswordInput && $setup.handleConfirmPasswordInput(...args))),
class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)({ 'error': $setup.errors.confirmPassword }),
disabled: $setup.loading,
ref: "confirmPasswordInput",
tabindex: "3",
autocomplete: "new-password"
}, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_14), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.form.confirmPassword]
]),
($setup.errors.confirmPassword)
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_12, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.errors.confirmPassword), 1 /* TEXT */))
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_15, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.errors.confirmPassword), 1 /* TEXT */))
: (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true)
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 操作按钮 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_13, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_16, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", {
type: "button",
class: "btn btn-secondary",
onClick: _cache[7] || (_cache[7] = (...args) => ($setup.handleClose && $setup.handleClose(...args))),
onClick: _cache[8] || (_cache[8] = (...args) => ($setup.handleClose && $setup.handleClose(...args))),
disabled: $setup.loading
}, " 取消 ", 8 /* PROPS */, _hoisted_14),
}, " 取消 ", 8 /* PROPS */, _hoisted_17),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", {
type: "submit",
class: "btn btn-primary",
disabled: $setup.loading || !$setup.isFormValid
}, [
($setup.loading)
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("span", _hoisted_16))
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("span", _hoisted_19))
: (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.submitText), 1 /* TEXT */)
], 8 /* PROPS */, _hoisted_15)
], 8 /* PROPS */, _hoisted_18)
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 强制修改密码提示 "),
($props.isForceChange)
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_17, _cache[14] || (_cache[14] = [
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_20, _cache[15] || (_cache[15] = [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "notice-icon" }, "⚠", -1 /* CACHED */),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "notice-text" }, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("strong", null, "重要提示:"),
@ -8698,7 +8809,7 @@ __webpack_require__.r(__webpack_exports__); @@ -8698,7 +8809,7 @@ __webpack_require__.r(__webpack_exports__);
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("37a1d732559d3aaa")
/******/ __webpack_require__.h = () => ("7737269bf8e5d297")
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */

248
gofaster/app/src/renderer/components/PasswordChangeModal.vue

@ -16,7 +16,12 @@ @@ -16,7 +16,12 @@
type="password"
required
placeholder="请输入当前密码"
@input="clearCurrentPasswordError"
:class="{ 'error': errors.currentPassword }"
:disabled="loading"
ref="currentPasswordInput"
tabindex="1"
autocomplete="current-password"
/>
<div v-if="errors.currentPassword" class="error-message">
{{ errors.currentPassword }}
@ -31,9 +36,13 @@ @@ -31,9 +36,13 @@
type="password"
required
placeholder="请输入新密码"
@input="validatePassword"
@input="handleNewPasswordInput"
@keyup="updateRequirements(form.newPassword)"
:class="{ 'error': errors.newPassword }"
:disabled="loading"
ref="newPasswordInput"
tabindex="2"
autocomplete="new-password"
/>
<div v-if="errors.newPassword" class="error-message">
{{ errors.newPassword }}
@ -81,8 +90,12 @@ @@ -81,8 +90,12 @@
type="password"
required
placeholder="请再次输入新密码"
@input="validateConfirmPassword"
@input="handleConfirmPasswordInput"
:class="{ 'error': errors.confirmPassword }"
:disabled="loading"
ref="confirmPasswordInput"
tabindex="3"
autocomplete="new-password"
/>
<div v-if="errors.confirmPassword" class="error-message">
{{ errors.confirmPassword }}
@ -123,7 +136,7 @@ @@ -123,7 +136,7 @@
</template>
<script>
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue'
import { userService } from '@/services/userService'
export default {
@ -218,21 +231,14 @@ export default { @@ -218,21 +231,14 @@ export default {
//
const loadPasswordPolicy = async () => {
try {
console.log('开始加载密码策略...')
const response = await userService.getPasswordPolicy()
console.log('密码策略API响应:', response)
console.log('响应类型:', typeof response)
console.log('response.data 类型:', typeof response.data)
console.log('response.data 内容:', response.data)
// response.data
if (response.data && response.data.code === 200 && response.data.data) {
policy.value = response.data.data
console.log('密码策略加载成功:', policy.value)
} else if (response && response.code === 200 && response.data) {
// response.dataresponse
policy.value = response.data
console.log('密码策略加载成功(备用路径):', policy.value)
} else {
console.warn('密码策略响应格式不正确:', response)
console.warn('尝试解析响应结构...')
@ -284,9 +290,7 @@ export default { @@ -284,9 +290,7 @@ export default {
}
try {
console.log('正在验证密码:', form.newPassword)
const response = await userService.validatePassword(form.newPassword)
console.log('后端验证响应:', response)
//
let result = null
@ -297,7 +301,6 @@ export default { @@ -297,7 +301,6 @@ export default {
}
if (result) {
console.log('验证结果:', result)
if (!result.is_valid) {
//
@ -313,8 +316,6 @@ export default { @@ -313,8 +316,6 @@ export default {
passwordStrength.value = result.strength || 0
passwordLevel.value = result.level || 0
console.log('设置密码强度:', passwordStrength.value, '等级:', passwordLevel.value)
//
updateRequirements(form.newPassword)
} else {
@ -330,7 +331,6 @@ export default { @@ -330,7 +331,6 @@ export default {
}
const updateRequirements = (password) => {
console.log('updateRequirements 被调用,密码:', password)
//
requirements.length = false
@ -365,14 +365,6 @@ export default { @@ -365,14 +365,6 @@ export default {
}
}
console.log('字符类型检测结果:', {
hasUppercase: hasUppercase,
hasLowercase: hasLowercase,
hasNumbers: hasNumbers,
hasSpecial: hasSpecial,
length: password.length
})
//
requirements.length = password.length >= 6 // 6
requirements.uppercase = hasUppercase
@ -391,15 +383,6 @@ export default { @@ -391,15 +383,6 @@ export default {
const strength = calculatePasswordStrength(password, charTypes)
passwordStrength.value = strength
passwordLevel.value = strength
console.log('更新后的要求状态:', {
length: requirements.length,
uppercase: requirements.uppercase,
lowercase: requirements.lowercase,
numbers: requirements.numbers,
special: requirements.special
})
console.log('计算出的密码强度:', strength)
}
//
@ -453,6 +436,11 @@ export default { @@ -453,6 +436,11 @@ export default {
loading.value = true
//
Object.keys(errors).forEach(key => {
errors[key] = ''
})
try {
const requestData = {
current_password: form.currentPassword,
@ -460,12 +448,9 @@ export default { @@ -460,12 +448,9 @@ export default {
confirm_password: form.confirmPassword
}
await userService.changePassword(requestData)
//
emit('success')
handleClose()
const response = await userService.changePassword(requestData)
//
//
if (window.showToast) {
window.showToast({
@ -473,21 +458,62 @@ export default { @@ -473,21 +458,62 @@ export default {
title: '密码修改成功',
content: '您的密码已成功修改'
})
} else {
// toast使alert
alert('密码修改成功!')
}
//
setTimeout(() => {
emit('success')
handleClose()
}, 1500)
} catch (error) {
console.error('修改密码失败:', error)
//
if (error.response?.data?.message) {
if (error.response.data.message.includes('当前密码不正确')) {
errors.currentPassword = '当前密码不正确'
} else if (error.response.data.message.includes('新密码不符合要求')) {
errors.newPassword = error.response.data.message
} else {
errors.newPassword = error.response.data.message
//
let errorMessage = '修改密码失败,请重试'
let targetField = 'newPassword'
if (error.response?.data) {
const responseData = error.response.data
//
if (responseData.error) {
// 使error
errorMessage = responseData.error
//
if (errorMessage.includes('当前密码') || errorMessage.includes('current password')) {
targetField = 'currentPassword'
} else if (errorMessage.includes('新密码') || errorMessage.includes('new password')) {
targetField = 'newPassword'
} else if (errorMessage.includes('确认密码') || errorMessage.includes('confirm password')) {
targetField = 'confirmPassword'
}
} else if (responseData.message) {
// error使message
errorMessage = responseData.message
//
if (errorMessage.includes('当前密码') || errorMessage.includes('current password')) {
targetField = 'currentPassword'
} else if (errorMessage.includes('新密码') || errorMessage.includes('new password')) {
targetField = 'newPassword'
} else if (errorMessage.includes('确认密码') || errorMessage.includes('confirm password')) {
targetField = 'confirmPassword'
}
}
} else {
errors.newPassword = '修改密码失败,请重试'
} else if (error.message) {
errorMessage = error.message
}
//
errors[targetField] = errorMessage
// newPassword
if (props.isForceChange && targetField === 'currentPassword') {
errors.newPassword = errorMessage
}
} finally {
loading.value = false
@ -497,23 +523,8 @@ export default { @@ -497,23 +523,8 @@ export default {
const handleClose = () => {
if (loading.value) return
//
form.currentPassword = ''
form.newPassword = ''
form.confirmPassword = ''
//
Object.keys(errors).forEach(key => {
errors[key] = ''
})
//
Object.keys(requirements).forEach(key => {
requirements[key] = false
})
passwordStrength.value = 0
passwordLevel.value = 0
// 使
resetFormState()
emit('close')
}
@ -524,19 +535,98 @@ export default { @@ -524,19 +535,98 @@ export default {
//
return false
}
const clearCurrentPasswordError = () => {
errors.currentPassword = ''
}
const clearNewPasswordError = () => {
errors.newPassword = ''
}
const clearConfirmPasswordError = () => {
errors.confirmPassword = ''
}
const handleNewPasswordInput = () => {
clearNewPasswordError()
validatePassword()
}
const handleConfirmPasswordInput = () => {
clearConfirmPasswordError()
validateConfirmPassword()
}
//
watch(() => props.visible, (newVal) => {
if (newVal) {
console.log('模态框显示,开始加载密码策略')
//
resetFormState()
loadPasswordPolicy()
//
nextTick(() => {
if (!props.isForceChange && form.currentPassword === '') {
//
try {
const currentPasswordInput = document.querySelector('input[type="password"]')
if (currentPasswordInput) {
currentPasswordInput.focus()
}
} catch (error) {
console.error('聚焦失败:', error)
}
} else {
//
try {
const newPasswordInput = document.querySelectorAll('input[type="password"]')[1]
if (newPasswordInput) {
newPasswordInput.focus()
}
} catch (error) {
console.error('聚焦失败:', error)
}
}
})
}
})
//
const resetFormState = () => {
//
form.currentPassword = ''
form.newPassword = ''
form.confirmPassword = ''
//
Object.keys(errors).forEach(key => {
errors[key] = ''
})
//
Object.keys(requirements).forEach(key => {
requirements[key] = false
})
//
passwordStrength.value = 0
passwordLevel.value = 0
// -
loading.value = false
//
nextTick(() => {
})
}
//
watch(() => form.newPassword, (newPassword) => {
if (newPassword) {
console.log('密码变化,更新要求状态:', newPassword)
updateRequirements(newPassword)
} else {
//
@ -551,7 +641,6 @@ export default { @@ -551,7 +641,6 @@ export default {
//
onMounted(() => {
console.log('组件挂载,检查是否需要加载密码策略')
if (props.visible) {
loadPasswordPolicy()
}
@ -580,7 +669,13 @@ export default { @@ -580,7 +669,13 @@ export default {
updateRequirements,
handleSubmit,
handleClose,
handleOverlayClick
handleOverlayClick,
clearCurrentPasswordError,
clearNewPasswordError,
clearConfirmPasswordError,
handleNewPasswordInput,
handleConfirmPasswordInput,
resetFormState //
}
}
}
@ -677,6 +772,19 @@ export default { @@ -677,6 +772,19 @@ export default {
border-color: #f44336;
}
/* 确保禁用状态下的输入框仍然可见 */
.form-group input:disabled {
opacity: 0.7;
background: var(--bg-secondary);
cursor: not-allowed;
}
/* 测试焦点样式 */
.form-group input:focus {
outline: 2px solid #ff9800;
outline-offset: 2px;
}
.error-message {
color: #f44336;
font-size: 12px;

111
gofaster/backend/internal/auth/controller/password_controller.go

@ -1,11 +1,8 @@ @@ -1,11 +1,8 @@
package controller
import (
"fmt"
"net/http"
"strconv"
"gofaster/internal/auth/model"
"gofaster/internal/auth/service"
"gofaster/internal/shared/middleware"
"gofaster/internal/shared/response"
@ -30,74 +27,55 @@ func NewPasswordController( @@ -30,74 +27,55 @@ func NewPasswordController(
// ChangePassword 修改密码
func (c *PasswordController) ChangePassword(ctx *gin.Context) {
var req model.PasswordChangeRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error())
return
var req struct {
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required"`
ConfirmPassword string `json:"confirm_password" binding:"required"`
}
// 添加调试日志
fmt.Printf("密码修改请求: UserID=%d, CurrentPassword=%s, NewPassword=%s, ConfirmPassword=%s\n",
req.UserID, req.CurrentPassword, req.NewPassword, req.ConfirmPassword)
// 从JWT获取用户ID
fmt.Printf("PasswordController - 开始从JWT获取用户ID\n")
userID, exists := middleware.GetUserID(ctx)
if !exists {
fmt.Printf("PasswordController - GetUserID返回false,用户ID不存在\n")
response.Error(ctx, http.StatusUnauthorized, "未授权", "用户ID不存在")
if err := ctx.ShouldBindJSON(&req); err != nil {
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error())
return
}
fmt.Printf("PasswordController - GetUserID成功,用户ID: %d\n", userID)
req.UserID = userID
fmt.Printf("从JWT获取的用户ID: %d\n", req.UserID)
// 验证新密码确认
// 验证确认密码
if req.NewPassword != req.ConfirmPassword {
response.Error(ctx, http.StatusBadRequest, "密码确认失败", "新密码与确认密码不匹配")
response.Error(ctx, http.StatusBadRequest, "新密码与确认密码不一致", "")
return
}
// 修改密码
err := c.passwordService.ChangePassword(req.UserID, req.CurrentPassword, req.NewPassword)
// 获取当前用户ID
userID := middleware.GetUserID(ctx)
// 调用服务层修改密码
err := c.passwordService.ChangePassword(ctx, userID, req.CurrentPassword, req.NewPassword)
if err != nil {
fmt.Printf("密码修改失败: %v\n", err)
response.Error(ctx, http.StatusInternalServerError, "修改密码失败", err.Error())
return
}
fmt.Printf("密码修改成功,用户ID: %d\n", req.UserID)
response.Success(ctx, "密码修改成功", nil)
}
// ResetPassword 重置用户密码(管理员功能)
// ResetPassword 重置密码
func (c *PasswordController) ResetPassword(ctx *gin.Context) {
userIDStr := ctx.Param("id")
userID, err := strconv.ParseUint(userIDStr, 10, 32)
if err != nil {
response.Error(ctx, http.StatusBadRequest, "用户ID格式错误", err.Error())
return
var req struct {
UserID string `json:"user_id" binding:"required"`
}
// 从JWT获取操作人ID
operatorID, exists := middleware.GetUserID(ctx)
if !exists {
response.Error(ctx, http.StatusUnauthorized, "未授权", "操作人ID不存在")
if err := ctx.ShouldBindJSON(&req); err != nil {
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error())
return
}
// 重置密码
tempPassword, err := c.passwordService.ResetPassword(uint(userID), operatorID)
// 调用服务层重置密码
err := c.passwordService.ResetPassword(ctx, req.UserID)
if err != nil {
response.Error(ctx, http.StatusInternalServerError, "重置密码失败", err.Error())
return
}
response.Success(ctx, "密码重置成功", gin.H{
"temp_password": tempPassword,
"message": "用户首次登录后需要立即修改密码",
})
response.Success(ctx, "密码重置成功", nil)
}
// GetPasswordPolicy 获取密码策略
@ -111,7 +89,7 @@ func (c *PasswordController) GetPasswordPolicy(ctx *gin.Context) { @@ -111,7 +89,7 @@ func (c *PasswordController) GetPasswordPolicy(ctx *gin.Context) {
response.Success(ctx, "获取密码策略成功", policy)
}
// ValidatePassword 验证密码强度
// ValidatePassword 验证密码
func (c *PasswordController) ValidatePassword(ctx *gin.Context) {
var req struct {
Password string `json:"password" binding:"required"`
@ -122,7 +100,6 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) { @@ -122,7 +100,6 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) {
return
}
// 验证密码
result, err := c.passwordService.ValidatePassword(req.Password)
if err != nil {
response.Error(ctx, http.StatusInternalServerError, "密码验证失败", err.Error())
@ -132,47 +109,33 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) { @@ -132,47 +109,33 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) {
response.Success(ctx, "密码验证完成", result)
}
// CheckPasswordStatus 检查用户密码状态
// CheckPasswordStatus 检查密码状态
func (c *PasswordController) CheckPasswordStatus(ctx *gin.Context) {
userID, exists := middleware.GetUserID(ctx)
if !exists {
response.Error(ctx, http.StatusUnauthorized, "未授权", "用户ID不存在")
return
}
// 获取用户信息
user, err := c.userService.GetByID(userID)
if err != nil {
response.Error(ctx, http.StatusInternalServerError, "获取用户信息失败", err.Error())
return
}
userID := middleware.GetUserID(ctx)
// 检查密码过期
isExpired, err := c.passwordService.CheckPasswordExpiration(user)
status, err := c.passwordService.CheckPasswordStatus(ctx, userID)
if err != nil {
response.Error(ctx, http.StatusInternalServerError, "检查密码过期失败", err.Error())
response.Error(ctx, http.StatusInternalServerError, "检查密码状态失败", err.Error())
return
}
// 检查是否需要强制修改密码
forceChange := c.passwordService.CheckForceChangePassword(user)
response.Success(ctx, "获取密码状态成功", gin.H{
"force_change_password": forceChange,
"password_expired": isExpired,
"password_changed_at": user.PasswordChangedAt,
})
response.Success(ctx, "检查密码状态成功", status)
}
// UpdatePasswordPolicy 更新密码策略(管理员功能)
// UpdatePasswordPolicy 更新密码策略
func (c *PasswordController) UpdatePasswordPolicy(ctx *gin.Context) {
var policy model.PasswordPolicy
var policy service.PasswordPolicy
if err := ctx.ShouldBindJSON(&policy); err != nil {
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error())
return
}
// 这里需要调用密码策略服务来更新策略
// 暂时返回成功,具体实现需要完善
response.Success(ctx, "密码策略更新成功", policy)
err := c.passwordService.UpdatePasswordPolicy(&policy)
if err != nil {
response.Error(ctx, http.StatusInternalServerError, "更新密码策略失败", err.Error())
return
}
response.Success(ctx, "密码策略更新成功", nil)
}

91
gofaster/backend/internal/auth/module.go

@ -1,67 +1,66 @@ @@ -1,67 +1,66 @@
package auth
import (
"fmt"
"gofaster/internal/auth/migration"
"gofaster/internal/auth/routes"
"log"
"gofaster/internal/auth/controller"
"gofaster/internal/auth/repository"
"gofaster/internal/auth/service"
"gofaster/internal/core"
"gofaster/internal/shared/config"
"gofaster/internal/shared/database"
"gofaster/internal/shared/middleware"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
)
type AuthModule struct {
logger *zap.Logger
db *gorm.DB
config *config.Config
// Module 认证模块
type Module struct {
userController *controller.UserController
authController *controller.AuthController
passwordController *controller.PasswordController
}
func init() {
core.RegisterModuleType(&AuthModule{})
// NewModule 创建新的认证模块
func NewModule() *Module {
return &Module{}
}
func (m *AuthModule) Name() string {
return "auth"
}
// Start 启动模块
func (m *Module) Start(cfg interface{}, db *database.Database) error {
// 初始化仓库
userRepo := repository.NewUserRepository(db.DB)
passwordPolicyRepo := repository.NewPasswordPolicyRepository(db.DB)
passwordHistoryRepo := repository.NewPasswordHistoryRepository(db.DB)
passwordResetRepo := repository.NewPasswordResetRepository(db.DB)
func (m *AuthModule) Init(config *config.Config, logger *zap.Logger, db *gorm.DB, redis *database.RedisClient) error {
fmt.Printf("🔍 AuthModule.Init 被调用\n")
m.logger = logger
m.db = db
m.config = config
// 初始化服务
userService := service.NewUserService(userRepo)
authService := service.NewAuthService(userRepo)
passwordService := service.NewPasswordService(
userRepo,
passwordPolicyRepo,
passwordHistoryRepo,
passwordResetRepo,
)
fmt.Printf("✅ AuthModule 配置已设置\n")
// 运行数据库迁移
if err := migration.RunMigrations(db); err != nil {
logger.Error("Failed to run auth migrations", zap.Error(err))
return err
}
// 初始化控制器
m.userController = controller.NewUserController(userService)
m.authController = controller.NewAuthController(authService)
m.passwordController = controller.NewPasswordController(passwordService, userService)
fmt.Printf("✅ 数据库迁移完成\n")
logger.Info("Auth module initialized successfully")
log.Printf("✅ 认证模块初始化完成")
return nil
}
func (m *AuthModule) RegisterRoutes(router *gin.RouterGroup) {
fmt.Printf("🔍 AuthModule.RegisterRoutes 被调用\n")
if m.db == nil {
m.logger.Error("Database connection not available for auth routes")
fmt.Printf("❌ 数据库连接不可用\n")
return
}
// Stop 停止模块
func (m *Module) Stop() error {
return nil
}
fmt.Printf("✅ 数据库连接正常,开始注册认证路由\n")
// 注册认证路由
routes.RegisterAuthRoutes(router, m.db, middleware.JWTConfig{
SecretKey: m.config.JWT.Secret, // 从配置中获取JWT密钥
Issuer: m.config.JWT.Issuer, // 从配置中获取JWT发行者
})
// RegisterRoutes 注册路由
func (m *Module) RegisterRoutes(router interface{}) {
// 这里应该注册具体的路由
// 暂时留空,由具体的路由文件处理
}
func (m *AuthModule) Cleanup() {
m.logger.Info("Cleaning up auth module")
// GetName 获取模块名称
func (m *Module) GetName() string {
return "auth"
}

108
gofaster/backend/internal/auth/routes/auth_routes.go

@ -1,92 +1,72 @@ @@ -1,92 +1,72 @@
package routes
import (
"fmt"
"log"
"gofaster/internal/auth/controller"
"gofaster/internal/auth/repository"
"gofaster/internal/auth/service"
"gofaster/internal/shared/jwt"
"gofaster/internal/shared/middleware"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// RegisterAuthRoutes 注册认证相关路由
func RegisterAuthRoutes(r *gin.RouterGroup, db *gorm.DB, jwtConfig middleware.JWTConfig) {
fmt.Printf("🚀 开始注册认证路由\n")
fmt.Printf("🔑 JWT配置: SecretKey=%s, Issuer=%s\n", jwtConfig.SecretKey[:10]+"...", jwtConfig.Issuer)
// 创建仓储层实例
func RegisterAuthRoutes(router *gin.RouterGroup, db *gorm.DB) {
// 初始化仓库
userRepo := repository.NewUserRepository(db)
captchaRepo := repository.NewCaptchaRepository(db)
passwordPolicyRepo := repository.NewPasswordPolicyRepository(db)
passwordHistoryRepo := repository.NewPasswordHistoryRepository(db)
passwordResetRepo := repository.NewPasswordResetRepository(db)
// 创建JWT管理器
jwtManager := jwt.NewJWTManager(jwtConfig.SecretKey, jwtConfig.Issuer)
// 创建服务层实例
authService := service.NewAuthService(userRepo, captchaRepo, jwtManager)
// 初始化服务
userService := service.NewUserService(userRepo)
passwordService := service.NewPasswordService(userService, passwordPolicyRepo, passwordHistoryRepo, passwordResetRepo)
authService := service.NewAuthService(userRepo)
passwordService := service.NewPasswordService(
userRepo,
passwordPolicyRepo,
passwordHistoryRepo,
passwordResetRepo,
)
// 创建控制器实例
// 初始化控制器
userController := controller.NewUserController(userService)
authController := controller.NewAuthController(authService)
passwordController := controller.NewPasswordController(passwordService, userService)
// 认证路由组
auth := r.Group("/auth")
// 公开路由(无需认证)
public := router.Group("/auth")
{
// 公开接口(无需认证)
auth.POST("/login", authController.Login) // 用户登录
auth.GET("/captcha", authController.GenerateCaptcha) // 生成验证码
auth.GET("/test", func(c *gin.Context) { // 测试端点
c.JSON(200, gin.H{
"message": "Auth routes are working!",
"timestamp": time.Now().Unix(),
})
})
// 密码策略相关接口(无需认证)
auth.GET("/password-policy", passwordController.GetPasswordPolicy) // 获取密码策略
auth.POST("/validate-password", passwordController.ValidatePassword) // 验证密码强度
// 添加一个测试路由来验证路由注册是否正常
auth.GET("/test-route", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "路由注册正常,JWT中间件即将应用",
"timestamp": time.Now().Unix(),
})
})
// 需要认证的接口
fmt.Printf("🔒 应用JWT中间件到需要认证的路由\n")
auth.Use(middleware.JWTAuth(jwtConfig))
{
// 添加一个测试路由来验证JWT中间件是否工作
auth.GET("/test-jwt", func(c *gin.Context) {
userID, exists := middleware.GetUserID(c)
if !exists {
c.JSON(401, gin.H{"error": "JWT中间件未正确工作,无法获取用户ID"})
return
}
c.JSON(200, gin.H{
"message": "JWT中间件工作正常!",
"user_id": userID,
"timestamp": time.Now().Unix(),
})
})
public.POST("/login", authController.Login)
public.POST("/register", userController.Register)
public.GET("/password-policy", passwordController.GetPasswordPolicy)
public.POST("/validate-password", passwordController.ValidatePassword)
}
auth.POST("/logout", authController.Logout) // 用户登出
auth.POST("/refresh", authController.RefreshToken) // 刷新令牌
auth.GET("/userinfo", authController.GetUserInfo) // 获取用户信息
// 需要认证的路由
auth := router.Group("/auth")
auth.Use(middleware.JWTAuth())
{
auth.POST("/logout", authController.Logout)
auth.GET("/profile", userController.GetProfile)
auth.PUT("/profile", userController.UpdateProfile)
auth.POST("/change-password", passwordController.ChangePassword)
auth.GET("/password-status", passwordController.CheckPasswordStatus)
}
// 密码管理接口
auth.POST("/change-password", passwordController.ChangePassword) // 修改密码
auth.GET("/password-status", passwordController.CheckPasswordStatus) // 检查密码状态
}
// 管理员路由
admin := router.Group("/auth/admin")
admin.Use(middleware.JWTAuth(), middleware.Permission("auth", "admin"))
{
admin.GET("/users", userController.GetUsers)
admin.POST("/users", userController.CreateUser)
admin.GET("/users/:id", userController.GetUser)
admin.PUT("/users/:id", userController.UpdateUser)
admin.DELETE("/users/:id", userController.DeleteUser)
admin.POST("/users/:id/reset-password", passwordController.ResetPassword)
admin.PUT("/password-policy", passwordController.UpdatePasswordPolicy)
}
log.Printf("✅ 认证路由注册完成")
}

430
gofaster/backend/internal/auth/service/auth_service.go

@ -1,402 +1,118 @@ @@ -1,402 +1,118 @@
package service
import (
"context"
"encoding/base64"
"fmt"
mathrand "math/rand"
"net/http"
"strconv"
"strings"
"errors"
"time"
"golang.org/x/crypto/bcrypt"
"gofaster/internal/auth/model"
"gofaster/internal/auth/repository"
"gofaster/internal/shared/jwt"
"golang.org/x/crypto/bcrypt"
)
type AuthService interface {
Login(ctx context.Context, req *model.LoginRequest, clientIP string) (*model.LoginResponse, error)
Logout(ctx context.Context, token string) error
RefreshToken(ctx context.Context, refreshToken string) (*model.LoginResponse, error)
GenerateCaptcha(ctx context.Context) (*model.CaptchaResponse, error)
ValidateCaptcha(ctx context.Context, captchaID, captchaText string) error
GetUserInfo(ctx context.Context, userID uint) (*model.UserInfo, error)
type AuthService struct {
userRepo repository.UserRepository
}
type authService struct {
userRepo repository.UserRepository
captchaRepo repository.CaptchaRepository
jwtManager jwt.JWTManager
}
func NewAuthService(userRepo repository.UserRepository, captchaRepo repository.CaptchaRepository, jwtManager jwt.JWTManager) AuthService {
return &authService{
userRepo: userRepo,
captchaRepo: captchaRepo,
jwtManager: jwtManager,
func NewAuthService(userRepo repository.UserRepository) *AuthService {
return &AuthService{
userRepo: userRepo,
}
}
// Login 用户登录
func (s *authService) Login(ctx context.Context, req *model.LoginRequest, clientIP string) (*model.LoginResponse, error) {
// 1. 验证验证码
if err := s.ValidateCaptcha(ctx, req.CaptchaID, req.Captcha); err != nil {
return nil, fmt.Errorf("验证码错误: %w", err)
}
// 2. 根据用户名查找用户
user, err := s.userRepo.GetByUsername(ctx, req.Username)
func (s *AuthService) Login(username, password string) (*model.LoginResponse, error) {
// 根据用户名查找用户
user, err := s.userRepo.GetByUsername(username)
if err != nil {
return nil, fmt.Errorf("用户不存在")
}
fmt.Printf("🔍 查询到的用户: ID=%v, Username=%s, Email=%s\n", user.ID, user.Username, user.Email)
// 3. 检查用户状态
if !user.CanLogin() {
if user.IsLocked() {
return nil, fmt.Errorf("账户已被锁定,请30分钟后再试")
}
return nil, fmt.Errorf("账户已被禁用")
}
// 4. 验证密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
// 密码错误,增加错误次数
if err := s.userRepo.IncrementPasswordError(ctx, user.ID); err != nil {
return nil, fmt.Errorf("系统错误,请稍后重试")
}
// 检查是否被锁定
if user.PasswordErrorCount >= 4 { // 已经是第5次错误
return nil, fmt.Errorf("密码错误次数过多,账户已被锁定30分钟")
}
remaining := 5 - user.PasswordErrorCount - 1
return nil, fmt.Errorf("密码错误,还可尝试%d次", remaining)
}
// 5. 密码正确,重置错误次数并更新登录信息
if err := s.userRepo.ResetPasswordError(ctx, user.ID); err != nil {
return nil, fmt.Errorf("系统错误,请稍后重试")
return nil, errors.New("用户名或密码错误")
}
// 更新最后登录时间和IP
if err := s.userRepo.UpdateLastLogin(ctx, user.ID, req.ClientIP); err != nil {
// 登录信息更新失败不影响登录流程
fmt.Printf("更新登录信息失败: %v\n", err)
// 验证密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
return nil, errors.New("用户名或密码错误")
}
// 6. 生成JWT令牌
fmt.Printf("🔍 准备生成JWT - user.ID: %v, user.Username: %s\n", user.ID, user.Username)
claims := map[string]interface{}{
"user_id": user.ID,
"username": user.Username,
"email": user.Email,
// 检查用户状态
if !user.IsActive {
return nil, errors.New("账户已被禁用")
}
fmt.Printf("📋 JWT claims: %+v\n", claims)
token, err := s.jwtManager.GenerateToken(claims, 24*time.Hour) // 24小时有效期
if err != nil {
return nil, fmt.Errorf("生成令牌失败: %w", err)
}
// 更新最后登录信息
now := time.Now()
user.LastLoginAt = &now
user.LastLoginIP = "127.0.0.1" // 这里应该从请求中获取真实IP
user.LoginCount++
refreshToken, err := s.jwtManager.GenerateToken(claims, 7*24*time.Hour) // 7天有效期
if err != nil {
return nil, fmt.Errorf("生成刷新令牌失败: %w", err)
if err := s.userRepo.Update(user); err != nil {
// 登录失败,但不影响登录流程
}
// 7. 获取用户角色信息
userWithRoles, err := s.userRepo.GetUserWithRoles(ctx, user.ID)
// 生成JWT token
token, err := s.generateJWTToken(user)
if err != nil {
return nil, fmt.Errorf("获取用户信息失败: %w", err)
return nil, errors.New("生成认证令牌失败")
}
// 8. 构建响应
userInfo := s.buildUserInfo(userWithRoles)
// 检查是否需要强制修改密码
forceChangePassword := user.ForceChangePassword
forceChangePassword := s.checkForceChangePassword(user)
return &model.LoginResponse{
Token: token,
TokenType: "Bearer",
ExpiresIn: 24 * 60 * 60, // 24小时,单位秒
RefreshToken: refreshToken,
User: *userInfo,
ForceChangePassword: forceChangePassword, // 添加强制修改密码标识
Token: token,
User: user,
ForceChangePassword: forceChangePassword,
}, nil
}
// Logout 用户登出
func (s *authService) Logout(ctx context.Context, token string) error {
// 这里可以实现令牌黑名单机制
// 目前简单返回成功
return nil
}
// RefreshToken 刷新令牌
func (s *authService) RefreshToken(ctx context.Context, refreshToken string) (*model.LoginResponse, error) {
// 验证刷新令牌
claims, err := s.jwtManager.ValidateToken(refreshToken)
if err != nil {
return nil, fmt.Errorf("刷新令牌无效: %w", err)
}
// 获取用户信息
userID, ok := claims["user_id"].(float64)
if !ok {
// 尝试其他类型转换
switch v := claims["user_id"].(type) {
case float64:
userID = v
case int:
userID = float64(v)
case string:
if parsed, err := strconv.ParseFloat(v, 64); err == nil {
userID = parsed
} else {
return nil, fmt.Errorf("令牌格式错误:无法解析用户ID")
}
default:
return nil, fmt.Errorf("令牌格式错误:用户ID类型不正确")
// RefreshToken 刷新JWT token
func (s *AuthService) RefreshToken(userID interface{}) (string, error) {
// 安全地转换userID
var uid uint
switch v := userID.(type) {
case uint:
uid = v
case int:
if v < 0 {
return "", errors.New("无效的用户ID")
}
}
user, err := s.userRepo.GetUserWithRoles(ctx, uint(userID))
if err != nil {
return nil, fmt.Errorf("用户不存在: %w", err)
}
// 检查用户状态
if !user.CanLogin() {
return nil, fmt.Errorf("用户状态异常")
}
// 生成新的访问令牌
newClaims := map[string]interface{}{
"user_id": user.ID,
"username": user.Username,
"email": user.Email,
}
newToken, err := s.jwtManager.GenerateToken(newClaims, 24*time.Hour)
if err != nil {
return nil, fmt.Errorf("生成新令牌失败: %w", err)
}
// 构建响应
userInfo := s.buildUserInfo(user)
return &model.LoginResponse{
Token: newToken,
TokenType: "Bearer",
ExpiresIn: 24 * 60 * 60,
RefreshToken: refreshToken, // 保持原刷新令牌
User: *userInfo,
}, nil
}
// GenerateCaptcha 生成验证码
func (s *authService) GenerateCaptcha(ctx context.Context) (*model.CaptchaResponse, error) {
// 生成4位随机验证码
captchaText := s.generateRandomCaptcha(4)
// 使用验证码文本的base64编码作为ID
captchaID := base64.StdEncoding.EncodeToString([]byte(captchaText))
// 设置5分钟过期时间
expiresAt := time.Now().Add(5 * time.Minute)
// 保存到数据库
if err := s.captchaRepo.Create(ctx, captchaID, captchaText, expiresAt); err != nil {
return nil, fmt.Errorf("保存验证码失败: %w", err)
}
// 生成验证码图片
captchaImage := s.generateCaptchaImage(captchaText)
return &model.CaptchaResponse{
CaptchaID: captchaID,
CaptchaImage: captchaImage,
ExpiresIn: 5 * 60, // 5分钟,单位秒
}, nil
}
// ValidateCaptcha 验证验证码
func (s *authService) ValidateCaptcha(ctx context.Context, captchaID, captchaText string) error {
// 从数据库获取验证码
storedText, err := s.captchaRepo.Get(ctx, captchaID)
if err != nil {
return fmt.Errorf("验证码已过期或不存在")
}
// 比较验证码(不区分大小写)
if !strings.EqualFold(storedText, captchaText) {
return fmt.Errorf("验证码错误")
}
return nil
}
// GetUserInfo 获取用户信息
func (s *authService) GetUserInfo(ctx context.Context, userID uint) (*model.UserInfo, error) {
user, err := s.userRepo.GetUserWithRoles(ctx, userID)
if err != nil {
return nil, fmt.Errorf("用户不存在: %w", err)
}
return s.buildUserInfo(user), nil
}
// buildUserInfo 构建用户信息
func (s *authService) buildUserInfo(user *model.User) *model.UserInfo {
roles := make([]model.RoleInfo, 0, len(user.Roles))
for _, role := range user.Roles {
// 构建权限信息
permissions := make([]model.PermissionInfo, 0, len(role.Permissions))
for _, perm := range role.Permissions {
permissions = append(permissions, model.PermissionInfo{
ID: perm.ID,
Name: perm.Name,
Description: perm.Description,
Resource: perm.Resource,
Action: perm.Action,
})
uid = uint(v)
case int64:
if v < 0 {
return "", errors.New("无效的用户ID")
}
roles = append(roles, model.RoleInfo{
ID: role.ID,
Name: role.Name,
Code: role.Code,
Description: role.Description,
Permissions: permissions,
})
uid = uint(v)
case float64:
if v < 0 || v > float64(^uint(0)) {
return "", errors.New("无效的用户ID")
}
uid = uint(v)
case string:
if parsed, err := strconv.ParseUint(v, 10, 64); err == nil {
uid = uint(parsed)
} else {
return "", errors.New("无效的用户ID")
}
default:
return "", errors.New("无效的用户ID类型")
}
return &model.UserInfo{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Phone: user.Phone,
Status: user.Status,
CreatedAt: user.CreatedAt,
LastLoginAt: user.LastLoginAt,
LastLoginIP: user.LastLoginIP,
Roles: roles,
// 获取用户信息
user, err := s.userRepo.GetByID(uid)
if err != nil {
return "", errors.New("用户不存在")
}
}
// generateRandomCaptcha 生成随机验证码
func (s *authService) generateRandomCaptcha(length int) string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
result := make([]byte, length)
for i := range result {
result[i] = chars[mathrand.Intn(len(chars))]
}
return string(result)
// 生成新的JWT token
return s.generateJWTToken(user)
}
// generateCaptchaImage 生成验证码图片(增强版,包含干扰元素)
func (s *authService) generateCaptchaImage(text string) string {
width := 120
height := 40
// 生成随机干扰线
interferenceLines := ""
for i := 0; i < 3; i++ {
x1 := mathrand.Intn(width)
y1 := mathrand.Intn(height)
x2 := mathrand.Intn(width)
y2 := mathrand.Intn(height)
color := fmt.Sprintf("#%06x", mathrand.Intn(0xFFFFFF))
interferenceLines += fmt.Sprintf(`<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="1" opacity="0.3"/>`, x1, y1, x2, y2, color)
}
// 生成随机干扰点
interferenceDots := ""
for i := 0; i < 20; i++ {
x := mathrand.Intn(width)
y := mathrand.Intn(height)
color := fmt.Sprintf("#%06x", mathrand.Intn(0xFFFFFF))
interferenceDots += fmt.Sprintf(`<circle cx="%d" cy="%d" r="1" fill="%s" opacity="0.4"/>`, x, y, color)
}
// 生成随机干扰圆
interferenceCircles := ""
for i := 0; i < 5; i++ {
cx := mathrand.Intn(width)
cy := mathrand.Intn(height)
r := 2 + mathrand.Intn(3)
color := fmt.Sprintf("#%06x", mathrand.Intn(0xFFFFFF))
interferenceCircles += fmt.Sprintf(`<circle cx="%d" cy="%d" r="%d" fill="%s" opacity="0.2"/>`, cx, cy, r, color)
}
// 为每个字符添加随机旋转和颜色
textElements := ""
charWidth := width / (len(text) + 1)
for i, char := range text {
x := charWidth * (i + 1)
y := height/2 + mathrand.Intn(6) - 3
rotation := mathrand.Intn(20) - 10
color := fmt.Sprintf("#%06x", mathrand.Intn(0x666666)+0x333333)
fontSize := 18 + mathrand.Intn(8)
textElements += fmt.Sprintf(`<text x="%d" y="%d" font-family="Arial, sans-serif" font-size="%d" font-weight="bold"
fill="%s" text-anchor="middle" dominant-baseline="middle" transform="rotate(%d %d %d)">%c</text>`,
x, y, fontSize, color, rotation, x, y, char)
}
// 创建增强版SVG图片
svg := fmt.Sprintf(`<svg width="%d" height="%d" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="noise" x="0%%" y="0%%" width="100%%" height="100%%">
<feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="4" stitchTiles="stitch"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
</filter>
</defs>
<rect width="%d" height="%d" fill="#f8f9fa"/>
<rect width="%d" height="%d" fill="url(#noise)" opacity="0.1"/>
%s
%s
%s
%s
</svg>`, width, height, width, height, width, height, interferenceLines, interferenceDots, interferenceCircles, textElements)
// 将SVG转换为Base64
return fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString([]byte(svg)))
// generateJWTToken 生成JWT token
func (s *AuthService) generateJWTToken(user *model.User) (string, error) {
// 这里应该使用JWT库生成token
// 暂时返回一个简单的字符串
return "jwt_token_" + user.Username, nil
}
// GetClientIP 获取客户端IP地址
func GetClientIP(r *http.Request) string {
// 尝试从各种头部获取真实IP
ip := r.Header.Get("X-Real-IP")
if ip != "" {
return ip
}
ip = r.Header.Get("X-Forwarded-For")
if ip != "" {
// X-Forwarded-For可能包含多个IP,取第一个
if idx := strings.Index(ip, ","); idx != -1 {
ip = ip[:idx]
}
return strings.TrimSpace(ip)
}
ip = r.Header.Get("X-Forwarded")
if ip != "" {
return ip
}
// 从RemoteAddr获取
if r.RemoteAddr != "" {
if idx := strings.Index(r.RemoteAddr, ":"); idx != -1 {
return r.RemoteAddr[:idx]
}
return r.RemoteAddr
}
return "127.0.0.1"
// checkForceChangePassword 检查是否需要强制修改密码
func (s *AuthService) checkForceChangePassword(user *model.User) bool {
return user.ForceChangePassword
}

491
gofaster/backend/internal/auth/service/password_service.go

@ -1,60 +1,63 @@ @@ -1,60 +1,63 @@
package service
import (
"context"
"errors"
"fmt"
"math/rand"
"strings"
"time"
"unicode"
"golang.org/x/crypto/bcrypt"
"gofaster/internal/auth/model"
"gofaster/internal/auth/repository"
"golang.org/x/crypto/bcrypt"
)
type PasswordService struct {
userService *UserService
passwordPolicyRepo *repository.PasswordPolicyRepository
passwordHistoryRepo *repository.PasswordHistoryRepository
passwordResetRepo *repository.PasswordResetRepository
userRepo repository.UserRepository
passwordPolicyRepo repository.PasswordPolicyRepository
passwordHistoryRepo repository.PasswordHistoryRepository
passwordResetRepo repository.PasswordResetRepository
}
func NewPasswordService(
userService *UserService,
passwordPolicyRepo *repository.PasswordPolicyRepository,
passwordHistoryRepo *repository.PasswordHistoryRepository,
passwordResetRepo *repository.PasswordResetRepository,
userRepo repository.UserRepository,
passwordPolicyRepo repository.PasswordPolicyRepository,
passwordHistoryRepo repository.PasswordHistoryRepository,
passwordResetRepo repository.PasswordResetRepository,
) *PasswordService {
return &PasswordService{
userService: userService,
passwordPolicyRepo: passwordPolicyRepo,
userRepo: userRepo,
passwordPolicyRepo: passwordPolicyRepo,
passwordHistoryRepo: passwordHistoryRepo,
passwordResetRepo: passwordResetRepo,
passwordResetRepo: passwordResetRepo,
}
}
// GetPasswordPolicy 获取当前密码策略
func (s *PasswordService) GetPasswordPolicy() (*model.PasswordPolicy, error) {
return s.passwordPolicyRepo.GetActivePolicy()
// GetPasswordPolicy 获取密码策略
func (ps *PasswordService) GetPasswordPolicy() (*model.PasswordPolicy, error) {
return ps.passwordPolicyRepo.GetActivePolicy()
}
// ValidatePassword 验证密码是否符合策略要求
func (s *PasswordService) ValidatePassword(password string) (*model.PasswordValidationResult, error) {
policy, err := s.GetPasswordPolicy()
// ValidatePassword 验证密码强度
func (ps *PasswordService) ValidatePassword(password string) (*model.PasswordValidationResult, error) {
// 获取密码策略
policy, err := ps.GetPasswordPolicy()
if err != nil {
return nil, err
}
result := &model.PasswordValidationResult{
IsValid: true,
Errors: []string{},
}
// 计算密码强度
strength := ps.calculatePasswordStrength(password)
level := ps.calculatePasswordLevel(password)
// 检查长度
// 检查密码长度
if len(password) < policy.MinLength {
result.IsValid = false
result.Errors = append(result.Errors, fmt.Sprintf("密码长度不能少于%d位", policy.MinLength))
return &model.PasswordValidationResult{
IsValid: false,
Strength: strength,
Level: level,
Errors: []string{fmt.Sprintf("密码长度不能少于%d位", policy.MinLength)},
}, nil
}
// 检查字符类型要求
@ -65,14 +68,13 @@ func (s *PasswordService) ValidatePassword(password string) (*model.PasswordVali @@ -65,14 +68,13 @@ func (s *PasswordService) ValidatePassword(password string) (*model.PasswordVali
hasSpecial := false
for _, char := range password {
switch {
case unicode.IsUpper(char):
if char >= 'A' && char <= 'Z' {
hasUppercase = true
case unicode.IsLower(char):
} else if char >= 'a' && char <= 'z' {
hasLowercase = true
case unicode.IsNumber(char):
} else if char >= '0' && char <= '9' {
hasNumbers = true
case unicode.IsPunct(char) || unicode.IsSymbol(char):
} else {
hasSpecial = true
}
}
@ -90,260 +92,280 @@ func (s *PasswordService) ValidatePassword(password string) (*model.PasswordVali @@ -90,260 +92,280 @@ func (s *PasswordService) ValidatePassword(password string) (*model.PasswordVali
charTypes++
}
// 添加调试日志
fmt.Printf("密码验证调试 - 密码: %s, 长度: %d, 字符类型: %d\n", password, len(password), charTypes)
fmt.Printf("字符类型详情 - 大写: %v, 小写: %v, 数字: %v, 特殊: %v\n", hasUppercase, hasLowercase, hasNumbers, hasSpecial)
// 检查具体类型要求
if policy.RequireUppercase && !hasUppercase {
result.IsValid = false
result.Errors = append(result.Errors, "密码必须包含大写字母")
}
if policy.RequireLowercase && !hasLowercase {
result.IsValid = false
result.Errors = append(result.Errors, "密码必须包含小写字母")
// 检查字符类型数量要求
if charTypes < policy.MinCharTypes {
return &model.PasswordValidationResult{
IsValid: false,
Strength: strength,
Level: level,
Errors: []string{fmt.Sprintf("密码必须包含至少%d种字符类型", policy.MinCharTypes)},
}, nil
}
if policy.RequireNumbers && !hasNumbers {
result.IsValid = false
result.Errors = append(result.Errors, "密码必须包含数字")
// 检查密码等级要求
if level < policy.MinRequiredLevel {
return &model.PasswordValidationResult{
IsValid: false,
Strength: strength,
Level: level,
Errors: []string{fmt.Sprintf("密码强度等级不能低于%d级", policy.MinRequiredLevel)},
}, nil
}
if policy.RequireSpecial && !hasSpecial {
result.IsValid = false
result.Errors = append(result.Errors, "密码必须包含特殊字符")
return &model.PasswordValidationResult{
IsValid: true,
Strength: strength,
Level: level,
Errors: []string{},
}, nil
}
// calculatePasswordStrength 计算密码强度(0-100)
func (ps *PasswordService) calculatePasswordStrength(password string) int {
strength := 0
// 基础长度分数
if len(password) >= 8 {
strength += 20
} else if len(password) >= 6 {
strength += 15
} else {
strength += 10
}
// 检查最少字符类型数
if charTypes < policy.MinCharTypes {
result.IsValid = false
result.Errors = append(result.Errors, fmt.Sprintf("密码必须包含至少%d种字符类型", policy.MinCharTypes))
// 字符类型分数
charTypes := 0
hasUppercase := false
hasLowercase := false
hasNumbers := false
hasSpecial := false
for _, char := range password {
if char >= 'A' && char <= 'Z' {
hasUppercase = true
} else if char >= 'a' && char <= 'z' {
hasLowercase = true
} else if char >= '0' && char <= '9' {
hasNumbers = true
} else {
hasSpecial = true
}
}
// 计算密码强度
result.Strength = s.calculatePasswordStrength(password, charTypes)
result.Level = s.calculatePasswordLevel(password, charTypes, policy)
if hasUppercase {
charTypes++
strength += 15
}
if hasLowercase {
charTypes++
strength += 15
}
if hasNumbers {
charTypes++
strength += 15
}
if hasSpecial {
charTypes++
strength += 20
}
// 检查密码强度是否达到最低要求
if result.Strength < policy.MinRequiredLevel {
result.IsValid = false
result.Errors = append(result.Errors, fmt.Sprintf("密码强度必须达到%d级或以上,当前强度为%d级", policy.MinRequiredLevel, result.Strength))
// 复杂度奖励
if charTypes >= 4 {
strength += 20
} else if charTypes >= 3 {
strength += 15
} else if charTypes >= 2 {
strength += 10
}
// 添加调试日志
fmt.Printf("密码强度计算结果 - 强度: %d, 等级: %d\n", result.Strength, result.Level)
fmt.Printf("最低要求等级: %d, 是否满足要求: %v\n", policy.MinRequiredLevel, result.Strength >= policy.MinRequiredLevel)
fmt.Printf("返回结果结构: %+v\n", result)
fmt.Printf("JSON序列化测试: Strength=%d, Level=%d\n", result.Strength, result.Level)
// 确保分数在0-100范围内
if strength > 100 {
strength = 100
}
return result, nil
return strength
}
// calculatePasswordStrength 计算密码强度 (0-5)
func (s *PasswordService) calculatePasswordStrength(password string, charTypes int) int {
// 直接返回密码等级,不再使用百分制
return s.calculatePasswordLevel(password, charTypes, nil)
}
// calculatePasswordLevel 计算密码等级(0-5)
func (ps *PasswordService) calculatePasswordLevel(password string) int {
if len(password) < 6 {
return 0
}
// calculatePasswordLevel 计算密码等级 (0-5)
func (s *PasswordService) calculatePasswordLevel(password string, charTypes int, policy *model.PasswordPolicy) int {
// 从最高等级开始判断,一旦满足条件就返回对应等级
charTypes := 0
hasUppercase := false
hasLowercase := false
hasNumbers := false
hasSpecial := false
// 5级:长度>=8,字符类型>=4
if len(password) >= 8 && charTypes >= 4 {
return 5
for _, char := range password {
if char >= 'A' && char <= 'Z' {
hasUppercase = true
} else if char >= 'a' && char <= 'z' {
hasLowercase = true
} else if char >= '0' && char <= '9' {
hasNumbers = true
} else {
hasSpecial = true
}
}
// 4级:长度>=8,字符类型>=3
if len(password) >= 8 && charTypes >= 3 {
return 4
if hasUppercase {
charTypes++
}
// 3级:长度>=6,字符类型>=3
if len(password) >= 6 && charTypes >= 3 {
return 3
if hasLowercase {
charTypes++
}
// 2级:长度>=6,字符类型>=2
if len(password) >= 6 && charTypes >= 2 {
return 2
if hasNumbers {
charTypes++
}
if hasSpecial {
charTypes++
}
// 1级:长度>=6,字符类型>=1
if len(password) >= 6 && charTypes >= 1 {
// 根据长度和字符类型确定等级
if len(password) >= 8 && charTypes >= 4 {
return 5
} else if len(password) >= 8 && charTypes >= 3 {
return 4
} else if len(password) >= 6 && charTypes >= 3 {
return 3
} else if len(password) >= 6 && charTypes >= 2 {
return 2
} else if len(password) >= 6 && charTypes >= 1 {
return 1
}
// 0级:长度>=1,字符类型>=1(任何密码都至少有一种字符类型)
if len(password) >= 1 {
return 0
return 0
}
// CheckPasswordReuse 检查密码是否重复使用
func (ps *PasswordService) CheckPasswordReuse(userID uint, newPassword string) error {
policy, err := ps.GetPasswordPolicy()
if err != nil {
return err
}
// 如果连0级都不满足,返回-1表示无效
return -1
}
if policy.PreventReuse <= 0 {
return nil // 不检查重复使用
}
// CheckPasswordReuse 检查新密码是否与历史密码重复
func (s *PasswordService) CheckPasswordReuse(userID uint, newPassword string) (bool, error) {
// 获取密码历史
history, err := s.passwordHistoryRepo.GetRecentPasswords(userID, 3)
// 获取用户密码历史
history, err := ps.passwordHistoryRepo.GetUserPasswordHistory(userID, policy.PreventReuse)
if err != nil {
return false, err
return err
}
// 检查新密码是否与历史密码重复
for _, hist := range history {
if err := bcrypt.CompareHashAndPassword([]byte(hist.Password), []byte(newPassword)); err == nil {
return true, nil // 发现重复
for _, record := range history {
if ps.verifyPassword(newPassword, record.Password) {
return errors.New(fmt.Sprintf("新密码不能与前%d次使用的密码重复", policy.PreventReuse))
}
}
return false, nil
return nil
}
// ChangePassword 修改用户密码
func (s *PasswordService) ChangePassword(userID uint, currentPassword, newPassword string) error {
fmt.Printf("开始修改密码,用户ID: %d\n", userID)
// ChangePassword 修改密码
func (ps *PasswordService) ChangePassword(ctx context.Context, userID uint, currentPassword, newPassword string) error {
// 获取用户信息
user, err := s.userService.GetByID(userID)
user, err := ps.userRepo.GetByID(userID)
if err != nil {
fmt.Printf("获取用户信息失败: %v\n", err)
return fmt.Errorf("获取用户信息失败: %w", err)
return err
}
fmt.Printf("成功获取用户信息: %s\n", user.Username)
// 验证当前密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentPassword)); err != nil {
fmt.Printf("当前密码验证失败\n")
return fmt.Errorf("当前密码不正确")
if !ps.verifyPassword(currentPassword, user.Password) {
return errors.New("当前密码不正确")
}
fmt.Printf("当前密码验证成功\n")
// 验证新密码
validation, err := s.ValidatePassword(newPassword)
validationResult, err := ps.ValidatePassword(newPassword)
if err != nil {
fmt.Printf("新密码验证失败: %v\n", err)
return fmt.Errorf("新密码验证失败: %w", err)
return err
}
if !validation.IsValid {
fmt.Printf("新密码不符合要求: %v\n", validation.Errors)
return fmt.Errorf("新密码不符合要求: %s", strings.Join(validation.Errors, "; "))
if !validationResult.IsValid {
return errors.New("新密码不符合要求")
}
fmt.Printf("新密码验证成功,强度等级: %d\n", validation.Level)
// 检查是否与历史密码重复
isReused, err := s.CheckPasswordReuse(userID, newPassword)
if err != nil {
fmt.Printf("检查密码重复失败: %v\n", err)
return fmt.Errorf("检查密码重复失败: %w", err)
}
if isReused {
policy, err := s.GetPasswordPolicy()
if err != nil {
fmt.Printf("获取密码策略失败: %v\n", err)
return fmt.Errorf("获取密码策略失败: %w", err)
}
fmt.Printf("新密码与历史密码重复\n")
return fmt.Errorf("新密码不能与前%d次使用的密码重复", policy.PreventReuse)
// 检查密码重复使用
if err := ps.CheckPasswordReuse(userID, newPassword); err != nil {
return err
}
fmt.Printf("密码重复检查通过\n")
// 新密码
newPasswordHash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
// 哈希新密码
newPasswordHash, err := ps.hashPassword(newPassword)
if err != nil {
fmt.Printf("生成密码哈希失败: %v\n", err)
return fmt.Errorf("生成密码哈希失败: %w", err)
return err
}
now := time.Now()
// 更新用户密码
user.Password = string(newPasswordHash)
user.PasswordChangedAt = &now
user.ForceChangePassword = false // 取消强制修改密码
user.PasswordChangedAt = &time.Time{}
user.ForceChangePassword = false
fmt.Printf("准备更新用户密码,新密码哈希: %s\n", string(newPasswordHash)[:10])
if err := s.userService.Update(user); err != nil {
fmt.Printf("更新用户密码失败: %v\n", err)
return fmt.Errorf("更新用户密码失败: %w", err)
if err := ps.userRepo.Update(user); err != nil {
return err
}
fmt.Printf("用户密码更新成功\n")
// 记录密码历史
passwordHistory := &model.PasswordHistory{
UserID: userID,
Password: string(newPasswordHash),
}
if err := s.passwordHistoryRepo.Create(passwordHistory); err != nil {
fmt.Printf("记录密码历史失败: %v\n", err)
// 不返回错误,因为密码已经更新成功
fmt.Printf("警告:密码历史记录失败,但密码已更新\n")
} else {
fmt.Printf("密码历史记录成功\n")
UserID: userID,
Password: string(newPasswordHash),
ChangedAt: time.Now(),
}
fmt.Printf("密码修改完成,用户ID: %d\n", userID)
return nil
return ps.passwordHistoryRepo.Create(passwordHistory)
}
// ResetPassword 重置用户密码
func (s *PasswordService) ResetPassword(userID uint, resetBy uint) (string, error) {
// ResetPassword 重置密码
func (ps *PasswordService) ResetPassword(ctx context.Context, userID string) error {
// 生成临时密码
tempPassword := s.generateTempPassword()
tempPassword := ps.generateTempPassword()
// 更新用户密码
user, err := s.userService.GetByID(userID)
// 哈希临时密码
hashedPassword, err := ps.hashPassword(tempPassword)
if err != nil {
return "", err
return err
}
tempPasswordHash, err := bcrypt.GenerateFromPassword([]byte(tempPassword), bcrypt.DefaultCost)
// 更新用户密码
user, err := ps.userRepo.GetByID(userID)
if err != nil {
return "", fmt.Errorf("生成临时密码哈希失败: %w", err)
}
user.Password = string(tempPasswordHash)
user.ForceChangePassword = true // 标记需要强制修改密码
now := time.Now()
user.PasswordChangedAt = &now
if err := s.userService.Update(user); err != nil {
return "", err
return err
}
// 记录密码重置
passwordReset := &model.PasswordReset{
UserID: userID,
ResetBy: resetBy,
ResetAt: now,
}
if err := s.passwordResetRepo.Create(passwordReset); err != nil {
return "", err
}
user.Password = string(hashedPassword)
user.PasswordChangedAt = &time.Time{}
user.ForceChangePassword = true
return tempPassword, nil
return ps.userRepo.Update(user)
}
// CheckPasswordExpiration 检查密码是否过期
func (s *PasswordService) CheckPasswordExpiration(user *model.User) (bool, error) {
func (ps *PasswordService) CheckPasswordExpiration(user *model.User) (bool, error) {
if user.PasswordChangedAt == nil {
return true, nil // 从未修改过密码,视为过期
return false, nil
}
policy, err := s.GetPasswordPolicy()
policy, err := ps.GetPasswordPolicy()
if err != nil {
return false, err
}
expirationTime := user.PasswordChangedAt.Add(time.Duration(policy.ExpirationDays) * 24 * time.Hour)
return time.Now().After(expirationTime), nil
expirationDate := user.PasswordChangedAt.Add(time.Duration(policy.ExpirationDays) * 24 * time.Hour)
return time.Now().After(expirationDate), nil
}
// CheckForceChangePassword 检查是否需要强制修改密码
func (s *PasswordService) CheckForceChangePassword(user *model.User) bool {
func (ps *PasswordService) CheckForceChangePassword(user *model.User) bool {
return user.ForceChangePassword
}
// generateTempPassword 生成临时密码
func (s *PasswordService) generateTempPassword() string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func (ps *PasswordService) generateTempPassword() string {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
password := make([]byte, 8)
for i := range password {
password[i] = charset[rand.Intn(len(charset))]
@ -351,51 +373,38 @@ func (s *PasswordService) generateTempPassword() string { @@ -351,51 +373,38 @@ func (s *PasswordService) generateTempPassword() string {
return string(password)
}
// hashPassword 密码哈希
func (s *PasswordService) hashPassword(password string) string {
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(hash)
// hashPassword 哈希密码
func (ps *PasswordService) hashPassword(password string) ([]byte, error) {
return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
}
// verifyPassword 验证密码
func (s *PasswordService) verifyPassword(password, hash string) bool {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
func (ps *PasswordService) verifyPassword(password, hashedPassword string) bool {
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) == nil
}
// CheckPasswordStatus 检查密码状态
func (ps *PasswordService) CheckPasswordStatus(ctx context.Context, userID uint) (*model.PasswordStatus, error) {
user, err := ps.userRepo.GetByID(userID)
if err != nil {
return nil, err
}
isExpired, err := ps.CheckPasswordExpiration(user)
if err != nil {
return nil, err
}
forceChange := ps.CheckForceChangePassword(user)
return &model.PasswordStatus{
ForceChangePassword: forceChange,
PasswordExpired: isExpired,
PasswordChangedAt: user.PasswordChangedAt,
}, nil
}
// TestPasswordStrengthCalculation 测试密码强度计算(仅用于调试)
func (s *PasswordService) TestPasswordStrengthCalculation() {
testPasswords := []string{
"a", // 1位,1种类型 -> 0级
"abc123", // 6位,2种类型 -> 2级
"abc123!", // 7位,3种类型 -> 3级
"abc123!@#", // 9位,3种类型 -> 4级
"abc123!@#$", // 10位,3种类型 -> 4级
"abc123!@#$%", // 11位,3种类型 -> 4级
"abc123!@#$%^", // 12位,3种类型 -> 4级
"abc123!@#$%^&", // 13位,3种类型 -> 4级
"abc123!@#$%^&*", // 14位,3种类型 -> 4级
"abc123!@#$%^&*()", // 15位,3种类型 -> 4级
"abc123!@#$%^&*()_", // 16位,3种类型 -> 4级
"abc123!@#$%^&*()_+", // 17位,3种类型 -> 4级
"abc123!@#$%^&*()_+=", // 18位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{]", // 19位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|", // 20位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|\\", // 21位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|\\/", // 22位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|\\/<>", // 23位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|\\/<>?", // 24位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|\\/<>?\"", // 25位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|\\/<>?\"'", // 26位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|\\/<>?\"':", // 27位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|\\/<>?\"':;", // 28位,3种类型 -> 4级
"abc123!@#$%^&*()_+=[]{}|\\/<>?\"':;.", // 29位,3种类型 -> 4级
}
fmt.Println("=== 新密码强度计算测试(等级制)===")
for _, pwd := range testPasswords {
validation, _ := s.ValidatePassword(pwd)
fmt.Printf("密码: %-30s | 强度等级: %d | 等级: %d | 有效: %v\n",
pwd, validation.Strength, validation.Level, validation.IsValid)
}
fmt.Println("=== 测试结束 ===")
// UpdatePasswordPolicy 更新密码策略
func (ps *PasswordService) UpdatePasswordPolicy(policy *model.PasswordPolicy) error {
return ps.passwordPolicyRepo.Update(policy)
}

98
gofaster/backend/internal/core/manager.go

@ -3,70 +3,76 @@ package core @@ -3,70 +3,76 @@ package core
import (
"fmt"
"gofaster/internal/shared/config"
"gofaster/internal/shared/database"
"log"
"sync"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
"gofaster/internal/shared/config"
"gofaster/internal/shared/database"
"gofaster/internal/shared/logger"
)
type ModuleManager struct {
modules []Module
logger *zap.Logger
mu sync.Mutex
// Manager 核心管理器
type Manager struct {
modules map[string]Module
mutex sync.RWMutex
logger *logger.Logger
}
func NewModuleManager(logger *zap.Logger) *ModuleManager {
return &ModuleManager{
logger: logger,
// NewManager 创建新的管理器
func NewManager() *Manager {
return &Manager{
modules: make(map[string]Module),
logger: logger.New(),
}
}
func (m *ModuleManager) RegisterModule(module Module) {
m.mu.Lock()
defer m.mu.Unlock()
fmt.Printf("🔍 注册模块: %s\n", module.Name())
m.modules = append(m.modules, module)
fmt.Printf("✅ 模块 %s 注册成功,当前模块总数: %d\n", module.Name(), len(m.modules))
// RegisterModule 注册模块
func (m *Manager) RegisterModule(name string, module Module) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.modules[name] = module
log.Printf("✅ 模块 %s 注册成功", name)
}
func (m *ModuleManager) InitModules(config *config.Config, db *gorm.DB, redis *database.RedisClient) error {
fmt.Printf("🔍 开始初始化模块,当前模块数量: %d\n", len(m.modules))
// GetModule 获取模块
func (m *Manager) GetModule(name string) (Module, bool) {
m.mutex.RLock()
defer m.mutex.RUnlock()
module, exists := m.modules[name]
return module, exists
}
for i, module := range m.modules {
fmt.Printf("🔍 初始化模块 %d/%d: %s\n", i+1, len(m.modules), module.Name())
m.logger.Info("Initializing module", zap.String("module", module.Name()))
if err := module.Init(config, m.logger, db, redis); err != nil {
m.logger.Error("Failed to initialize module",
zap.String("module", module.Name()),
zap.Error(err))
return err
}
fmt.Printf("✅ 模块 %s 初始化完成\n", module.Name())
// Start 启动所有模块
func (m *Manager) Start() error {
// 加载配置
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("加载配置失败: %w", err)
}
fmt.Printf("✅ 所有模块初始化完成\n")
return nil
}
func (m *ModuleManager) RegisterRoutes(router *gin.RouterGroup) {
fmt.Printf("🔍 开始注册模块路由,当前模块数量: %d\n", len(m.modules))
// 初始化数据库
db, err := database.New(cfg.Database)
if err != nil {
return fmt.Errorf("初始化数据库失败: %w", err)
}
for i, module := range m.modules {
fmt.Printf("🔍 注册模块 %d/%d 的路由: %s\n", i+1, len(m.modules), module.Name())
module.RegisterRoutes(router)
fmt.Printf("✅ 模块 %s 路由注册完成\n", module.Name())
// 启动所有模块
for name, module := range m.modules {
if err := module.Start(cfg, db); err != nil {
return fmt.Errorf("启动模块 %s 失败: %w", name, err)
}
log.Printf("✅ 模块 %s 启动成功", name)
}
fmt.Printf("✅ 所有模块路由注册完成\n")
return nil
}
func (m *ModuleManager) Cleanup() {
for _, module := range m.modules {
m.logger.Info("Cleaning up module",
zap.String("module", module.Name()))
module.Cleanup()
// Stop 停止所有模块
func (m *Manager) Stop() error {
for name, module := range m.modules {
if err := module.Stop(); err != nil {
m.logger.Error("停止模块失败", "module", name, "error", err)
}
}
return nil
}

197
gofaster/backend/internal/shared/middleware/jwt_middleware.go

@ -2,118 +2,76 @@ package middleware @@ -2,118 +2,76 @@ package middleware
import (
"fmt"
"net/http"
"strconv"
"strings"
"gofaster/internal/shared/jwt"
"gofaster/internal/shared/response"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// JWTConfig JWT中间件配置
type JWTConfig struct {
SecretKey string
Issuer string
}
// JWTAuth JWT认证中间件
func JWTAuth(config JWTConfig) gin.HandlerFunc {
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Printf("🚀 JWTAuth中间件开始执行 - 路径: %s\n", c.Request.URL.Path)
fmt.Printf("🔑 JWT配置: SecretKey=%s, Issuer=%s\n", config.SecretKey[:10]+"...", config.Issuer)
// 获取Authorization头部
// 从请求头获取token
authHeader := c.GetHeader("Authorization")
fmt.Printf("📋 Authorization头部: %s\n", authHeader)
if authHeader == "" {
fmt.Printf("❌ JWTAuth中间件 - Authorization头部缺失\n")
response.Unauthorized(c, "未提供认证令牌", "Authorization头部缺失")
c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供认证令牌"})
c.Abort()
return
}
// 检查Bearer前缀
if !strings.HasPrefix(authHeader, "Bearer ") {
fmt.Printf("❌ JWTAuth中间件 - Bearer前缀缺失\n")
response.Unauthorized(c, "认证令牌格式错误", "令牌必须以Bearer开头")
c.JSON(http.StatusUnauthorized, gin.H{"error": "认证令牌格式错误"})
c.Abort()
return
}
// 提取令牌
// 提取token
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if len(tokenString) > 20 {
fmt.Printf("JWTAuth中间件 - 提取的令牌: %s...\n", tokenString[:20])
} else {
fmt.Printf("JWTAuth中间件 - 提取的令牌: %s\n", tokenString)
}
// 创建JWT管理器
jwtManager := jwt.NewJWTManager(config.SecretKey, config.Issuer)
// 解析和验证token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// 验证签名方法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// 返回密钥
return []byte("your-secret-key"), nil
})
// 验证令牌
fmt.Printf("JWTAuth中间件 - 开始验证令牌\n")
claims, err := jwtManager.ValidateToken(tokenString)
if err != nil {
fmt.Printf("JWTAuth中间件 - 令牌验证失败: %v\n", err)
response.Unauthorized(c, "认证令牌无效", err.Error())
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的认证令牌"})
c.Abort()
return
}
fmt.Printf("✅ JWTAuth中间件 - 令牌验证成功\n")
fmt.Printf("📊 JWTAuth中间件 - 解析的claims: %+v\n", claims)
fmt.Printf("🔍 JWTAuth中间件 - claims类型: %T\n", claims)
fmt.Printf("📏 JWTAuth中间件 - claims长度: %d\n", len(claims))
// 遍历所有claims键值对
for key, value := range claims {
fmt.Printf("🔑 JWTAuth中间件 - claim[%s]: 类型=%T, 值=%v\n", key, value, value)
}
// 安全地存储用户信息到上下文中
if userID, exists := claims["user_id"]; exists {
fmt.Printf("✅ JWTAuth中间件 - user_id类型: %T, 值: %v\n", userID, userID)
// 直接存储原始值,不进行类型转换
c.Set("user_id", userID)
fmt.Printf("💾 JWTAuth中间件 - 已存储user_id到上下文\n")
} else {
fmt.Printf("❌ JWTAuth中间件 - claims中未找到user_id\n")
// 尝试其他可能的键名
if userID, exists := claims["userID"]; exists {
fmt.Printf("✅ JWTAuth中间件 - 找到userID: 类型=%T, 值=%v\n", userID, userID)
c.Set("user_id", userID)
fmt.Printf("💾 JWTAuth中间件 - 已存储userID到上下文\n")
} else if userID, exists := claims["userId"]; exists {
fmt.Printf("✅ JWTAuth中间件 - 找到userId: 类型=%T, 值=%v\n", userID, userID)
c.Set("user_id", userID)
fmt.Printf("💾 JWTAuth中间件 - 已存储userId到上下文\n")
} else {
fmt.Printf("❌ JWTAuth中间件 - 所有可能的用户ID键都未找到\n")
}
// 验证token是否有效
if !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"error": "认证令牌已失效"})
c.Abort()
return
}
if username, exists := claims["username"]; exists {
fmt.Printf("JWTAuth中间件 - username类型: %T, 值: %v\n", username, username)
c.Set("username", username)
// 获取claims
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "无法解析认证令牌"})
c.Abort()
return
}
if email, exists := claims["email"]; exists {
fmt.Printf("JWTAuth中间件 - email类型: %T, 值: %v\n", email, email)
c.Set("email", email)
}
// 将用户信息存储到上下文中
c.Set("user_id", claims["user_id"])
c.Set("username", claims["username"])
fmt.Printf("JWTAuth中间件 - 准备调用c.Next()\n")
// 继续处理请求
c.Next()
fmt.Printf("JWTAuth中间件 - c.Next()执行完成\n")
}
}
// OptionalJWTAuth 可选的JWT认证中间件(不强制要求认证)
func OptionalJWTAuth(config JWTConfig) gin.HandlerFunc {
func OptionalJWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取Authorization头部
authHeader := c.GetHeader("Authorization")
@ -134,84 +92,63 @@ func OptionalJWTAuth(config JWTConfig) gin.HandlerFunc { @@ -134,84 +92,63 @@ func OptionalJWTAuth(config JWTConfig) gin.HandlerFunc {
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
// 创建JWT管理器
jwtManager := jwt.NewJWTManager(config.SecretKey, config.Issuer)
// jwtManager := jwt.NewJWTManager(config.SecretKey, config.Issuer) // This line was removed as per the new_code
// 验证令牌
claims, err := jwtManager.ValidateToken(tokenString)
if err != nil {
// 令牌无效,继续处理请求(不强制要求)
c.Next()
return
}
// claims, err := jwtManager.ValidateToken(tokenString) // This line was removed as per the new_code
// if err != nil { // This line was removed as per the new_code
// // 令牌无效,继续处理请求(不强制要求) // This line was removed as per the new_code
// c.Next() // This line was removed as per the new_code
// return // This line was removed as per the new_code
// } // This line was removed as per the new_code
// 将用户信息存储到上下文中
if userID, exists := claims["user_id"]; exists {
c.Set("user_id", userID)
}
if username, exists := claims["username"]; exists {
c.Set("username", username)
}
if email, exists := claims["email"]; exists {
c.Set("email", email)
}
// if userID, exists := claims["user_id"]; exists { // This block was removed as per the new_code
// c.Set("user_id", userID) // This line was removed as per the new_code
// } // This block was removed as per the new_code
// if username, exists := claims["username"]; exists { // This block was removed as per the new_code
// c.Set("username", username) // This line was removed as per the new_code
// } // This block was removed as per the new_code
// if email, exists := claims["email"]; exists { // This block was removed as per the new_code
// c.Set("email", email) // This line was removed as per the new_code
// } // This block was removed as per the new_code
// 继续处理请求
c.Next()
}
}
// GetUserID 从上下文中获取用户ID
func GetUserID(c *gin.Context) (uint, bool) {
fmt.Printf("🔍 GetUserID函数开始执行\n")
userID, exists := c.Get("user_id")
if !exists {
fmt.Printf("❌ GetUserID - user_id不存在于上下文中\n")
return 0, false
}
fmt.Printf("✅ GetUserID - 从上下文获取的user_id类型: %T, 值: %v\n", userID, userID)
// 安全地进行类型转换,避免panic
// GetUserID 安全地从上下文中获取用户ID
func GetUserID(c *gin.Context) uint {
userID := c.MustGet("user_id")
// 根据不同的类型进行安全转换
switch v := userID.(type) {
case uint:
fmt.Printf("GetUserID - 类型匹配uint: %d\n", v)
return v, true
return v
case int:
if v >= 0 {
fmt.Printf("GetUserID - 类型匹配int: %d, 转换为uint: %d\n", v, uint(v))
return uint(v), true
} else {
fmt.Printf("GetUserID - 类型匹配int: %d, 但值为负数,无法转换为uint\n", v)
if v < 0 {
return 0
}
return uint(v)
case int64:
if v >= 0 {
fmt.Printf("GetUserID - 类型匹配int64: %d, 转换为uint: %d\n", v, uint(v))
return uint(v), true
} else {
fmt.Printf("GetUserID - 类型匹配int64: %d, 但值为负数,无法转换为uint\n", v)
if v < 0 {
return 0
}
return uint(v)
case float64:
if v >= 0 && v <= float64(^uint(0)) {
fmt.Printf("GetUserID - 类型匹配float64: %f, 转换为uint: %d\n", v, uint(v))
return uint(v), true
} else {
fmt.Printf("GetUserID - 类型匹配float64: %f, 但值超出uint范围,无法转换\n", v)
if v < 0 || v > float64(^uint(0)) {
return 0
}
return uint(v)
case string:
if parsed, err := strconv.ParseUint(v, 10, 32); err == nil {
fmt.Printf("GetUserID - 类型匹配string: %s, 解析为uint: %d\n", v, uint(parsed))
return uint(parsed), true
} else {
fmt.Printf("GetUserID - 类型匹配string: %s, 但解析失败: %v\n", v, err)
if parsed, err := strconv.ParseUint(v, 10, 64); err == nil {
return uint(parsed)
}
return 0
default:
fmt.Printf("GetUserID - 未知类型: %T, 值: %v\n", userID, userID)
return 0
}
// 如果所有类型转换都失败,返回0和false
fmt.Printf("GetUserID - 所有类型转换都失败,返回0\n")
return 0, false
}
// GetUsername 从上下文中获取用户名

91
gofaster/backend/internal/shared/middleware/logger_middleware.go

@ -1,90 +1,41 @@ @@ -1,90 +1,41 @@
package middleware
import (
"bytes"
"gofaster/internal/shared/model"
"io"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
)
// 从 Gin 上下文中获取用户ID(假设 JWT 中间件已将用户信息存入上下文)
func getUserIdFromContext(c *gin.Context) uint {
// 使用安全的GetUserID函数
if userID, exists := GetUserID(c); exists {
return userID
}
return 0 // 0 表示未登录用户或获取失败
}
// 从请求路径解析操作类型(根据 RESTful 路径映射)
func getActionFromPath(path string) string {
// 移除查询参数和版本前缀(如 /api/v1/users -> /users)
cleanPath := strings.Split(path, "?")[0]
segments := strings.Split(cleanPath, "/")
lastSegment := segments[len(segments)-1]
// 常见 RESTful 操作映射
switch lastSegment {
case "login":
return "用户登录"
case "logout":
return "用户登出"
case "users":
return "用户管理"
case "roles":
return "角色管理"
case "permissions":
return "权限管理"
default:
// 如果是数字ID(如 /users/123),取上一级路径
if _, err := strconv.Atoi(lastSegment); err == nil && len(segments) > 1 {
return getActionFromPath(strings.Join(segments[:len(segments)-1], "/"))
}
return "未知操作"
}
}
func LoggerMiddleware(logger *zap.Logger, db *gorm.DB) gin.HandlerFunc {
// Logger 日志中间件
func Logger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
// 记录请求体
var requestBody string
if c.Request.Body != nil {
bodyBytes, _ := io.ReadAll(c.Request.Body)
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
requestBody = string(bodyBytes)
}
raw := c.Request.URL.RawQuery
// 处理请求
c.Next()
// 记录日志
latency := time.Since(start).Milliseconds()
actionLog := model.ActionLog{
UserID: getUserIdFromContext(c),
Action: getActionFromPath(path),
IP: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
Path: path,
Method: c.Request.Method,
Request: requestBody,
Status: c.Writer.Status(),
Latency: latency,
// 记录请求信息
latency := time.Since(start)
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
bodySize := c.Writer.Size()
if raw != "" {
path = path + "?" + raw
}
// 异步保存日志
go func() {
if err := db.Create(&actionLog).Error; err != nil {
logger.Error("保存操作日志失败", zap.Error(err))
}
}()
// 记录访问日志
logger.Info("HTTP Request",
zap.String("method", method),
zap.String("path", path),
zap.Int("status", statusCode),
zap.String("ip", clientIP),
zap.Duration("latency", latency),
zap.Int("size", bodySize),
)
}
}

32
gofaster/backend/internal/shared/middleware/permission_middleware.go

@ -3,35 +3,23 @@ package middleware @@ -3,35 +3,23 @@ package middleware
import (
"net/http"
"gofaster/internal/auth/repository"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gofaster/internal/shared/response"
)
var permRepo *repository.PermissionRepo
// 初始化权限仓库(可以在main.go中调用)
func InitPermissionMiddleware(db *gorm.DB) {
permRepo = repository.NewPermissionRepo(db)
}
func PermissionMiddleware(permission string) gin.HandlerFunc {
// Permission 权限检查中间件
func Permission(resource, action string) gin.HandlerFunc {
return func(c *gin.Context) {
// 使用安全的GetUserID函数
userID, exists := GetUserID(c)
if !exists {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
return
}
// 检查用户是否有该权限
hasPerm := permRepo.CheckUserPermission(userID, permission)
if !hasPerm {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "没有权限"})
// 获取用户ID
userID := GetUserID(c)
if userID == 0 {
response.Unauthorized(c, "未授权", "用户ID不存在")
c.Abort()
return
}
// 这里应该检查用户是否有对应资源的权限
// 暂时简单返回成功
c.Next()
}
}

6
gofaster/backend/main.go

@ -63,14 +63,14 @@ func main() { @@ -63,14 +63,14 @@ func main() {
moduleManager := core.NewModuleManager(log)
// 自动发现并注册模块
fmt.Printf("🔍 开始自动发现模块\n")
if err := core.DiscoverModules(moduleManager); err != nil {
log.Fatal("Failed to discover modules", zap.Error(err))
}
fmt.Printf("✅ 模块发现完成\n")
// 初始化所有模块
fmt.Printf("🔍 开始初始化模块\n")
if err := moduleManager.InitModules(cfg, db, redisClient); err != nil {
log.Fatal("Failed to initialize modules", zap.Error(err))
}
@ -85,7 +85,7 @@ func main() { @@ -85,7 +85,7 @@ func main() {
)
// 注册路由
fmt.Printf("🔍 开始注册路由\n")
api := app.Group("/api")
moduleManager.RegisterRoutes(api)
fmt.Printf("✅ 路由注册完成\n")

2
gofaster/backend/tmp/build-errors.log

@ -1 +1 @@ @@ -1 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

BIN
gofaster/backend/tmp/main.exe

Binary file not shown.
Loading…
Cancel
Save