后端¶
数据库设计¶
user表¶
- id int 主键
- phone varchar(20)
- username varchar(20)
- createdDate 创建时间
- isDeleted int 删除位
create table user
(
id int auto_increment
primary key,
username varchar(20) null,
phone varchar(20) not null,
isDeleted int default 0 not null,
createdTime timestamp default CURRENT_TIMESTAMP null
);
创建项目¶
使用IDEA创建项目
添加依赖项
数据库配置¶
配置MySQL数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/RedisSessionDemo?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
导入MybatisPlus坐标(⭐️:注意需要导boot-starter的版本)
<!-- Mybatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.2</version>
</dependency>
使用MybatisX生成代码
给UserMapper加@Mapper
注解(⭐️:包一定要选对,不能选父文件夹)
@SpringBootApplication
@MapperScan("cn.wmhwiki.redissessiondemo.mapper")
public class RedisSessionDemoApplication {...}
配置逻辑删除
mybatis-plus:
global-config:
db-config:
logic-delete-field: isDeleted # 全局逻辑删除的实体字段名
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
关闭驼峰转换下划线
配置Redis¶
spring:
data:
redis:
host: localhost
password: 123.com
port: 12345
database: 0
timeout: 100000
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: 10
min-idle: 0
通用返回对象¶
定义通用返回对象
@Data
public class BaseResponse<T> implements Serializable {
private int code;
private T data;
private String msg;
}
通用返回对象工具类
public class ResponseUtils {
public static <T> BaseResponse<T> ok(T data) {
BaseResponse<T> response = new BaseResponse<>();
response.setCode(200);
response.setData(data);
response.setMsg("OK");
return response;
}
public static <T> BaseResponse<T> error(String msg) {
BaseResponse<T> response = new BaseResponse<>();
response.setCode(500);
response.setMsg(msg);
return response;
}
}
全局异常处理类¶
定义业务异常类
@Data
@AllArgsConstructor
public class BusinessException extends RuntimeException {
private int code;
private String description;
}
编写全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public BaseResponse handleBusinessException(BusinessException e) {
log.error("BusinessException: {}", e.getDescription());
return ResponseUtils.error(e.getDescription());
}
@ExceptionHandler(RuntimeException.class)
public BaseResponse handleRuntimeException(RuntimeException e) {
log.error("RuntimeException: {}", e.getMessage());
return ResponseUtils.error(e.getMessage());
}
}
获取验证码接口 /code¶
思路:
graph LR
1([begin])
2([end])
a[提交手机号]
b{手机号合法校验}
e[保存到Redis]
f[发送验证码]
1 --> a
a --> b
b -->|"合法"| e --> f --> 2
b -->|"不合法"| 1
使用 Hutools工具类 生成随机验证码
将验证码保存到Redis
登录接口 /login¶
思路
graph
1([begin])
2([end])
a[提交手机号和验证码]
b{手机号合法校验}
e[在Redis上通过手机号获取验证码]
f{验证码校验}
h[生成Token]
c{判断用户是否存在}
i[删除Redis上的验证码]
g[将用户信息保存到Redis]
k[创建新用户]
d[返回Token]
1 --> a
a --> b
b -->|"合法"| e
b -->|"不合法"| 1
e --> f
f -->|"一致"| i
f -->|"不一致"| 1
i --> c
c -->|存在| h
c -->|不存在| k
k --> 保存到数据库 --> h
h --> g
g --> d --> 2
配置拦截器¶
思路
graph LR
1([请求])
2([Controller])
1 --> Token刷新拦截器 --> 登录拦截器 --> 2
拦截器配置类
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private StringRedisTemplate template;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor(template)).addPathPatterns(
"/**"
).order(0);
registry.addInterceptor(new LoginInterceptor()).addPathPatterns(
"/user"
).order(2);
}
}
Token刷新拦截器¶
拦截所有请求
用于刷新Token有效期,并将用户信息保存到ThreadLocal。之后使用ThreadLocal获取用户信息。
graph LR
1([请求])
2([Controller])
a[提交Token]
b[在Redis中查询用户]
1 --> a --> b
b -->|不存在| 2
b -->|存在| 查询用户信息 --> 保存到ThreadLocal --> Token续期 --> 2
在请求头上获取Token
使用ThreadLocal保存用户信息
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<UserDTO>();
public static void saveUser(UserDTO user) {
tl.set(user);
}
public static UserDTO getUser() {
return tl.get();
}
public static void removeUser() {
tl.remove();
}
}
登录拦截器¶
拦截需要登录才能访问的接口(/user 个人信息)
用于判断是否登录,通过ThreadLocal判空操作获取登录信息。
graph LR
1([请求])
2([Controller])
3(["❎ 401"])
a[从ThreadLoacl中获取用户信息]
1 -->a -->|存在| 2
a -->|不存在| 3
@Data
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (UserHolder.getUser() == null) {
response.setStatus(401);
return false;
}
return true;
}
}
配置允许跨域¶
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600)
.exposedHeaders("Authorization");
}
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
config.setMaxAge(3600L);
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}