Skip to content

用户体系 ​

这篇解决什么 ​

后台员工和 C 端会员不是同一张用户表。读完能分清 AdminUser / MemberUser、从 LoginUser 取当前人、接账号/短信/社交登录,以及在用户管理里新增管理员。

默认端口 48080。管理端前缀 /admin-api,会员端前缀 /app-api。Token 认证见 功能权限与数据权限。

示意图:管理后台和办公 UniApp 走管理员用户;会员端走会员用户。令牌解析在框架层完成。

两套用户 ​

AdminUser 服务 B 端:PC 管理端、办公 UniApp,接口在 /admin-api/**。MemberUser 服务 C 端会员,接口在 /app-api/**。

两边登录都发 OAuth2 Token,请求头 Authorization: Bearer {accessToken}。令牌落在同一张表,用 user_type 区分。

名称说明仓库路径
AdminUserDO管理员。账号、部门、岗位ruoyi-office/yudao-module-system/.../dal/dataobject/user/AdminUserDO.java
MemberUserDO会员。手机号、等级、积分ruoyi-office/yudao-module-member/.../dal/dataobject/user/MemberUserDO.java
UserTypeEnumMEMBER = 1,ADMIN = 2ruoyi-office/yudao-framework/yudao-common/.../enums/UserTypeEnum.java
用户管理左部门树、右用户表ruoyi-office-vben/apps/web-antd/src/views/system/user/
会员用户后台查看、改等级与积分ruoyi-office-vben/apps/web-antd/src/views/member/user/
办公登录UniApp 调 /system/auth/loginruoyi-office-uniapp/src/api/login.ts

用户管理

本系统截图:系统管理 → 用户管理。左侧按部门过滤,右侧维护管理员账号。

表结构 ​

管理员表是 system_users(SQL Server 里 system_user 是关键字)。会员表是 member_user。两张表都继承 TenantBaseDO,带 tenant_id。

按本仓库生成的 ER:管理员挂部门;令牌表用 user_id + user_type 同时指向两类用户。

名称说明仓库路径
system_users@TableName("system_users")。密码 BCrypt,无独立盐字段.../dal/dataobject/user/AdminUserDO.java
member_user@TableName("member_user")。uk_mobile 按手机号唯一.../dal/dataobject/user/MemberUserDO.java
system_dept部门树。orgType:0 部门,1 公司ruoyi-office/yudao-module-system/.../dal/dataobject/dept/DeptDO.java
system_user_post用户—岗位.../dal/dataobject/dept/UserPostDO.java
system_oauth2_access_token访问令牌。userInfo 里放昵称、部门.../dal/dataobject/oauth2/OAuth2AccessTokenDO.java

AdminUserDO.postIds 是 JSON 数组,列表展示用;真正的岗位关联在 system_user_post。deptId 是主部门。多组织开启后,会话里的当前部门可以和主部门不同。

为什么拆两张表

管理员要部门、岗位、角色;会员要等级、积分、标签。字段和迭代节奏都不一样,拆表比加 type 再挂扩展表更干净。共享表(令牌、社交绑定)用 user_type 指向 UserTypeEnum。

用户类型 ​

UserTypeEnum 是全局枚举。写跨类型表时不要自己发明数字。

名称说明仓库路径
MEMBER1,C 端会员yudao-framework/yudao-common/.../enums/UserTypeEnum.java
ADMIN2,管理后台 / 办公端同上

登录成功后,OAuth2TokenServiceImpl#createAccessToken 把 userType 写入令牌。过滤器还原 LoginUser 时带着这个类型。

当前登录用户 ​

业务代码用 SecurityFrameworkUtils,不要自己解 Token。

名称说明仓库路径
getLoginUser()完整 LoginUserruoyi-office/yudao-framework/yudao-spring-boot-starter-security/.../util/SecurityFrameworkUtils.java
getLoginUserId()用户编号,最常用同上
getLoginUserNickname()昵称。读 LoginUser.info同上
getLoginUserDeptId()当前部门。读 LoginUser.info同上
LoginUserid / userType / info / tenantId.../security/core/LoginUser.java
java
Long userId = SecurityFrameworkUtils.getLoginUserId();
Long deptId = SecurityFrameworkUtils.getLoginUserDeptId();

昵称和部门只对管理员有值

OAuth2TokenServiceImpl#buildUserInfo 在 ADMIN 时写入 nickname、deptId。MEMBER 目前返回空 Map。会员端不要用这两个方法当业务字段,去查 member_user。

多组织开启时,buildUserInfo 会用刷新令牌对应的会话部门覆盖 AdminUserDO.deptId。getLoginUserDeptId() 拿到的是当前任职部门,不一定是主部门。

要在 Token 里多带字段(例如手机号),改 buildUserInfo,再在 SecurityFrameworkUtils 加对应 getXxx()。会员侧要先把读取补上,不要只加工具方法。

java
if (userType.equals(UserTypeEnum.ADMIN.getValue())) {
    AdminUserDO user = adminUserService.getUser(userId);
    return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname())
            .put(LoginUser.INFO_KEY_DEPT_ID, StrUtil.toStringOrNull(deptId)).build();
} else if (userType.equals(UserTypeEnum.MEMBER.getValue())) {
    return Collections.emptyMap();
}

路径:ruoyi-office/yudao-module-system/.../service/oauth2/OAuth2TokenServiceImpl.java。

账号密码登录 ​

管理后台用 username + password。会员端用 mobile + password。返回体都是 userId / accessToken / refreshToken / expiresTime。

验证码开关见 验证码。开发环境可关 yudao.captcha.enable。

管理后台 ​

POST /admin-api/system/auth/login,@PermitAll。

名称说明仓库路径
接口@RequestMapping("/system/auth")ruoyi-office/yudao-module-system/.../controller/admin/auth/AuthController.java
请求username / password,可选 socialType + socialCode + socialState.../controller/admin/auth/vo/AuthLoginReqVO.java
响应AuthLoginRespVO.../controller/admin/auth/vo/AuthLoginRespVO.java
实现校验验证码 → authenticate → 发令牌.../service/auth/AdminAuthServiceImpl.java
java
@PostMapping("/login")
@PermitAll
@Operation(summary = "使用账号密码登录")
public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
    return success(authService.login(reqVO));
}

authenticate 按用户名查 system_users,用 BCryptPasswordEncoder 比对密码。账号不存在、密码错、状态停用都会记登录日志。

请求里带上 socialType 时,同一请求会把社交账号绑到这个管理员。

会员端 ​

POST /app-api/member/auth/login。

名称说明仓库路径
接口@RequestMapping("/member/auth")ruoyi-office/yudao-module-member/.../controller/app/auth/AppAuthController.java
请求mobile / password,可选社交绑定字段.../controller/app/auth/vo/AppAuthLoginReqVO.java
响应多一个 openid(社交登录时).../controller/app/auth/vo/AppAuthLoginRespVO.java
java
@PostMapping("/login")
@PermitAll
@Operation(summary = "使用手机 + 密码登录")
public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
    return success(authService.login(reqVO));
}

办公 UniApp 不是会员登录,它调的是 /system/auth/login,用户仍在 system_users。

短信验证码登录 ​

先发码,再带 mobile + code 登录。场景号在 SmsSceneEnum:管理员登录 21,会员登录 1。

管理后台 ​

名称说明仓库路径
发码POST /system/auth/send-sms-codeAuthController#sendLoginSmsCode
登录POST /system/auth/sms-loginAuthController#smsLogin
场景ADMIN_MEMBER_LOGIN = 21yudao-module-system-api/.../enums/sms/SmsSceneEnum.java

管理员发码前会查手机号是否已在 system_users。没有对应账号直接报错,不会顺手注册。

会员端 ​

名称说明仓库路径
发码POST /member/auth/send-sms-codeAppAuthController#sendSmsCode
登录POST /member/auth/sms-loginAppAuthController#smsLogin
自动建用户MemberUserService#createUserIfAbsent.../service/user/MemberUserServiceImpl.java

短信登录时,手机号若不存在会插入一条 member_user,昵称默认 用户 + 6 位随机数。这个接口同时承担注册。

管理员短信登录不会自动开户

会员可以「先登录再落档」。管理员必须先在用户管理里建号,或打开注册开关走 /system/auth/register。不要把会员的 createUserIfAbsent 抄到管理端。

三方登录 ​

两端流程相同:先拿授权地址,再用 code 换登录态。未绑定的管理员不能快捷登录;未绑定的会员会先建用户再绑定。

名称说明仓库路径
管理端跳转GET /system/auth/social-auth-redirectAuthController
管理端快捷登录POST /system/auth/social-login同上
会员跳转GET /member/auth/social-auth-redirectAppAuthController
会员快捷登录POST /member/auth/social-login同上
微信小程序POST /member/auth/weixin-mini-app-login同上;phoneCode + loginCode

管理员绑定登录复用账号密码接口:/system/auth/login 额外传 socialType / socialCode / socialState。会员绑定登录同样把这三项加到 /member/auth/login 或短信登录请求里。

小程序一键登录会按手机号 createUserIfAbsent,再绑定微信。细节见 OAuth 2.0。

注册 ​

管理员默认不开放自助注册,在用户管理里新增。会员靠短信或社交登录自动建档。

管理后台 ​

POST /admin-api/system/user/create,权限 system:user:create。

java
@PostMapping("/create")
@PreAuthorize("@ss.hasPermission('system:user:create')")
public CommonResult<Long> createUser(@Valid @RequestBody UserSaveReqVO reqVO) {
    return success(userService.createUser(reqVO));
}

路径:ruoyi-office/yudao-module-system/.../controller/admin/user/UserController.java。

另有 POST /system/auth/register。参数管理里 system.user.register-enabled 必须是 true,否则抛「注册关闭」。通过后会赋默认普通角色,并写登录令牌。

名称说明仓库路径
后台新增用户管理「新增用户」UserController#createUser
自助注册配置键 system.user.register-enabledAdminUserServiceImpl#registerUser
前端页左树右表、导入导出、分配角色ruoyi-office-vben/apps/web-antd/src/views/system/user/

会员端 ​

没有单独的「注册页」接口。sms-login、社交未绑定、微信小程序一键登录都会走 createUserIfAbsent 或 createUser。

后台「会员用户」只能改资料、等级、积分,不能当开户入口。

登出 ​

登出就是删 Token。库和 Redis 一起清,已发出的 accessToken 立刻失效。

示意图:管理端 POST /system/auth/logout,会员端 POST /member/auth/logout。都是 @PermitAll。

名称说明仓库路径
管理端登出从请求头取 Token 再删AuthController#logout
会员登出同上AppAuthController#logout
删令牌同时清刷新令牌OAuth2TokenServiceImpl#removeAccessToken

改密码也会作废令牌,所以这里不用无状态 JWT。过期时间看 system_oauth2_client 里 client_id = default。

停用账号不会自动踢线

status 改成停用后,已发出的 Token 要等过期或主动登出。需要立刻踢人时,调 removeAccessToken(userId, userType)。

配置与操作 ​

管理员在 系统管理 → 用户管理 建账号,必填用户名、密码、部门;角色在用户页或角色页互绑。保存后立刻能登录,已发出的 Token 不会因为改部门自动失效。

停用只改 status,要立刻踢人再调删令牌。租户套餐裁的是菜单,不是这张用户表。开启见 系统功能设计。截图见上文用户列表。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版