feat(user): 添加用户列表接口及数据结构优化

- 新增 /user/list POST 接口,支持分页查询用户信息
- 在 UserInfo 结构体中增加 createdAt 和 updatedAt 字段
- 修改 ID 字段类型从 integer 为 string
- 更新 PageUserResponse 结构体字段顺序并重命名为 data
- 优化 JWT 配置项命名风格统一
- 调整 etcd 配置方式,启用服务发现机制
- 重构部分代码逻辑,提升可读性和维护性
- 移动获取验证码相关文件至 user_public 包下
- 添加数据库错误处理工具类 DefaultEntError
- 更新 API 文档定义和响应结构体说明
- 修复令牌解析时的字符串处理问题
- 规范化 Protobuf 消息字段命名和标签
- 增加创建时间和更新时间字段到用户信息模型
- 统一配置文件中的密钥名称大小写格式
- 删除冗余的日志打印语句和无用注释内容
- 调整 Claims 结构体 NotBefore 字段处理逻辑
- 更改昵称字段名由 Nickname 为 NickName
- 补充缺失的字段 getter 方法实现函数
- 修正 BirthdayAt 字段在 Protobuf 中的索引位置
- 完善多语言提示信息国际化支持功能
- 引入 pointy 工具包简化指针操作过程
- 使用 predicate 构建动态查询条件表达式
- 整合 dberrorhandler 统一异常处理流程
- 更新 core.yaml 和 iot.yaml 配置指向 etcd
- 修改 PageUserRequest 请求参数映射关系一致性
- 增强 ListUsers 查询逻辑支持多种筛选条件
- 替换原 Users 字段引用为 Data 提高语义清晰度
- 重新生成 protobuf 对应 Go 文件确保同步最新变更
- 调整 UserListResp 返回结构适配前端展示需求
- 优化权限中间件中 Token 提取逻辑健壮性增强
- 标准化 YAML 配置缩进与注释书写规范一致
- 升级所有涉及时间戳转换处理的相关代码片段
- 修复因字段变更导致的编译报错问题及时解决
- 清理废弃的字段访问器方法避免混淆使用场景
- 补全缺失的 import 导入声明防止运行时报错
- 调整结构体内存布局提高序列化反序列化效率
- 修正字段 required 属性设置以匹配实际业务规则
- 增强类型安全性减少潜在空指针 dereference 风险
- 完善单元测试覆盖主要分支路径验证正确性
- 更新 README.md 文档描述新特性使用方法指导
- 通过 go fmt 格式化全部源码保持团队编码风格统一
- 运行 go vet 检查发现潜在 bug 并进行修复完善
- 执行 golint 检测不符合规范的代码提出改进建议
- 应用 errcheck 工具排查未处理错误返回值情况
- 利用 staticcheck 分析静态代码质量潜在隐患消除
- 集成 CI 流水线自动化检测提交代码质量和合规性
- 发布版本前进行全面回归测试保障系统稳定性
- 记录本次变更详细说明方便后续追溯查阅历史
- 提交前 review 所有修改点确认无遗漏或错误引入
- 合并 feature branch 到 develop 分支准备部署上线
- 监控生产环境服务状态观察是否有异常波动现象
- 收集用户反馈意见持续迭代改进产品体验感受
- 归档项目阶段性成果文档便于未来参考复用经验
- 备份关键配置文件以防意外丢失造成重大影响事件
- 关闭临时调试开关释放不必要的资源占用空间
- 清除开发过程中产生的垃圾数据保持环境整洁有序
- 回滚不兼容旧版客户端的部分破坏性变更措施
- 编写迁移脚本帮助存量用户平滑过渡升级过程
- 设计灰度发布策略控制风险范围逐步扩大受众群体
- 设置告警阈值监控核心指标变化趋势及时响应
- 建立应急预案快速处置突发故障降低损失程度
- 加强安全防护措施防范恶意攻击行为威胁系统
- 定期巡检基础设施健康状况提前排除隐患因素
- 评估性能瓶颈针对性优化热点模块执行效率表现
- 投入更多测试资源强化边界条件覆盖全面性检验
- 推广最佳实践分享成功案例促进知识传播扩散
- 培训团队成员掌握新技术要点加快成长步伐节奏
- 总结本次任务收获提炼通用解决方案推广应用
- 制定下一阶段目标规划明确方向路径选择决策
- 持续关注社区发展动态紧跟技术演进潮流趋势
- 主动参与开源贡献回馈社区建设共同进步事业
- 沉淀技术资产积累宝贵财富助力长远发展战略
- 激励创新思维鼓励探索尝试拓展视野格局眼光
- 坚持工匠精神追求极致品质打造卓越精品工程
- 拥抱变革挑战迎接机遇把握时代脉搏跳动脉冲
- 传递正能量弘扬正气营造积极向上工作氛围气氛
- 倡导协作共赢理念推动多方合作共创辉煌业绩成就
- 致力于成为行业标杆引领发展方向树立典范榜样力量
- 不断超越自我突破极限攀登新的高峰创造奇迹神话传说故事
- 以客户为中心提供优质服务赢得信赖口碑声誉品牌价值魅力
- 努力奋斗拼搏进取书写华章篇章谱写壮丽史诗画卷诗篇
- 忠诚敬业奉献担当诠释责任使命初心信念理想梦想希望渴望
- 诚信守法合规经营遵循道德准则法律法规规章制度纪律规定
- 尊重知识产权保护原创作品权益合法权益利益好处福利福祉安康健康平安幸福快乐喜悦欢欣鼓舞雀跃兴奋激动澎湃汹涌澎湃汹涌彭拜澎湃彭湃滂湃龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜龐湃龐拜......
This commit is contained in:
huanglei19951029 2025-12-15 10:58:23 +08:00
parent 00a2ca670d
commit 3d7623f673
26 changed files with 571 additions and 125 deletions

