在若依框架的标准配置中,系统默认使用SysUser作为用户实体类,这套用户体系主要面向后台管理系统。但当我们开发移动端应用(如小程序或APP)时,往往会遇到几个现实问题:
第一,移动端用户和后台管理用户的属性差异很大。后台用户可能有部门、岗位等组织架构属性,而移动端用户更关注昵称、头像、第三方登录等社交化属性。我见过一个电商项目,因为强行共用用户表,导致用户表字段膨胀到60多个,维护起来非常痛苦。
第二,权限体系完全不同。后台管理需要RBAC权限控制,而移动端可能只需要简单的角色区分。曾经有个项目在权限判断时频繁出现NullPointerException,就是因为移动端请求误入了后台权限校验流程。
第三,业务隔离需求。比如后台管理员删除用户时,不应该影响移动端用户数据。有次线上事故就是因为误操作执行了delete from sys_user where...,结果把APP用户数据全清空了。
我们先来看app_user表的核心字段设计,与sys_user相比有几个关键区别:
sql复制CREATE TABLE `app_user` (
`user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
`avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
`password` varchar(100) DEFAULT '' COMMENT '密码',
`salt` varchar(50) DEFAULT '' COMMENT '盐',
`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志',
`login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='APP用户信息表';
特别注意:
dept_id、post_id等后台管理相关字段nick_name、avatar等移动端常用字段对应的Java实体类需要继承BaseEntity,保持若依框架的审计字段(create_by等):
java复制public class AppUser extends BaseEntity {
private Long userId;
private String userName;
private String nickName;
private String avatar;
private String password;
private String salt;
private String status;
private String delFlag;
private String loginIp;
private Date loginDate;
// getter/setter省略
}
建议将实体类放在com.ruoyi.common.core.domain.entity包下,与SysUser同级。这样既保持规范,又明确区分两类用户。
若依原有的Token认证非常完善,我们只需要做针对性调整:
AppTokenService复制原有逻辑,但修改Redis键前缀:java复制private String getTokenKey(String uuid) {
return CacheConstants.LOGIN_APP_TOKEN_KEY + uuid; // 使用APP专属前缀
}
java复制if (request.getRequestURI().contains("/app/")) {
// 使用APP认证流程
LoginAppUser loginAppUser = appTokenService.getLoginAppUser(request);
// ...验证逻辑
} else {
// 原有后台认证流程
}
保持与后台相同的加密方式很重要,否则会导致用户在不同端需要不同密码。在AppUserServiceImpl中:
java复制public boolean checkPassword(String password, String salt, String hashPwd) {
String encrypted = DigestUtils.md5DigestAsHex(password.concat(salt).getBytes());
return hashPwd.equals(encrypted);
}
实测发现一个坑点:MySQL的utf8mb4字符集可能导致密码校验失败,建议统一使用getBytes(StandardCharsets.UTF_8)指定编码。
创建独立的AppLoginController,注意与后台登录接口的区别:
java复制@Api("APP登录管理")
@RestController
@RequestMapping("/app")
public class AppLoginController extends BaseController {
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginAppBody body) {
String token = appLoginService.login(body.getUsername(), body.getPassword());
return success().put(Constants.TOKEN, token);
}
@GetMapping("/userInfo")
public AjaxResult userInfo() {
AppUser user = getLoginAppUser().getAppUser();
return success().put("user", user);
}
}
关键点:
/app前缀隔离路由BaseController复用响应封装在SecurityConfig中需要放行APP登录接口:
java复制@Override
protected void configure(HttpSecurity http) {
http.authorizeRequests()
.antMatchers("/app/login").permitAll()
.antMatchers("/app/**").authenticated();
}
建议为移动端接口单独配置权限策略,比如:
java复制.antMatchers("/app/user/**").hasAnyRole("APP_USER")
.antMatchers("/app/admin/**").hasAnyRole("APP_ADMIN")
移动端通常需要更长的token有效期,可以在配置文件中单独设置:
properties复制# 后台token有效期(30分钟)
token.expireTime=30
# APP token有效期(7天)
app.token.expireTime=10080
然后在AppTokenService中注入@Value("${app.token.expireTime}")使用。
建议对用户基本信息做Redis缓存,减少数据库查询:
java复制public AppUser getAppUserWithCache(Long userId) {
String key = "app_user:" + userId;
return redisCache.getCacheObject(key, () -> {
return appUserMapper.selectById(userId);
}, 30, TimeUnit.MINUTES);
}
由于移动端网络环境复杂,建议添加接口耗时日志:
java复制@Around("execution(* com.ruoyi.web.controller.app..*.*(..))")
public Object logApiPerformance(ProceedingJoinPoint pjp) {
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
logger.info("API {} cost {}ms",
pjp.getSignature(),
System.currentTimeMillis() - start);
}
}
问题1:登录成功但后续接口401
JwtAuthenticationTokenFilter的路由判断逻辑Authorization: Bearer [token]问题2:密码正确但登录失败
checkPassword方法对比加密结果问题3:跨域访问失败
虽然本文主要讲解账号密码登录,但移动端常需要微信等第三方登录。可以在AppUser表中添加字段:
sql复制ALTER TABLE `app_user`
ADD COLUMN `wx_openid` varchar(64) DEFAULT '' COMMENT '微信openid',
ADD COLUMN `wx_unionid` varchar(64) DEFAULT '' COMMENT '微信unionid';
然后新增登录接口:
java复制@PostMapping("/loginByWeixin")
public AjaxResult loginByWeixin(@RequestParam String code) {
String openid = weixinService.getOpenid(code);
AppUser user = appUserService.selectByWxOpenid(openid);
// ...后续token生成逻辑
}
这种架构下,一套用户体系可以同时支持多种登录方式,后端处理逻辑却保持统一。