用户系统
1.接口
1.1 注册(Regisater)
1.1.1 前端请求
当用户提交注册信息时,前端将用户名(邮箱)和密码发送给后端,后端负责处理注册逻辑并发送邮箱验证链接。
前端请求参数:
- email: 用户的邮箱地址。
- password: 用户的密码。
前端请求示例:
POST /api/v1/register
Content-Type: application/json
Origin: http://localhost:8100
{
"email": "user@example.com",
"password": "userpassword"
"user_type": 1
}
Origin 是必填信息,通常浏览器会自动携带 user_type 根据业务需求自定义,示例中 0 学生 1 教授
1.1.2 后端处理逻辑
后端接收到注册请求后,创建用户,并发送邮箱验证链接。如果注册成功,返回注册成功状态,并告知前端验证邮件已发送。
后端返回示例:
{ "data": null, "ok": true, "msg": null, "code": 1 }
1.1.3 前端处理逻辑
前端接收到成功响应后,提示用户验证邮件已发送,并引导用户查看邮箱进行验证。
1.1.4 用户验证
用户打开邮箱,邮箱内容示例如下.链接进行验证,验证通过后 会自动跳转到下面的前端地址 正式环境中 localhost:8100 是前端的域名
Dear User,
Thank you for signing up for College Bot AI!
Please verify your email address by clicking the link below:
http://localhost:8100/verification/email?email=litongjava001@gmail.com&code=358412
This will ensure your account is secure and fully activated.
If you did not request this verification, please disregard this email.
Best regards,
The College Bot AI Team
用户打开邮箱后会点击上面的地址,前端应该使用一个路由拦截到上面的地址并获取 email 和 code 参数,并请后端发送请求确认验证码有效, 验证失败前端自行处理失败,例如显示失败信息 验证成功的前端跳转到登录地址
/api/v1/login?role={instructor|student} 统一为小写
1.2. 登录(Login)
1.2.1 前端请求
当用户提交登录信息时,前端将邮箱和密码发送到后端,后端完成身份验证并返回用户的 idToken
前端请求参数:
- email: 用户的邮箱地址。
- password: 用户的密码。
前端请求示例:
POST /api/v1/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "userpassword"
}
1.2.2 后端处理逻辑
后端进行登录验证。登录成功后,生成并返回 idToken 给前端。
后端返回参数:
- idToken: idToken。
- expires_in: 令牌过期时间,单位秒
后端返回示例(登录成功):
响应数据示例
{
"data": {
"token": "token",
"expires_in": xxx
"refresh_token":"token",
"user_id":"user_id"
},
"code": 1,
"msg": null,
"ok": true
}
{
"data": {
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOi0xLCJ1c2VySWQiOiI0ODgyMzYyNzIzMzg4NzAyNzIifQ==.a2Nipd4YQc0DQ4S5lUlFVrh6Xn_GuI4qBbFS7yRddIQ",
"user_id": "488236272338870272",
"expires_in": 1742088368,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDIwODgzNjgsInVzZXJJZCI6IjQ4ODIzNjI3MjMzODg3MDI3MiJ9.liKfYdWzULDPCeKfktyKyEanZIO3Q7uhZ9mAKMfjWyM"
},
"error": null,
"msg": null,
"code": 1,
"ok": true
}
1.2.3 前端处理逻辑
前端接收到 idToken 后,后续请求如果需在请求头中携带该 token:格式为
authorization: Bearer {token}
1.3 验证接口
http://localhost:8100/api/v1/verify?email=litongjava001@gmail.com&code=358412
1.4 刷新 token
携带 token
POST /api/v1/user/refresh
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOi0xLCJ1c2VySWQiOiI0ODgyMzYyNzIzMzg4NzAyNzIifQ==.a2Nipd4YQc0DQ4S5lUlFVrh6Xn_GuI4qBbFS7yRddIQ"
}
{
"data": {
"user_id": "488236272338870272",
"expires_in": 604800,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjYwNDgwMCwidXNlcklkIjoiNDg4MjM2MjcyMzM4ODcwMjcyIn0=.fCbcrxdxgpbdC81wS3nPRMRyuKA0_G7iTyBfH5qOF2w"
},
"error": null,
"ok": true,
"code": 1,
"msg": null
}
1.5 登出用户
退出当前系统的登录 注意:系统采用 单 Token 设计,后端不会存储后端 token,退出登录后上次生成的 token 依旧有效,所以用户登出实际上不用调用任何接口,前端将 token 删除即可.如果考虑后续的扩展,前端可以使用该接口
GET /api/v1/logout
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDIwODgzNjgsInVzZXJJZCI6IjQ4ODIzNjI3MjMzODg3MDI3MiJ9.liKfYdWzULDPCeKfktyKyEanZIO3Q7uhZ9mAKMfjWyM
1.6 删除用户
从系统中删除该用户
GET /api/v1/user/remove
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDIwODgzNjgsInVzZXJJZCI6IjQ4ODIzNjI3MjMzODg3MDI3MiJ9.liKfYdWzULDPCeKfktyKyEanZIO3Q7uhZ9mAKMfjWyM
1.7 其他后端接口
GET /api/v1/profile
{"data":{"school_id":null,"user_type":0},"ok":true,"code":1,"msg":null}
update
GET /api/v1/user/update?user_type=1
{"data":null,"msg":null,"ok":true,"code":1}
下面给出一套基于 Java(Tio-boot 框架风格)的示例实现,包含注册、登录、发送邮件以及邮箱验证码验证等接口。示例代码中保留了 SQL 建表语句和详细注释,便于理解各个步骤的逻辑。注意:以下代码仅为示例,实际项目中需要根据项目情况进行完善(如密码加盐算法、Token 生成逻辑、异常处理等)。
2.数据表
1.1 用户表
drop table if exists app_users;
CREATE TABLE "public"."app_users" (
"id" VARCHAR PRIMARY KEY,-- 用户唯一标识 (对应 uid)
"email" VARCHAR UNIQUE,-- 用户邮箱
"phone" varchar unique,
"email_verified" BOOLEAN DEFAULT FALSE,-- 邮箱是否已验证
"updated_profile" BOOLEAN DEFAULT FALSE,-- 用户信息是否已经更新
"display_name" VARCHAR,-- 用户显示名称
"bio" TEXT,
"photo_url" VARCHAR,-- 用户头像 URL
"background_url" VARCHAR,-- 用户背景 URL
"phone_number" VARCHAR,-- 用户电话号码
"disabled" BOOLEAN DEFAULT FALSE,-- 用户是否已禁用
"birthday" TIMESTAMPTZ,-- 用户生日 (可选)
"coin" BIGINT DEFAULT 0,-- 用户金币数量
"invited_by_user_id" VARCHAR,-- 邀请人的用户 ID
"of" VARCHAR,-- 用户注册的系统
"platform" VARCHAR,-- 用户注册来源平台
"third_platform_url" VARCHAR,-- 第三方平台的 URL
"school_id" BIGINT,-- 学校 ID (可选字段)
"user_type" INT DEFAULT 0,-- 用户类型 (如 0: 普通用户, 1: 管理员等)
"password_salt" VARCHAR,-- 密码加盐 (用于存储加密的密码相关信息)
"password_hash" VARCHAR,-- 密码哈希值
provider_data JSONB DEFAULT '[]',-- 提供信息,存储为 JSON 格式
"mfa_info" JSONB DEFAULT '[]',-- 多因素认证信息,存储为 JSON 格式
"metadata" JSONB DEFAULT '{}',-- 用户元数据 (如创建时间、最后登录时间)
"user_info" JSONB DEFAULT '{}',
google_id VARCHAR,
google_info JSONB DEFAULT '{}' :: jsonb,
facebook_id VARCHAR,
facebook_info JSONB DEFAULT '{}' :: jsonb,
twitter_id VARCHAR,
twitter_info JSONB DEFAULT '{}' :: jsonb,
github_id VARCHAR,
github_info JSONB DEFAULT '{}' :: jsonb,
wechat_id VARCHAR,
wechat_info JSONB DEFAULT '{}' :: jsonb,
qq_id VARCHAR,
qq_info JSONB DEFAULT '{}' :: jsonb,
weibo_id VARCHAR,
weibo_info JSONB DEFAULT '{}' :: jsonb,
"remark" VARCHAR,-- 备注信息
"creator" VARCHAR DEFAULT '' :: CHARACTER VARYING,-- 创建人
"create_time" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,-- 创建时间
"updater" VARCHAR DEFAULT '' :: CHARACTER VARYING,-- 更新人
"update_time" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,-- 更新时间
"deleted" SMALLINT NOT NULL DEFAULT 0,-- 逻辑删除标识 (0: 未删除, 1: 已删除)
"tenant_id" BIGINT NOT NULL DEFAULT 0 -- 租户 ID (支持多租户架构)
);
-- 邮箱索引 (唯一索引已自动创建,因为字段定义为 UNIQUE)
-- 但为确保清晰,显式声明
CREATE UNIQUE INDEX idx_app_users_email ON "public"."app_users" ("email") WHERE "deleted" = 0;
-- 租户 ID 索引 (多租户环境下的查询优化)
CREATE INDEX idx_app_users_tenant_id ON "public"."app_users" ("tenant_id") WHERE "deleted" = 0;
-- 用户类型索引 (用于按用户类型筛选)
CREATE INDEX idx_app_users_user_type ON "public"."app_users" ("user_type") WHERE "deleted" = 0;
-- 邀请关系索引 (用于查询邀请关系)
CREATE INDEX idx_app_users_invited_by ON "public"."app_users" ("invited_by_user_id") WHERE "deleted" = 0;
-- 学校索引 (用于按学校筛选用户)
CREATE INDEX idx_app_users_school_id ON "public"."app_users" ("school_id") WHERE "deleted" = 0;
-- 平台索引 (用于按注册平台筛选)
CREATE INDEX idx_app_users_platform ON "public"."app_users" ("platform") WHERE "deleted" = 0;
-- 创建时间索引 (用于按时间排序或筛选)
CREATE INDEX idx_app_users_create_time ON "public"."app_users" ("create_time") WHERE "deleted" = 0;
-- 更新时间索引 (用于按更新时间排序或筛选)
CREATE INDEX idx_app_users_update_time ON "public"."app_users" ("update_time") WHERE "deleted" = 0;
-- 复合索引:租户ID + 用户类型 (常见的多条件查询)
CREATE INDEX idx_app_users_tenant_user_type ON "public"."app_users" ("tenant_id", "user_type") WHERE "deleted" = 0;
-- 金币数量索引 (用于排行榜等功能)
CREATE INDEX idx_app_users_coin ON "public"."app_users" ("coin") WHERE "deleted" = 0;
1.2 邮箱验证码表
用于存储邮件验证码信息,便于后续验证邮箱:
CREATE TABLE "public"."app_email_verification" (
"id" SERIAL PRIMARY KEY,
"email" VARCHAR NOT NULL, -- 验证的邮箱
"verification_code" VARCHAR NOT NULL, -- 验证码
"expire_time" TIMESTAMPTZ NOT NULL, -- 过期时间
"verified" BOOLEAN DEFAULT FALSE, -- 是否已验证
"creator" VARCHAR DEFAULT ''::character varying, -- 创建人
"create_time" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
"updater" VARCHAR DEFAULT ''::character varying, -- 更新人
"update_time" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
"deleted" SMALLINT NOT NULL DEFAULT 0, -- 逻辑删除标识 (0: 未删除, 1: 已删除)
"tenant_id" BIGINT NOT NULL DEFAULT 0 -- 租户 ID (支持多租户架构)
);
3. 代码实现
下面实现注册、登录、发送验证邮件及邮箱验证逻辑。整体采用类似 Tio-boot 的 Handler 方式实现。
2.1 注册接口
当用户提交注册请求时,系统完成用户信息存储(密码进行加盐和哈希处理),同时调用发送邮件接口生成验证码记录并发送验证链接。
2.1.1 请求对象
package com.litongjava.tio.boot.admin.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AppUserRegisterRequest {
private String email;
private String password;
private int userType; // 0:学生,1:教授
}
2.1.2 注册 Handler
package com.litongjava.tio.boot.admin.handler;
import java.util.ArrayList;
import java.util.List;
import com.litongjava.db.activerecord.Db;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.model.validate.ValidateResult;
import com.litongjava.tio.boot.admin.costants.TioBootAdminTableNames;
import com.litongjava.tio.boot.admin.services.AppEmailService;
import com.litongjava.tio.boot.admin.services.AppUserService;
import com.litongjava.tio.boot.admin.vo.AppUserRegisterRequest;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.utils.hutool.StrUtil;
import com.litongjava.tio.utils.json.Json;
import com.litongjava.tio.utils.validator.EmailValidator;
import com.litongjava.tio.utils.validator.PasswordValidator;
public class AppUserRegisterHandler {
public HttpResponse register(HttpRequest request) {
String origin = request.getOrigin();
HttpResponse response = TioRequestContext.getResponse();
String body = request.getBodyString();
List<ValidateResult> validateResults = new ArrayList<>();
boolean ok = true;
if (StrUtil.isEmpty(origin)) {
ValidateResult validateResult = ValidateResult.by("origin", "Failed to valiate origin:" + origin);
validateResults.add(validateResult);
ok = false;
}
// 解析注册请求参数
AppUserRegisterRequest req = Json.getJson().parse(body, AppUserRegisterRequest.class);
String email = req.getEmail();
boolean validate = EmailValidator.validate(email);
if (!validate) {
ValidateResult validateResult = ValidateResult.by("eamil", "Failed to valiate email:" + email);
validateResults.add(validateResult);
ok = false;
}
String password = req.getPassword();
validate = PasswordValidator.validate(password);
if (!validate) {
ValidateResult validateResult = ValidateResult.by("password", "Failed to valiate password:" + password);
validateResults.add(validateResult);
ok = false;
}
if (!ok) {
return response.setJson(RespBodyVo.failData(validateResults));
}
boolean exists = Db.exists(TioBootAdminTableNames.app_users, "email", email);
if (exists) {
ValidateResult validateResult = ValidateResult.by("eamil", "Eamil already taken" + email);
validateResults.add(validateResult);
}
if (!ok) {
return response.setJson(RespBodyVo.failData(validateResults));
}
AppUserService appUserService = Aop.get(AppUserService.class);
// 注册用户(内部会处理密码加盐和哈希等逻辑)
boolean success = appUserService.registerUser(req.getEmail(), req.getPassword(), req.getUserType(), origin);
if (success) {
// 注册成功后发送验证邮件(验证码及链接)
AppEmailService emailService = Aop.get(AppEmailService.class);
boolean sent = emailService.sendVerificationEmail(req.getEmail(), origin);
if(sent) {
return response.setJson(RespBodyVo.ok());
}else {
return response.setJson(RespBodyVo.fail("Failed to send email"));
}
} else {
return response.setJson(RespBodyVo.fail());
}
}
}
2.2 登录接口
登录时前端提交邮箱和密码。后端进行密码校验后生成 Token(及 refreshToken),不再强制要求邮箱验证(即使 email_verified 为 false,也允许登录)。
2.2.1 请求对象
package com.litongjava.tio.boot.admin.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class AppUserLoginRequest {
private String email;
private String password;
}
2.2.2 登录 Handler
package com.litongjava.tio.boot.admin.handler;
import com.jfinal.kit.Kv;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.tio.boot.admin.services.AppUserService;
import com.litongjava.tio.boot.admin.vo.AppUser;
import com.litongjava.tio.boot.admin.vo.AppUserLoginRequest;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.json.JsonUtils;
public class AppUserLoginHandler {
public HttpResponse login(HttpRequest request) {
HttpResponse response = TioRequestContext.getResponse();
String body = request.getBodyString();
AppUserLoginRequest req = JsonUtils.parse(body, AppUserLoginRequest.class);
AppUserService appUserService = Aop.get(AppUserService.class);
AppUser user = appUserService.getUserByEmail(req.getEmail());
// 此处允许未验证邮箱的用户登录
if (user != null && appUserService.verifyPassword(user, req.getPassword())) {
// 生成 token,有效期 7 天(604800秒)
Long timeout = EnvUtils.getLong("app.token.timeout", 604800L);
Long tokenTimeout = System.currentTimeMillis() / 1000 + timeout;
String token = appUserService.createToken(user.getId(), tokenTimeout);
String refreshToken = appUserService.createRefreshToken(user.getId());
Kv kv = Kv.by("user_id", user.getId()).set("token", token).set("expires_in", tokenTimeout.intValue())
//
.set("refresh_token", refreshToken);
return response.setJson(RespBodyVo.ok(kv));
}
return response.setJson(RespBodyVo.fail("username or password is not correct"));
}
public HttpResponse logout(HttpRequest request) {
HttpResponse response = TioRequestContext.getResponse();
String userId = TioRequestContext.getUserIdString();
AppUserService appUserService = Aop.get(AppUserService.class);
boolean logout = appUserService.logout(userId);
if (logout) {
response.setJson(RespBodyVo.ok());
} else {
response.setJson(RespBodyVo.fail());
}
return response;
}
}
2.3 用户接口
package com.litongjava.tio.boot.admin.handler;
import com.jfinal.kit.Kv;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.tio.boot.admin.costants.AppConstant;
import com.litongjava.tio.boot.admin.services.AppUserService;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.json.FastJson2Utils;
import com.litongjava.tio.utils.jwt.JwtUtils;
public class AppUserHandler {
public HttpResponse refresh(HttpRequest request) {
HttpResponse response = TioRequestContext.getResponse();
String bodyString = request.getBodyString();
String refresh_token = FastJson2Utils.parseObject(bodyString).getString("refresh_token");
String key = EnvUtils.getStr(AppConstant.ADMIN_SECRET_KEY);
boolean verify = JwtUtils.verify(key, refresh_token);
if (verify) {
String userId = JwtUtils.parseUserIdString(refresh_token);
AppUserService appUserService = Aop.get(AppUserService.class);
// 生成 token,有效期 7 天(604800秒)
Long timeout = EnvUtils.getLong("app.token.timeout", 604800L);
Long tokenTimeout = System.currentTimeMillis() / 1000 + timeout;
String token = appUserService.createToken(userId, tokenTimeout);
Kv kv = Kv.by("user_id", userId).set("token", token).set("expires_in", tokenTimeout.intValue());
response.setJson(RespBodyVo.ok(kv));
} else {
response.setJson(RespBodyVo.fail("Failed to validate refresh_token"));
}
return response;
}
public HttpResponse remove(HttpRequest request) {
HttpResponse response = TioRequestContext.getResponse();
String userIdString = TioRequestContext.getUserIdString();
AppUserService appUserService = Aop.get(AppUserService.class);
boolean ok = appUserService.remove(userIdString);
if (ok) {
response.setJson(RespBodyVo.ok());
} else {
response.setJson(RespBodyVo.fail());
}
return response;
}
}
3. 服务层实现
3.1 用户服务(AppUserService)
负责用户注册、密码验证、用户查询等操作。注意:此处示例使用简单的密码加密(SHA-256),实际项目中可采用更安全的加盐算法。
package com.litongjava.tio.boot.admin.services;
import org.apache.commons.codec.digest.DigestUtils;
import com.litongjava.db.activerecord.Db;
import com.litongjava.tio.boot.admin.costants.AppConstant;
import com.litongjava.tio.boot.admin.costants.TioBootAdminTableNames;
import com.litongjava.tio.boot.admin.vo.AppUser;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.jwt.JwtUtils;
import com.litongjava.tio.utils.snowflake.SnowflakeIdUtils;
public class AppUserService {
// 注册用户:先检查邮箱是否已存在,然后插入用户记录
public boolean registerUser(String email, String password, int userType, String orgin) {
boolean exists = Db.exists(TioBootAdminTableNames.app_users, "email", email);
if (exists) {
return true;
}
// 生成加盐字符串(示例中直接使用随机数,实际可使用更复杂逻辑)
String salt = String.valueOf(System.currentTimeMillis());
// 生成密码哈希(密码+盐)
String passwordHash = DigestUtils.sha256Hex(password + salt);
// 插入用户记录(id 这里简单采用 email 作为唯一标识)
long id = SnowflakeIdUtils.id();
String insertSql = "INSERT INTO app_users (id, email, password_salt, password_hash, user_type,of) VALUES (?,?,?,?,?,?)";
int rows = Db.updateBySql(insertSql, id + "", email, salt, passwordHash, userType, orgin);
return rows > 0;
}
// 根据邮箱获取用户信息
public AppUser getUserByEmail(String email) {
String sql = "SELECT * FROM app_users WHERE email=? AND deleted=0";
return Db.findFirst(AppUser.class, sql, email);
}
public AppUser getUserById(Long userId) {
String sql = "SELECT * FROM app_users WHERE id=? AND deleted=0";
return Db.findFirst(AppUser.class, sql, userId);
}
// 校验用户密码
public boolean verifyPassword(AppUser user, String password) {
String salt = user.getPasswordSalt();
String hash = DigestUtils.sha256Hex(password + salt);
return hash.equals(user.getPasswordHash());
}
public boolean verifyPassword(String email, String password) {
AppUser appUser = getUserByEmail(email);
return verifyPassword(appUser, password);
}
public boolean verifyPassword(Long userId, String password) {
AppUser appUser = getUserById(userId);
return verifyPassword(appUser, password);
}
public String createToken(String id, Long timeout) {
String key = EnvUtils.getStr(AppConstant.ADMIN_SECRET_KEY);
return JwtUtils.createTokenByUserId(key, id, timeout);
}
public String createRefreshToken(String id) {
String key = EnvUtils.getStr(AppConstant.ADMIN_SECRET_KEY);
return JwtUtils.createTokenByUserId(key, id, -1);
}
public boolean logout(String userId) {
return true;
}
public boolean remove(String userId) {
String sql = "update app_users set deleted=1 WHERE id=?";
Db.updateBySql(sql, userId);
return true;
}
}
4. 路由配置
最后在路由配置中绑定各个接口。下面示例代码与 Tio-boot 的路由配置类似:
package com.example.app.config;
import com.example.app.handler.RegisterHandler;
import com.example.app.handler.LoginHandler;
import com.example.app.handler.EmailVerificationHandler;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.http.server.router.HttpRequestRouter;
public class AppHandlerConfiguration {
public void config() {
HttpRequestRouter router = TioBootServer.me().getRequestRouter();
if (router == null) {
return;
}
AppUserRegisterHandler appUserRegisterHandler = Aop.get(AppUserRegisterHandler.class);
AppUserLoginHandler loginHandler = Aop.get(AppUserLoginHandler.class);
EmailVerificationHandler emailVerificationHandler = Aop.get(EmailVerificationHandler.class);
AppUserHandler appUserHandler = Aop.get(AppUserHandler.class);
// 注册接口
r.add("/api/v1/register", appUserRegisterHandler::register);
// 登录接口
r.add("/api/v1/login", loginHandler::login);
//刷新
r.add("/api/v1/user/refresh", appUserHandler::refresh);
// 登出
r.add("/api/v1/logout", loginHandler::logout);
//删除
r.add("/api/v1/user/remove", appUserHandler::remove);
// 发送验证码邮件接口
r.add("/api/v1/sendVerification", emailVerificationHandler::sendVerification);
// 邮箱验证接口
r.add("/api/v1/verify", emailVerificationHandler::verifyEmail);
r.add("/verification/email", emailVerificationHandler::verifyEmail);
}
}
5.拦截器配置
package com.litongjava.tio.boot.admin.services;
import java.util.function.Predicate;
import com.litongjava.tio.boot.admin.costants.AppConstant;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.jwt.JwtUtils;
public class TokenPredicate implements Predicate<String> {
@Override
public boolean test(String token) {
String adminToken = EnvUtils.get(AppConstant.ADMIN_TOKEN);
//system token
boolean equals = adminToken.equals(token);
if (equals) {
TioRequestContext.setUserId(0L);
return true;
}
// user and admin token
String key = EnvUtils.getStr(AppConstant.ADMIN_SECRET_KEY);
boolean verify = JwtUtils.verify(key, token);
if (verify) {
String userId = JwtUtils.parseUserIdString(token);
TioRequestContext.setUserId(userId);
return true;
}
return false;
}
public Long parseUserIdLong(String token) {
String key = EnvUtils.getStr(AppConstant.ADMIN_SECRET_KEY);
boolean verify = JwtUtils.verify(key, token);
if (verify) {
return JwtUtils.parseUserIdLong(token);
}
return null;
}
}
设置 allow urls
model.addAllowUrls("/api/v1/login", "/api/v1/register", "/api/v1/user/refresh", "/api/v1/sendVerification", "/api/v1/verify", "/verification/email");
package com.litongjava.tio.boot.admin.config;
import java.util.function.Predicate;
import com.litongjava.tio.boot.admin.services.TokenPredicate;
import com.litongjava.tio.boot.http.interceptor.HttpInteceptorConfigure;
import com.litongjava.tio.boot.http.interceptor.HttpInterceptorModel;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.boot.token.AuthTokenInterceptor;
public class TioAdminInterceptorConfiguration {
private String[] permitUrls;
private boolean alloweStaticFile;
private Predicate<String> validateTokenLogic;
public TioAdminInterceptorConfiguration() {
}
public TioAdminInterceptorConfiguration(String[] permitUrls) {
this.permitUrls = permitUrls;
}
public TioAdminInterceptorConfiguration(String[] permitUrls, Predicate<String> validateTokenLogic) {
this.permitUrls = permitUrls;
this.validateTokenLogic = validateTokenLogic;
}
public TioAdminInterceptorConfiguration(String[] permitUrls, boolean b) {
this.permitUrls = permitUrls;
this.alloweStaticFile = b;
}
public void config() {
// 创建 SaToken 拦截器实例
if (validateTokenLogic == null) {
validateTokenLogic = new TokenPredicate();
}
AuthTokenInterceptor authTokenInterceptor = new AuthTokenInterceptor(validateTokenLogic);
HttpInterceptorModel model = new HttpInterceptorModel();
model.setInterceptor(authTokenInterceptor);
model.addBlockUrl("/**"); // 拦截所有路由
// index
model.addAllowUrls("", "/");
//user
model.addAllowUrls("/register/*", "/api/login/account", "/api/login/outLogin"); // 设置例外路由
model.addAllowUrls("/api/v1/login", "/api/v1/register", "/api/v1/user/refresh", "/api/v1/sendVerification", "/api/v1/verify", "/verification/email");
model.addAllowUrls("/api/event/add");
String[] previewUrls = { "/table/json/tio_boot_admin_system_article/get/*",
//
"/table/json/tio_boot_admin_system_docx/get/*", "/table/json/tio_boot_admin_system_pdf/get/*" };
model.addAllowUrls(previewUrls);
if (permitUrls != null) {
model.addAllowUrls(permitUrls);
}
model.setAlloweStaticFile(alloweStaticFile);
HttpInteceptorConfigure inteceptorConfigure = new HttpInteceptorConfigure();
inteceptorConfigure.add(model);
// 将拦截器配置添加到 Tio 服务器
TioBootServer.me().setHttpInteceptorConfigure(inteceptorConfigure);
}
}
8.邮件发送
lark.mail.host=smtp.larksuite.com
lark.mail.protocol=smtp
lark.mail.smpt.port=465
lark.mail.user=noreply@myget.ai
lark.mail.password=1111
lark.mail.from=no-reply
emails\register_mail.txt
Dear User,
Thank you for signing up for College Bot AI!
Please verify your email address by clicking the link below:
#(link)
This will ensure your account is secure and fully activated.
If you did not request this verification, please disregard this email.
Best regards,
The College Bot AI Team
com.litongjava.tio.boot.admin.config.TioAdminLarkSuitMailConfig.TioAdminLarkSuitMailConfig()
new TioAdminLarkSuitMailConfig().config();
package com.litongjava.myget.config;
import com.litongjava.annotation.AConfiguration;
import com.litongjava.annotation.Initialization;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.myget.email.MyGetEmailSender;
import com.litongjava.tio.boot.admin.config.*;
import com.litongjava.tio.boot.admin.handler.SystemFileTencentCosHandler;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.http.server.router.HttpRequestRouter;
@AConfiguration
public class AdminAppConfig {
@Initialization
public void config() {
// 配置数据库相关
new TioAdminDbConfiguration().config();
new TioAdminRedisDbConfiguration().config();
new TioAdminMongoDbConfiguration().config();
new TioAdminInterceptorConfiguration().config();
new TioAdminHandlerConfiguration().config();
new TioAdminLarkSuitMailConfig().config();
// 获取 HTTP 请求路由器
TioBootServer server = TioBootServer.me();
HttpRequestRouter r = server.getRequestRouter();
if (r != null) {
// 获取文件处理器,并添加文件上传和获取 URL 的接口
SystemFileTencentCosHandler systemUploadHandler = Aop.get(SystemFileTencentCosHandler.class);
r.add("/api/system/file/upload", systemUploadHandler::upload);
r.add("/api/system/file/url", systemUploadHandler::getUrl);
}
server.setEmailSender(new MyGetEmailSender());
// 配置控制器
new TioAdminControllerConfiguration().config();
}
}
package com.litongjava.myget.email;
import com.jfinal.kit.Kv;
import com.jfinal.template.Template;
import com.litongjava.myget.utils.MailJetUtils;
import com.litongjava.template.EmailEngine;
import com.litongjava.tio.boot.admin.mail.LarkSuitEmailUtils;
import com.litongjava.tio.boot.email.EmailSender;
import com.mailjet.client.MailjetResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyGetEmailSender implements EmailSender {
@Override
public boolean send(String to, String subject, String content) {
return false;
}
@Override
public boolean sendVerificationEmail(String email, String origin, String code) {
Template template = EmailEngine.getTemplate("register_mail.txt");
String link = origin + "/verification/email?email=%s&code=%s";
link = String.format(link, email, code);
String content = template.renderToString(Kv.by("link", link));
// 从邮箱地址中提取用户名作为收件人姓名
String name = email.split("@")[0];
String subject = "College Bot AI Email Verification";
// 发送 HTML 格式的邀请邮件
return sendWithLark(name, email, subject, content);
//return sendWithMailjet(name, email, subject,content);
}
private boolean sendWithLark(String name, String email, String subject, String content) {
try {
LarkSuitEmailUtils.send(email, subject, content);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
private boolean sendWithMailjet(String name, String email, String subject, String content) {
try {
MailjetResponse response = MailJetUtils.sendText(name, email, subject, content);
int status = response.getStatus();
if (status == 200) {
return true;
} else {
log.error(response.getRawResponseContent());
return false;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
9.基本配置
admin.secret.key=123456
admin.token=123456
app.token.timeout=604800
8. 总结
- 注册:前端提交邮箱、密码和用户类型,后端完成用户记录创建(包含密码加盐与哈希),并调用邮件服务发送验证码。
- 登录:前端提交邮箱和密码,后端验证后生成 idToken(以及 refreshToken),即使邮箱未验证也允许登录。
- 邮箱验证码发送与验证:提供独立接口用于发送验证码邮件和验证验证码;验证成功后更新用户表中的邮箱验证状态。
以上示例实现了用户名密码注册和登录,并包含邮箱验证码发送和验证逻辑。所有代码均保留了原始逻辑和必要注释,方便直接参考和二次开发。