sa-token 登录注册
在本章节中,我们将使用 tio-boot 和 Sa-Token 开发一个用户注册和登录系统。该系统将:
- 从 MySQL 数据库读取和存储用户信息。
- 生成用于认证的 JWT 令牌。
- 将会话数据存储在 Redis 中。
本示例将引导您完成以下步骤:
- 创建必要的数据库表。
- 添加项目所需的依赖。
- 配置应用程序属性。
- 设置应用程序、数据库、Redis 和 Sa-Token 的基础配置类。
- 实现数据层、业务逻辑层和接口层。
- 测试注册和登录功能。
创建表
首先会在 mysql 中创建一张表 然后使用 java-db 连接 mysql sys_user_info
DROP TABLE IF EXISTS `sys_user_info`;
CREATE TABLE `sys_user_info` (
`id` bigint(20) COMMENT '主键',
`app_id` int(11) COMMENT '应用id',
`username` varchar(32) COMMENT '用户名称',
`password` varchar(64) COMMENT '登录密码',
`area_code` varchar(8) COMMENT '手机区号',
`phone` varchar(16) COMMENT '手机号码',
`email` varchar(64) COMMENT '邮箱',
`wx_open_id` varchar(32) COMMENT '微信openid',
`wx_user_info` json NULL COMMENT '微信用户信息',
`facebook_id` varchar(64),
`facebook_user_info` json NULL,
`user_channel` int(11) COMMENT '用户注册渠道,网页,安卓APP,IOS APP,小程序等',
`user_from` int(11) COMMENT '用户来源,email,手机号,第三方授权等',
`user_level` int(11) COMMENT '用户级别',
`name` varchar(32) COMMENT '名字',
`nick_name` varchar(32) COMMENT '昵称',
`sex` int(11) NULL DEFAULT NULL COMMENT '性别',
`header_image_url` varchar(255) COMMENT '头像图片url',
`config_info` json NULL COMMENT '配置信息',
`status` int(11) default 0 COMMENT '账号状态',
`v` int(11) default 0 COMMENT '版本号',
`locale` varchar(6) COMMENT '语系编码',
`remark` varchar(256) DEFAULT NULL,
`creator` varchar(64) DEFAULT '',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updater` varchar(64) DEFAULT '',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` smallint(6) DEFAULT 0,
`tenant_id` bigint(20) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) COMMENT = '用户表'
添加依赖
添加的依赖有
- tio-boot
- slf4j-api
- lombok
- hotswap-classloader
- api-table
- mysql-connector-java
- HikariCP
- sa-token-core
- sa-token-jwt
- java-db
- jedis
- fst
- junit
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<graalvm.version>23.1.1</graalvm.version>
<lombok-version>1.18.30</lombok-version>
<java-model.version>1.1.2</java-model.version>
<tio.version>3.7.3.v20241008-RELEASE</tio.version>
<tio-boot.version>1.9.0</tio-boot.version>
<hotswap-classloader.version>1.2.6</hotswap-classloader.version>
<jfinal-aop.version>1.3.3</jfinal-aop.version>
<java-db.version>1.4.2</java-db.version>
<api-table.version>1.4.5</api-table.version>
<java.java-openai>1.0.4</java.java-openai>
<qcloudsms.version>3.1.270</qcloudsms.version>
<final.name>maliang-pen-app-backend</final.name>
<main.class>com.enoleap.maliang.app.AppServer</main.class>
</properties>
<dependencies>
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>java-model</artifactId>
<version>${java-model.version}</version>
</dependency>
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>tio-utils</artifactId>
<version>${tio.version}</version>
</dependency>
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>tio-boot</artifactId>
<version>${tio-boot.version}</version>
</dependency>
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>hotswap-classloader</artifactId>
<version>${hotswap-classloader.version}</version>
</dependency>
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>api-table</artifactId>
<version>${api-table.version}</version>
</dependency>
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>jfinal-aop</artifactId>
<version>${jfinal-aop.version}</version>
</dependency>
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>java-db</artifactId>
<version>${java-db.version}</version>
</dependency>
<dependency>
<groupId>com.litongjava</groupId>
<artifactId>java-openai</artifactId>
<version>${java.java-openai}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.52</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.9</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok-version}</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>0.2.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
</dependency>
<!-- Sa-Token 核心库 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>1.37.0</version>
</dependency>
<!-- Jedis,Redis 的 Java 客户端 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- FST 序列化工具,用于对象和字节流间的转换 -->
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
<version>2.57</version> <!-- 注意:更高版本不支持 JDK 8 -->
</dependency>
</dependencies>
配置文件
app.properties
app.env=dev
server.port=9204
server.context-path=/pen-api
sa.token.jwt.secret.key=xxxx
sa.token.admin.token=xxxx
app-dev.properties
jdbc.url=jdbc:mysql://192.168.3.9/enote?characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=UTC
jdbc.user=root
jdbc.pswd=robot_123456#
jdbc.MaximumPoolSize=2
redis.host=127.0.0.1
redis.port=6379
redis.password=
redis.database=2
redis.timeout=15000
i18n_en_US.properties
The_username_or_password_cannot_be_empty=The username or password cannot be empty
The_locale_cannot_be_empty=The locale cannot be empty
The_userChannel_cannot_be_empty=The userChannel cannot be empty
The_appId_cannot_be_empty=The appId cannot be empty
The_userFrom_cannot_be_empty=The userFrom cannot be empty
The_v_cannot_be_empty=The v cannot be empty
The_username_at_least_6_characters=The username at least 6 characters
The_password_at_least_8_characters=The password at least 8 characters
The_password_should_contain_uppercase_letters,_lowercase_letters_and_numbers=The password should contain uppercase letters, lowercase letters and numbers
The_locale_at_least_5_characters=The locale at least 5 characters
The_username_exists=The username exists
i18n_zh_CN.properties
The_username_or_password_cannot_be_empty=用户名或者密码不能为空
The_locale_cannot_be_empty=locale不能为空
The_userChannel_cannot_be_empty=The userChannel不能为空
The_appId_cannot_be_empty=The appId不能为空
The_userFrom_cannot_be_empty=The userFrom不能为空
The_v_cannot_be_empty=The v不能为空
The_username_at_least_6_characters=username至少6个字符
The_password_at_least_8_characters=password至少8个字符
The_password_should_contain_uppercase_letters,_lowercase_letters_and_numbers=password需要包含大写字母,小写字母和数字
The_locale_at_least_5_characters=locale至少5个字符
The_username_exists=用户名已经存在
基础配置类
启动类
package com.enoleap.maliang.app;
import com.litongjava.annotation.AComponentScan;
import com.litongjava.hotswap.watcher.HotSwapResolver;
import com.litongjava.hotswap.wrapper.tio.boot.TioApplicationWrapper;
@AComponentScan
public class AppServer {
public static void main(String[] args) {
long start = System.currentTimeMillis();
// 支持fastjson2
HotSwapResolver.addHotSwapClassPrefix("com.alibaba.fastjson2.");
TioApplicationWrapper.run(AppServer.class, args);
// TioApplication.run(MaLiangPenAiServerApp.class, args);
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
}
连接 mysql
package com.enoleap.maliang.app.config;
import com.enoleap.maliang.app.db.model._MappingKit;
import com.jfinal.template.Engine;
import com.jfinal.template.source.ClassPathSourceFactory;
import com.litongjava.annotation.AConfiguration;
import com.litongjava.annotation.Initialization;
import com.litongjava.db.activerecord.ActiveRecordPlugin;
import com.litongjava.db.activerecord.OrderedFieldContainerFactory;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.boot.utils.TioRequestParamUtils;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
@AConfiguration
@Slf4j
public class EnoteMysqlDbConfig {
/*
*
* config ActiveRecordPlugin
*/
@Initialization
public void config() {
String jdbcUrl = EnvUtils.get("jdbc.url");
log.info("jdbcUrl:{}", jdbcUrl);
String jdbcUser = EnvUtils.get("jdbc.user");
String jdbcPswd = EnvUtils.get("jdbc.pswd");
int maximumPoolSize = EnvUtils.getInt("jdbc.MaximumPoolSize", 2);
HikariConfig config = new HikariConfig();
// 设定基本参数
config.setJdbcUrl(jdbcUrl);
config.setUsername(jdbcUser);
config.setPassword(jdbcPswd);
config.setMaximumPoolSize(maximumPoolSize);
config.setConnectionTimeout(30000);
HikariDataSource hikariDataSource = new HikariDataSource(config);
HookCan.me().addDestroyMethod(hikariDataSource::close);
ActiveRecordPlugin mysqlArp = new ActiveRecordPlugin(hikariDataSource);
mysqlArp.setContainerFactory(new OrderedFieldContainerFactory());
Engine engine = mysqlArp.getEngine();
engine.setSourceFactory(new ClassPathSourceFactory());
engine.setCompressorOn(' ');
engine.setCompressorOn('\n');
if (EnvUtils.isDev()) {
mysqlArp.setDevMode(true);
engine.setDevMode(true);
mysqlArp.setShowSql(true);
}
mysqlArp.addSqlTemplate("/sql/all_sqls.sql");
_MappingKit.mapping(mysqlArp);
mysqlArp.start();
HookCan.me().addDestroyMethod(mysqlArp::stop);
// add
TioRequestParamUtils.types.add("bigint");
}
}
连接 reids
package com.enoleap.maliang.app.config;
import com.litongjava.annotation.AConfiguration;
import com.litongjava.annotation.Initialization;
import com.litongjava.redis.Redis;
import com.litongjava.redis.RedisDb;
import com.litongjava.redis.RedisPlugin;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.utils.environment.EnvUtils;
@AConfiguration
public class RedisDbConfig {
@Initialization
public RedisPlugin redisPlugin() {
String redisHost = EnvUtils.get("redis.host", "127.0.0.1");
int redisPort = EnvUtils.getInt("redis.port", 6379);
String redisPassword = EnvUtils.getStr("redis.password");
int redistimeout = EnvUtils.getInt("redis.timeout", 60);
int redisDatabase = EnvUtils.getInt("redis.database", 0);
// 创建并启动 Redis 插件
RedisPlugin redisPlugin = new RedisPlugin("main", redisHost, redisPort, redistimeout, redisPassword, redisDatabase);
redisPlugin.start();
HookCan.me().addDestroyMethod(redisPlugin::stop);
// 测试连接
RedisDb redisDb = Redis.use("main");
redisDb.getJedis().connect();
return redisPlugin;
}
}
整合 sa-token
SaTokenConfiguration
package com.enoleap.maliang.app.config;
import com.litongjava.annotation.AConfiguration;
import com.litongjava.annotation.Initialization;
import com.litongjava.satoken.SaTokenDaoRedis;
import com.litongjava.tio.boot.satoken.SaTokenContextForTio;
import com.litongjava.tio.utils.environment.EnvUtils;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaCookieConfig;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
@AConfiguration
public class SaTokenConfiguration {
@Initialization
public void config() {
// 初始化 Sa-Token 上下文
SaTokenContext saTokenContext = new SaTokenContextForTio();
// 设置 Cookie 配置,例如启用 HttpOnly 属性
SaCookieConfig saCookieConfig = new SaCookieConfig();
saCookieConfig.setHttpOnly(true);
// 初始化和配置 Sa-Token 主配置
SaTokenConfig saTokenConfig = new SaTokenConfig();
saTokenConfig.setTokenStyle(SaTokenConsts.TOKEN_STYLE_SIMPLE_UUID);
saTokenConfig.setActiveTimeout(50 * 60); // 设置活动超时时间为 50 分钟
saTokenConfig.setIsShare(false);
saTokenConfig.setTokenName("token"); // 设置 token 的名称
saTokenConfig.setIsWriteHeader(true); // 将 token 写入响应头
saTokenConfig.setIsReadHeader(true); // 从请求头中读取 token
//设置不将cookie信息存入到cookie
saTokenConfig.setIsReadCookie(false);
saTokenConfig.setCookie(saCookieConfig);
String secretKey = EnvUtils.get("sa.token.jwt.secret.key");
saTokenConfig.setJwtSecretKey(secretKey);
//saTokenConfig.setTokenPrefix("Bearer");
StpLogicJwtForSimple stpLogicJwtForSimple = new StpLogicJwtForSimple();
StpUtil.setStpLogic(stpLogicJwtForSimple);
// 应用配置到 Sa-Token 管理器
SaManager.setConfig(saTokenConfig);
SaManager.setSaTokenContext(saTokenContext);
SaManager.setSaTokenDao(new SaTokenDaoRedis("main"));
// 增加一个Api用户设置token永不过期
SaLoginModel loginModel = new SaLoginModel();
loginModel.setTimeout(-1);
String adminToken = EnvUtils.get("sa.token.admin.token");
loginModel.setToken(adminToken);
StpUtil.createLoginSession("1", loginModel);
}
}
InterceptorConfiguration
package com.enoleap.maliang.app.config;
import java.util.HashMap;
import java.util.Map;
import com.litongjava.annotation.AConfiguration;
import com.litongjava.annotation.Initialization;
import com.litongjava.tio.boot.http.interceptor.HttpInteceptorConfigure;
import com.litongjava.tio.boot.http.interceptor.HttpInterceptorModel;
import com.litongjava.tio.boot.satoken.AuthTokenInterceptor;
import com.litongjava.tio.boot.server.TioBootServer;
@AConfiguration
public class InterceptorConfiguration {
@Initialization
public void config() {
// 创建 SaToken 拦截器实例
Map<String, Object> data = new HashMap<>();
data.put("msg", "please login");
AuthTokenInterceptor saTokenInterceptor = new AuthTokenInterceptor(data);
HttpInterceptorModel model = new HttpInterceptorModel();
model.setInterceptor(saTokenInterceptor);
model.addblockeUrl("/**"); // 拦截所有路由
// 设置例外路由
model.addAllowUrls("/exception/*", "/status", "/user/register", "/auth/login", "/auth/logout");
model.addAllowUrls("/sms/send");
// model.addAlloweUrl("/enote/puma/*");
//
model.addAllowUrls("/license/get");
//
model.addAllowUrls("/version");
HttpInteceptorConfigure serverInteceptorConfigure = new HttpInteceptorConfigure();
serverInteceptorConfigure.add(model);
// 将拦截器配置添加到 Tio 服务器
TioBootServer.me().setHttpInteceptorConfigure(serverInteceptorConfigure);
}
}
实体类
UserRegisterVO
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class UserRegisterVO {
private String username;
private String password;
private String locale;
private Integer appId;
//1 Android App,2 IOS App 3 小程序 4 网页
private Integer userChannel;
//用户来源:1:手机号,2:email,3:第三方授权等,4:用户名
private Integer userFrom;
//版本号
private Integer v;
}
数据层
SysUserInfoDao
import com.litongjava.db.activerecord.Db;
public class SysUserInfoDao {
/**
* 判断用户是否存在
* @param username
* @return
*/
public boolean existsUsername(String username) {
//String sql = "select count(1) from sys_user_info where username=?";
//return Db.exists(sql, username);
String tableName="sys_user_info";
return Db.exists(tableName,"username",username);
}
}
单元测试 SysUserInfoDao
SysUserInfoDaoTest
package com.enoleap.maliang.app.services.login;
import org.junit.Test;
import com.enoleap.maliang.app.config.EnoteMysqlDbConfig;
import com.enoleap.maliang.app.dao.SysUserInfoDao;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.tio.boot.tesing.TioBootTest;
public class SysUserInfoDaoTest {
@Test
public void test() {
TioBootTest.runWith(EnoteMysqlDbConfig.class);
boolean existsUsername = Aop.get(SysUserInfoDao.class).existsUsername("litong");
System.out.println(existsUsername);
}
}
业务层 业务逻辑
AuthService
package com.enoleap.maliang.app.services;
import com.enoleap.maliang.app.model.LoginVo;
import com.jfinal.kit.Kv;
import com.litongjava.db.TableResult;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Row;
import com.litongjava.model.body.RespBodyVo;
import cn.dev33.satoken.stp.StpUtil;
public class AuthService {
public TableResult<Kv> doLogin(String username, String password) {
String sql = "select id from sys_user_info where username=? and password=?";
Long result = Db.queryLong(sql, username, password);
if (result != null && result > 0) {
// 登录
StpUtil.login(result);
String tokenValue = StpUtil.getTokenValue();
long tokenTimeout = StpUtil.getTokenTimeout();
Kv kv = new Kv();
kv.set("token", tokenValue);
kv.set("tokenTimeout", tokenTimeout);
Row row = Db.findById("sys_user_info", result);
kv.set(row.toMap());
return TableResult.ok(kv);
} else {
return TableResult.fail("username or password is not correct");
}
}
public RespBodyVo loginByPhone(LoginVo vo) {
// TODO Auto-generated method stub
return null;
}
}
UserRegisterService
package com.enoleap.maliang.app.services;
import com.enoleap.maliang.app.constants.ENoteTableNames;
import com.enoleap.maliang.app.model.UserRegisterVO;
import com.enoleap.maliang.app.utils.UserIdUtils;
import com.jfinal.kit.Kv;
import com.litongjava.annotation.Inject;
import com.litongjava.db.TableResult;
import com.litongjava.db.activerecord.Db;
import com.litongjava.db.activerecord.Row;
import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserRegisterService {
@Inject
private AuthService authService;
public TableResult<Kv> index(UserRegisterVO reqVo) {
Integer appId = reqVo.getAppId();
String username = reqVo.getUsername();
String password = reqVo.getPassword();
Integer userChannel = reqVo.getUserChannel();
Integer userFrom = reqVo.getUserFrom();
Integer userLevel = 1;
Integer status = 1;
Integer v = reqVo.getV();
String locale = reqVo.getLocale();
boolean success = false;
long userId = UserIdUtils.random();
while (Db.exists(ENoteTableNames.SYS_USER_INFO, "id", userId)) {
log.info("user id alread exits,random again:{}", userId);
userId = UserIdUtils.random();
}
try {
Row row = new Row();
row.set("id", userId);
row.set("app_id", appId).set("username", username).set("password", password).set("user_channel", userChannel);
row.set("user_from", userFrom).set("user_level", userLevel).set("locale", locale);
row.set("status", status).set("v", v);
success = Db.tx(() -> {
boolean save = Db.save(ENoteTableNames.SYS_USER_INFO, row);
log.info("update result:{}", save);
return save;
});
} catch (Exception e) {
e.printStackTrace();
return TableResult.fail(e.getMessage());
}
if (!success) {
return TableResult.fail(-1, "register user fail");
}
TableResult<Kv> doLogin = authService.doLogin(username, password);
return doLogin;
}
}
单元测试 UserRegisterServiceTest
package com.enoleap.maliang.app.services.login;
import org.junit.Test;
import com.enoleap.maliang.app.config.EnoteMysqlDbConfig;
import com.enoleap.maliang.app.config.RedisDbConfig;
import com.enoleap.maliang.app.config.SaTokenConfiguration;
import com.enoleap.maliang.app.model.UserRegisterVO;
import com.enoleap.maliang.app.services.UserRegisterService;
import com.jfinal.kit.Kv;
import com.litongjava.db.TableResult;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.boot.mock.TioMock;
import com.litongjava.tio.boot.tesing.TioBootTest;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.utils.json.JsonUtils;
public class UserRegisterServiceTest {
@Test
public void test() {
//basic config
TioBootTest.runWith(EnoteMysqlDbConfig.class, RedisDbConfig.class, SaTokenConfiguration.class);
// mock http request
HttpRequest request = TioMock.mockGetHttpRequest();
TioRequestContext.hold(request);
UserRegisterService userRegisterService = Aop.get(UserRegisterService.class);
UserRegisterVO reqVo = UserRegisterVO.builder().username("litong").password("Litong2516")
//
.locale("zh_CN").userChannel(1).appId(1).userFrom(4).v(1)
//
.build();
TableResult<Kv> result = userRegisterService.index(reqVo);
System.out.println(JsonUtils.toJson(result));
}
}
接口层
注册
注册的业务逻辑是获取请求信息,验证请求信息,验证通过插入到数据库
UserRegisterController
package com.enoleap.maliang.app.controller;
import com.enoleap.maliang.app.model.UserRegisterVO;
import com.enoleap.maliang.app.services.UserRegisterService;
import com.enoleap.maliang.app.validator.UserRegisteValidator;
import com.jfinal.kit.Kv;
import com.litongjava.annotation.Inject;
import com.litongjava.annotation.RequestPath;
import com.litongjava.db.TableResult;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.util.Resps;
@RequestPath("/user/register")
public class UserRegisterController {
@Inject
private UserRegisteValidator userRegisteValidator;
@Inject
private UserRegisterService userRegisterService;
@RequestPath
public HttpResponse index(UserRegisterVO reqVo) {
TableResult<Kv> respVo = userRegisteValidator.index(reqVo);
HttpResponse response = TioRequestContext.getResponse();
if (respVo != null) {
return Resps.json(response, respVo);
}
return Resps.json(response, userRegisterService.index(reqVo));
}
}
UserRegisteValidator
package com.enoleap.maliang.app.validator;
import com.enoleap.maliang.app.model.UserRegisterVO;
import com.enoleap.maliang.app.dao.SysUserInfoDao;
import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import com.litongjava.annotation.Inject;
import com.litongjava.db.TableResult;
import com.litongjava.tio.boot.i18n.I18n;
import com.litongjava.tio.boot.i18n.I18nLocale;
import com.litongjava.tio.boot.i18n.Res;
public class UserRegisteValidator {
@Inject
private SysUserInfoDao SysUserInfoDao;
public TableResult<Kv> index(UserRegisterVO reqVo) {
String username = reqVo.getUsername();
String password = reqVo.getPassword();
String locale = reqVo.getLocale();
Integer userChannel = reqVo.getUserChannel();
Integer appId = reqVo.getAppId();
Integer userFrom = reqVo.getUserFrom();
Integer v = reqVo.getV();
Res res = null;
if (I18nLocale.ZH_CN.equals(locale)) {
res = I18n.use(I18nLocale.ZH_CN);
} else {
res = I18n.use(I18nLocale.EN_US);
}
// 验证username
if (StrKit.isBlank(username)) {
String string = res.get("The_username_or_password_cannot_be_empty");
return TableResult.fail(string);
}
if (StrKit.isBlank(password)) {
return TableResult.fail(res.get("The_username_or_password_cannot_be_empty"));
}
if (StrKit.isBlank(locale)) {
return TableResult.fail(res.get("The_locale_cannot_be_empty"));
}
if (userChannel == null) {
return TableResult.fail(res.get("The_userChannel_cannot_be_empty"));
}
if (appId == null) {
return TableResult.fail(res.get("The_appId_cannot_be_empty"));
}
if (userFrom == null) {
return TableResult.fail(res.get("The_userFrom_cannot_be_empty"));
}
if (v == null) {
return TableResult.fail(res.get("The_v_cannot_be_empty"));
}
if (username.length() < 6) {
return TableResult.fail("The_username_at_least_6_characters");
}
if (password.length() < 8) {
return TableResult.fail("The_password_at_least_8_characters");
}
// 至少包含大小,小写和数字
if (!PasswordValidator.validatePassword(password)) {
return TableResult.fail(res.get("The_password_should_contain_uppercase_letters,_lowercase_letters_and_numbers"));
}
if (locale.length() < 5) {
return TableResult.fail(res.get("The_locale_at_least_5_characters"));
}
// 判断用户名是否存在
if (SysUserInfoDao.existsUsername(username)) {
return TableResult.fail(res.get("The_username_exists"));
}
return null;
}
}
UserRegisteValidatorTest
package com.enoleap.maliang.app.validator;
import org.junit.Test;
import com.jfinal.kit.StrKit;
import com.litongjava.tio.boot.i18n.I18n;
import com.litongjava.tio.boot.i18n.I18nLocale;
import com.litongjava.tio.boot.i18n.Res;
public class UserRegisteValidatorTest {
@Test
public void test() {
String locale = I18nLocale.ZH_CN;
String username = null;
Res res = null;
if (I18nLocale.ZH_CN.equals(locale)) {
res = I18n.use(I18nLocale.ZH_CN);
} else {
res = I18n.use(I18nLocale.EN_US);
}
// 验证username
String string = null;
if (StrKit.isBlank(username)) {
string = res.get("The_username_or_password_cannot_be_empty");
}
System.out.println(string);
}
}
登录
AuthController
package com.enoleap.maliang.app.controller;
import com.enoleap.maliang.app.model.LoginVo;
import com.enoleap.maliang.app.services.AuthService;
import com.enoleap.maliang.app.validator.AuthValidator;
import com.jfinal.kit.Kv;
import com.litongjava.annotation.Inject;
import com.litongjava.annotation.RequestPath;
import com.litongjava.db.TableResult;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.util.Resps;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
@RequestPath("/auth")
@Slf4j
public class AuthController {
@Inject
private AuthValidator authValidator;
@Inject
private AuthService authService;
public HttpResponse login(String username, String password, String locale) {
log.info("username:{}", username);
RespBodyVo respVo = authValidator.doLogin(username, password, locale);
if (respVo != null) {
return Resps.json(TioRequestContext.getResponse(), TableResult.fail(-1, respVo.getMsg()));
}
TableResult<Kv> result = authService.doLogin(username, password);
return Resps.json(TioRequestContext.getResponse(), result);
}
public HttpResponse logout() {
StpUtil.logout();
HttpResponse response = TioRequestContext.getResponse();
return Resps.json(response, RespBodyVo.ok());
}
public RespBodyVo validateToken() {
try {
StpUtil.checkActiveTimeout();
return RespBodyVo.ok();
} catch (Exception e) {
return RespBodyVo.fail();
}
}
}
AuthValidator
package com.enoleap.maliang.app.validator;
import com.jfinal.kit.StrKit;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.tio.boot.i18n.I18n;
import com.litongjava.tio.boot.i18n.I18nLocale;
import com.litongjava.tio.boot.i18n.Res;
public class AuthValidator {
public RespBodyVo doLogin(String username, String password,String locale) {
Res res = null;
if (I18nLocale.ZH_CN.equals(locale)) {
res = I18n.use(I18nLocale.ZH_CN);
} else {
res = I18n.use(I18nLocale.EN_US);
}
if (StrKit.isBlank(locale)) {
return RespBodyVo.fail(res.get("The_locale_cannot_be_empty"));
}
if(StrKit.isBlank(username) || StrKit.isBlank(password)) {
String string = res.get("The_username_or_password_cannot_be_empty");
return RespBodyVo.fail(string);
}
return null;
}
}
发送请求测试
注册
curl --location --request POST 'http://localhost:9204/pen-api/user/register' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Accept: */*' \
--header 'Host: localhost:9204' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=litong' \
--data-urlencode 'password=Litong2516' \
--data-urlencode 'locale=zh_CN' \
--data-urlencode 'userChannel=1' \
--data-urlencode 'appId=1' \
--data-urlencode 'userFrom=4' \
--data-urlencode 'v=1'
响应体如下
{
"data": {
"tenant_id": "0",
"wx_user_info": null,
"locale": "zh_CN",
"config_info": null,
"header_pic_id": null,
"password": "Litong2516",
"id": "58880372218307226",
"add_time_long": "1729540045700",
"app_id": 1,
"email": null,
"area_code": null,
"sex": null,
"user_from": 4,
"tokenTimeout": "2592000",
"facebook_user_info": null,
"wx_open_id": null,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjU4ODgwMzcyMjE4MzA3MjI2LCJyblN0ciI6InFzSkVBTzRVUGdHck1RMVRqQUo1eW9YbmR5SEFVSnY0In0.meLVvKv0STYpw-gHco_l0z_avwdUj1uySP6XQv5hgUY",
"facebook_id": null,
"update_time_long": "1729540045700",
"deleted": 0,
"user_channel": 1,
"phone": null,
"v": 1,
"nick_name": null,
"name": null,
"user_level": 1,
"username": "litong2",
"status": 1
},
"code": 1,
"msg": null,
"ok": true
}
登录
curl --location --request POST 'http://localhost:9204/pen-api/auth/doLogin' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Accept: */*' \
--header 'Host: localhost:9204' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=litong2' \
--data-urlencode 'password=Litong2516' \
--data-urlencode 'locale=zh_CN'
响应体如下
{
"data": {
"tenant_id": "0",
"wx_user_info": null,
"locale": "zh_CN",
"config_info": null,
"header_pic_id": null,
"password": "Litong2516",
"id": "28597511211391080",
"add_time_long": "1729424379475",
"app_id": 1,
"email": null,
"area_code": null,
"sex": null,
"user_from": 4,
"tokenTimeout": "2591995",
"facebook_user_info": null,
"wx_open_id": null,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjI4NTk3NTExMjExMzkxMDgwLCJyblN0ciI6Ik05S2JuZ0ZwR2U3cFFpTUdLMnZTRms4bUw2cEhpVWJvIn0.aUxyQAIGhtGgO8IRi7_uLPaEeI3kHdkZLy7zeNInixw",
"facebook_id": null,
"update_time_long": "1729424379475",
"deleted": 0,
"user_channel": 1,
"phone": null,
"v": 1,
"nick_name": null,
"name": null,
"user_level": 1,
"username": "litong2",
"status": 1
},
"msg": null,
"code": 1,
"ok": true
}
验证 token 是否有效
curl --location --request GET 'http://localhost:9204/pen-api/auth/validateToken' \
--header 'token: 4eb6ac726a7d4f42bb10fe365823f9f7' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Accept: */*' \
--header 'Host: localhost:9204' \
--header 'Connection: keep-alive' \
获取用户 id
curl --location --request GET 'http://localhost:9204/pen-api/auth/getUserId' \
--header 'token: 4eb6ac726a7d4f42bb10fe365823f9f7' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Accept: */*' \
--header 'Host: localhost:9204' \
--header 'Connection: keep-alive'
{
"data": {
"tokenTimeout": 2591819,
"userId": "35",
"token": "4eb6ac726a7d4f42bb10fe365823f9f7"
},
"ok": true
}
登出
curl --location --request GET 'http://localhost:9204/pen-api/auth/logout' \
--header 'token: 4eb6ac726a7d4f42bb10fe365823f9f7' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Accept: */*' \
--header 'Host: localhost:9204' \
--header 'Connection: keep-alive'