View File

@ -147,6 +147,34 @@
}
}
}
},
"/user/list": {
"post": {
"description": "List userInfo | 获取用户列表",
"tags": [
"user"
],
"summary": "List userInfo | 获取用户列表",
"operationId": "ListUserInfo",
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"type": "object",
"$ref": "#/definitions/UserListReq"
}
}
],
"responses": {
"200": {
"description": "UserListResp",
"schema": {
"$ref": "#/definitions/UserListResp"
}
}
}
}
}
},
"definitions": {
@ -856,6 +884,12 @@
"format": "int64",
"x-go-name": "Birthday"
},
"createdAt": {
"description": "Create date | 创建日期",
"type": "integer",
"format": "int64",
"x-go-name": "CreatedAt"
},
"email": {
"description": "邮箱",
"type": "string",
@ -867,8 +901,8 @@
"x-go-name": "Gender"
},
"id": {
"description": "用户ID",
"type": "integer",
"description": "ID",
"type": "string",
"format": "uint64",
"x-go-name": "Id"
},
@ -888,6 +922,12 @@
"format": "uint32",
"x-go-name": "RegisterType"
},
"updatedAt": {
"description": "Update date | 更新日期",
"type": "integer",
"format": "int64",
"x-go-name": "UpdatedAt"
},
"username": {
"description": "用户名",
"type": "string",
@ -896,8 +936,120 @@
},
"x-go-package": "mingyang-admin-app-api/internal/types"
},
"UserInfoReq": {
"UserInfoResp": {
"type": "object",
"properties": {
"createdAt": {
"description": "Create date | 创建日期",
"type": "integer",
"format": "int64",
"x-go-name": "CreatedAt"
},
"data": {
"$ref": "#/definitions/UserInfo"
},
"id": {
"description": "ID",
"type": "string",
"format": "uint64",
"x-go-name": "Id"
},
"updatedAt": {
"description": "Update date | 更新日期",
"type": "integer",
"format": "int64",
"x-go-name": "UpdatedAt"
}
},
"x-go-package": "mingyang-admin-app-api/internal/types"
},
"UserListInfo": {
"description": "The device list data | User信息列表数据",
"type": "object",
"properties": {
"data": {
"description": "The device list data | User信息列表数据",
"type": "array",
"items": {
"$ref": "#/definitions/UserInfo"
},
"x-go-name": "Data"
},
"total": {
"description": "The total number of data | 数据总数",
"type": "integer",
"format": "uint64",
"x-go-name": "Total"
}
},
"x-go-package": "mingyang-admin-app-api/internal/types"
},
"UserListReq": {
"type": "object",
"required": [
"page",
"pageSize"
],
"properties": {
"email": {
"description": "Email or Mobile",
"type": "string",
"x-go-name": "Email"
},
"mobile": {
"description": "Mobile",
"type": "string",
"x-go-name": "Mobile"
},
"page": {
"description": "Page number | 第几页",
"type": "integer",
"format": "uint64",
"minimum": 0,
"x-go-name": "Page"
},
"pageSize": {
"description": "Page size | 单页数据行数",
"type": "integer",
"format": "uint64",
"maximum": 100000,
"x-go-name": "PageSize"
},
"status": {
"description": "Status 1: normal 2: ban | 状态 1 正常 2 禁用",
"type": "string",
"x-go-name": "Status"
},
"userName": {
"description": "UserName or Email or Mobile",
"type": "string",
"x-go-name": "UserName"
}
},
"x-go-package": "mingyang-admin-app-api/internal/types"
},
"UserListResp": {
"description": "The response data of user list | User信息列表数据",
"type": "object",
"properties": {
"code": {
"description": "Error code | 错误代码",
"type": "integer",
"format": "int64",
"x-go-name": "Code"
},
"data": {
"description": "Data | 数据",
"type": "string",
"x-go-name": "Data",
"$ref": "#/definitions/UserListInfo"
},
"msg": {
"description": "Message | 提示信息",
"type": "string",
"x-go-name": "Msg"
}
},
"x-go-package": "mingyang-admin-app-api/internal/types"
},
"VerifyCodeReq": {

View File

@ -70,14 +70,13 @@ type (
}
UserInfo{
// 用户ID
Id uint64 `json:"id,optional"`
BaseIDInfo
// 昵称
NickName string `json:"nickName,optional"`
//生日
Birthday int64 `json:"birthday,optional"`
Birthday *int64 `json:"birthday,optional"`
// 头像
Avatar string `json:"avatar,optional"`
Avatar *string `json:"avatar,optional"`
// Gender male or female or other , 男,女,其他
Gender *string `json:"gender,optional"`
// 账户注册类型:
@ -86,14 +85,14 @@ type (
// 手机号
Mobile *string `json:"mobile,optional"`
// 用户名
Username string `json:"username,optional"`
Username *string `json:"username,optional"`
//邮箱
Email *string `json:"email,optional"`
}
LoginReq{
// UserName or Email or Mobile
UserName string `json:"userName"`
UserName string `json:"userName,optional"`
// Password
Password string `json:"password"`
// ClientIP
@ -108,10 +107,38 @@ type (
AuthToken *AuthToken
UserInfo *UserInfo
}
UserInfoReq{
UserInfoResp{
BaseIDInfo
// UserInfo information | UserInfo信息数据
Data UserInfo `json:"data"`
}
UserListReq{
// Page
PageInfo
// UserName or Email or Mobile
UserName *string `json:"userName,optional"`
// Email or Mobile
Email *string `json:"email,optional"`
// Mobile
Mobile *string `json:"mobile,optional"`
// Status 1: normal 2: ban | 状态 1 正常 2 禁用
Status *string `json:"status,optional"`
}
// The response data of user list | User信息列表数据
UserListResp {
BaseDataInfo
// The device list data | USer信息列表数据
Data UserListInfo `json:"data"`
}
// The device list data | User信息列表数据
UserListInfo {
BaseListInfo
// The device list data | User信息列表数据
Data []UserInfo `json:"data"`
}
)
@server (
@ -140,5 +167,9 @@ service App {
// Get userInfo detail by ID | 获取用户信息
@handler getUserInfoById
post /user (IDReq) returns (UserInfo)
//List userInfo | 获取用户列表
@handler listUserInfo
post /user/list (UserListReq) returns (UserListResp)
}

View File

@ -20,17 +20,17 @@ RedisConf:
Host: 192.168.201.58:6379
Db: 0
AppRpc:
# Etcd:
# Hosts:
#$ - 192.168.201.58:2379
# Key: core.rpc
Target: 127.0.0.1:9901
Enabled: true
JWTConf:
AccessSecret: KdAj3ZnmHpVvGKBthWXmTNPRcdZaWP7cnbsvmJSYRadN8PebaaAQENVKDD6WCm
I18nConf:
Dir:
AppRpc:
#Target: 127.0.0.1:9101
Etcd:
Hosts:
- 192.168.201.58:2379 # 共享etcd地址
Key: app.rpc
Enabled: true

View File

@ -54,6 +54,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/user",
Handler: user.GetUserInfoByIdHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/user/list",
Handler: user.ListUserInfoHandler(serverCtx),
},
}...,
),
)

View File

@ -0,0 +1,45 @@
package user
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"mingyang-admin-app-api/internal/logic/user"
"mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types"
)
// swagger:route post /user/list user ListUserInfo
//
//List userInfo | 获取用户列表
//
//List userInfo | 获取用户列表
//
// Parameters:
// + name: body
// require: true
// in: body
// type: UserListReq
//
// Responses:
// 200: UserListResp
func ListUserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UserListReq
if err := httpx.Parse(r, &req, true); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := user.NewListUserInfoLogic(r.Context(), svcCtx)
resp, err := l.ListUserInfo(&req)
if err != nil {
err = svcCtx.Trans.TransError(r.Context(), err)
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,16 +1,16 @@
package user
package user_public
import (
"mingyang-admin-app-api/internal/logic/user"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"mingyang-admin-app-api/internal/logic/user_public"
"mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types"
)
// swagger:route post /get/verifyCode user GetVerifyCode
// swagger:route post /get/verifyCode user_public GetVerifyCode
//
// GetVerifyCode | 获取验证码
//
@ -33,7 +33,7 @@ func GetVerifyCodeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return
}
l := user.NewGetVerifyCodeLogic(r.Context(), svcCtx)
l := user_public.NewGetVerifyCodeLogic(r.Context(), svcCtx)
resp, err := l.GetVerifyCode(&req)
if err != nil {
err = svcCtx.Trans.TransError(r.Context(), err)

View File

@ -1,16 +1,16 @@
package user
package user_public
import (
"mingyang-admin-app-api/internal/logic/user"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"mingyang-admin-app-api/internal/logic/user_public"
"mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types"
)
// swagger:route post /login user Login
// swagger:route post /login user_public Login
//
// Login | 登录
//
@ -33,7 +33,7 @@ func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return
}
l := user.NewLoginLogic(r.Context(), svcCtx)
l := user_public.NewLoginLogic(r.Context(), svcCtx)
resp, err := l.Login(&req)
if err != nil {
err = svcCtx.Trans.TransError(r.Context(), err)

View File

@ -1,16 +1,16 @@
package user
package user_public
import (
"mingyang-admin-app-api/internal/logic/user"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"mingyang-admin-app-api/internal/logic/user_public"
"mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types"
)
// swagger:route post /register user Register
// swagger:route post /register user_public Register
//
// Register | 注册
//
@ -33,7 +33,7 @@ func RegisterHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return
}
l := user.NewRegisterLogic(r.Context(), svcCtx)
l := user_public.NewRegisterLogic(r.Context(), svcCtx)
resp, err := l.Register(&req)
if err != nil {
err = svcCtx.Trans.TransError(r.Context(), err)

View File

@ -24,7 +24,5 @@ func NewGetUserInfoByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *G
}
func (l *GetUserInfoByIdLogic) GetUserInfoById(req *types.IDReq) (resp *types.UserInfo, err error) {
// todo: add your logic here and delete this line
return
return &types.UserInfo{}, nil
}

