文章目录
- 创建Spring Boot工程
- 引入依赖库和插件
- 编写配置文件
- Spring Boot 配置文件
- 日志配置文件
- API 文档生成插件配置文件
- 设计项目包结构
- 编写 Java 配置类
- 程序入口加注解 `@EnableCaching`
- 配置 Mybatis-Plus
- 配置跨域
- 配置 Redis
- 配置 Sa-Token 拦截器
- 统一后端返回格式
- 返回码定义
- 返回结果统一封装
- 返回结果统一处理
- 全局异常拦截
- 引入工具类
- 操作 Redis 的 RedisUtil
- 连接 COS 的工具类
- 实现权限认证功能
- 编写实体
- 根据实体编写对应的ORM层和Service层
- 实现 Sa-Token 的权限认证接口
- 实现 Redis 缓存功能
- 实体和对象映射
- Service 集成 Redis 缓存机制
- 编写单元测试
在学spring boot的过程中慢慢整合常用的组件,想着汇总成一个项目,方便下次开发拿来即用。
创建Spring Boot工程
使用 idea 的 Spring Initializr 创建一个 Spring Boot 工程,这一步全部默认即可。
引入依赖库和插件
pom.xml
里的 dependencies
和 build
添加以下内容,刷新等待依赖全部导进来即可。
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.28.0</version>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.61</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.github.shalousun</groupId>
<artifactId>smart-doc-maven-plugin</artifactId>
<version>2.2.9</version>
<configuration>
<configFile>./src/main/resources/smart-doc.json</configFile>
</configuration>
</plugin>
</plugins>
</build>
编写配置文件
Spring Boot 配置文件
我习惯使用 yml
文件格式,可以右键默认的 properties
文件把后缀名改成 yml
然后填入以下内容
# application.yml
server:
port: 8088
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=UTC
username: root
password: 123456
hikari:
minimum-idle: 1
maximum-pool-size: 20
initialization-fail-timeout: 60000
jpa:
show-sql: false
database-platform: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
redis:
host: localhost
port: 6379
database: 1
password:
lettuce:
pool:
max-idle: 8
max-active: 8
max-wait: -1ms
min-idle: 0
sa-token:
token-name: satoken
timeout: 86400
activity-timeout: -1
is-share: false
token-style: uuid
is-log: true
这里自行修改 MySQL 和 Redis 配置
日志配置文件
我们使用 log4j2
作为我们的日志框架,虽然说最近曝出来有大漏洞,但对于我们来说问题不大,学生跟着学就行了,可以不管这些安全性问题。
在 /resources
目录下新建 log4j2.xml
文件,填入以下内容
<!-- log4j2.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--变量配置-->
<Properties>
<!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<!-- %logger{36} 表示 Logger 名字最长36个字符 -->
<property name="LOG_PATTERN" value="[%date{YYYY-MM-dd HH:mm:ss}][%thread] %-5level %logger{36} - %msg%n" />
<!-- 定义日志存储的路径 -->
<property name="FILE_PATH" value="log/" />
<property name="FILE_NAME" value="example" />
</Properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<!-- <PatternLayout pattern="${LOG_PATTERN}"/>-->
<PatternLayout
pattern="[%date{YYYY-MM-dd HH:mm:ss}]%style{[%t]}{bright,magenta} %highlight{%-5level}{ERROR=Bright RED, WARN=Bright Yellow, INFO=Bright Green, DEBUG=Bright Cyan, TRACE=Bright White} %style{%c{1.}.%M(%L)}{cyan}: %msg%n"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
<File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
<PatternLayout pattern="${LOG_PATTERN}"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
</appenders>
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<!--监控系统信息-->
<!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="Filelog"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
</root>
</loggers>
</configuration>
这里记得修改项目名称和日志文件存放路径,也可以选择默认
API 文档生成插件配置文件
我们使用 smart-doc
作为项目的API文档生成器,主要优点是无侵入性,只要在接口上写上标准的 javadoc
即可,很方便。
在 /resources
目录下新建 smart-doc.json
文件,填入以下内容
// smart-doc.json
{
"serverUrl": "http://localhost:8088/",
"isStrict": false,
"allInOne": true,
"outPath": "src/main/resources/docs",
"coverOld": true,
"createDebugPage": true,
"md5EncryptedHtmlName": false,
"projectName": "example",
"showAuthor": true
}
这里想改的话建议参考官方给的文档。
设计项目包结构
这里参考羊哥公众号发的那个,根据我的理解改了一点
编写 Java 配置类
程序入口加注解 @EnableCaching
加这个注解启用缓存
@EnableCaching
@SpringBootApplication
public class SpringBootExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootExampleApplication.class, args);
}
}
配置 Mybatis-Plus
有一说一,这玩意儿挺香的,反正我是真香了
package com.wjl.example.config;
@Configuration
@MapperScan("com.wjl.example.repository")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
配置跨域
因为一般写的是前后端分离项目,所以要进行跨域处理
package com.wjl.example.config;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.allowedOriginPatterns()
.maxAge(3600)
.allowedHeaders("*");
}
}
配置 Redis
进行必要的配置,这里我下午还卡了以下,不是很懂
package com.wjl.example.config;
@EnableCaching
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<?> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
serializer.setObjectMapper(objectMapper);
template.setKeySerializer(redisSerializer);
template.setHashKeySerializer(redisSerializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
配置 Sa-Token 拦截器
我们使用的这个Java权限认证框架很轻量级,官网文档也比较齐全
package com.wjl.example.config;
@Configuration
public class SaIntercepConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaAnnotationInterceptor())
.addPathPatterns("/**");
}
}
统一后端返回格式
SpringBoot 提供的不能满足我们的需求,需要我们自己封装返回结果,并进行配置
返回码定义
package com.wjl.example.common.enums;
public enum HttpStatus {
// 2xx Success
OK(200, "OK"),
// --- 4xx Client Error ---
BAD_REQUEST(400, "Bad Request"),
UNAUTHORIZED(401, "Unauthorized"),
PAYMENT_REQUIRED(402, "Payment Required"),
FORBIDDEN(403, "Forbidden"),
NOT_FOUND(404, "Not Found"),
METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
NOT_ACCEPTABLE(406, "Not Acceptable"),
PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
REQUEST_TIMEOUT(408, "Request Timeout"),
CONFLICT(409, "Conflict"),
// --- 5xx Server Error ---
INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
NOT_IMPLEMENTED(501, "Not Implemented"),
BAD_GATEWAY(502, "Bad Gateway"),
private final int code;
private final String msg;
HttpStatus(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
返回结果统一封装
package com.wjl.example.common.constants;
@Data
public class Result<T> {
private int status;
private String msg;
private T data;
public static <T> Result<T> success(T data) {
Result<T> Result = new Result<>();
Result.setStatus(HttpStatus.OK.getCode());
Result.setMsg(HttpStatus.OK.getMsg());
Result.setData(data);
return Result;
}
public static <T> Result<T> success(String msg) {
Result<T> Result = new Result<>();
Result.setStatus(HttpStatus.OK.getCode());
Result.setMsg(msg);
return Result;
}
public static <T> Result<T> fail(int code, String msg) {
Result<T> Result = new Result<>();
Result.setStatus(code);
Result.setMsg(msg);
return Result;
}
}
返回结果统一处理
package com.wjl.example.component;
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Resource
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter parameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter parameter, MediaType mediaType,
Class<? extends HttpMessageConverter<?>> aClass,
ServerHttpRequest request, ServerHttpResponse response) {
if (o instanceof String) {
return objectMapper.writeValueAsString(Result.success(o));
}
if (o instanceof Result) {
return o;
}
return Result.success(o);
}
}
全局异常拦截
package com.wjl.example.exception;
@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
@ExceptionHandler(Exception.class)
@ResponseStatus(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR)
public Result<String> exception(Exception e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
return Result.fail(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), e.getMessage());
}
}
引入工具类
操作 Redis 的 RedisUtil
package com.wjl.example.util;
@Component
public class RedisUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
// ============================== common ==================================
/**
* set expire time
*
* @param key key
* @param time time
* @return state
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
// ============================== string ==================================
/**
* get value by key
*
* @param key key
* @return value
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
// ================================ map ===================================
/**
* HashGet
*
* @param key key - Notnull
* @param item item - NotNull
* @return data
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
// ================================ set ===================================
/**
* get value in set by key
*
* @param key key
* @return set
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
连接 COS 的工具类
我一般倾向于使用腾讯云,所以这里是腾讯云的COS对象存储,阿里云OSS同理,根据官方文档来就行。
这里可以先定义全局常量,关于COS存储桶的信息,注意这些信息不能上传到GitHub,我上次没注意,直接 git push
一把梭,结果腾讯云立马给我打电话说密钥泄露,千万要注意。
package com.wjl.example.common.constants;
public class GlobalConstants {
public static final String AVATAR = "https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png";
public static final String SECRET_ID = "";
public static final String SECRET_KEY = "";
public static final String REGION = "";
public static final String BUCKET = "";
public static final String FOLDER = "";
}
package com.wjl.example.util;
public class CosClientUtil {
public static COSClient createClient() {
String secretId = GlobalConstants.SECRET_ID;
String secretKey = GlobalConstants.SECRET_KEY;
COSCredentials credentials = new BasicCOSCredentials(secretId, secretKey);
Region region = new Region(GlobalConstants.REGION);
ClientConfig clientConfig = new ClientConfig(region);
return new COSClient(credentials, clientConfig);
}
}
实现权限认证功能
这里我们使用RBAC认证模式
编写实体
这里我们选择使用 Spring Data JPA 作为 ORM 框架,定义 User
、 Role
、 Permission
、 UserRole
、 RolePermission
五个实体
package com.wjl.example.model.entity;
@Getter
@Setter
@Entity
@Table(name = "user")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "token")
private String token;
}
package com.wjl.example.model.entity;
@Getter
@Setter
@Entity
@Table(name = "role")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "role")
private String role;
}
package com.wjl.example.model.entity;
@Getter
@Setter
@Entity
@Table(name = "permission")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "permission")
private String permission;
}
package com.wjl.example.model.entity;
@Getter
@Setter
@Entity
@Table(name = "user_role")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class UserRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
private Long uid;
private Long rid;
}
package com.wjl.example.model.entity;
@Getter
@Setter
@Entity
@Table(name = "role_permission")
@JsonIgnoreProperties({"handler", "hibernateLazyInitializer"})
public class RolePermission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
private Long rid;
private Long pid;
}
根据实体编写对应的ORM层和Service层
这里我就放一起了,因为单个代码比较少
package com.wjl.example.repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findUserById(Long id);
User findUserByUsername(String username);
User findUserByUsernameAndPassword(String username, String password);
}
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Role findRoleById(Long id);
}
@Repository
public interface PermissionRepository extends JpaRepository<Permission, Long> {
Permission findPermissionById(Long id);
}
@Repository
public interface UserRoleRepository extends JpaRepository<UserRole, Long> {
List<UserRole> findAllByUid(Long uid);
}
@Repository
public interface RolePermissionRepository extends JpaRepository<RolePermission, Long> {
List<RolePermission> findAllByRid(Long rid);
}
package com.wjl.example.service.impl;
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserRepository userRepository;
@Override
public User findUserById(Long id) {
return userRepository.findUserById(id);
}
@Override
public User findUserByUsername(String username) {
return userRepository.findUserByUsername(username);
}
@Override
public User findUserByUsernameAndPassword(String username, String password) {
return userRepository.findUserByUsernameAndPassword(username, password);
}
}
@Service
public class RoleServiceImpl implements RoleService {
@Resource
private RoleRepository roleRepository;
@Override
public Role findRoleById(Long rid) {
return roleRepository.findRoleById(rid);
}
}
@Service
public class PermissionServiceImpl implements PermissionService {
@Resource
private PermissionRepository permissionRepository;
@Override
public Permission findPermissionById(Long pid) {
return permissionRepository.findPermissionById(pid);
}
}
@Service
public class UserRoleServiceImpl implements UserRoleService {
@Resource
private UserRoleRepository userRoleRepository;
@Override
public List<UserRole> findRolesByUser(User user) {
return userRoleRepository.findAllByUid(user.getId());
}
}
@Service
public class RolePermissionServiceImpl implements RolePermissionService {
@Resource
private RolePermissionRepository rolePermissionRepository;
@Override
public List<RolePermission> findAllByRole(Role role) {
return rolePermissionRepository.findAllByRid(role.getId());
}
}
实现 Sa-Token 的权限认证接口
根据官方文档,只要实现 StpInterface
接口里的两个方法即可
package com.wjl.example.component;
@Component
public class StpInterfaceImpl implements StpInterface {
@Resource
private UserService userService;
@Resource
private RoleService roleService;
@Resource
private PermissionService permissionService;
@Resource
private UserRoleService userRoleService;
@Resource
private RolePermissionService rolePermissionService;
@Override
public List<String> getPermissionList(Object loginId, String s) {
User user = userService.findUserByUsername(loginId.toString());
List<UserRole> userRoles = userRoleService.findRolesByUser(user);
Set<String> permissions = new HashSet<>();
for (UserRole userRole : userRoles) {
Role role = roleService.findRoleById(userRole.getRid());
List<RolePermission> rolePermissions = rolePermissionService.findAllByRole(role);
for (RolePermission rolePermission : rolePermissions) {
Permission permission = permissionService.findPermissionById(rolePermission.getPid());
permissions.add(permission.getPermission());
}
}
return new ArrayList<>(permissions);
}
@Override
public List<String> getRoleList(Object loginId, String s) {
User user = userService.findUserByUsername(loginId.toString());
List<UserRole> userRoles = userRoleService.findRolesByUser(user);
List<String> roles = new ArrayList<>();
for (UserRole userRole : userRoles) {
roles.add(roleService.findRoleById(userRole.getRid()).getRole());
}
return roles;
}
}
登录注册接口什么的就先不写了。
实现 Redis 缓存功能
实体和对象映射
让我们先定义一个 Todo
实体类,并使用 Mybatis 的注解进行对象映射,不要忘了先创建表
package com.wjl.example.model.entity;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Todo implements Serializable {
private Long id;
private String description;
private String details;
private boolean done;
}
package com.wjl.example.repository;
@Mapper
@Repository
public interface TodoRepository {
@Select("select count(*) from todo")
int countTodo();
@Select("select * from todo")
List<Todo> findAll();
@Select("select * from todo where id=#{id}")
Todo findTodoById(@Param(value = "id") Long id);
@Insert("insert into todo(id, description, details, done) values (#{id}, #{description}, #{details}, #{done})")
int addTodo(Todo todo);
@Update("update todo set description=#{description}, details=#{details}, done=#{done} where id=#{id}")
int updateTodo(Todo todo);
@Delete("delete from todo where id=#{id}")
int deleteTodo(@Param(value = "id") Long id);
}
Service 集成 Redis 缓存机制
这里就直接贴代码了,因为我现在对这个还不是很熟
package com.wjl.example.service.impl;
@Slf4j
@Service
@CacheConfig(cacheNames = "todo")
public class TodoServiceImpl implements TodoService {
@Resource
private TodoRepository todoRepository;
@Resource
private RedisUtil redisUtil;
public static final String CACHE_KEY_TODO = "todo:";
@Override
public int countTodo() {
return todoRepository.countTodo();
}
@Override
public List<Todo> findAll() {
int i = countTodo();
Object o = redisUtil.get(CACHE_KEY_TODO + "number:" + i);
if (!Objects.isNull(o)) {
return (List<Todo>) o;
}
List<Todo> todos = todoRepository.findAll();
redisUtil.set(CACHE_KEY_TODO + "number:" + i, todos);
log.info("cache not exist, do this method");
return todos;
}
@Override
public Todo findTodoById(Long id) {
Object o = redisUtil.get(CACHE_KEY_TODO + id);
if (!Objects.isNull(o)) {
log.info("get from redis: {}", o);
return (Todo) o;
}
Todo todo = todoRepository.findTodoById(id);
redisUtil.set(CACHE_KEY_TODO + id, todo);
log.info("get from mysql, and set into redis: {}", todo);
return todo;
}
@Override
public int addTodo(Todo todo) {
int i = todoRepository.addTodo(todo);
redisUtil.set(CACHE_KEY_TODO + todo.getId(), todo);
log.info("write into redis: {}", todo);
return i;
}
@Override
public int updateTodoById(Todo todo) {
int i = todoRepository.updateTodo(todo);
redisUtil.set(CACHE_KEY_TODO + todo.getId(), todo);
log.info("update data and delete cache");
return i;
}
@Override
public int deleteTodoById(Long id) {
int i = todoRepository.deleteTodo(id);
redisUtil.del(CACHE_KEY_TODO + id);
return i;
}
}
编写单元测试
这是 TodoService
的单元测试
package com.wjl.example.service;
@SpringBootTest
@RunWith(SpringRunner.class)
public class TodoServiceTest {
@Resource
private TodoService todoService;
@Test
public void test() {
List<Todo> todos = todoService.findAll();
System.out.println(todos);
}
@Test
public void selectById() {
Todo todo = todoService.findTodoById(1L);
System.out.println(todo);
}
@Test
public void add() {
todoService.addTodo(new Todo(2L, "test3", "rveref", false));
}
@Test
public void update() {
todoService.updateTodoById(new Todo(3L, "test33", "rveref", false));
}
@Test
public void delete() {
todoService.deleteTodoById(2L);
}
}
这是 RedisUtil
的单元测试
package com.wjl.example.service;
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisServiceTest {
@Resource
private RedisUtil redisUtil;
@Test
public void save() {
Todo todo = new Todo(1L, "test1", "test111111111111111111", false);
redisUtil.set("todo:" + todo.getId(), todo);
System.out.println(redisUtil.get("todo:" + todo.getId()));
todo.setDetails("test123221122141");
redisUtil.del("todo:" + todo.getId());
redisUtil.set("todo:" + todo.getId(), todo);
System.out.println(redisUtil.get("todo:" + todo.getId()));
}
/**
* test redisService common part
*/
@Test
public void commonTest() {
System.out.println(redisUtil.expire("todo:1", 1000));
System.out.println(redisUtil.getExpire("todo:1"));
System.out.println(redisUtil.hasKey("todo:1"));
System.out.println(redisUtil.hasKey("todo:4"));
redisUtil.del("todo:1", "todo:4");
System.out.println(redisUtil.hasKey("todo:1"));
}
/**
* test redisService string part
*/
@Test
public void stringTest() {
redisUtil.set("test:1", "test1111");
System.out.println(redisUtil.get("test:1"));
redisUtil.set("test:2", "test2222", 1000);
System.out.println(redisUtil.getExpire("test:2"));
redisUtil.set("test1", 1);
System.out.println(redisUtil.incr("test1", 1));
redisUtil.set("test2", 100);
System.out.println(redisUtil.decr("test2", 1));
}
/**
* test redisService map part
*/
@Test
public void mapTest() {
Map<String, Object> map = new HashMap<>();
map.put(String.valueOf(1L), "aaa");
map.put(String.valueOf(2L), "bbb");
map.put(String.valueOf(3L), "ccc");
System.out.println(redisUtil.hmset("map-test1", map));
System.out.println(redisUtil.hmset("map-test2", map, 1000));
System.out.println(redisUtil.hmget("map-test1"));
System.out.println(redisUtil.hmget("map-test2"));
System.out.println(redisUtil.hget("map-test1", String.valueOf(1L)));
System.out.println(redisUtil.hset("map-test1", "4", "ddd"));
System.out.println(redisUtil.hset("map-test3", "4", "ddd", 30));
redisUtil.hdel("map-test2", "1", "2");
System.out.println(redisUtil.hHasKey("map-test2", "2"));
System.out.println(redisUtil.hHasKey("map-test2", "3"));
System.out.println(redisUtil.hincr("maptest1", "123", 1));
System.out.println(redisUtil.hdecr("maptest2", "231", 2));
}
/**
* test redisService set part
*/
@Test
public void setTest() {
System.out.println(redisUtil.sSet("set-test1", "set1", "set2", "set3", "set4"));
System.out.println(redisUtil.sGet("set-test1"));
System.out.println(redisUtil.sSetAndTime("set-test2", 100, "set1", "set2"));
System.out.println(redisUtil.sGetSetSize("set-test1"));
System.out.println(redisUtil.setRemove("set-test1", "set3"));
System.out.println(redisUtil.sGetSetSize("set-test1"));
System.out.println(redisUtil.sHashKey("set-test1", "set2"));
}
/**
* test redisService list part
*/
@Test
public void listTest() {
List<Object> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("aaa");
list.add("ccc");
System.out.println(redisUtil.lSet("list-test1", "aaaa"));
System.out.println(redisUtil.lSet("list-test3", "aaaa", 10));
System.out.println(redisUtil.lSet("list-test2", list));
System.out.println(redisUtil.lSet("list-test3", list, 100));
System.out.println(redisUtil.lGet("list-test2", 2, 4));
System.out.println(redisUtil.lGetListSize("list-test2"));
System.out.println(redisUtil.lGetIndex("list-test2", 2));
System.out.println(redisUtil.lUpdateIndex("list-test2", 2, "123"));
System.out.println(redisUtil.lGetListSize("list-test1"));
System.out.println(redisUtil.lRemove("list-test1", 4, "aaaa"));
System.out.println(redisUtil.lGetListSize("list-test1"));
}
}
个人觉得单元测试覆盖面还是很广的。
–完–