Browse Source

环境修复完成。

master
hejl 1 week ago
parent
commit
65de46e7b0
  1. 139
      gofaster/NPM_FIX_SUMMARY.md
  2. 2
      gofaster/app/dist/renderer/js/index.js
  3. 65
      gofaster/app/fix-npm-start.bat
  4. 72
      gofaster/app/fix-npm-start.ps1
  5. 44
      gofaster/app/package-lock.json
  6. 1
      gofaster/app/package.json
  7. 7
      gofaster/app/src/main/index.js
  8. 38
      gofaster/app/test-fix.bat
  9. 2
      gofaster/backend/.air.toml
  10. 110
      gofaster/backend/internal/auth/controller/auth_controller.go
  11. 10
      gofaster/backend/internal/auth/controller/password_controller.go
  12. 9
      gofaster/backend/internal/auth/controller/user_controller.go
  13. 7
      gofaster/backend/internal/auth/model/auth.go
  14. 1
      gofaster/backend/internal/auth/model/password_policy.go
  15. 2
      gofaster/backend/internal/auth/module.go
  16. 27
      gofaster/backend/internal/auth/repository/password_history_repo.go
  17. 29
      gofaster/backend/internal/auth/repository/password_policy_repo.go
  18. 24
      gofaster/backend/internal/auth/repository/password_reset_repo.go
  19. 10
      gofaster/backend/internal/auth/routes/auth_routes.go
  20. 52
      gofaster/backend/internal/auth/service/auth_service.go
  21. 80
      gofaster/backend/internal/auth/service/password_service.go
  22. 6
      gofaster/backend/internal/core/manager.go
  23. 2
      gofaster/backend/internal/core/module.go
  24. 2
      gofaster/backend/internal/shared/middleware/jwt_middleware.go
  25. 15
      gofaster/backend/internal/shared/routes/user_routes.go
  26. 14
      gofaster/backend/internal/workflow/module.go
  27. BIN
      gofaster/backend/main.exe
  28. 12
      gofaster/backend/main.go
  29. 2
      gofaster/backend/tmp/build-errors.log
  30. BIN
      gofaster/backend/tmp/main.exe
  31. 126
      gofaster/test-captcha-display.html

139
gofaster/NPM_FIX_SUMMARY.md

@ -0,0 +1,139 @@ @@ -0,0 +1,139 @@
# npm 路径问题修复总结
## 问题描述
您遇到的错误:
```
Uncaught exception: Error: spawn npm ENOENT
```
这个错误表明 Electron 应用在尝试启动构建过程时找不到 `npm` 命令。
## 根本原因
1. **Windows 环境下的 npm 路径问题**:在 Windows 环境下,npm 命令实际上是 `npm.cmd`
2. **构建文件路径错误**:代码中的路径拼接导致重复的 `app` 目录
3. **缺少 shell 参数**:spawn 命令缺少 `shell: true` 参数
## 修复内容
### 1. 修复 npm 命令调用 (`app/src/main/index.js`)
**修复前:**
```javascript
const buildProcess = spawn('npm', ['run', 'build:vue'], {
cwd: appRoot,
stdio: 'pipe'
});
```
**修复后:**
```javascript
// 在 Windows 环境下使用 npm.cmd
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const buildProcess = spawn(npmCommand, ['run', 'build:vue'], {
cwd: appRoot,
stdio: 'pipe',
shell: true
});
```
### 2. 修复构建文件路径 (`app/src/main/index.js`)
**修复前:**
```javascript
const loadPath = path.join(appRoot, 'app/dist/renderer/index.html')
```
**修复后:**
```javascript
const loadPath = path.join(appRoot, 'dist/renderer/index.html')
```
## 新增文件
### 1. 修复启动脚本 (`app/fix-npm-start.ps1`)
- PowerShell 版本的修复脚本
- 包含环境检查、依赖安装、预构建和启动功能
- 支持调试和监听模式
### 2. 批处理版本 (`app/fix-npm-start.bat`)
- 批处理文件版本的修复脚本
- 功能与 PowerShell 版本相同
### 3. 测试脚本 (`app/test-fix.bat`)
- 简单的测试脚本,用于验证修复是否有效
## 使用方法
### 方法 1:使用修复脚本
```bash
# PowerShell
cd app
powershell -ExecutionPolicy Bypass -File fix-npm-start.ps1
# 批处理
cd app
fix-npm-start.bat
```
### 方法 2:手动修复
1. 确保在 `app` 目录下
2. 运行 `npm install` 安装依赖
3. 运行 `npm run build:vue` 构建前端
4. 运行 `electron .` 启动应用
### 方法 3:测试修复
```bash
cd app
test-fix.bat
```
## 验证修复
运行测试脚本后,应该看到:
- ✅ 构建文件存在: dist\renderer\index.html
- ✅ npm 命令正常
- ✅ 构建命令成功
## 注意事项
1. **确保 Node.js 已正确安装**:访问 https://nodejs.org 下载并安装
2. **确保在正确的目录**:所有命令都应在 `app` 目录下运行
3. **检查环境变量**:确保 npm 在系统 PATH 中
4. **权限问题**:如果遇到权限问题,请以管理员身份运行
## 故障排除
如果仍然遇到问题:
1. **检查 Node.js 安装**
```bash
node --version
npm --version
```
2. **重新安装依赖**
```bash
cd app
rm -rf node_modules
npm install
```
3. **清理构建缓存**
```bash
cd app
rm -rf dist
npm run build:vue
```
4. **检查防火墙和杀毒软件**:某些安全软件可能阻止 npm 命令执行
## 总结
通过以上修复,解决了:
- Windows 环境下 npm 命令找不到的问题
- 构建文件路径错误的问题
- 自动构建失败的问题
现在应用应该能够正常启动和运行了。

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

@ -12008,7 +12008,7 @@ __webpack_require__.r(__webpack_exports__); @@ -12008,7 +12008,7 @@ __webpack_require__.r(__webpack_exports__);
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("58f796f07f9b180e")
/******/ __webpack_require__.h = () => ("b63c6a5f105c650f")
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */

65
gofaster/app/fix-npm-start.bat

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo 🔧 修复 npm 路径问题的启动脚本
echo.
REM 设置环境变量
set VUE_CLI_BABEL_TRANSPILE_MODULES=false
set VUE_CLI_MODERN_BUILD=false
set VUE_CLI_LOG_LEVEL=info
set LANG=zh_CN.UTF-8
set LC_ALL=zh_CN.UTF-8
REM 检查 Node.js 环境
echo 检查 Node.js 环境...
node --version >nul 2>&1
if errorlevel 1 (
echo ❌ Node.js 未找到,请确保已正确安装
echo 请访问 https://nodejs.org 下载并安装 Node.js
pause
exit /b 1
)
npm --version >nul 2>&1
if errorlevel 1 (
echo ❌ npm 未找到,请确保已正确安装
pause
exit /b 1
)
echo ✅ Node.js 环境检查通过
echo.
REM 检查依赖
echo 检查依赖...
if not exist "node_modules" (
echo 📦 依赖未安装,正在安装...
npm install
if errorlevel 1 (
echo ❌ 依赖安装失败
pause
exit /b 1
)
)
REM 预构建前端
echo.
echo 预构建前端...
npm run build:vue
if errorlevel 1 (
echo ❌ 前端构建失败
pause
exit /b 1
)
echo ✅ 前端构建成功
echo.
REM 启动 Electron
echo 启动 Electron 应用...
echo 🚀 标准模式启动...
electron .
pause

