package user import ( "context" "perms-system-server/internal/consts" "perms-system-server/internal/loaders" "perms-system-server/internal/middleware" userModel "perms-system-server/internal/model/user" "perms-system-server/internal/response" "perms-system-server/internal/svc" "perms-system-server/internal/types" "perms-system-server/internal/util" "github.com/zeromicro/go-zero/core/logx" ) type UserListLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewUserListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserListLogic { return &UserListLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } // UserList 用户列表。超管查看全量用户列表,产品管理者查看当前产品下的成员列表。支持分页。 func (l *UserListLogic) UserList(req *types.UserListReq) (resp *types.PageResp, err error) { page, pageSize := util.NormalizePage(req.Page, req.PageSize) caller := middleware.GetUserDetails(l.ctx) if caller == nil { return nil, response.ErrUnauthorized("未登录") } if !caller.IsSuperAdmin { if req.ProductCode == "" { return nil, response.ErrForbidden("非超管用户必须指定产品编码") } if caller.ProductCode != req.ProductCode { return nil, response.ErrForbidden("无权访问该产品的数据") } } var list []*userModel.SysUser var total int64 var memberMap map[int64]string if req.ProductCode != "" { var mtMap map[int64]string list, mtMap, total, err = l.svcCtx.SysUserModel.FindListByProductMembers(l.ctx, req.ProductCode, page, pageSize) if err != nil { return nil, err } memberMap = mtMap } else { list, total, err = l.svcCtx.SysUserModel.FindListByPage(l.ctx, page, pageSize) if err != nil { return nil, err } } items := make([]types.UserItem, 0, len(list)) for _, u := range list { avatar := "" if u.Avatar.Valid { avatar = u.Avatar.String } var memberType string if memberMap != nil { memberType = memberMap[u.Id] } email, phone, remark := maskUserPII(caller, u) items = append(items, types.UserItem{ Id: u.Id, Username: u.Username, Nickname: u.Nickname, Avatar: avatar, Email: email, Phone: phone, Remark: remark, DeptId: u.DeptId, Status: u.Status, MemberType: memberType, CreateTime: u.CreateTime, }) } return &types.PageResp{ Total: total, List: items, }, nil } // maskUserPII 审计 M-R16-1:同产品普通 MEMBER 不应拉到他人联系方式。 // 规则:SuperAdmin / 产品 ADMIN / 产品 DEVELOPER,或 caller 看自己,返回原值;其他情况(含普通 // MEMBER 看他人、无 MemberType 的只读视角)把 email/phone/remark 三个 PII 字段置空。 // 返回字段保留其它字段(Username / Nickname / DeptId / Status 等)的原值: // 业务上"看得到谁在这个产品里"是合理读权限,但"拉通讯录"不是。 // // 与 MemberList 的口径对齐:MemberListLogic 根本不在响应体里返回 email/phone;UserList/UserDetail // 则保留"自己 + 管理层"可读的最小授权子集,以便个人资料页、管理台审核流正常工作。 func maskUserPII(caller *loaders.UserDetails, u *userModel.SysUser) (email, phone, remark string) { if caller == nil || u == nil { return "", "", "" } if caller.IsSuperAdmin || caller.MemberType == consts.MemberTypeAdmin || caller.MemberType == consts.MemberTypeDeveloper || caller.UserId == u.Id { return u.Email, u.Phone, u.Remark } return "", "", "" }