diff --git a/cmd/server/user.go b/cmd/server/user.go index 20cd1a5..eb08d13 100644 --- a/cmd/server/user.go +++ b/cmd/server/user.go @@ -50,14 +50,14 @@ var userCreateCmd = &cobra.Command{ ctx := context.Background() - // 1. 尝试查询 admin 角色 + // 尝试查询 admin 角色 adminRole, err := db.Client.Role.Query(). Where(role.CodeEQ("admin")). Only(ctx) if err != nil { if ent.IsNotFound(err) { - // 2. 如果不存在 admin 角色,自动创建 + // 如果不存在 admin 角色,自动创建 adminRole, err = db.Client.Role.Create(). SetCode("admin"). SetName("超级管理员"). @@ -75,11 +75,11 @@ var userCreateCmd = &cobra.Command{ } } - // 3. 直接使用 db.Client 创建用户并挂载角色 + // 直接使用 db.Client 创建用户并挂载角色 u, err := db.Client.User.Create(). SetUsername(username). SetPassword(hashedPwd). - AddRoles(adminRole). // 💡 重点:挂载角色 + AddRoles(adminRole). Save(ctx) if err != nil { global.Log.Error("创建用户失败", zap.Error(err)) diff --git a/internal/api/enter.go b/internal/api/enter.go index 1391948..857e087 100644 --- a/internal/api/enter.go +++ b/internal/api/enter.go @@ -1,9 +1,13 @@ package api -import "EnGin/internal/api/auth_api" +import ( + "EnGin/internal/api/auth_api" + "EnGin/internal/api/user_api" +) type Api struct { AuthApi auth_api.AuthApi + UserApi user_api.UserApi } // App 统一导出全局 API 实例 diff --git a/internal/api/user_api/enter.go b/internal/api/user_api/enter.go new file mode 100644 index 0000000..779ee7a --- /dev/null +++ b/internal/api/user_api/enter.go @@ -0,0 +1,4 @@ +package user_api + +type UserApi struct { +} diff --git a/internal/api/user_api/user_list.go b/internal/api/user_api/user_list.go new file mode 100644 index 0000000..f29ffbf --- /dev/null +++ b/internal/api/user_api/user_list.go @@ -0,0 +1,53 @@ +package user_api + +import ( + "EnGin/internal/global" + "EnGin/internal/middleware" + "EnGin/internal/service/user_ser" + "EnGin/internal/utils/res" + "time" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type UserListRequest struct { + Page int `form:"page"` + Limit int `form:"limit"` + UsernameKey string `form:"username_key"` // 可选模糊搜索 +} + +type UserListResponse struct { + Username string `json:"username"` + Email string `json:"email"` + IP string `json:"ip"` + CreateAt time.Time `json:"create_at"` +} + +func (UserApi) UserListView(c *gin.Context) { + cr := middleware.GetBind[UserListRequest](c) + + list, count, err := user_ser.GetUserListWithPage( + c.Request.Context(), + cr.UsernameKey, + cr.Page, + cr.Limit, + ) + if err != nil { + global.Log.Error("查询用户列表失败", zap.Error(err)) + res.InternalError("获取用户列表失败", c) + return + } + + var users = make([]UserListResponse, 0, count) + for _, u := range list { + users = append(users, UserListResponse{ + Username: u.Username, + Email: u.Email, + IP: u.IP, + CreateAt: u.CreatedAt, + }) + } + + res.OkWithList(users, count, c) +} diff --git a/internal/conf/conf.go b/internal/conf/conf.go index 7a9e7ca..5ec2e6c 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -11,7 +11,7 @@ type Config struct { Server ServerConfig `mapstructure:"server" yaml:"server"` Database DatabaseConfig `mapstructure:"database" yaml:"database"` Logger LoggerConfig `mapstructure:"logger" yaml:"logger"` - Jwt JwtConfig `mapstructure:"logger" yaml:"jwt"` + Jwt JwtConfig `mapstructure:"jwt" yaml:"jwt"` Redis RedisConfig `mapstructure:"redis" yaml:"redis"` } diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 8888484..0203723 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -35,7 +35,7 @@ func Auth(c *gin.Context) { func AuthAdmin(c *gin.Context) { accessToken := c.GetHeader("access_token") if accessToken == "" { - res.Unauthorized("未携带 access_token", c) + res.Unauthorized("认证失败", c) c.Abort() return } @@ -47,7 +47,7 @@ func AuthAdmin(c *gin.Context) { return } - // 💡 预加载用户的关联角色列表 (WithRoles) + // 预加载用户的关联角色列表 (WithRoles) ctx := c.Request.Context() u, err := db.Client.User.Query(). Where(user.ID(int(claims.UserID))). @@ -60,7 +60,7 @@ func AuthAdmin(c *gin.Context) { return } - // 💡 遍历用户的角色列表,检查是否有 "admin" 角色代码 + // 遍历用户的角色列表,检查是否有 "admin" 角色代码 isAdmin := false for _, role := range u.Edges.Roles { if role.Code == "admin" { diff --git a/internal/router/enter.go b/internal/router/enter.go index bd55ff9..54abac5 100644 --- a/internal/router/enter.go +++ b/internal/router/enter.go @@ -9,4 +9,5 @@ func RegisterRoutes(r *gin.Engine) { // 分组并调用子路由 g := r.Group("api/v1") AuthRouter(g) + UserRouter(g) } diff --git a/internal/router/user_router.go b/internal/router/user_router.go new file mode 100644 index 0000000..57a5f11 --- /dev/null +++ b/internal/router/user_router.go @@ -0,0 +1,17 @@ +package router + +import ( + "EnGin/internal/api" + "EnGin/internal/api/user_api" + "EnGin/internal/middleware" + + "github.com/gin-gonic/gin" +) + +func UserRouter(g *gin.RouterGroup) { + app := api.App.UserApi + g.GET("users", + middleware.BindQueryMiddleware[user_api.UserListRequest], + middleware.AuthAdmin, + app.UserListView) +} diff --git a/internal/service/common/query.go b/internal/service/common/query.go new file mode 100644 index 0000000..cbb717d --- /dev/null +++ b/internal/service/common/query.go @@ -0,0 +1,49 @@ +package common + +import "context" + +// EntQuery 定义满足 Ent 所有查询构建器的泛型接口 +// Q 为具体的查询器类型(如 *ent.UserQuery),T 为返回的实体类型(如 *ent.User) +type EntQuery[Q any, T any] interface { + Limit(int) Q + Offset(int) Q + Count(ctx context.Context) (int, error) + All(ctx context.Context) ([]T, error) +} + +// QueryOptions 通用列表查询配置项 +type QueryOptions[Q any] struct { + Page int + Limit int + Query func(Q) Q // Query 闭包:在这里传入具体的过滤条件(Where)、排序(Order)和预加载(With) +} + +func QueryList[Q EntQuery[Q, T], T any](ctx context.Context, query Q, opts QueryOptions[Q]) (list []T, count int, err error) { + if opts.Query != nil { + query = opts.Query(query) + } + + count, err = query.Count(ctx) + if err != nil { + return nil, 0, err + } + + page := opts.Page + if page <= 0 { + page = 1 + } + + limit := opts.Limit + if limit <= 0 { + limit = 10 // 默认每页 10 条 + } + offset := (page - 1) * limit + + list, err = query.Limit(limit).Offset(offset).All(ctx) + + if err != nil { + return nil, 0, err + } + + return list, count, nil +} diff --git a/internal/service/user_ser/page.go b/internal/service/user_ser/page.go new file mode 100644 index 0000000..5fa85ec --- /dev/null +++ b/internal/service/user_ser/page.go @@ -0,0 +1,27 @@ +package user_ser + +import ( + "EnGin/internal/db" + "EnGin/internal/ent" + "EnGin/internal/ent/user" + "EnGin/internal/service/common" + "context" + + "entgo.io/ent/dialect/sql" +) + +func GetUserListWithPage(ctx context.Context, usernameKey string, page, limit int) ([]*ent.User, int, error) { + return common.QueryList(ctx, db.Client.User.Query(), common.QueryOptions[*ent.UserQuery]{ + Page: page, + Limit: limit, + Query: func(q *ent.UserQuery) *ent.UserQuery { + // 模糊匹配(编译期安全,杜绝拼写错误) + if usernameKey != "" { + q = q.Where(user.UsernameContains(usernameKey)) + } + + // 预加载关联角色,并按照创建时间倒序 + return q.WithRoles().Order(user.ByCreatedAt(sql.OrderDesc())) + }, + }) +} diff --git a/internal/utils/res/enter.go b/internal/utils/res/enter.go index e84040a..72034b3 100644 --- a/internal/utils/res/enter.go +++ b/internal/utils/res/enter.go @@ -61,7 +61,7 @@ func OkWithMsg(msg string, c *gin.Context) { Ok(gin.H{}, Success, msg, c) } -func OkWithList(list any, count int64, c *gin.Context) { +func OkWithList(list any, count int, c *gin.Context) { Ok(map[string]any{ "list": list, "count": count,