Browse Source

密码修改可以了,但是报错和提示需要优化

master
hejl 2 weeks ago
parent
commit
67fd60a1a0
  1. 81
      gofaster/app/dist/renderer/js/index.js
  2. 32
      gofaster/app/src/renderer/components/MainLayout.vue
  3. 42
      gofaster/app/src/renderer/components/PasswordChangeModal.vue
  4. 26
      gofaster/backend/internal/auth/controller/password_controller.go
  5. 28
      gofaster/backend/internal/auth/migration/migration.go
  6. 4
      gofaster/backend/internal/auth/model/password_policy.go
  7. 2
      gofaster/backend/internal/auth/model/user.go
  8. 7
      gofaster/backend/internal/auth/module.go
  9. 34
      gofaster/backend/internal/auth/routes/auth_routes.go
  10. 20
      gofaster/backend/internal/auth/service/auth_service.go
  11. 82
      gofaster/backend/internal/auth/service/password_service.go
  12. 11
      gofaster/backend/internal/core/discovery.go
  13. 21
      gofaster/backend/internal/core/manager.go
  14. 90
      gofaster/backend/internal/shared/middleware/jwt_middleware.go
  15. 7
      gofaster/backend/internal/shared/middleware/logger_middleware.go
  16. 5
      gofaster/backend/internal/shared/middleware/permission_middleware.go
  17. BIN
      gofaster/backend/main.exe
  18. 6
      gofaster/backend/main.go
  19. 2
      gofaster/backend/tmp/build-errors.log
  20. BIN
      gofaster/backend/tmp/main.exe

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

@ -1210,6 +1210,30 @@ ___CSS_LOADER_EXPORT___.push([module.id, ` @@ -1210,6 +1210,30 @@ ___CSS_LOADER_EXPORT___.push([module.id, `
justify-content: flex-end;
margin-top: 30px;
}
/* 强制修改密码提示 */
.force-change-notice[data-v-52d75f2c] {
margin-top: 20px;
padding: 16px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 8px;
display: flex;
align-items: flex-start;
gap: 12px;
}
.notice-icon[data-v-52d75f2c] {
font-size: 20px;
flex-shrink: 0;
}
.notice-text[data-v-52d75f2c] {
color: #856404;
font-size: 14px;
line-height: 1.5;
}
.notice-text strong[data-v-52d75f2c] {
color: #856404;
}
.btn[data-v-52d75f2c] {
padding: 12px 24px;
border: none;
@ -3264,8 +3288,9 @@ __webpack_require__.r(__webpack_exports__); @@ -3264,8 +3288,9 @@ __webpack_require__.r(__webpack_exports__);
detail: { user: currentUser, token: token }
}))
// 显示欢迎消息
showToastMessage('success', '登录成功', `欢迎回来,${currentUser.name}`)
// 显示欢迎消息 - 确保用户名称存在
const userName = currentUser.name || userData.username || '用户'
showToastMessage('success', '登录成功', `欢迎回来,${userName}`)
// 跳转到首页
router.push('/')
@ -3274,7 +3299,7 @@ __webpack_require__.r(__webpack_exports__); @@ -3274,7 +3299,7 @@ __webpack_require__.r(__webpack_exports__);
} catch (error) {
console.error('获取用户信息失败:', error)
// 如果获取用户信息失败,使用登录响应中的基本信息
// 如果获取用户信息失败,使用登录响应中的基本信息
if (user) {
currentUser.id = user.id || null
currentUser.name = user.username || '用户'
@ -3300,6 +3325,19 @@ __webpack_require__.r(__webpack_exports__); @@ -3300,6 +3325,19 @@ __webpack_require__.r(__webpack_exports__);
last_login_ip: ''
}
localStorage.setItem('user', JSON.stringify(userForStorage))
// 显示欢迎消息 - 使用基本信息
const userName = currentUser.name || user.username || '用户'
showToastMessage('success', '登录成功', `欢迎回来,${userName}`)
// 跳转到首页
router.push('/')
} else {
// 如果连基本信息都没有,显示通用消息
showToastMessage('success', '登录成功', '欢迎回来!')
// 跳转到首页
router.push('/')
}
}
@ -3321,6 +3359,15 @@ __webpack_require__.r(__webpack_exports__); @@ -3321,6 +3359,15 @@ __webpack_require__.r(__webpack_exports__);
showToast.value = true
}
// 显示Toast消息的便捷函数
const showToastMessage = (type, title, content, duration = 3000) => {
toastConfig.type = type
toastConfig.title = title
toastConfig.content = content
toastConfig.duration = duration
showToast.value = true
}
const logout = async () => {
try {
// 获取当前token
@ -3553,7 +3600,8 @@ __webpack_require__.r(__webpack_exports__); @@ -3553,7 +3600,8 @@ __webpack_require__.r(__webpack_exports__);
handleAddTab,
addTabIfNotExists,
showPasswordModal,
isForceChange
isForceChange,
showToastMessage
}
}
});
@ -3971,9 +4019,10 @@ __webpack_require__.r(__webpack_exports__); @@ -3971,9 +4019,10 @@ __webpack_require__.r(__webpack_exports__);
}
const handleOverlayClick = () => {
if (!props.isForceChange) {
handleClose()
}
// 密码修改模态窗不应该在点击外部区域时关闭
// 避免用户意外丢失已输入的密码内容
// 只有通过明确的取消按钮或关闭按钮才能关闭
return false
}
// 监听器
@ -5389,6 +5438,10 @@ const _hoisted_16 = { @@ -5389,6 +5438,10 @@ const _hoisted_16 = {
key: 0,
class: "loading-spinner"
}
const _hoisted_17 = {
key: 1,
class: "force-change-notice"
}
function render(_ctx, _cache, $props, $setup, $data, $options) {
return ($props.visible)
@ -5511,7 +5564,17 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { @@ -5511,7 +5564,17 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
: (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)
])
]),
(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__.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, "重要提示:"),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)("您的密码已被重置,必须立即修改密码才能继续使用系统。 ")
], -1 /* CACHED */)
])))
: (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true)
], 32 /* NEED_HYDRATION */)
])
])
@ -8635,7 +8698,7 @@ __webpack_require__.r(__webpack_exports__); @@ -8635,7 +8698,7 @@ __webpack_require__.r(__webpack_exports__);
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
/******/ __webpack_require__.h = () => ("21017825e4a5a270")
/******/ __webpack_require__.h = () => ("37a1d732559d3aaa")
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */

