package system import ( "context" "time" "golang.org/x/crypto/bcrypt" ) type User struct { ID uint CreatedAt time.Time UpdatedAt time.Time UUID string Username string Password string NickName string HeaderImg string AuthorityID uint Authority Authority Authorities []Authority DeptID uint Department *Department Departments []Department Positions []Position Phone string Email string Enable int OriginSetting map[string]any MustChangePassword bool PasswordUpdatedAt *time.Time } type UserListFilter struct { Username, NickName, Phone, Email, OrderKey string Desc bool } type UserRepo interface { FindUserByUsername(context.Context, string) (*User, error) FindUserByID(context.Context, uint) (*User, error) FindUserByUUID(context.Context, string) (*User, error) HasAuthorityMenu(context.Context, uint, string) (bool, error) MenusByAuthority(context.Context, uint) ([]*Menu, error) ListUsers(context.Context, int, int, *UserListFilter) ([]*User, int64, error) CreateUser(context.Context, *User) (*User, error) CreateUserWithAuthorities(context.Context, *User, []uint) (*User, error) UpdateUser(context.Context, *User) error UpdateSelfUser(context.Context, *User) error UpdateUserWithAuthorities(context.Context, *User, []uint) error DeleteUser(context.Context, uint) error UpdatePassword(context.Context, uint, string, bool) error SetUserAuthorities(context.Context, uint, []uint) error SetUserAuthority(context.Context, uint, uint) error SetUserSetting(context.Context, uint, map[string]any) error ListAuthorities(context.Context) ([]*Authority, error) FillDepartmentNamePaths(context.Context, *User) error } type UserUsecase struct{ repo UserRepo } func NewUserUsecase(repo UserRepo) *UserUsecase { return &UserUsecase{repo: repo} } func (uc *UserUsecase) Login(ctx context.Context, username, password string) (*User, error) { u, err := uc.repo.FindUserByUsername(ctx, username) if err != nil || bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) != nil { return nil, ErrInvalidCredentials } uc.fallbackDefaultRouter(ctx, u) return u, nil } func (uc *UserUsecase) User(ctx context.Context, id uint) (*User, error) { user, err := uc.repo.FindUserByID(ctx, id) if err != nil { return nil, err } _ = uc.repo.FillDepartmentNamePaths(ctx, user) uc.fallbackDefaultRouter(ctx, user) return user, nil } // UserByUUID resolves the identity from the UUID embedded in the JWT rather // than from the mutable numeric ID used by administrative records. func (uc *UserUsecase) UserByUUID(ctx context.Context, uuid string) (*User, error) { user, err := uc.repo.FindUserByUUID(ctx, uuid) if err != nil { return nil, err } _ = uc.repo.FillDepartmentNamePaths(ctx, user) uc.fallbackDefaultRouter(ctx, user) return user, nil } func (uc *UserUsecase) fallbackDefaultRouter(ctx context.Context, user *User) { if user == nil { return } hasMenu, err := uc.repo.HasAuthorityMenu(ctx, user.AuthorityID, user.Authority.DefaultRouter) if err != nil { return } if !hasMenu { user.Authority.DefaultRouter = "404" } } func (uc *UserUsecase) Menus(ctx context.Context, authorityID uint) ([]*Menu, error) { return uc.repo.MenusByAuthority(ctx, authorityID) } func (uc *UserUsecase) ListUsers(ctx context.Context, page, pageSize int, filter *UserListFilter) ([]*User, int64, error) { return uc.repo.ListUsers(ctx, page, pageSize, filter) } func (uc *UserUsecase) CreateUser(ctx context.Context, user *User, authorityIDs []uint) (*User, error) { hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) if err != nil { return nil, err } user.Password = string(hash) return uc.repo.CreateUserWithAuthorities(ctx, user, authorityIDs) } func (uc *UserUsecase) UpdateUser(ctx context.Context, user *User, authorityIDs []uint) error { // Role associations are updated first only when authorityIds was supplied, // and then updates the basic user fields as a separate operation. Preserve // that ordering and partial-failure behavior instead of coupling both writes // into the data-layer transaction used by lower-level callers. if len(authorityIDs) > 0 { if err := uc.repo.SetUserAuthorities(ctx, user.ID, authorityIDs); err != nil { return err } } return uc.repo.UpdateUser(ctx, user) } func (uc *UserUsecase) UpdateSelfUser(ctx context.Context, user *User) error { return uc.repo.UpdateSelfUser(ctx, user) } func (uc *UserUsecase) DeleteUser(ctx context.Context, id uint) error { return uc.repo.DeleteUser(ctx, id) } func (uc *UserUsecase) ResetPassword(ctx context.Context, id uint, password string) error { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return err } return uc.repo.UpdatePassword(ctx, id, string(hash), false) } func (uc *UserUsecase) ChangePassword(ctx context.Context, id uint, oldPassword, newPassword string) error { user, err := uc.repo.FindUserByID(ctx, id) if err != nil { return err } if bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(oldPassword)) != nil { return ErrInvalidCredentials } hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost) if err != nil { return err } return uc.repo.UpdatePassword(ctx, id, string(hash), true) } func (uc *UserUsecase) Authorities(ctx context.Context) ([]*Authority, error) { return uc.repo.ListAuthorities(ctx) } func (uc *UserUsecase) SetUserAuthorities(ctx context.Context, id uint, authorityIDs []uint) error { if len(authorityIDs) == 0 { return ErrInvalidCredentials } return uc.repo.SetUserAuthorities(ctx, id, authorityIDs) } func (uc *UserUsecase) SetUserAuthority(ctx context.Context, id, authorityID uint) error { return uc.repo.SetUserAuthority(ctx, id, authorityID) } func (uc *UserUsecase) SetUserSetting(ctx context.Context, id uint, setting map[string]any) error { return uc.repo.SetUserSetting(ctx, id, setting) }