Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62e8acf757 |
@@ -65,13 +65,7 @@ func (tm *tokenManager) Delete(userID uint, tokenID uint) error {
|
|||||||
// Verify looks up a DB API token and returns a Session if valid.
|
// Verify looks up a DB API token and returns a Session if valid.
|
||||||
func (tm *tokenManager) Verify(rawToken string) (*model.Session, error) {
|
func (tm *tokenManager) Verify(rawToken string) (*model.Session, error) {
|
||||||
var t model.Token
|
var t model.Token
|
||||||
err := db.Default.Session().
|
if err := db.Default.Session().Where("token = ?", rawToken).First(&t).Error; err != nil {
|
||||||
Where("token = ?", rawToken).
|
|
||||||
Preload("User").
|
|
||||||
Preload("User.Role").
|
|
||||||
First(&t).Error
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("无效的 API Token")
|
return nil, errors.New("无效的 API Token")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,16 +73,30 @@ func (tm *tokenManager) Verify(rawToken string) (*model.Session, error) {
|
|||||||
return nil, errors.New("API Token 已过期")
|
return nil, errors.New("API Token 已过期")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var user model.User
|
||||||
|
if err := db.Default.Session().First(&user, t.UserID).Error; err != nil {
|
||||||
|
return nil, errors.New("Token 关联用户不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user.Active {
|
||||||
|
return nil, errors.New("账号已被禁用")
|
||||||
|
}
|
||||||
|
|
||||||
|
var role model.Role
|
||||||
|
if err := db.Default.Session().First(&role, user.RoleID).Error; err != nil {
|
||||||
|
return nil, errors.New("账号角色异常")
|
||||||
|
}
|
||||||
|
|
||||||
// Update last_used_at asynchronously
|
// Update last_used_at asynchronously
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
go db.Default.Session().Model(&t).Update("last_used_at", now) //nolint:errcheck
|
go db.Default.Session().Model(&t).Update("last_used_at", now) //nolint:errcheck
|
||||||
|
|
||||||
session := &model.Session{
|
session := &model.Session{
|
||||||
UserID: t.User.ID,
|
UserID: user.ID,
|
||||||
Username: t.User.Username,
|
Username: user.Username,
|
||||||
Role: t.User.Role.Name,
|
Role: role.Name,
|
||||||
RoleLabel: t.User.Role.Label,
|
RoleLabel: role.Label,
|
||||||
Permissions: t.User.Role.PermissionList(),
|
Permissions: role.PermissionList(),
|
||||||
LoginAt: now.Unix(),
|
LoginAt: now.Unix(),
|
||||||
Token: rawToken,
|
Token: rawToken,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ func (um *userManager) Login(username, password string) (*model.Session, error)
|
|||||||
user := new(model.User)
|
user := new(model.User)
|
||||||
if err := db.Default.Session().
|
if err := db.Default.Session().
|
||||||
Where("username = ? AND active = ?", username, true).
|
Where("username = ? AND active = ?", username, true).
|
||||||
Preload("Role").
|
|
||||||
First(user).Error; err != nil {
|
First(user).Error; err != nil {
|
||||||
return nil, errors.New("账号或密码错误")
|
return nil, errors.New("账号或密码错误")
|
||||||
}
|
}
|
||||||
@@ -98,12 +97,17 @@ func (um *userManager) Login(username, password string) (*model.Session, error)
|
|||||||
return nil, errors.New("账号或密码错误")
|
return nil, errors.New("账号或密码错误")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var role model.Role
|
||||||
|
if err := db.Default.Session().First(&role, user.RoleID).Error; err != nil {
|
||||||
|
return nil, errors.New("账号角色异常,请联系管理员")
|
||||||
|
}
|
||||||
|
|
||||||
session := &model.Session{
|
session := &model.Session{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
Role: user.Role.Name,
|
Role: role.Name,
|
||||||
RoleLabel: user.Role.Label,
|
RoleLabel: role.Label,
|
||||||
Permissions: user.Role.PermissionList(),
|
Permissions: role.PermissionList(),
|
||||||
LoginAt: now.Unix(),
|
LoginAt: now.Unix(),
|
||||||
Token: tool.RandomString(32),
|
Token: tool.RandomString(32),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/loveuer/nf"
|
"github.com/loveuer/nf"
|
||||||
"github.com/loveuer/nf/nft/log"
|
"github.com/loveuer/nf/nft/log"
|
||||||
@@ -12,14 +13,65 @@ import (
|
|||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// userResp is the JSON response shape for a user including role info,
|
||||||
|
// built manually at the business layer instead of relying on GORM associations.
|
||||||
|
type userResp struct {
|
||||||
|
ID uint `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
RoleID uint `json:"role_id"`
|
||||||
|
Role model.Role `json:"role"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUserResp(u model.User, r model.Role) userResp {
|
||||||
|
return userResp{
|
||||||
|
ID: u.ID,
|
||||||
|
Username: u.Username,
|
||||||
|
RoleID: u.RoleID,
|
||||||
|
Role: r,
|
||||||
|
Active: u.Active,
|
||||||
|
CreatedAt: u.CreatedAt,
|
||||||
|
UpdatedAt: u.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func AdminListUsers() nf.HandlerFunc {
|
func AdminListUsers() nf.HandlerFunc {
|
||||||
return func(c *nf.Ctx) error {
|
return func(c *nf.Ctx) error {
|
||||||
var users []model.User
|
var users []model.User
|
||||||
if err := db.Default.Session().Preload("Role").Find(&users).Error; err != nil {
|
if err := db.Default.Session().Find(&users).Error; err != nil {
|
||||||
log.Error("handler.AdminListUsers: %s", err.Error())
|
log.Error("handler.AdminListUsers: %s", err.Error())
|
||||||
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询失败"})
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询失败"})
|
||||||
}
|
}
|
||||||
return c.Status(http.StatusOK).JSON(map[string]any{"data": users})
|
|
||||||
|
// Collect unique role IDs and query them in one shot
|
||||||
|
roleIDSet := make(map[uint]struct{})
|
||||||
|
for _, u := range users {
|
||||||
|
roleIDSet[u.RoleID] = struct{}{}
|
||||||
|
}
|
||||||
|
roleIDs := make([]uint, 0, len(roleIDSet))
|
||||||
|
for id := range roleIDSet {
|
||||||
|
roleIDs = append(roleIDs, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
var roles []model.Role
|
||||||
|
if err := db.Default.Session().Where("id IN ?", roleIDs).Find(&roles).Error; err != nil {
|
||||||
|
log.Error("handler.AdminListUsers: query roles: %s", err.Error())
|
||||||
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询失败"})
|
||||||
|
}
|
||||||
|
|
||||||
|
roleMap := make(map[uint]model.Role, len(roles))
|
||||||
|
for _, r := range roles {
|
||||||
|
roleMap[r.ID] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := make([]userResp, 0, len(users))
|
||||||
|
for _, u := range users {
|
||||||
|
resp = append(resp, toUserResp(u, roleMap[u.RoleID]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Status(http.StatusOK).JSON(map[string]any{"data": resp})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +109,11 @@ func AdminCreateUser() nf.HandlerFunc {
|
|||||||
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "用户名已存在"})
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "用户名已存在"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var role model.Role
|
||||||
|
if err := db.Default.Session().First(&role, req.RoleID).Error; err != nil {
|
||||||
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无效的角色"})
|
||||||
|
}
|
||||||
|
|
||||||
user := &model.User{
|
user := &model.User{
|
||||||
Username: req.Username,
|
Username: req.Username,
|
||||||
Password: tool.NewPassword(req.Password),
|
Password: tool.NewPassword(req.Password),
|
||||||
@@ -69,11 +126,7 @@ func AdminCreateUser() nf.HandlerFunc {
|
|||||||
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "创建用户失败"})
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "创建用户失败"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Default.Session().Preload("Role").First(user, user.ID).Error; err != nil {
|
return c.Status(http.StatusOK).JSON(map[string]any{"data": toUserResp(*user, role)})
|
||||||
log.Error("handler.AdminCreateUser: preload role: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(http.StatusOK).JSON(map[string]any{"data": user})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,11 +150,16 @@ func AdminUpdateUser() nf.HandlerFunc {
|
|||||||
|
|
||||||
session := c.Locals("user").(*model.Session)
|
session := c.Locals("user").(*model.Session)
|
||||||
|
|
||||||
user := new(model.User)
|
var user model.User
|
||||||
if err := db.Default.Session().Preload("Role").First(user, id).Error; err != nil {
|
if err := db.Default.Session().First(&user, id).Error; err != nil {
|
||||||
return c.Status(http.StatusNotFound).JSON(map[string]string{"msg": "用户不存在"})
|
return c.Status(http.StatusNotFound).JSON(map[string]string{"msg": "用户不存在"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var currentRole model.Role
|
||||||
|
if err := db.Default.Session().First(¤tRole, user.RoleID).Error; err != nil {
|
||||||
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询角色失败"})
|
||||||
|
}
|
||||||
|
|
||||||
updates := map[string]any{}
|
updates := map[string]any{}
|
||||||
|
|
||||||
if req.RoleID != nil && *req.RoleID != user.RoleID {
|
if req.RoleID != nil && *req.RoleID != user.RoleID {
|
||||||
@@ -110,7 +168,7 @@ func AdminUpdateUser() nf.HandlerFunc {
|
|||||||
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无效的角色"})
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "无效的角色"})
|
||||||
}
|
}
|
||||||
// If demoting from admin, ensure at least one other active admin remains
|
// If demoting from admin, ensure at least one other active admin remains
|
||||||
if user.Role.Name == model.RoleAdmin && newRole.Name != model.RoleAdmin {
|
if currentRole.Name == model.RoleAdmin && newRole.Name != model.RoleAdmin {
|
||||||
var adminCount int64
|
var adminCount int64
|
||||||
db.Default.Session().Model(&model.User{}).
|
db.Default.Session().Model(&model.User{}).
|
||||||
Where("role_id = ? AND active = ? AND id != ?", user.RoleID, true, id).
|
Where("role_id = ? AND active = ? AND id != ?", user.RoleID, true, id).
|
||||||
@@ -120,13 +178,14 @@ func AdminUpdateUser() nf.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
updates["role_id"] = *req.RoleID
|
updates["role_id"] = *req.RoleID
|
||||||
|
currentRole = newRole
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Active != nil && *req.Active != user.Active {
|
if req.Active != nil && *req.Active != user.Active {
|
||||||
if user.ID == session.UserID && !*req.Active {
|
if user.ID == session.UserID && !*req.Active {
|
||||||
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "不能禁用自己的账号"})
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "不能禁用自己的账号"})
|
||||||
}
|
}
|
||||||
if user.Role.Name == model.RoleAdmin && !*req.Active {
|
if currentRole.Name == model.RoleAdmin && !*req.Active {
|
||||||
var adminCount int64
|
var adminCount int64
|
||||||
db.Default.Session().Model(&model.User{}).
|
db.Default.Session().Model(&model.User{}).
|
||||||
Where("role_id = ? AND active = ? AND id != ?", user.RoleID, true, id).
|
Where("role_id = ? AND active = ? AND id != ?", user.RoleID, true, id).
|
||||||
@@ -149,16 +208,12 @@ func AdminUpdateUser() nf.HandlerFunc {
|
|||||||
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "没有需要更新的字段"})
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "没有需要更新的字段"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Default.Session().Model(user).Updates(updates).Error; err != nil {
|
if err := db.Default.Session().Model(&user).Updates(updates).Error; err != nil {
|
||||||
log.Error("handler.AdminUpdateUser: %s", err.Error())
|
log.Error("handler.AdminUpdateUser: %s", err.Error())
|
||||||
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "更新失败"})
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "更新失败"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Default.Session().Preload("Role").First(user, user.ID).Error; err != nil {
|
return c.Status(http.StatusOK).JSON(map[string]any{"data": toUserResp(user, currentRole)})
|
||||||
log.Error("handler.AdminUpdateUser: preload: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Status(http.StatusOK).JSON(map[string]any{"data": user})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,13 +229,18 @@ func AdminDeleteUser() nf.HandlerFunc {
|
|||||||
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "不能删除自己的账号"})
|
return c.Status(http.StatusBadRequest).JSON(map[string]string{"msg": "不能删除自己的账号"})
|
||||||
}
|
}
|
||||||
|
|
||||||
user := new(model.User)
|
var user model.User
|
||||||
if err := db.Default.Session().Preload("Role").First(user, id).Error; err != nil {
|
if err := db.Default.Session().First(&user, id).Error; err != nil {
|
||||||
return c.Status(http.StatusNotFound).JSON(map[string]string{"msg": "用户不存在"})
|
return c.Status(http.StatusNotFound).JSON(map[string]string{"msg": "用户不存在"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent deleting the last admin
|
// Prevent deleting the last admin: check via role name
|
||||||
if user.Role.Name == model.RoleAdmin {
|
var userRole model.Role
|
||||||
|
if err := db.Default.Session().First(&userRole, user.RoleID).Error; err != nil {
|
||||||
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "查询角色失败"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if userRole.Name == model.RoleAdmin {
|
||||||
var adminCount int64
|
var adminCount int64
|
||||||
db.Default.Session().Model(&model.User{}).
|
db.Default.Session().Model(&model.User{}).
|
||||||
Where("role_id = ? AND id != ?", user.RoleID, id).
|
Where("role_id = ? AND id != ?", user.RoleID, id).
|
||||||
@@ -190,7 +250,7 @@ func AdminDeleteUser() nf.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Default.Session().Delete(user).Error; err != nil {
|
if err := db.Default.Session().Delete(&user).Error; err != nil {
|
||||||
log.Error("handler.AdminDeleteUser: %s", err.Error())
|
log.Error("handler.AdminDeleteUser: %s", err.Error())
|
||||||
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "删除失败"})
|
return c.Status(http.StatusInternalServerError).JSON(map[string]string{"msg": "删除失败"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import "time"
|
|||||||
type Token struct {
|
type Token struct {
|
||||||
ID uint `gorm:"primarykey" json:"id"`
|
ID uint `gorm:"primarykey" json:"id"`
|
||||||
UserID uint `gorm:"not null;index" json:"user_id"`
|
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||||
User User `gorm:"foreignKey:UserID" json:"-"`
|
|
||||||
Name string `gorm:"not null" json:"name"`
|
Name string `gorm:"not null" json:"name"`
|
||||||
Token string `gorm:"uniqueIndex;not null" json:"-"`
|
Token string `gorm:"uniqueIndex;not null" json:"-"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ type User struct {
|
|||||||
Username string `gorm:"uniqueIndex;not null" json:"username"`
|
Username string `gorm:"uniqueIndex;not null" json:"username"`
|
||||||
Password string `gorm:"not null" json:"-"`
|
Password string `gorm:"not null" json:"-"`
|
||||||
RoleID uint `gorm:"not null" json:"role_id"`
|
RoleID uint `gorm:"not null" json:"role_id"`
|
||||||
Role Role `gorm:"foreignKey:RoleID" json:"role"`
|
|
||||||
Active bool `gorm:"default:true" json:"active"`
|
Active bool `gorm:"default:true" json:"active"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|||||||
Reference in New Issue
Block a user