32
gofaster/app/src/renderer/components/MainLayout.vue

@ -609,8 +609,9 @@ export default { @@ -609,8 +609,9 @@ export default {
detail: { user: currentUser, token: token }
}))
//
showToastMessage('success', '登录成功', `欢迎回来,${currentUser.name}`)
// -
const userName = currentUser.name || userData.username || '用户'
showToastMessage('success', '登录成功', `欢迎回来,${userName}`)
//
router.push('/')
@ -619,7 +620,7 @@ export default { @@ -619,7 +620,7 @@ export default {
} catch (error) {
console.error('获取用户信息失败:', error)
// 使
// 使
if (user) {
currentUser.id = user.id || null
currentUser.name = user.username || '用户'
@ -645,6 +646,19 @@ export default { @@ -645,6 +646,19 @@ export default {
last_login_ip: ''
}
localStorage.setItem('user', JSON.stringify(userForStorage))
// - 使
const userName = currentUser.name || user.username || '用户'
showToastMessage('success', '登录成功', `欢迎回来,${userName}`)
//
router.push('/')
} else {
//
showToastMessage('success', '登录成功', '欢迎回来!')
//
router.push('/')
}
}
@ -666,6 +680,15 @@ export default { @@ -666,6 +680,15 @@ export default {
showToast.value = true
}
// Toast便
const showToastMessage = (type, title, content, duration = 3000) => {
toastConfig.type = type
toastConfig.title = title
toastConfig.content = content
toastConfig.duration = duration
showToast.value = true
}
const logout = async () => {
try {
// token
@ -898,7 +921,8 @@ export default { @@ -898,7 +921,8 @@ export default {
handleAddTab,
addTabIfNotExists,
showPasswordModal,
isForceChange
isForceChange,
showToastMessage
}
}
}

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

@ -108,6 +108,14 @@ @@ -108,6 +108,14 @@
{{ submitText }}
</button>
</div>
<!-- 强制修改密码提示 -->
<div v-if="isForceChange" class="force-change-notice">
<div class="notice-icon"></div>
<div class="notice-text">
<strong>重要提示</strong>您的密码已被重置必须立即修改密码才能继续使用系统
</div>
</div>
</form>
</div>
</div>
@ -511,9 +519,10 @@ export default { @@ -511,9 +519,10 @@ export default {
}
const handleOverlayClick = () => {
if (!props.isForceChange) {
handleClose()
}
//
//
//
return false
}
//
@ -746,6 +755,33 @@ export default { @@ -746,6 +755,33 @@ export default {
margin-top: 30px;
}
/* 强制修改密码提示 */
.force-change-notice {
margin-top: 20px;
padding: 16px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 8px;
display: flex;
align-items: flex-start;
gap: 12px;
}
.notice-icon {
font-size: 20px;
flex-shrink: 0;
}
.notice-text {
color: #856404;
font-size: 14px;
line-height: 1.5;
}
.notice-text strong {
color: #856404;
}
.btn {
padding: 12px 24px;
border: none;

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

@ -1,11 +1,13 @@ @@ -1,11 +1,13 @@
package controller
import (
"fmt"
"net/http"
"strconv"
"gofaster/internal/auth/model"
"gofaster/internal/auth/service"
"gofaster/internal/shared/middleware"
"gofaster/internal/shared/response"
"github.com/gin-gonic/gin"
@ -34,14 +36,22 @@ func (c *PasswordController) ChangePassword(ctx *gin.Context) { @@ -34,14 +36,22 @@ func (c *PasswordController) ChangePassword(ctx *gin.Context) {
return
}
// 添加调试日志
fmt.Printf("密码修改请求: UserID=%d, CurrentPassword=%s, NewPassword=%s, ConfirmPassword=%s\n",
req.UserID, req.CurrentPassword, req.NewPassword, req.ConfirmPassword)
// 从JWT获取用户ID
userID, exists := ctx.Get("user_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不存在")
return
}
req.UserID = userID.(uint)
fmt.Printf("PasswordController - GetUserID成功,用户ID: %d\n", userID)
req.UserID = userID
fmt.Printf("从JWT获取的用户ID: %d\n", req.UserID)
// 验证新密码确认
if req.NewPassword != req.ConfirmPassword {
@ -52,10 +62,12 @@ func (c *PasswordController) ChangePassword(ctx *gin.Context) { @@ -52,10 +62,12 @@ func (c *PasswordController) ChangePassword(ctx *gin.Context) {
// 修改密码
err := c.passwordService.ChangePassword(req.UserID, req.CurrentPassword, req.NewPassword)
if err != nil {
response.Error(ctx, http.StatusBadRequest, "修改密码失败", err.Error())
fmt.Printf("密码修改失败: %v\n", err)
response.Error(ctx, http.StatusInternalServerError, "修改密码失败", err.Error())
return
}
fmt.Printf("密码修改成功,用户ID: %d\n", req.UserID)
response.Success(ctx, "密码修改成功", nil)
}
@ -69,14 +81,14 @@ func (c *PasswordController) ResetPassword(ctx *gin.Context) { @@ -69,14 +81,14 @@ func (c *PasswordController) ResetPassword(ctx *gin.Context) {
}
// 从JWT获取操作人ID
operatorID, exists := ctx.Get("user_id")
operatorID, exists := middleware.GetUserID(ctx)
if !exists {
response.Error(ctx, http.StatusUnauthorized, "未授权", "操作人ID不存在")
return
}
// 重置密码
tempPassword, err := c.passwordService.ResetPassword(uint(userID), operatorID.(uint))
tempPassword, err := c.passwordService.ResetPassword(uint(userID), operatorID)
if err != nil {
response.Error(ctx, http.StatusInternalServerError, "重置密码失败", err.Error())
return
@ -122,14 +134,14 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) { @@ -122,14 +134,14 @@ func (c *PasswordController) ValidatePassword(ctx *gin.Context) {
// CheckPasswordStatus 检查用户密码状态
func (c *PasswordController) CheckPasswordStatus(ctx *gin.Context) {
userID, exists := ctx.Get("user_id")
userID, exists := middleware.GetUserID(ctx)
if !exists {
response.Error(ctx, http.StatusUnauthorized, "未授权", "用户ID不存在")
return
}
// 获取用户信息
user, err := c.userService.GetByID(userID.(uint))
user, err := c.userService.GetByID(userID)
if err != nil {
response.Error(ctx, http.StatusInternalServerError, "获取用户信息失败", err.Error())
return

28
gofaster/backend/internal/auth/migration/migration.go

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
package migration
import (
"fmt"
"gofaster/internal/auth/model"
"gofaster/internal/auth/repository"
@ -14,6 +15,11 @@ func RunMigrations(db *gorm.DB) error { @@ -14,6 +15,11 @@ func RunMigrations(db *gorm.DB) error {
return err
}
// 手动检查并添加可能缺失的字段
if err := ensureUserFields(db); err != nil {
return err
}
// 自动迁移角色表
if err := db.AutoMigrate(&model.Role{}); err != nil {
return err
@ -56,6 +62,28 @@ func RunMigrations(db *gorm.DB) error { @@ -56,6 +62,28 @@ func RunMigrations(db *gorm.DB) error {
return nil
}
// ensureUserFields 确保用户表有必要的字段
func ensureUserFields(db *gorm.DB) error {
// 检查PasswordChangedAt字段是否存在
if !db.Migrator().HasColumn(&model.User{}, "password_changed_at") {
fmt.Println("添加 password_changed_at 字段到 users 表")
if err := db.Exec("ALTER TABLE users ADD COLUMN password_changed_at TIMESTAMP NULL").Error; err != nil {
return fmt.Errorf("添加 password_changed_at 字段失败: %w", err)
}
}
// 检查ForceChangePassword字段是否存在
if !db.Migrator().HasColumn(&model.User{}, "force_change_password") {
fmt.Println("添加 force_change_password 字段到 users 表")
if err := db.Exec("ALTER TABLE users ADD COLUMN force_change_password BOOLEAN DEFAULT FALSE").Error; err != nil {
return fmt.Errorf("添加 force_change_password 字段失败: %w", err)
}
}
fmt.Println("用户表字段检查完成")
return nil
}
// createDefaultRoles 创建默认角色
func createDefaultRoles(db *gorm.DB) error {
// 检查是否已存在角色

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

@ -28,8 +28,8 @@ type PasswordPolicy struct { @@ -28,8 +28,8 @@ type PasswordPolicy struct {
// PasswordHistory 密码历史记录
type PasswordHistory struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"not null;index" json:"user_id"` // 用户ID
Password string `gorm:"not null" json:"password"` // 加密后的密码
UserID uint `gorm:"not null;index" json:"user_id"` // 用户ID
Password string `gorm:"not null;size:255" json:"password"` // 加密后的密码(bcrypt哈希)
CreatedAt time.Time `json:"created_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}

2
gofaster/backend/internal/auth/model/user.go

@ -8,7 +8,7 @@ import ( @@ -8,7 +8,7 @@ import (
type User struct {
model.BaseModel
Username string `gorm:"uniqueIndex;size:50" json:"username"`
Password string `gorm:"size:100" json:"-"`
Password string `gorm:"size:255" json:"-"` // bcrypt哈希
Email string `gorm:"size:100" json:"email"`
Phone string `gorm:"size:20" json:"phone"`
Status int `gorm:"default:1" json:"status"` // 1-正常 2-禁用 3-锁定

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

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
package auth
import (
"fmt"
"gofaster/internal/auth/migration"
"gofaster/internal/auth/routes"
"gofaster/internal/core"
@ -28,26 +29,32 @@ func (m *AuthModule) Name() string { @@ -28,26 +29,32 @@ func (m *AuthModule) Name() string {
}
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
fmt.Printf("✅ AuthModule 配置已设置\n")
// 运行数据库迁移
if err := migration.RunMigrations(db); err != nil {
logger.Error("Failed to run auth migrations", zap.Error(err))
return err
}
fmt.Printf("✅ 数据库迁移完成\n")
logger.Info("Auth module initialized successfully")
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
}
fmt.Printf("✅ 数据库连接正常,开始注册认证路由\n")
// 注册认证路由
routes.RegisterAuthRoutes(router, m.db, middleware.JWTConfig{
SecretKey: m.config.JWT.Secret, // 从配置中获取JWT密钥

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

@ -1,11 +1,13 @@ @@ -1,11 +1,13 @@
package routes
import (
"fmt"
"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"
@ -13,6 +15,9 @@ import ( @@ -13,6 +15,9 @@ import (
// 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)
// 创建仓储层实例
userRepo := repository.NewUserRepository(db)
captchaRepo := repository.NewCaptchaRepository(db)
@ -38,14 +43,43 @@ func RegisterAuthRoutes(r *gin.RouterGroup, db *gorm.DB, jwtConfig middleware.JW @@ -38,14 +43,43 @@ func RegisterAuthRoutes(r *gin.RouterGroup, db *gorm.DB, jwtConfig middleware.JW
// 公开接口(无需认证)
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(),
})
})
auth.POST("/logout", authController.Logout) // 用户登出
auth.POST("/refresh", authController.RefreshToken) // 刷新令牌
auth.GET("/userinfo", authController.GetUserInfo) // 获取用户信息

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

@ -6,6 +6,7 @@ import ( @@ -6,6 +6,7 @@ import (
"fmt"
mathrand "math/rand"
"net/http"
"strconv"
"strings"
"time"
@ -51,6 +52,7 @@ func (s *authService) Login(ctx context.Context, req *model.LoginRequest, client @@ -51,6 +52,7 @@ func (s *authService) Login(ctx context.Context, req *model.LoginRequest, client
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() {
@ -88,11 +90,13 @@ func (s *authService) Login(ctx context.Context, req *model.LoginRequest, client @@ -88,11 +90,13 @@ func (s *authService) Login(ctx context.Context, req *model.LoginRequest, client
}
// 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,
}
fmt.Printf("📋 JWT claims: %+v\n", claims)
token, err := s.jwtManager.GenerateToken(claims, 24*time.Hour) // 24小时有效期
if err != nil {
@ -144,7 +148,21 @@ func (s *authService) RefreshToken(ctx context.Context, refreshToken string) (*m @@ -144,7 +148,21 @@ func (s *authService) RefreshToken(ctx context.Context, refreshToken string) (*m
// 获取用户信息
userID, ok := claims["user_id"].(float64)
if !ok {
return nil, fmt.Errorf("令牌格式错误")
// 尝试其他类型转换
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类型不正确")
}
}
user, err := s.userRepo.GetUserWithRoles(ctx, uint(userID))

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

@ -1,16 +1,16 @@ @@ -1,16 +1,16 @@
package service
import (
"crypto/sha256"
"encoding/hex"
"fmt"
mathrand "math/rand"
"math/rand"
"strings"
"time"
"unicode"
"gofaster/internal/auth/model"
"gofaster/internal/auth/repository"
"golang.org/x/crypto/bcrypt"
)
type PasswordService struct {
@ -181,27 +181,17 @@ func (s *PasswordService) calculatePasswordLevel(password string, charTypes int, @@ -181,27 +181,17 @@ func (s *PasswordService) calculatePasswordLevel(password string, charTypes int,
return -1
}
// CheckPasswordReuse 检查密码是否与历史密码重复
// CheckPasswordReuse 检查密码是否与历史密码重复
func (s *PasswordService) CheckPasswordReuse(userID uint, newPassword string) (bool, error) {
policy, err := s.GetPasswordPolicy()
if err != nil {
return false, err
}
if policy.PreventReuse <= 0 {
return false, nil // 不检查重复
}
// 获取历史密码
history, err := s.passwordHistoryRepo.GetRecentPasswords(userID, policy.PreventReuse)
// 获取密码历史
history, err := s.passwordHistoryRepo.GetRecentPasswords(userID, 3)
if err != nil {
return false, err
}
// 检查新密码是否与历史密码重复
newPasswordHash := s.hashPassword(newPassword)
for _, hist := range history {
if hist.Password == newPasswordHash {
if err := bcrypt.CompareHashAndPassword([]byte(hist.Password), []byte(newPassword)); err == nil {
return true, nil // 发现重复
}
}
@ -211,60 +201,86 @@ func (s *PasswordService) CheckPasswordReuse(userID uint, newPassword string) (b @@ -211,60 +201,86 @@ func (s *PasswordService) CheckPasswordReuse(userID uint, newPassword string) (b
// ChangePassword 修改用户密码
func (s *PasswordService) ChangePassword(userID uint, currentPassword, newPassword string) error {
fmt.Printf("开始修改密码,用户ID: %d\n", userID)
// 获取用户信息
user, err := s.userService.GetByID(userID)
if err != nil {
return err
fmt.Printf("获取用户信息失败: %v\n", err)
return fmt.Errorf("获取用户信息失败: %w", err)
}
fmt.Printf("成功获取用户信息: %s\n", user.Username)
// 验证当前密码
if !s.verifyPassword(currentPassword, user.Password) {
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentPassword)); err != nil {
fmt.Printf("当前密码验证失败\n")
return fmt.Errorf("当前密码不正确")
}
fmt.Printf("当前密码验证成功\n")
// 验证新密码
validation, err := s.ValidatePassword(newPassword)
if err != nil {
return err
fmt.Printf("新密码验证失败: %v\n", err)
return fmt.Errorf("新密码验证失败: %w", err)
}
if !validation.IsValid {
fmt.Printf("新密码不符合要求: %v\n", validation.Errors)
return fmt.Errorf("新密码不符合要求: %s", strings.Join(validation.Errors, "; "))
}
fmt.Printf("新密码验证成功,强度等级: %d\n", validation.Level)
// 检查是否与历史密码重复
isReused, err := s.CheckPasswordReuse(userID, newPassword)
if err != nil {
return err
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)
}
fmt.Printf("密码重复检查通过\n")
// 更新密码
newPasswordHash := s.hashPassword(newPassword)
newPasswordHash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
fmt.Printf("生成密码哈希失败: %v\n", err)
return fmt.Errorf("生成密码哈希失败: %w", err)
}
now := time.Now()
user.Password = newPasswordHash
user.Password = string(newPasswordHash)
user.PasswordChangedAt = &now
user.ForceChangePassword = false // 取消强制修改密码
fmt.Printf("准备更新用户密码,新密码哈希: %s\n", string(newPasswordHash)[:10])
if err := s.userService.Update(user); err != nil {
return err
fmt.Printf("更新用户密码失败: %v\n", err)
return fmt.Errorf("更新用户密码失败: %w", err)
}
fmt.Printf("用户密码更新成功\n")
// 记录密码历史
passwordHistory := &model.PasswordHistory{
UserID: userID,
Password: newPasswordHash,
Password: string(newPasswordHash),
}
if err := s.passwordHistoryRepo.Create(passwordHistory); err != nil {
return err
fmt.Printf("记录密码历史失败: %v\n", err)
// 不返回错误,因为密码已经更新成功
fmt.Printf("警告:密码历史记录失败,但密码已更新\n")
} else {
fmt.Printf("密码历史记录成功\n")
}
fmt.Printf("密码修改完成,用户ID: %d\n", userID)
return nil
}
@ -279,7 +295,11 @@ func (s *PasswordService) ResetPassword(userID uint, resetBy uint) (string, erro @@ -279,7 +295,11 @@ func (s *PasswordService) ResetPassword(userID uint, resetBy uint) (string, erro
return "", err
}
user.Password = s.hashPassword(tempPassword)
tempPasswordHash, err := bcrypt.GenerateFromPassword([]byte(tempPassword), bcrypt.DefaultCost)
if err != nil {
return "", fmt.Errorf("生成临时密码哈希失败: %w", err)
}
user.Password = string(tempPasswordHash)
user.ForceChangePassword = true // 标记需要强制修改密码
now := time.Now()
user.PasswordChangedAt = &now
@ -326,20 +346,20 @@ func (s *PasswordService) generateTempPassword() string { @@ -326,20 +346,20 @@ func (s *PasswordService) generateTempPassword() string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
password := make([]byte, 8)
for i := range password {
password[i] = charset[mathrand.Intn(len(charset))]
password[i] = charset[rand.Intn(len(charset))]
}
return string(password)
}
// hashPassword 密码哈希
func (s *PasswordService) hashPassword(password string) string {
hash := sha256.Sum256([]byte(password))
return hex.EncodeToString(hash[:])
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(hash)
}
// verifyPassword 验证密码
func (s *PasswordService) verifyPassword(password, hash string) bool {
return s.hashPassword(password) == hash
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
}
// TestPasswordStrengthCalculation 测试密码强度计算(仅用于调试)

11
gofaster/backend/internal/core/discovery.go

@ -2,19 +2,28 @@ @@ -2,19 +2,28 @@
package core
import (
"fmt"
"reflect"
)
var moduleTypes []reflect.Type
func RegisterModuleType(module Module) {
fmt.Printf("🔍 注册模块类型: %T\n", module)
moduleTypes = append(moduleTypes, reflect.TypeOf(module))
fmt.Printf("✅ 模块类型注册成功,当前注册的模块类型数量: %d\n", len(moduleTypes))
}
func DiscoverModules(manager *ModuleManager) error {
for _, typ := range moduleTypes {
fmt.Printf("🔍 开始发现模块,当前注册的模块类型数量: %d\n", len(moduleTypes))
for i, typ := range moduleTypes {
fmt.Printf("🔍 发现模块类型 %d: %s\n", i+1, typ.String())
module := reflect.New(typ.Elem()).Interface().(Module)
fmt.Printf("✅ 创建模块实例: %s\n", module.Name())
manager.RegisterModule(module)
}
fmt.Printf("✅ 模块发现完成,总共发现 %d 个模块\n", len(moduleTypes))
return nil
}

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

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
package core
import (
"fmt"
"gofaster/internal/shared/config"
"gofaster/internal/shared/database"
"sync"
@ -26,11 +27,16 @@ func NewModuleManager(logger *zap.Logger) *ModuleManager { @@ -26,11 +27,16 @@ func NewModuleManager(logger *zap.Logger) *ModuleManager {
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))
}
func (m *ModuleManager) InitModules(config *config.Config, db *gorm.DB, redis *database.RedisClient) error {
for _, module := range m.modules {
fmt.Printf("🔍 开始初始化模块,当前模块数量: %d\n", len(m.modules))
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",
@ -38,16 +44,23 @@ func (m *ModuleManager) InitModules(config *config.Config, db *gorm.DB, redis *d @@ -38,16 +44,23 @@ func (m *ModuleManager) InitModules(config *config.Config, db *gorm.DB, redis *d
zap.Error(err))
return err
}
fmt.Printf("✅ 模块 %s 初始化完成\n", module.Name())
}
fmt.Printf("✅ 所有模块初始化完成\n")
return nil
}
func (m *ModuleManager) RegisterRoutes(router *gin.RouterGroup) {
for _, module := range m.modules {
m.logger.Info("Registering routes for module",
zap.String("module", module.Name()))
fmt.Printf("🔍 开始注册模块路由,当前模块数量: %d\n", len(m.modules))
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())
}
fmt.Printf("✅ 所有模块路由注册完成\n")
}
func (m *ModuleManager) Cleanup() {

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

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
package middleware
import (
"fmt"
"strconv"
"strings"
@ -19,9 +20,15 @@ type JWTConfig struct { @@ -19,9 +20,15 @@ type JWTConfig struct {
// JWTAuth JWT认证中间件
func JWTAuth(config JWTConfig) 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头部
authHeader := c.GetHeader("Authorization")
fmt.Printf("📋 Authorization头部: %s\n", authHeader)
if authHeader == "" {
fmt.Printf("❌ JWTAuth中间件 - Authorization头部缺失\n")
response.Unauthorized(c, "未提供认证令牌", "Authorization头部缺失")
c.Abort()
return
@ -29,6 +36,7 @@ func JWTAuth(config JWTConfig) gin.HandlerFunc { @@ -29,6 +36,7 @@ func JWTAuth(config JWTConfig) gin.HandlerFunc {
// 检查Bearer前缀
if !strings.HasPrefix(authHeader, "Bearer ") {
fmt.Printf("❌ JWTAuth中间件 - Bearer前缀缺失\n")
response.Unauthorized(c, "认证令牌格式错误", "令牌必须以Bearer开头")
c.Abort()
return
@ -36,31 +44,71 @@ func JWTAuth(config JWTConfig) gin.HandlerFunc { @@ -36,31 +44,71 @@ func JWTAuth(config JWTConfig) gin.HandlerFunc {
// 提取令牌
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)
// 验证令牌
fmt.Printf("JWTAuth中间件 - 开始验证令牌\n")
claims, err := jwtManager.ValidateToken(tokenString)
if err != nil {
fmt.Printf("JWTAuth中间件 - 令牌验证失败: %v\n", err)
response.Unauthorized(c, "认证令牌无效", err.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")
}
}
if username, exists := claims["username"]; exists {
fmt.Printf("JWTAuth中间件 - username类型: %T, 值: %v\n", username, username)
c.Set("username", username)
}
if email, exists := claims["email"]; exists {
fmt.Printf("JWTAuth中间件 - email类型: %T, 值: %v\n", email, email)
c.Set("email", email)
}
fmt.Printf("JWTAuth中间件 - 准备调用c.Next()\n")
// 继续处理请求
c.Next()
fmt.Printf("JWTAuth中间件 - c.Next()执行完成\n")
}
}
@ -114,21 +162,55 @@ func OptionalJWTAuth(config JWTConfig) gin.HandlerFunc { @@ -114,21 +162,55 @@ func OptionalJWTAuth(config JWTConfig) gin.HandlerFunc {
// 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
switch v := userID.(type) {
case float64:
return uint(v), true
case uint:
fmt.Printf("GetUserID - 类型匹配uint: %d\n", v)
return v, true
case int:
return uint(v), true
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)
}
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)
}
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)
}
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)
}
default:
fmt.Printf("GetUserID - 未知类型: %T, 值: %v\n", userID, userID)
}
// 如果所有类型转换都失败,返回0和false
fmt.Printf("GetUserID - 所有类型转换都失败,返回0\n")
return 0, false
}

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

@ -15,10 +15,9 @@ import ( @@ -15,10 +15,9 @@ import (
// 从 Gin 上下文中获取用户ID(假设 JWT 中间件已将用户信息存入上下文)
func getUserIdFromContext(c *gin.Context) uint {
if userId, exists := c.Get("userID"); exists {
if uid, ok := userId.(uint); ok {
return uid
}
// 使用安全的GetUserID函数
if userID, exists := GetUserID(c); exists {
return userID
}
return 0 // 0 表示未登录用户或获取失败
}

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

@ -18,14 +18,15 @@ func InitPermissionMiddleware(db *gorm.DB) { @@ -18,14 +18,15 @@ func InitPermissionMiddleware(db *gorm.DB) {
func PermissionMiddleware(permission string) gin.HandlerFunc {
return func(c *gin.Context) {
userID, exists := c.Get("user_id")
// 使用安全的GetUserID函数
userID, exists := GetUserID(c)
if !exists {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
return
}
// 检查用户是否有该权限
hasPerm := permRepo.CheckUserPermission(userID.(uint), permission)
hasPerm := permRepo.CheckUserPermission(userID, permission)
if !hasPerm {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "没有权限"})
return

BIN
gofaster/backend/main.exe

Binary file not shown.

6
gofaster/backend/main.go

@ -63,14 +63,18 @@ func main() { @@ -63,14 +63,18 @@ 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))
}
fmt.Printf("✅ 模块初始化完成\n")
// 创建Gin应用
app := gin.New()
@ -81,8 +85,10 @@ func main() { @@ -81,8 +85,10 @@ func main() {
)
// 注册路由
fmt.Printf("🔍 开始注册路由\n")
api := app.Group("/api")
moduleManager.RegisterRoutes(api)
fmt.Printf("✅ 路由注册完成\n")
// Swagger路由
app.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

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

BIN
gofaster/backend/tmp/main.exe

Binary file not shown.
Loading…
Cancel
Save