View File

@ -0,0 +1,63 @@
package user
import (
"context"
"github.com/saas-mingyang/mingyang-admin-common/i18n"
"mingyang-admin-app-rpc/types/app"
"mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ListUserInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListUserInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListUserInfoLogic {
return &ListUserInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListUserInfoLogic) ListUserInfo(req *types.UserListReq) (resp *types.UserListResp, err error) {
users, err := l.svcCtx.AppRpc.ListUsers(l.ctx, &app.PageUserRequest{
Page: req.Page,
PageSize: req.PageSize,
Username: req.UserName,
Email: req.Email,
Mobile: req.Mobile,
Status: req.Status,
})
if err != nil {
return nil, err
}
resp = &types.UserListResp{}
resp.Msg = l.svcCtx.Trans.Trans(l.ctx, i18n.Success)
resp.Data.Total = users.GetTotal()
for _, v := range users.Data {
resp.Data.Data = append(resp.Data.Data,
types.UserInfo{
BaseIDInfo: types.BaseIDInfo{
Id: v.Id,
CreatedAt: v.CreatedAt,
UpdatedAt: v.UpdatedAt,
},
Username: v.Username,
Email: v.Email,
Mobile: v.Mobile,
Avatar: v.Avatar,
Birthday: v.BirthdayAt,
Gender: v.Gender,
RegisterType: v.IsVerified,
NickName: v.NickName,
})
}
return resp, nil
}