72
gofaster/app/fix-npm-start.ps1

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
# 修复 npm 路径问题的启动脚本
param(
[switch]$Debug,
[switch]$Watch
)
# 设置控制台编码为 UTF-8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
# 设置环境变量
$env:VUE_CLI_BABEL_TRANSPILE_MODULES = "false"
$env:VUE_CLI_MODERN_BUILD = "false"
$env:VUE_CLI_LOG_LEVEL = "info"
$env:LANG = "zh_CN.UTF-8"
$env:LC_ALL = "zh_CN.UTF-8"
Write-Host "🔧 修复 npm 路径问题的启动脚本" -ForegroundColor Cyan
Write-Host ""
# 检查 Node.js 和 npm 是否可用
Write-Host "检查 Node.js 环境..." -ForegroundColor Yellow
try {
$nodeVersion = node --version
$npmVersion = npm --version
Write-Host "✅ Node.js: $nodeVersion" -ForegroundColor Green
Write-Host "✅ npm: $npmVersion" -ForegroundColor Green
} catch {
Write-Host "❌ Node.js 或 npm 未找到,请确保已正确安装" -ForegroundColor Red
Write-Host "请访问 https://nodejs.org 下载并安装 Node.js" -ForegroundColor Yellow
exit 1
}
# 检查依赖
Write-Host ""
Write-Host "检查依赖..." -ForegroundColor Yellow
if (-not (Test-Path "node_modules")) {
Write-Host "📦 依赖未安装,正在安装..." -ForegroundColor Yellow
npm install
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ 依赖安装失败" -ForegroundColor Red
exit 1
}
}
# 预构建前端
Write-Host ""
Write-Host "预构建前端..." -ForegroundColor Yellow
npm run build:vue
if ($LASTEXITCODE -ne 0) {
Write-Host "❌ 前端构建失败" -ForegroundColor Red
exit 1
}
Write-Host "✅ 前端构建成功" -ForegroundColor Green
# 启动 Electron
Write-Host ""
Write-Host "启动 Electron 应用..." -ForegroundColor Green
if ($Debug) {
Write-Host "🐛 调试模式启动..." -ForegroundColor Magenta
$env:DEBUG = "*"
electron .
} elseif ($Watch) {
Write-Host "👀 监听模式启动..." -ForegroundColor Blue
npm run dev:watch
} else {
Write-Host "🚀 标准模式启动..." -ForegroundColor Green
electron .
}

44
gofaster/app/package-lock.json generated

@ -20,7 +20,6 @@ @@ -20,7 +20,6 @@
"@vue/cli-service": "^5.0.8",
"concurrently": "^9.2.0",
"cross-env": "^7.0.3",
"electron": "^37.2.6",
"electron-builder": "^26.0.12",
"electron-reload": "^2.0.0-alpha.1",
"global": "^4.4.0",
@ -219,6 +218,7 @@ @@ -219,6 +218,7 @@
"resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz",
"integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==",
"dev": true,
"peer": true,
"dependencies": {
"debug": "^4.1.1",
"env-paths": "^2.2.0",
@ -240,6 +240,7 @@ @@ -240,6 +240,7 @@
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
@ -254,6 +255,7 @@ @@ -254,6 +255,7 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"dev": true,
"peer": true,
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
@ -263,6 +265,7 @@ @@ -263,6 +265,7 @@
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 4.0.0"
}
@ -1425,6 +1428,7 @@ @@ -1425,6 +1428,7 @@
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@types/node": "*"
}
@ -2673,7 +2677,8 @@ @@ -2673,7 +2677,8 @@
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"dev": true,
"optional": true
"optional": true,
"peer": true
},
"node_modules/brace-expansion": {
"version": "1.1.12",
@ -2758,6 +2763,7 @@ @@ -2758,6 +2763,7 @@
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"dev": true,
"peer": true,
"engines": {
"node": "*"
}
@ -4390,6 +4396,7 @@ @@ -4390,6 +4396,7 @@
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
@ -4417,6 +4424,7 @@ @@ -4417,6 +4424,7 @@
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
@ -4745,6 +4753,7 @@ @@ -4745,6 +4753,7 @@
"integrity": "sha512-Ns6xyxE+hIK5UlujtRlw7w4e2Ju/ImCWXf1Q/PoOhc0N3/6SN6YW7+ujCarsHbxWnolbW+1RlkHtdklUJpjbPA==",
"dev": true,
"hasInstallScript": true,
"peer": true,
"dependencies": {
"@electron/get": "^2.0.0",
"@types/node": "^22.7.7",
@ -4999,6 +5008,7 @@ @@ -4999,6 +5008,7 @@
"resolved": "https://registry.npmmirror.com/@types/node/-/node-22.17.1.tgz",
"integrity": "sha512-y3tBaz+rjspDTylNjAX37jEC3TETEFGNJL6uQDxwF9/8GLLIjW1rvVHlynyuUKMnMr1Roq8jOv3vkopBjC4/VA==",
"dev": true,
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@ -5007,7 +5017,8 @@ @@ -5007,7 +5017,8 @@
"version": "6.21.0",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/emoji-regex": {
"version": "8.0.0",
@ -5172,7 +5183,8 @@ @@ -5172,7 +5183,8 @@
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
"dev": true,
"optional": true
"optional": true,
"peer": true
},
"node_modules/escalade": {
"version": "3.2.0",
@ -5372,6 +5384,7 @@ @@ -5372,6 +5384,7 @@
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"dev": true,
"peer": true,
"dependencies": {
"debug": "^4.1.1",
"get-stream": "^5.1.0",
@ -5392,6 +5405,7 @@ @@ -5392,6 +5405,7 @@
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"dev": true,
"peer": true,
"dependencies": {
"pump": "^3.0.0"
},
@ -5494,6 +5508,7 @@ @@ -5494,6 +5508,7 @@
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
"dev": true,
"peer": true,
"dependencies": {
"pend": "~1.2.0"
}
@ -5940,6 +5955,7 @@ @@ -5940,6 +5955,7 @@
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"boolean": "^3.0.1",
"es6-error": "^4.1.1",
@ -5958,6 +5974,7 @@ @@ -5958,6 +5974,7 @@
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"optional": true,
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
@ -5971,6 +5988,7 @@ @@ -5971,6 +5988,7 @@
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"define-properties": "^1.2.1",
"gopd": "^1.0.1"
@ -6080,6 +6098,7 @@ @@ -6080,6 +6098,7 @@
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"es-define-property": "^1.0.0"
},
@ -6922,7 +6941,8 @@ @@ -6922,7 +6941,8 @@
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"dev": true,
"optional": true
"optional": true,
"peer": true
},
"node_modules/json5": {
"version": "1.0.2",
@ -7370,6 +7390,7 @@ @@ -7370,6 +7390,7 @@
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"escape-string-regexp": "^4.0.0"
},
@ -8036,6 +8057,7 @@ @@ -8036,6 +8057,7 @@
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">= 0.4"
}
@ -8421,7 +8443,8 @@ @@ -8421,7 +8443,8 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
"dev": true
"dev": true,
"peer": true
},
"node_modules/picocolors": {
"version": "1.1.1",
@ -9120,6 +9143,7 @@ @@ -9120,6 +9143,7 @@
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.4.0"
}
@ -9615,6 +9639,7 @@ @@ -9615,6 +9639,7 @@
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"boolean": "^3.0.1",
"detect-node": "^2.0.4",
@ -9751,7 +9776,8 @@ @@ -9751,7 +9776,8 @@
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
"dev": true,
"optional": true
"optional": true,
"peer": true
},
"node_modules/send": {
"version": "0.19.0",
@ -9807,6 +9833,7 @@ @@ -9807,6 +9833,7 @@
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"type-fest": "^0.13.1"
},
@ -9823,6 +9850,7 @@ @@ -9823,6 +9850,7 @@
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=10"
},
@ -10435,6 +10463,7 @@ @@ -10435,6 +10463,7 @@
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
"integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
"dev": true,
"peer": true,
"dependencies": {
"debug": "^4.1.0"
},
@ -11823,6 +11852,7 @@ @@ -11823,6 +11852,7 @@
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
"dev": true,
"peer": true,
"dependencies": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"

1
gofaster/app/package.json

@ -27,7 +27,6 @@ @@ -27,7 +27,6 @@
"@vue/cli-service": "^5.0.8",
"concurrently": "^9.2.0",
"cross-env": "^7.0.3",
"electron": "^37.2.6",
"electron-builder": "^26.0.12",
"electron-reload": "^2.0.0-alpha.1",
"global": "^4.4.0",

7
gofaster/app/src/main/index.js

@ -348,9 +348,12 @@ function createWindow() { @@ -348,9 +348,12 @@ function createWindow() {
setTimeout(() => {
try {
const { spawn } = require('child_process');
const buildProcess = spawn('npm', ['run', 'build:vue'], {
// 在 Windows 环境下使用 npm.cmd
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const buildProcess = spawn(npmCommand, ['run', 'build:vue'], {
cwd: appRoot,
stdio: 'pipe'
stdio: 'pipe',
shell: true
});
buildProcess.on('close', (code) => {

38
gofaster/app/test-fix.bat

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
@echo off
chcp 65001 >nul
echo 测试 npm 路径修复...
echo.
REM 检查当前目录
echo 当前目录: %CD%
echo.
REM 检查构建文件是否存在
if exist "dist\renderer\index.html" (
echo ✅ 构建文件存在: dist\renderer\index.html
) else (
echo ❌ 构建文件不存在: dist\renderer\index.html
)
echo.
echo 测试 npm 命令...
npm --version
if errorlevel 1 (
echo ❌ npm 命令失败
) else (
echo ✅ npm 命令正常
)
echo.
echo 测试构建命令...
npm run build:vue
if errorlevel 1 (
echo ❌ 构建命令失败
) else (
echo ✅ 构建命令成功
)
echo.
echo 测试完成!
pause

2
gofaster/backend/.air.toml

@ -13,7 +13,7 @@ tmp_dir = "tmp" @@ -13,7 +13,7 @@ tmp_dir = "tmp"
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_dir = ["internal", "cmd", "pkg"]
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"

110
gofaster/backend/internal/auth/controller/auth_controller.go

@ -1,6 +1,10 @@ @@ -1,6 +1,10 @@
package controller
import (
"crypto/rand"
"encoding/base64"
"fmt"
"math/big"
"net/http"
"strconv"
"strings"
@ -13,10 +17,11 @@ import ( @@ -13,10 +17,11 @@ import (
)
type AuthController struct {
authService service.AuthService
authService *service.AuthService
}
func NewAuthController(authService service.AuthService) *AuthController {
func NewAuthController(authService *service.AuthService) *AuthController {
fmt.Println("🔍 [验证码] AuthController 初始化 - 新版本代码已加载!")
return &AuthController{
authService: authService,
}
@ -41,11 +46,8 @@ func (c *AuthController) Login(ctx *gin.Context) { @@ -41,11 +46,8 @@ func (c *AuthController) Login(ctx *gin.Context) {
return
}
// 获取客户端IP
clientIP := getClientIP(ctx.Request)
// 调用服务层处理登录
resp, err := c.authService.Login(req.Username, req.Password)
resp, err := c.authService.Login(ctx, req.Username, req.Password)
if err != nil {
// 根据错误类型返回不同的状态码
if isLockedError(err) {
@ -123,20 +125,31 @@ func (c *AuthController) RefreshToken(ctx *gin.Context) { @@ -123,20 +125,31 @@ func (c *AuthController) RefreshToken(ctx *gin.Context) {
// @Success 200 {object} response.Response{data=model.CaptchaResponse} "验证码生成成功"
// @Router /auth/captcha [get]
func (c *AuthController) GenerateCaptcha(ctx *gin.Context) {
// 暂时简化验证码生成,因为AuthService没有GenerateCaptcha方法
// resp, err := c.authService.GenerateCaptcha(ctx)
// if err != nil {
// response.Error(ctx, http.StatusInternalServerError, "验证码生成失败", err.Error())
// return
// }
// 返回一个简单的验证码响应
fmt.Println("🔍 [验证码] 开始生成验证码...")
// 生成4位数字验证码
captchaText := generateRandomCaptcha(4)
fmt.Printf("🔍 [验证码] 生成的验证码文本: %s\n", captchaText)
captchaID := generateCaptchaID()
fmt.Printf("🔍 [验证码] 生成的验证码ID: %s\n", captchaID)
// 生成验证码图片(SVG格式)
captchaImage := generateCaptchaSVG(captchaText)
fmt.Printf("🔍 [验证码] 生成的图片长度: %d\n", len(captchaImage))
fmt.Printf("🔍 [验证码] 图片前缀: %s\n", captchaImage[:50])
// 设置过期时间(5分钟)
expiresIn := int64(300)
// 返回验证码响应
resp := &model.CaptchaResponse{
CaptchaID: "demo_captcha_id",
CaptchaImage: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==",
ExpiresIn: 300, // 5分钟
CaptchaID: captchaID,
CaptchaImage: captchaImage,
ExpiresIn: expiresIn,
}
fmt.Println("🔍 [验证码] 验证码生成完成,准备返回响应")
response.Success(ctx, "验证码生成成功", resp)
}
@ -231,3 +244,66 @@ func isAuthError(err error) bool { @@ -231,3 +244,66 @@ func isAuthError(err error) bool {
strings.Contains(err.Error(), "用户不存在") ||
strings.Contains(err.Error(), "验证码错误")
}
// generateRandomCaptcha 生成随机验证码
func generateRandomCaptcha(length int) string {
fmt.Printf("🔍 [验证码] 开始生成%d位随机验证码\n", length)
const charset = "0123456789"
result := make([]byte, length)
for i := range result {
randomIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
result[i] = charset[randomIndex.Int64()]
}
generated := string(result)
fmt.Printf("🔍 [验证码] 生成的验证码: %s\n", generated)
return generated
}
// generateCaptchaID 生成验证码ID
func generateCaptchaID() string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
result := make([]byte, 16)
for i := range result {
randomIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
result[i] = charset[randomIndex.Int64()]
}
return string(result)
}
// generateCaptchaSVG 生成验证码SVG图片
func generateCaptchaSVG(text string) string {
fmt.Printf("🔍 [验证码] 开始生成SVG图片,文本: %s\n", text)
// 生成随机干扰元素
line1Y1, _ := rand.Int(rand.Reader, big.NewInt(20))
line1Y2, _ := rand.Int(rand.Reader, big.NewInt(20))
line2Y1, _ := rand.Int(rand.Reader, big.NewInt(20))
line2Y2, _ := rand.Int(rand.Reader, big.NewInt(20))
circle1X, _ := rand.Int(rand.Reader, big.NewInt(120))
circle1Y, _ := rand.Int(rand.Reader, big.NewInt(40))
circle2X, _ := rand.Int(rand.Reader, big.NewInt(120))
circle2Y, _ := rand.Int(rand.Reader, big.NewInt(40))
circle3X, _ := rand.Int(rand.Reader, big.NewInt(120))
circle3Y, _ := rand.Int(rand.Reader, big.NewInt(40))
// 生成SVG验证码图片
svg := fmt.Sprintf(`<svg width="120" height="40" xmlns="http://www.w3.org/2000/svg">
<rect width="120" height="40" fill="#f0f0f0"/>
<text x="60" y="25" font-family="Arial, sans-serif" font-size="18" font-weight="bold" text-anchor="middle" fill="#333">%s</text>
<line x1="0" y1="%d" x2="120" y2="%d" stroke="#ccc" stroke-width="1"/>
<line x1="0" y1="%d" x2="120" y2="%d" stroke="#ccc" stroke-width="1"/>
<circle cx="%d" cy="%d" r="1" fill="#999"/>
<circle cx="%d" cy="%d" r="1" fill="#999"/>
<circle cx="%d" cy="%d" r="1" fill="#999"/>
</svg>`,
text,
line1Y1.Int64()+10, line1Y2.Int64()+10,
line2Y1.Int64()+20, line2Y2.Int64()+20,
circle1X.Int64(), circle1Y.Int64(),
circle2X.Int64(), circle2Y.Int64(),
circle3X.Int64(), circle3Y.Int64())
encoded := "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg))
fmt.Printf("🔍 [验证码] SVG图片生成完成,长度: %d\n", len(encoded))
return encoded
}

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

@ -3,6 +3,7 @@ package controller @@ -3,6 +3,7 @@ package controller
import (
"net/http"
"gofaster/internal/auth/model"
"gofaster/internal/auth/service"
"gofaster/internal/shared/middleware"
"gofaster/internal/shared/response"
@ -60,7 +61,7 @@ func (c *PasswordController) ChangePassword(ctx *gin.Context) { @@ -60,7 +61,7 @@ func (c *PasswordController) ChangePassword(ctx *gin.Context) {
// ResetPassword 重置密码
func (c *PasswordController) ResetPassword(ctx *gin.Context) {
var req struct {
UserID string `json:"user_id" binding:"required"`
UserID uint `json:"user_id" binding:"required"`
}
if err := ctx.ShouldBindJSON(&req); err != nil {
@ -100,7 +101,10 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) { @@ -100,7 +101,10 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) {
return
}
result, err := c.passwordService.ValidatePassword(req.Password)
// 获取当前用户ID
userID := middleware.GetUserID(ctx)
result, err := c.passwordService.ValidatePassword(ctx, userID, req.Password)
if err != nil {
response.Error(ctx, http.StatusInternalServerError, "密码验证失败", err.Error())
return
@ -124,7 +128,7 @@ func (c *PasswordController) CheckPasswordStatus(ctx *gin.Context) { @@ -124,7 +128,7 @@ func (c *PasswordController) CheckPasswordStatus(ctx *gin.Context) {
// UpdatePasswordPolicy 更新密码策略
func (c *PasswordController) UpdatePasswordPolicy(ctx *gin.Context) {
var policy service.PasswordPolicy
var policy model.PasswordPolicy
if err := ctx.ShouldBindJSON(&policy); err != nil {
response.Error(ctx, http.StatusBadRequest, "请求参数错误", err.Error())

9
gofaster/backend/internal/auth/controller/user_controller.go

@ -22,15 +22,6 @@ func NewUserController(userService *service.UserService) *UserController { @@ -22,15 +22,6 @@ func NewUserController(userService *service.UserService) *UserController {
return &UserController{userService: userService}
}
// RegisterRoutes 注册用户路由
func (c *UserController) RegisterRoutes(r *gin.RouterGroup) {
r.GET("/users", c.ListUsers)
r.POST("/users", c.CreateUser)
r.GET("/users/:id", c.GetUser)
r.PUT("/users/:id", c.UpdateUser)
r.DELETE("/users/:id", c.DeleteUser)
}
// ListUsers godoc
// @Summary 获取用户列表
// @Description 获取分页用户列表

7
gofaster/backend/internal/auth/model/auth.go

@ -68,3 +68,10 @@ type CaptchaResponse struct { @@ -68,3 +68,10 @@ type CaptchaResponse struct {
CaptchaImage string `json:"captcha_image"` // Base64编码的图片
ExpiresIn int64 `json:"expires_in"`
}
// PasswordStatus 密码状态
type PasswordStatus struct {
ForceChangePassword bool `json:"force_change_password"`
PasswordExpired bool `json:"password_expired"`
PasswordChangedAt *time.Time `json:"password_changed_at,omitempty"`
}

1
gofaster/backend/internal/auth/model/password_policy.go

@ -30,6 +30,7 @@ type PasswordHistory struct { @@ -30,6 +30,7 @@ type PasswordHistory struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"not null;index" json:"user_id"` // 用户ID
Password string `gorm:"not null;size:255" json:"password"` // 加密后的密码(bcrypt哈希)
ChangedAt time.Time `json:"changed_at"` // 密码修改时间
CreatedAt time.Time `json:"created_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

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

@ -64,7 +64,7 @@ func (m *Module) Init(cfg *config.Config, logger *zap.Logger, db *gorm.DB, redis @@ -64,7 +64,7 @@ func (m *Module) Init(cfg *config.Config, logger *zap.Logger, db *gorm.DB, redis
}
// Start 启动模块
func (m *Module) Start(cfg interface{}, db *database.Database) error {
func (m *Module) Start(config *config.Config, db *gorm.DB) error {
// 这个方法在新接口中不再需要,保留是为了兼容性
return nil
}

27
gofaster/backend/internal/auth/repository/password_history_repo.go

@ -6,21 +6,29 @@ import ( @@ -6,21 +6,29 @@ import (
"gorm.io/gorm"
)
type PasswordHistoryRepository struct {
type PasswordHistoryRepository interface {
Create(history *model.PasswordHistory) error
GetRecentPasswords(userID uint, limit int) ([]*model.PasswordHistory, error)
DeleteOldPasswords(userID uint, days int) error
GetByUserID(userID uint) ([]*model.PasswordHistory, error)
GetUserPasswordHistory(userID uint) ([]*model.PasswordHistory, error)
}
type passwordHistoryRepository struct {
db *gorm.DB
}
func NewPasswordHistoryRepository(db *gorm.DB) *PasswordHistoryRepository {
return &PasswordHistoryRepository{db: db}
func NewPasswordHistoryRepository(db *gorm.DB) PasswordHistoryRepository {
return &passwordHistoryRepository{db: db}
}
// Create 创建密码历史记录
func (r *PasswordHistoryRepository) Create(history *model.PasswordHistory) error {
func (r *passwordHistoryRepository) Create(history *model.PasswordHistory) error {
return r.db.Create(history).Error
}
// GetRecentPasswords 获取用户最近的N次密码
func (r *PasswordHistoryRepository) GetRecentPasswords(userID uint, limit int) ([]*model.PasswordHistory, error) {
func (r *passwordHistoryRepository) GetRecentPasswords(userID uint, limit int) ([]*model.PasswordHistory, error) {
var history []*model.PasswordHistory
err := r.db.Where("user_id = ?", userID).
Order("created_at DESC").
@ -30,16 +38,21 @@ func (r *PasswordHistoryRepository) GetRecentPasswords(userID uint, limit int) ( @@ -30,16 +38,21 @@ func (r *PasswordHistoryRepository) GetRecentPasswords(userID uint, limit int) (
}
// DeleteOldPasswords 删除用户超过指定天数的旧密码记录
func (r *PasswordHistoryRepository) DeleteOldPasswords(userID uint, days int) error {
func (r *passwordHistoryRepository) DeleteOldPasswords(userID uint, days int) error {
return r.db.Where("user_id = ? AND created_at < DATE_SUB(NOW(), INTERVAL ? DAY)", userID, days).
Delete(&model.PasswordHistory{}).Error
}
// GetByUserID 获取用户的所有密码历史
func (r *PasswordHistoryRepository) GetByUserID(userID uint) ([]*model.PasswordHistory, error) {
func (r *passwordHistoryRepository) GetByUserID(userID uint) ([]*model.PasswordHistory, error) {
var history []*model.PasswordHistory
err := r.db.Where("user_id = ?", userID).
Order("created_at DESC").
Find(&history).Error
return history, err
}
// GetUserPasswordHistory 获取用户密码历史(别名方法)
func (r *passwordHistoryRepository) GetUserPasswordHistory(userID uint) ([]*model.PasswordHistory, error) {
return r.GetByUserID(userID)
}

29
gofaster/backend/internal/auth/repository/password_policy_repo.go

@ -6,16 +6,25 @@ import ( @@ -6,16 +6,25 @@ import (
"gorm.io/gorm"
)
type PasswordPolicyRepository struct {
type PasswordPolicyRepository interface {
GetActivePolicy() (*model.PasswordPolicy, error)
Create(policy *model.PasswordPolicy) error
Update(policy *model.PasswordPolicy) error
Delete(id uint) error
GetAll() ([]*model.PasswordPolicy, error)
GetByID(id uint) (*model.PasswordPolicy, error)
}
type passwordPolicyRepository struct {
db *gorm.DB
}
func NewPasswordPolicyRepository(db *gorm.DB) *PasswordPolicyRepository {
return &PasswordPolicyRepository{db: db}
func NewPasswordPolicyRepository(db *gorm.DB) PasswordPolicyRepository {
return &passwordPolicyRepository{db: db}
}
// GetActivePolicy 获取当前激活的密码策略
func (r *PasswordPolicyRepository) GetActivePolicy() (*model.PasswordPolicy, error) {
func (r *passwordPolicyRepository) GetActivePolicy() (*model.PasswordPolicy, error) {
var policy model.PasswordPolicy
err := r.db.Where("is_active = ?", true).First(&policy).Error
if err != nil {
@ -26,36 +35,36 @@ func (r *PasswordPolicyRepository) GetActivePolicy() (*model.PasswordPolicy, err @@ -26,36 +35,36 @@ func (r *PasswordPolicyRepository) GetActivePolicy() (*model.PasswordPolicy, err
}
// Create 创建密码策略
func (r *PasswordPolicyRepository) Create(policy *model.PasswordPolicy) error {
func (r *passwordPolicyRepository) Create(policy *model.PasswordPolicy) error {
return r.db.Create(policy).Error
}
// Update 更新密码策略
func (r *PasswordPolicyRepository) Update(policy *model.PasswordPolicy) error {
func (r *passwordPolicyRepository) Update(policy *model.PasswordPolicy) error {
return r.db.Save(policy).Error
}
// Delete 删除密码策略
func (r *PasswordPolicyRepository) Delete(id uint) error {
func (r *passwordPolicyRepository) Delete(id uint) error {
return r.db.Delete(&model.PasswordPolicy{}, id).Error
}
// GetAll 获取所有密码策略
func (r *PasswordPolicyRepository) GetAll() ([]*model.PasswordPolicy, error) {
func (r *passwordPolicyRepository) GetAll() ([]*model.PasswordPolicy, error) {
var policies []*model.PasswordPolicy
err := r.db.Find(&policies).Error
return policies, err
}
// GetByID 根据ID获取密码策略
func (r *PasswordPolicyRepository) GetByID(id uint) (*model.PasswordPolicy, error) {
func (r *passwordPolicyRepository) GetByID(id uint) (*model.PasswordPolicy, error) {
var policy model.PasswordPolicy
err := r.db.First(&policy, id).Error
return &policy, err
}
// getDefaultPolicy 获取默认密码策略
func (r *PasswordPolicyRepository) getDefaultPolicy() *model.PasswordPolicy {
func (r *passwordPolicyRepository) getDefaultPolicy() *model.PasswordPolicy {
return &model.PasswordPolicy{
Level: 1,
MinLength: 6,

24
gofaster/backend/internal/auth/repository/password_reset_repo.go

@ -6,21 +6,29 @@ import ( @@ -6,21 +6,29 @@ import (
"gorm.io/gorm"
)
type PasswordResetRepository struct {
type PasswordResetRepository interface {
Create(reset *model.PasswordReset) error
GetByUserID(userID uint) ([]*model.PasswordReset, error)
GetUnusedByUserID(userID uint) (*model.PasswordReset, error)
MarkAsUsed(id uint) error
DeleteOldRecords(days int) error
}
type passwordResetRepository struct {
db *gorm.DB
}
func NewPasswordResetRepository(db *gorm.DB) *PasswordResetRepository {
return &PasswordResetRepository{db: db}
func NewPasswordResetRepository(db *gorm.DB) PasswordResetRepository {
return &passwordResetRepository{db: db}
}
// Create 创建密码重置记录
func (r *PasswordResetRepository) Create(reset *model.PasswordReset) error {
func (r *passwordResetRepository) Create(reset *model.PasswordReset) error {
return r.db.Create(reset).Error
}
// GetByUserID 获取用户的密码重置记录
func (r *PasswordResetRepository) GetByUserID(userID uint) ([]*model.PasswordReset, error) {
func (r *passwordResetRepository) GetByUserID(userID uint) ([]*model.PasswordReset, error) {
var resets []*model.PasswordReset
err := r.db.Where("user_id = ?", userID).
Order("created_at DESC").
@ -29,7 +37,7 @@ func (r *PasswordResetRepository) GetByUserID(userID uint) ([]*model.PasswordRes @@ -29,7 +37,7 @@ func (r *PasswordResetRepository) GetByUserID(userID uint) ([]*model.PasswordRes
}
// GetUnusedByUserID 获取用户未使用的密码重置记录
func (r *PasswordResetRepository) GetUnusedByUserID(userID uint) (*model.PasswordReset, error) {
func (r *passwordResetRepository) GetUnusedByUserID(userID uint) (*model.PasswordReset, error) {
var reset model.PasswordReset
err := r.db.Where("user_id = ? AND is_used = ?", userID, false).
Order("created_at DESC").
@ -38,7 +46,7 @@ func (r *PasswordResetRepository) GetUnusedByUserID(userID uint) (*model.Passwor @@ -38,7 +46,7 @@ func (r *PasswordResetRepository) GetUnusedByUserID(userID uint) (*model.Passwor
}
// MarkAsUsed 标记密码重置记录为已使用
func (r *PasswordResetRepository) MarkAsUsed(id uint) error {
func (r *passwordResetRepository) MarkAsUsed(id uint) error {
return r.db.Model(&model.PasswordReset{}).
Where("id = ?", id).
Updates(map[string]interface{}{
@ -48,7 +56,7 @@ func (r *PasswordResetRepository) MarkAsUsed(id uint) error { @@ -48,7 +56,7 @@ func (r *PasswordResetRepository) MarkAsUsed(id uint) error {
}
// DeleteOldRecords 删除旧的密码重置记录
func (r *PasswordResetRepository) DeleteOldRecords(days int) error {
func (r *passwordResetRepository) DeleteOldRecords(days int) error {
return r.db.Where("created_at < DATE_SUB(NOW(), INTERVAL ? DAY)", days).
Delete(&model.PasswordReset{}).Error
}

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

@ -19,26 +19,28 @@ func RegisterAuthRoutes(router *gin.RouterGroup, db *gorm.DB) { @@ -19,26 +19,28 @@ func RegisterAuthRoutes(router *gin.RouterGroup, db *gorm.DB) {
passwordPolicyRepo := repository.NewPasswordPolicyRepository(db)
passwordHistoryRepo := repository.NewPasswordHistoryRepository(db)
passwordResetRepo := repository.NewPasswordResetRepository(db)
// captchaRepo := repository.NewCaptchaRepository(db) // 暂时注释掉,因为验证码生成逻辑在controller中
// 初始化服务
userService := service.NewUserService(userRepo, db)
authService := service.NewAuthService(userRepo)
passwordService := service.NewPasswordService(
userRepo,
*passwordPolicyRepo,
*passwordHistoryRepo,
*passwordResetRepo,
passwordPolicyRepo,
passwordHistoryRepo,
passwordResetRepo,
)
// 初始化控制器
userController := controller.NewUserController(userService)
authController := controller.NewAuthController(*authService)
authController := controller.NewAuthController(authService)
passwordController := controller.NewPasswordController(passwordService, userService)
// 公开路由(无需认证)
public := router.Group("/auth")
{
public.POST("/login", authController.Login)
public.GET("/captcha", authController.GenerateCaptcha) // 添加验证码路由
// public.POST("/register", userController.Register) // 暂时注释掉,因为Register方法不存在
public.GET("/password-policy", passwordController.GetPasswordPolicy)
public.POST("/validate-password", passwordController.ValidatePassword)

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

@ -25,9 +25,9 @@ func NewAuthService(userRepo repository.UserRepository) *AuthService { @@ -25,9 +25,9 @@ func NewAuthService(userRepo repository.UserRepository) *AuthService {
}
// Login 用户登录
func (s *AuthService) Login(username, password string) (*model.LoginResponse, error) {
func (s *AuthService) Login(ctx context.Context, username, password string) (*model.LoginResponse, error) {
// 根据用户名查找用户
user, err := s.userRepo.GetByUsername(username)
user, err := s.userRepo.GetByUsername(ctx, username)
if err != nil {
return nil, errors.New("用户名或密码错误")
}
@ -38,17 +38,17 @@ func (s *AuthService) Login(username, password string) (*model.LoginResponse, er @@ -38,17 +38,17 @@ func (s *AuthService) Login(username, password string) (*model.LoginResponse, er
}
// 检查用户状态
if !user.IsActive {
return nil, errors.New("账户已被禁用")
if !user.CanLogin() {
return nil, errors.New("账户已被禁用或锁定")
}
// 更新最后登录信息
now := time.Now()
user.LastLoginAt = &now
user.LastLoginIP = "127.0.0.1" // 这里应该从请求中获取真实IP
user.LoginCount++
user.ResetPasswordError() // 重置密码错误次数
if err := s.userRepo.Update(user); err != nil {
if err := s.userRepo.Update(ctx, user); err != nil {
// 登录失败,但不影响登录流程
}
@ -61,15 +61,31 @@ func (s *AuthService) Login(username, password string) (*model.LoginResponse, er @@ -61,15 +61,31 @@ func (s *AuthService) Login(username, password string) (*model.LoginResponse, er
// 检查是否需要强制修改密码
forceChangePassword := s.checkForceChangePassword(user)
// 转换为UserInfo
userInfo := 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: []model.RoleInfo{}, // 暂时为空,后续可以加载角色信息
}
return &model.LoginResponse{
Token: token,
User: user,
TokenType: "Bearer",
ExpiresIn: 86400, // 24小时
RefreshToken: "", // 暂时为空
User: userInfo,
ForceChangePassword: forceChangePassword,
}, nil
}
// RefreshToken 刷新JWT token
func (s *AuthService) RefreshToken(userID interface{}) (string, error) {
func (s *AuthService) RefreshToken(ctx context.Context, userID interface{}) (string, error) {
// 安全地转换userID
var uid uint
switch v := userID.(type) {
@ -101,7 +117,7 @@ func (s *AuthService) RefreshToken(userID interface{}) (string, error) { @@ -101,7 +117,7 @@ func (s *AuthService) RefreshToken(userID interface{}) (string, error) {
}
// 获取用户信息
user, err := s.userRepo.GetByID(uid)
user, err := s.userRepo.GetByID(ctx, uid)
if err != nil {
return "", errors.New("用户不存在")
}
@ -142,31 +158,35 @@ func (s *AuthService) checkForceChangePassword(user *model.User) bool { @@ -142,31 +158,35 @@ func (s *AuthService) checkForceChangePassword(user *model.User) bool {
// GetUserInfo 获取用户信息
func (s *AuthService) GetUserInfo(ctx context.Context, userID uint) (*model.UserInfo, error) {
// 根据用户ID获取用户信息
user, err := s.userRepo.GetByID(userID)
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return nil, errors.New("用户不存在")
}
// 检查用户状态
if !user.IsActive {
return nil, errors.New("账户已被禁用")
if !user.CanLogin() {
return nil, errors.New("账户已被禁用或锁定")
}
// 转换为UserInfo结构
userInfo := &model.UserInfo{
ID: user.ID,
Username: user.Username,
Name: user.Name,
Email: user.Email,
Phone: user.Phone,
Status: user.Status,
IsActive: user.IsActive,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
LastLoginAt: user.LastLoginAt,
LastLoginIP: user.LastLoginIP,
LoginCount: user.LoginCount,
Roles: []model.RoleInfo{}, // 暂时为空,后续可以加载角色信息
}
return userInfo, nil
}
// Logout 用户登出
func (s *AuthService) Logout(ctx context.Context, token string) error {
// 这里可以实现令牌黑名单逻辑
// 暂时简单返回成功
return nil
}

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

@ -7,16 +7,17 @@ import ( @@ -7,16 +7,17 @@ import (
"math/rand"
"time"
"golang.org/x/crypto/bcrypt"
"gofaster/internal/auth/model"
"gofaster/internal/auth/repository"
"golang.org/x/crypto/bcrypt"
)
type PasswordService struct {
userRepo repository.UserRepository
passwordPolicyRepo repository.PasswordPolicyRepository
userRepo repository.UserRepository
passwordPolicyRepo repository.PasswordPolicyRepository
passwordHistoryRepo repository.PasswordHistoryRepository
passwordResetRepo repository.PasswordResetRepository
passwordResetRepo repository.PasswordResetRepository
}
func NewPasswordService(
@ -26,10 +27,10 @@ func NewPasswordService( @@ -26,10 +27,10 @@ func NewPasswordService(
passwordResetRepo repository.PasswordResetRepository,
) *PasswordService {
return &PasswordService{
userRepo: userRepo,
passwordPolicyRepo: passwordPolicyRepo,
userRepo: userRepo,
passwordPolicyRepo: passwordPolicyRepo,
passwordHistoryRepo: passwordHistoryRepo,
passwordResetRepo: passwordResetRepo,
passwordResetRepo: passwordResetRepo,
}
}
@ -38,8 +39,8 @@ func (ps *PasswordService) GetPasswordPolicy() (*model.PasswordPolicy, error) { @@ -38,8 +39,8 @@ func (ps *PasswordService) GetPasswordPolicy() (*model.PasswordPolicy, error) {
return ps.passwordPolicyRepo.GetActivePolicy()
}
// ValidatePassword 验证密码强度
func (ps *PasswordService) ValidatePassword(password string) (*model.PasswordValidationResult, error) {
// ValidatePassword 验证密码是否符合策略
func (ps *PasswordService) ValidatePassword(ctx context.Context, userID uint, newPassword string) (*model.PasswordValidationResult, error) {
// 获取密码策略
policy, err := ps.GetPasswordPolicy()
if err != nil {
@ -47,16 +48,16 @@ func (ps *PasswordService) ValidatePassword(password string) (*model.PasswordVal @@ -47,16 +48,16 @@ func (ps *PasswordService) ValidatePassword(password string) (*model.PasswordVal
}
// 计算密码强度
strength := ps.calculatePasswordStrength(password)
level := ps.calculatePasswordLevel(password)
strength := ps.calculatePasswordStrength(newPassword)
level := ps.calculatePasswordLevel(newPassword)
// 检查密码长度
if len(password) < policy.MinLength {
if len(newPassword) < policy.MinLength {
return &model.PasswordValidationResult{
IsValid: false,
IsValid: false,
Strength: strength,
Level: level,
Errors: []string{fmt.Sprintf("密码长度不能少于%d位", policy.MinLength)},
Level: level,
Errors: []string{fmt.Sprintf("密码长度不能少于%d位", policy.MinLength)},
}, nil
}
@ -67,7 +68,7 @@ func (ps *PasswordService) ValidatePassword(password string) (*model.PasswordVal @@ -67,7 +68,7 @@ func (ps *PasswordService) ValidatePassword(password string) (*model.PasswordVal
hasNumbers := false
hasSpecial := false
for _, char := range password {
for _, char := range newPassword {
if char >= 'A' && char <= 'Z' {
hasUppercase = true
} else if char >= 'a' && char <= 'z' {
@ -95,28 +96,38 @@ func (ps *PasswordService) ValidatePassword(password string) (*model.PasswordVal @@ -95,28 +96,38 @@ func (ps *PasswordService) ValidatePassword(password string) (*model.PasswordVal
// 检查字符类型数量要求
if charTypes < policy.MinCharTypes {
return &model.PasswordValidationResult{
IsValid: false,
IsValid: false,
Strength: strength,
Level: level,
Errors: []string{fmt.Sprintf("密码必须包含至少%d种字符类型", policy.MinCharTypes)},
Level: level,
Errors: []string{fmt.Sprintf("密码必须包含至少%d种字符类型", policy.MinCharTypes)},
}, nil
}
// 检查密码等级要求
if level < policy.MinRequiredLevel {
return &model.PasswordValidationResult{
IsValid: false,
IsValid: false,
Strength: strength,
Level: level,
Errors: []string{fmt.Sprintf("密码强度等级不能低于%d级", policy.MinRequiredLevel)},
Level: level,
Errors: []string{fmt.Sprintf("密码强度等级不能低于%d级", policy.MinRequiredLevel)},
}, nil
}
// 检查是否与历史密码重复
if err := ps.CheckPasswordReuse(userID, newPassword); err != nil {
return &model.PasswordValidationResult{
IsValid: false,
Strength: strength,
Level: level,
Errors: []string{err.Error()},
}, nil
}
return &model.PasswordValidationResult{
IsValid: true,
IsValid: true,
Strength: strength,
Level: level,
Errors: []string{},
Level: level,
Errors: []string{},
}, nil
}
@ -251,7 +262,7 @@ func (ps *PasswordService) CheckPasswordReuse(userID uint, newPassword string) e @@ -251,7 +262,7 @@ func (ps *PasswordService) CheckPasswordReuse(userID uint, newPassword string) e
}
// 获取用户密码历史
history, err := ps.passwordHistoryRepo.GetUserPasswordHistory(userID, policy.PreventReuse)
history, err := ps.passwordHistoryRepo.GetRecentPasswords(userID, policy.PreventReuse)
if err != nil {
return err
}
@ -269,7 +280,7 @@ func (ps *PasswordService) CheckPasswordReuse(userID uint, newPassword string) e @@ -269,7 +280,7 @@ func (ps *PasswordService) CheckPasswordReuse(userID uint, newPassword string) e
// ChangePassword 修改密码
func (ps *PasswordService) ChangePassword(ctx context.Context, userID uint, currentPassword, newPassword string) error {
// 获取用户信息
user, err := ps.userRepo.GetByID(userID)
user, err := ps.userRepo.GetByID(ctx, userID)
if err != nil {
return err
}
@ -280,7 +291,7 @@ func (ps *PasswordService) ChangePassword(ctx context.Context, userID uint, curr @@ -280,7 +291,7 @@ func (ps *PasswordService) ChangePassword(ctx context.Context, userID uint, curr
}
// 验证新密码
validationResult, err := ps.ValidatePassword(newPassword)
validationResult, err := ps.ValidatePassword(ctx, userID, newPassword)
if err != nil {
return err
}
@ -305,7 +316,7 @@ func (ps *PasswordService) ChangePassword(ctx context.Context, userID uint, curr @@ -305,7 +316,7 @@ func (ps *PasswordService) ChangePassword(ctx context.Context, userID uint, curr
user.PasswordChangedAt = &time.Time{}
user.ForceChangePassword = false
if err := ps.userRepo.Update(user); err != nil {
if err := ps.userRepo.Update(ctx, user); err != nil {
return err
}
@ -320,7 +331,7 @@ func (ps *PasswordService) ChangePassword(ctx context.Context, userID uint, curr @@ -320,7 +331,7 @@ func (ps *PasswordService) ChangePassword(ctx context.Context, userID uint, curr
}
// ResetPassword 重置密码
func (ps *PasswordService) ResetPassword(ctx context.Context, userID string) error {
func (ps *PasswordService) ResetPassword(ctx context.Context, userID uint) error {
// 生成临时密码
tempPassword := ps.generateTempPassword()
@ -331,16 +342,17 @@ func (ps *PasswordService) ResetPassword(ctx context.Context, userID string) err @@ -331,16 +342,17 @@ func (ps *PasswordService) ResetPassword(ctx context.Context, userID string) err
}
// 更新用户密码
user, err := ps.userRepo.GetByID(userID)
user, err := ps.userRepo.GetByID(ctx, userID)
if err != nil {
return err
}
user.Password = string(hashedPassword)
user.PasswordChangedAt = &time.Time{}
now := time.Now()
user.PasswordChangedAt = &now
user.ForceChangePassword = true
return ps.userRepo.Update(user)
return ps.userRepo.Update(ctx, user)
}
// CheckPasswordExpiration 检查密码是否过期
@ -385,7 +397,7 @@ func (ps *PasswordService) verifyPassword(password, hashedPassword string) bool @@ -385,7 +397,7 @@ func (ps *PasswordService) verifyPassword(password, hashedPassword string) bool
// CheckPasswordStatus 检查密码状态
func (ps *PasswordService) CheckPasswordStatus(ctx context.Context, userID uint) (*model.PasswordStatus, error) {
user, err := ps.userRepo.GetByID(userID)
user, err := ps.userRepo.GetByID(ctx, userID)
if err != nil {
return nil, err
}

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

@ -75,13 +75,13 @@ func (m *ModuleManager) RegisterRoutes(router *gin.RouterGroup) { @@ -75,13 +75,13 @@ func (m *ModuleManager) RegisterRoutes(router *gin.RouterGroup) {
// Start 启动所有模块
func (m *ModuleManager) Start() error {
// 加载配置
cfg, err := config.Load()
cfg, err := config.LoadConfig()
if err != nil {
return fmt.Errorf("加载配置失败: %w", err)
}
// 初始化数据库
db, err := database.New(cfg.Database)
db, err := database.NewDB(&cfg.DB)
if err != nil {
return fmt.Errorf("初始化数据库失败: %w", err)
}
@ -101,7 +101,7 @@ func (m *ModuleManager) Start() error { @@ -101,7 +101,7 @@ func (m *ModuleManager) Start() error {
func (m *ModuleManager) Stop() error {
for name, module := range m.modules {
if err := module.Stop(); err != nil {
m.logger.Error("停止模块失败", "module", name, "error", err)
m.logger.Error("停止模块失败", zap.String("module", name), zap.Error(err))
}
}
return nil

2
gofaster/backend/internal/core/module.go

@ -14,5 +14,7 @@ type Module interface { @@ -14,5 +14,7 @@ type Module interface {
Name() string
Init(config *config.Config, logger *zap.Logger, db *gorm.DB, redis *database.RedisClient) error
RegisterRoutes(router *gin.RouterGroup)
Start(config *config.Config, db *gorm.DB) error
Stop() error
Cleanup()
}

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

@ -89,7 +89,7 @@ func OptionalJWTAuth() gin.HandlerFunc { @@ -89,7 +89,7 @@ func OptionalJWTAuth() gin.HandlerFunc {
}
// 提取令牌
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
_ = strings.TrimPrefix(authHeader, "Bearer ")
// 创建JWT管理器
// jwtManager := jwt.NewJWTManager(config.SecretKey, config.Issuer) // This line was removed as per the new_code

15
gofaster/backend/internal/shared/routes/user_routes.go

@ -1,15 +0,0 @@ @@ -1,15 +0,0 @@
package routes
import (
"gofaster/internal/auth/controller"
"github.com/gin-gonic/gin"
)
func RegisterUserRoutes(router *gin.RouterGroup, userCtrl *controller.UserController) {
router.GET("/users", userCtrl.ListUsers)
router.POST("/users", userCtrl.CreateUser)
router.GET("/users/:id", userCtrl.GetUser)
router.PUT("/users/:id", userCtrl.UpdateUser)
router.DELETE("/users/:id", userCtrl.DeleteUser)
}

14
gofaster/backend/internal/workflow/module.go

@ -92,3 +92,17 @@ func (m *WorkflowModule) Cleanup() { @@ -92,3 +92,17 @@ func (m *WorkflowModule) Cleanup() {
m.logger.Info("Cleaning up workflow module")
// 这里可以添加资源释放逻辑
}
// Start 启动工作流模块
func (m *WorkflowModule) Start(config *config.Config, db *gorm.DB) error {
m.logger.Info("Starting workflow module")
// 这里可以添加启动逻辑,比如启动工作流引擎
return nil
}
// Stop 停止工作流模块
func (m *WorkflowModule) Stop() error {
m.logger.Info("Stopping workflow module")
// 这里可以添加停止逻辑,比如停止工作流引擎
return nil
}

BIN
gofaster/backend/main.exe

Binary file not shown.

12
gofaster/backend/main.go

@ -22,6 +22,7 @@ import ( @@ -22,6 +22,7 @@ import (
"gofaster/internal/shared/database"
"gofaster/internal/shared/logger"
"gofaster/internal/shared/middleware"
"time"
// 导入各模块
_ "gofaster/internal/auth"
@ -39,6 +40,17 @@ import ( @@ -39,6 +40,17 @@ import (
func main() {
printBanner()
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Printf("时区加载失败: %v\n", err)
} else {
fmt.Printf("时区加载成功: %v\n", loc)
}
// 列出可用时区(部分)
fmt.Println("当前时间:", time.Now())
fmt.Println("UTC时间:", time.Now().UTC())
// 初始化配置
cfg, err := config.LoadConfig()
if err != nil {

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 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 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.

126
gofaster/test-captcha-display.html

@ -0,0 +1,126 @@ @@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>验证码测试</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.captcha-container {
margin: 20px 0;
padding: 20px;
border: 1px solid #ddd;
border-radius: 4px;
}
.captcha-image {
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 10px 5px;
}
button:hover {
background: #0056b3;
}
.error {
color: red;
margin: 10px 0;
}
.success {
color: green;
margin: 10px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>验证码显示测试</h1>
<div class="captcha-container">
<h3>验证码图片:</h3>
<img id="captchaImage" class="captcha-image" alt="验证码" style="display: none;" />
<div id="captchaPlaceholder">点击按钮获取验证码</div>
<br>
<button onclick="getCaptcha()">获取验证码</button>
<button onclick="refreshCaptcha()">刷新验证码</button>
</div>
<div id="status"></div>
<div id="response"></div>
</div>
<script>
let currentCaptchaId = '';
async function getCaptcha() {
try {
document.getElementById('status').innerHTML = '<div class="success">正在获取验证码...</div>';
const response = await fetch('http://localhost:8080/api/auth/captcha', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('验证码响应:', data);
if (data.code === 200 && data.data) {
const captchaImage = document.getElementById('captchaImage');
const placeholder = document.getElementById('captchaPlaceholder');
captchaImage.src = data.data.captcha_image;
captchaImage.style.display = 'block';
placeholder.style.display = 'none';
currentCaptchaId = data.data.captcha_id;
document.getElementById('status').innerHTML = '<div class="success">验证码获取成功!</div>';
document.getElementById('response').innerHTML = `
<h4>响应数据:</h4>
<pre>${JSON.stringify(data, null, 2)}</pre>
`;
} else {
throw new Error(data.message || '获取验证码失败');
}
} catch (error) {
console.error('获取验证码失败:', error);
document.getElementById('status').innerHTML = `<div class="error">获取验证码失败: ${error.message}</div>`;
}
}
function refreshCaptcha() {
getCaptcha();
}
// 页面加载时自动获取验证码
window.onload = function() {
getCaptcha();
};
</script>
</body>
</html>
Loading…
Cancel
Save