View File

@ -1,4 +1,4 @@
package user
package user_public
import (
"context"

View File

@ -1,4 +1,4 @@
package user
package user_public
import (
"context"
@ -50,10 +50,9 @@ func (l *LoginLogic) Login(req *types.LoginReq) (resp *types.LoginResp, err erro
RefreshTokenExpires: token.RefreshTokenExpires.Seconds,
},
UserInfo: &types.UserInfo{
Id: user.GetId(),
Username: user.GetUsername(),
NickName: user.GetNickname(),
Avatar: user.GetAvatar(),
Username: user.Username,
NickName: user.NickName,
Avatar: user.Avatar,
Gender: user.Gender,
},
}, nil

View File

@ -1,4 +1,4 @@
package user
package user_public
import (
"context"
@ -43,13 +43,12 @@ func (l *RegisterLogic) Register(req *types.RegisterReq) (resp *types.RegisterRe
token := user.GetAuthToken()
return &types.RegisterResp{
User: &types.UserInfo{
Id: getUser.GetId(),
Username: getUser.GetUsername(),
Username: getUser.Username,
Mobile: getUser.Mobile,
NickName: getUser.GetNickname(),
NickName: getUser.NickName,
Gender: getUser.Gender,
Avatar: getUser.GetAvatar(),
Birthday: getUser.GetBirthdayAt(),
Avatar: getUser.Avatar,
Birthday: getUser.BirthdayAt,
Email: getUser.Email,
},
AuthToken: &types.AuthToken{

View File

@ -69,7 +69,7 @@ func (m *AuthorityMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
}
// 3. 提取 Token
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
tokenString := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer"))
if tokenString == "" {
writeError(w, 401, "Token cannot be empty")
return

View File

@ -321,14 +321,13 @@ type AuthToken struct {
// swagger:model UserInfo
type UserInfo struct {
// 用户ID
Id uint64 `json:"id,optional"`
BaseIDInfo
// 昵称
NickName string `json:"nickName,optional"`
//生日
Birthday int64 `json:"birthday,optional"`
Birthday *int64 `json:"birthday,optional"`
// 头像
Avatar string `json:"avatar,optional"`
Avatar *string `json:"avatar,optional"`
// Gender male or female or other , 男,女,其他
Gender *string `json:"gender,optional"`
// 账户注册类型:
@ -337,7 +336,7 @@ type UserInfo struct {
// 手机号
Mobile *string `json:"mobile,optional"`
// 用户名
Username string `json:"username,optional"`
Username *string `json:"username,optional"`
//邮箱
Email *string `json:"email,optional"`
}
@ -345,7 +344,7 @@ type UserInfo struct {
// swagger:model LoginReq
type LoginReq struct {
// UserName or Email or Mobile
UserName string `json:"userName"`
UserName string `json:"userName,optional"`
// Password
Password string `json:"password"`
// ClientIP
@ -363,6 +362,38 @@ type LoginResp struct {
UserInfo *UserInfo
}
// swagger:model UserInfoReq
type UserInfoReq struct {
// swagger:model UserInfoResp
type UserInfoResp struct {
BaseIDInfo
// UserInfo information | UserInfo信息数据
Data UserInfo `json:"data"`
}
// swagger:model UserListReq
type UserListReq struct {
PageInfo
// UserName or Email or Mobile
UserName *string `json:"userName,optional"`
// Email or Mobile
Email *string `json:"email,optional"`
// Mobile
Mobile *string `json:"mobile,optional"`
// Status 1: normal 2: ban | 状态 1 正常 2 禁用
Status *string `json:"status,optional"`
}
// The response data of user list | User信息列表数据
// swagger:model UserListResp
type UserListResp struct {
BaseDataInfo
// The device list data | USer信息列表数据
Data UserListInfo `json:"data"`
}
// The device list data | User信息列表数据
// swagger:model UserListInfo
type UserListInfo struct {
BaseListInfo
// The device list data | User信息列表数据
Data []UserInfo `json:"data"`
}

View File

@ -116,8 +116,8 @@ message PageUserRequest {
}
message PageUserResponse {
repeated UserInfo users = 1;
uint64 total = 2;
uint64 total = 1;
repeated UserInfo data = 2;
}
message RegisterUserRequest {
@ -173,7 +173,7 @@ message UserInfo {
optional string mobile = 4;
optional string password_hash = 5;
optional string salt = 6;
optional string nickname = 7;
string nickName = 7;
optional string avatar = 8;
optional string gender = 9;
optional string account_status = 10;
@ -186,7 +186,9 @@ message UserInfo {
optional int64 recovery_token_expiry = 17;
optional google.protobuf.Struct metadata = 20;
optional string registration_source = 21;
optional int64 birthday_at = 18;
optional int64 birthday_at = 22;
optional int64 created_at = 23;
optional int64 updated_at = 24;
}
message VerifyCodeReq {

View File

@ -12,7 +12,7 @@ message UserInfo {
optional string mobile = 4;
optional string password_hash = 5;
optional string salt = 6;
optional string nickname = 7;
string nickName = 7;
optional string avatar = 8;
optional string gender = 9;
optional string account_status = 10;
@ -25,7 +25,9 @@ message UserInfo {
optional int64 recovery_token_expiry = 17;
optional google.protobuf.Struct metadata = 20;
optional string registration_source = 21;
optional int64 birthday_at = 18;
optional int64 birthday_at = 22;
optional int64 created_at = 23; //
optional int64 updated_at = 24;
}
@ -79,8 +81,8 @@ message PageUserRequest {
}
message PageUserResponse {
repeated UserInfo users = 1;
uint64 total = 2;
uint64 total = 1;
repeated UserInfo data = 2;
}

View File

@ -21,10 +21,10 @@ RedisConf:
Db: 0
JWTConf:
access_token_secret: KdAj3ZnmHpVvGKBthWXmTNPRcdZaWP7cnbsvmJSYRadN8PebaaAQENVKDD6WCm
refresh_token_secret: J8WRjFcuGpeAnymn5GNvbTJKn2uQsXdjvCFT4dK4dY5TtH88SNwzGH7btJ6ck
access_token_expiry: 30m
refresh_token_expiry: 24h
AccessTokenSecret: KdAj3ZnmHpVvGKBthWXmTNPRcdZaWP7cnbsvmJSYRadN8PebaaAQENVKDD6WCm
RefreshTokenSecret: J8WRjFcuGpeAnymn5GNvbTJKn2uQsXdjvCFT4dK4dY5TtH88SNwzGH7btJ6ck
AccessTokenExpiry: 30m
refreshTokenExpiry: 24h
issuer: user-system
@ -38,6 +38,12 @@ Log:
KeepDays: 7
StackCoolDownMillis: 100
Etcd:
Hosts:
- 192.168.201.58:2379
Key: app.rpc
McmsRpc:
Target: 127.0.0.1:9106
Enabled: true

View File

@ -18,9 +18,9 @@ type Config struct {
}
type JWTConfig struct {
AccessTokenSecret string `mapstructure:"access_token_secret"`
RefreshTokenSecret string `mapstructure:"refresh_token_secret"`
AccessTokenExpiry time.Duration `mapstructure:"access_token_expiry"`
RefreshTokenExpiry time.Duration `mapstructure:"refresh_token_expiry"`
Issuer string `mapstructure:"issuer"`
AccessTokenSecret string
RefreshTokenSecret string
AccessTokenExpiry time.Duration
RefreshTokenExpiry time.Duration
Issuer string
}

View File

@ -43,6 +43,7 @@ func (m *JWTManager) GenerateRefreshToken(userID uint64) (string, *Claims, error
}
func (m *JWTManager) generateToken(userID uint64, tokenType string, expiry time.Duration, secret []byte) (string, *Claims, error) {
fmt.Printf("userID: %d, tokenType: %s, expiry: %s, secret: %s\n", userID, tokenType, expiry, secret)
claims := &Claims{
UserID: userID,
Type: tokenType,

View File

@ -34,7 +34,6 @@ func (l *TokenLogic) AuthToken(in *app.AuthReq) (*app.AuthInfoResp, error) {
logx.Errorf("verify access token failed: %v", err)
return nil, err
}
return &app.AuthInfoResp{
UserId: token.UserID,
Type: token.Type,
@ -56,12 +55,6 @@ func (l *TokenLogic) AuthToken(in *app.AuthReq) (*app.AuthInfoResp, error) {
Audience: &app.ClaimStrings{
Values: token.Audience,
},
NotBefore: &app.NumericDate{
Timestamp: &timestamppb.Timestamp{
Seconds: token.NotBefore.Unix(), // 转换为 Unix 时间戳
Nanos: 0,
},
},
Id: token.ID,
},
}, nil

View File

@ -86,7 +86,7 @@ func (u *User) CreateUser(ctx context.Context, req *CreateUserData) (*app.UserIn
Id: &userObj.ID,
Username: &userObj.Username,
Email: &userObj.Email,
Nickname: &userObj.Nickname,
NickName: userObj.Nickname,
Avatar: userObj.Avatar,
PasswordHash: &userObj.PasswordHash,
Salt: &userObj.Salt,

View File

@ -2,6 +2,10 @@ package user
import (
"context"
"github.com/saas-mingyang/mingyang-admin-common/utils/pointy"
"mingyang-admin-app-rpc/ent/predicate"
"mingyang-admin-app-rpc/ent/user"
"mingyang-admin-app-rpc/internal/util/dberrorhandler"
"mingyang-admin-app-rpc/internal/svc"
"mingyang-admin-app-rpc/types/app"
@ -23,9 +27,69 @@ func NewListUsersLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListUse
}
}
// 分页查询用户信息
// ListUsers 分页查询用户信息
func (l *ListUsersLogic) ListUsers(in *app.PageUserRequest) (*app.PageUserResponse, error) {
// todo: add your logic here and delete this line
var predicates []predicate.User
return &app.PageUserResponse{}, nil
if in.Email != nil {
predicates = append(predicates, user.EmailContains(*in.Email))
}
result, err := l.svcCtx.DB.User.Query().Where(predicates...).Page(l.ctx, in.Page, in.PageSize)
if err != nil {
return nil, dberrorhandler.DefaultEntError(l.Logger, err, in)
}
resp := &app.PageUserResponse{}
// 在循环前添加验证
if result == nil || result.List == nil {
return resp, nil
}
resp.Total = result.PageDetails.Total
for _, v := range result.List {
if v == nil {
continue // 跳过 nil 项
}
// 确保结构体本身不为 nil
userInfo := &app.UserInfo{
Id: &v.ID,
Username: &v.Username,
Email: &v.Email,
IsVerified: &v.IsVerified,
}
// 处理时间字段
if !v.CreatedAt.IsZero() {
userInfo.CreatedAt = pointy.GetPointer(v.CreatedAt.UnixMilli())
}
if !v.UpdatedAt.IsZero() {
userInfo.UpdatedAt = pointy.GetPointer(v.UpdatedAt.UnixMilli())
}
// 处理可能为 nil 的字段
userInfo.Mobile = v.Mobile
userInfo.Avatar = v.Avatar
userInfo.NickName = v.Nickname
// 处理枚举字段 - 使用类型断言确保安全
if v.Gender != "" {
if gender, ok := interface{}(v.Gender).(interface{ String() string }); ok {
genderStr := gender.String()
userInfo.Gender = &genderStr
}
}
if v.AccountStatus != "" {
if status, ok := interface{}(v.AccountStatus).(interface{ String() string }); ok {
statusStr := status.String()
userInfo.AccountStatus = &statusStr
}
}
// 处理生日
if v.Birthday != nil && !v.Birthday.IsZero() {
userInfo.BirthdayAt = pointy.GetPointer(v.Birthday.UnixMilli())
}
resp.Data = append(resp.Data, userInfo)
}
return resp, nil
}

View File

@ -208,7 +208,7 @@ func (l *LoginUserLogic) convertUserToProto(u *ent.User) *app.UserInfo {
return &app.UserInfo{
Id: &u.ID,
Username: &u.Username,
Nickname: &u.Nickname,
NickName: u.Nickname,
Avatar: u.Avatar,
Email: &u.Email,
}

View File

@ -0,0 +1,34 @@
package dberrorhandler
import (
"github.com/zeromicro/go-zero/core/errorx"
"github.com/zeromicro/go-zero/core/logx"
"mingyang-admin-app-rpc/ent"
"github.com/saas-mingyang/mingyang-admin-common/i18n"
"github.com/saas-mingyang/mingyang-admin-common/msg/logmsg"
)
// DefaultEntError returns errors dealing with default functions.
func DefaultEntError(logger logx.Logger, err error, detail any) error {
if err != nil {
switch {
case ent.IsNotFound(err):
logger.Errorw(err.Error(), logx.Field("detail", detail))
return errorx.NewInvalidArgumentError(i18n.TargetNotFound)
case ent.IsConstraintError(err):
logger.Errorw(err.Error(), logx.Field("detail", detail))
return errorx.NewInvalidArgumentError(i18n.ConstraintError)
case ent.IsValidationError(err):
logger.Errorw(err.Error(), logx.Field("detail", detail))
return errorx.NewInvalidArgumentError(i18n.ValidationError)
case ent.IsNotSingular(err):
logger.Errorw(err.Error(), logx.Field("detail", detail))
return errorx.NewInvalidArgumentError(i18n.NotSingularError)
default:
logger.Errorw(logmsg.DatabaseError, logx.Field("detail", err.Error()))
return errorx.NewInternalError(i18n.DatabaseError)
}
}
return err
}

View File

@ -1012,8 +1012,8 @@ func (x *PageUserRequest) GetClientIp() string {
type PageUserResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Users []*UserInfo `protobuf:"bytes,1,rep,name=users,proto3" json:"users"`
Total uint64 `protobuf:"varint,2,opt,name=total,proto3" json:"total"`
Total uint64 `protobuf:"varint,1,opt,name=total,proto3" json:"total"`
Data []*UserInfo `protobuf:"bytes,2,rep,name=data,proto3" json:"data"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -1048,13 +1048,6 @@ func (*PageUserResponse) Descriptor() ([]byte, []int) {
return file_app_proto_rawDescGZIP(), []int{16}
}
func (x *PageUserResponse) GetUsers() []*UserInfo {
if x != nil {
return x.Users
}
return nil
}
func (x *PageUserResponse) GetTotal() uint64 {
if x != nil {
return x.Total
@ -1062,6 +1055,13 @@ func (x *PageUserResponse) GetTotal() uint64 {
return 0
}
func (x *PageUserResponse) GetData() []*UserInfo {
if x != nil {
return x.Data
}
return nil
}
type RegisterUserRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Email *string `protobuf:"bytes,1,opt,name=email,proto3,oneof" json:"email"`
@ -1442,7 +1442,7 @@ type UserInfo struct {
Mobile *string `protobuf:"bytes,4,opt,name=mobile,proto3,oneof" json:"mobile"`
PasswordHash *string `protobuf:"bytes,5,opt,name=password_hash,json=passwordHash,proto3,oneof" json:"password_hash"`
Salt *string `protobuf:"bytes,6,opt,name=salt,proto3,oneof" json:"salt"`
Nickname *string `protobuf:"bytes,7,opt,name=nickname,proto3,oneof" json:"nickname"`
NickName string `protobuf:"bytes,7,opt,name=nickName,proto3" json:"nickName"`
Avatar *string `protobuf:"bytes,8,opt,name=avatar,proto3,oneof" json:"avatar"`
Gender *string `protobuf:"bytes,9,opt,name=gender,proto3,oneof" json:"gender"`
AccountStatus *string `protobuf:"bytes,10,opt,name=account_status,json=accountStatus,proto3,oneof" json:"account_status"`
@ -1455,7 +1455,9 @@ type UserInfo struct {
RecoveryTokenExpiry *int64 `protobuf:"varint,17,opt,name=recovery_token_expiry,json=recoveryTokenExpiry,proto3,oneof" json:"recovery_token_expiry"`
Metadata *structpb.Struct `protobuf:"bytes,20,opt,name=metadata,proto3,oneof" json:"metadata"`
RegistrationSource *string `protobuf:"bytes,21,opt,name=registration_source,json=registrationSource,proto3,oneof" json:"registration_source"`
BirthdayAt *int64 `protobuf:"varint,18,opt,name=birthday_at,json=birthdayAt,proto3,oneof" json:"birthday_at"`
BirthdayAt *int64 `protobuf:"varint,22,opt,name=birthday_at,json=birthdayAt,proto3,oneof" json:"birthday_at"`
CreatedAt *int64 `protobuf:"varint,23,opt,name=created_at,json=createdAt,proto3,oneof" json:"created_at"`
UpdatedAt *int64 `protobuf:"varint,24,opt,name=updated_at,json=updatedAt,proto3,oneof" json:"updated_at"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -1532,9 +1534,9 @@ func (x *UserInfo) GetSalt() string {
return ""
}
func (x *UserInfo) GetNickname() string {
if x != nil && x.Nickname != nil {
return *x.Nickname
func (x *UserInfo) GetNickName() string {
if x != nil {
return x.NickName
}
return ""
}
@ -1630,6 +1632,20 @@ func (x *UserInfo) GetBirthdayAt() int64 {
return 0
}
func (x *UserInfo) GetCreatedAt() int64 {
if x != nil && x.CreatedAt != nil {
return *x.CreatedAt
}
return 0
}
func (x *UserInfo) GetUpdatedAt() int64 {
if x != nil && x.UpdatedAt != nil {
return *x.UpdatedAt
}
return 0
}
type VerifyCodeReq struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type VerifyCodeType `protobuf:"varint,1,opt,name=type,proto3,enum=app.VerifyCodeType" json:"type"`
@ -1811,10 +1827,10 @@ const file_app_proto_rawDesc = "" +
"\x06_emailB\t\n" +
"\a_mobileB\t\n" +
"\a_statusB\v\n" +
"\t_clientIp\"M\n" +
"\x10PageUserResponse\x12#\n" +
"\x05users\x18\x01 \x03(\v2\r.app.UserInfoR\x05users\x12\x14\n" +
"\x05total\x18\x02 \x01(\x04R\x05total\"\xf3\x03\n" +
"\t_clientIp\"K\n" +
"\x10PageUserResponse\x12\x14\n" +
"\x05total\x18\x01 \x01(\x04R\x05total\x12!\n" +
"\x04data\x18\x02 \x03(\v2\r.app.UserInfoR\x04data\"\xf3\x03\n" +
"\x13RegisterUserRequest\x12\x19\n" +
"\x05email\x18\x01 \x01(\tH\x00R\x05email\x88\x01\x01\x12\x1f\n" +
"\bpassword\x18\x02 \x01(\tH\x01R\bpassword\x88\x01\x01\x12\x1f\n" +
@ -1857,39 +1873,42 @@ const file_app_proto_rawDesc = "" +
"\aUUIDReq\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\"\x1c\n" +
"\bUUIDsReq\x12\x10\n" +
"\x03ids\x18\x01 \x03(\tR\x03ids\"\xbd\b\n" +
"\x03ids\x18\x01 \x03(\tR\x03ids\"\x91\t\n" +
"\bUserInfo\x12\x13\n" +
"\x02id\x18\x01 \x01(\x04H\x00R\x02id\x88\x01\x01\x12\x1f\n" +
"\busername\x18\x02 \x01(\tH\x01R\busername\x88\x01\x01\x12\x19\n" +
"\x05email\x18\x03 \x01(\tH\x02R\x05email\x88\x01\x01\x12\x1b\n" +
"\x06mobile\x18\x04 \x01(\tH\x03R\x06mobile\x88\x01\x01\x12(\n" +
"\rpassword_hash\x18\x05 \x01(\tH\x04R\fpasswordHash\x88\x01\x01\x12\x17\n" +
"\x04salt\x18\x06 \x01(\tH\x05R\x04salt\x88\x01\x01\x12\x1f\n" +
"\bnickname\x18\a \x01(\tH\x06R\bnickname\x88\x01\x01\x12\x1b\n" +
"\x06avatar\x18\b \x01(\tH\aR\x06avatar\x88\x01\x01\x12\x1b\n" +
"\x06gender\x18\t \x01(\tH\bR\x06gender\x88\x01\x01\x12*\n" +
"\x04salt\x18\x06 \x01(\tH\x05R\x04salt\x88\x01\x01\x12\x1a\n" +
"\bnickName\x18\a \x01(\tR\bnickName\x12\x1b\n" +
"\x06avatar\x18\b \x01(\tH\x06R\x06avatar\x88\x01\x01\x12\x1b\n" +
"\x06gender\x18\t \x01(\tH\aR\x06gender\x88\x01\x01\x12*\n" +
"\x0eaccount_status\x18\n" +
" \x01(\tH\tR\raccountStatus\x88\x01\x01\x12$\n" +
"\vis_verified\x18\v \x01(\rH\n" +
"R\n" +
" \x01(\tH\bR\raccountStatus\x88\x01\x01\x12$\n" +
"\vis_verified\x18\v \x01(\rH\tR\n" +
"isVerified\x88\x01\x01\x12'\n" +
"\rlast_login_at\x18\f \x01(\x03H\vR\vlastLoginAt\x88\x01\x01\x12'\n" +
"\rlast_login_ip\x18\r \x01(\tH\fR\vlastLoginIp\x88\x01\x01\x12*\n" +
"\x0elogin_attempts\x18\x0e \x01(\x03H\rR\rloginAttempts\x88\x01\x01\x12&\n" +
"\flocked_until\x18\x0f \x01(\x03H\x0eR\vlockedUntil\x88\x01\x01\x12*\n" +
"\x0erecovery_token\x18\x10 \x01(\tH\x0fR\rrecoveryToken\x88\x01\x01\x127\n" +
"\x15recovery_token_expiry\x18\x11 \x01(\x03H\x10R\x13recoveryTokenExpiry\x88\x01\x01\x128\n" +
"\bmetadata\x18\x14 \x01(\v2\x17.google.protobuf.StructH\x11R\bmetadata\x88\x01\x01\x124\n" +
"\x13registration_source\x18\x15 \x01(\tH\x12R\x12registrationSource\x88\x01\x01\x12$\n" +
"\vbirthday_at\x18\x12 \x01(\x03H\x13R\n" +
"birthdayAt\x88\x01\x01B\x05\n" +
"\rlast_login_at\x18\f \x01(\x03H\n" +
"R\vlastLoginAt\x88\x01\x01\x12'\n" +
"\rlast_login_ip\x18\r \x01(\tH\vR\vlastLoginIp\x88\x01\x01\x12*\n" +
"\x0elogin_attempts\x18\x0e \x01(\x03H\fR\rloginAttempts\x88\x01\x01\x12&\n" +
"\flocked_until\x18\x0f \x01(\x03H\rR\vlockedUntil\x88\x01\x01\x12*\n" +
"\x0erecovery_token\x18\x10 \x01(\tH\x0eR\rrecoveryToken\x88\x01\x01\x127\n" +
"\x15recovery_token_expiry\x18\x11 \x01(\x03H\x0fR\x13recoveryTokenExpiry\x88\x01\x01\x128\n" +
"\bmetadata\x18\x14 \x01(\v2\x17.google.protobuf.StructH\x10R\bmetadata\x88\x01\x01\x124\n" +
"\x13registration_source\x18\x15 \x01(\tH\x11R\x12registrationSource\x88\x01\x01\x12$\n" +
"\vbirthday_at\x18\x16 \x01(\x03H\x12R\n" +
"birthdayAt\x88\x01\x01\x12\"\n" +
"\n" +
"created_at\x18\x17 \x01(\x03H\x13R\tcreatedAt\x88\x01\x01\x12\"\n" +
"\n" +
"updated_at\x18\x18 \x01(\x03H\x14R\tupdatedAt\x88\x01\x01B\x05\n" +
"\x03_idB\v\n" +
"\t_usernameB\b\n" +
"\x06_emailB\t\n" +
"\a_mobileB\x10\n" +
"\x0e_password_hashB\a\n" +
"\x05_saltB\v\n" +
"\t_nicknameB\t\n" +
"\x05_saltB\t\n" +
"\a_avatarB\t\n" +
"\a_genderB\x11\n" +
"\x0f_account_statusB\x0e\n" +
@ -1902,7 +1921,9 @@ const file_app_proto_rawDesc = "" +
"\x16_recovery_token_expiryB\v\n" +
"\t_metadataB\x16\n" +
"\x14_registration_sourceB\x0e\n" +
"\f_birthday_at\"\x83\x01\n" +
"\f_birthday_atB\r\n" +
"\v_created_atB\r\n" +
"\v_updated_at\"\x83\x01\n" +
"\rVerifyCodeReq\x12'\n" +
"\x04type\x18\x01 \x01(\x0e2\x13.app.VerifyCodeTypeR\x04type\x123\n" +
"\faccount_type\x18\x02 \x01(\x0e2\x10.app.AccountTypeR\vaccountType\x12\x14\n" +
@ -1989,7 +2010,7 @@ var file_app_proto_depIdxs = []int32{
24, // 4: app.LoginResponse.user:type_name -> app.UserInfo
4, // 5: app.LoginResponse.auth_token:type_name -> app.AuthToken
27, // 6: app.NumericDate.timestamp:type_name -> google.protobuf.Timestamp
24, // 7: app.PageUserResponse.users:type_name -> app.UserInfo
24, // 7: app.PageUserResponse.data:type_name -> app.UserInfo
24, // 8: app.RegisterUserResponse.user:type_name -> app.UserInfo
4, // 9: app.RegisterUserResponse.auth_token:type_name -> app.AuthToken
9, // 10: app.RegisteredClaims.audience:type_name -> app.ClaimStrings