Article Directory
In the process of learning spring boot, commonly used components are slowly integrated, thinking about summarizing them into a project, which is convenient for the next development and ready-to-use.
The example project has been open source: wjl-lab/spring-boot-example: a project that gradually integrates major components in the process of learning spring boot (github.com)
Create a Spring Boot project
Use idea's Spring Initializr to create a Spring Boot project, which is all by default.
Introduce dependent libraries and plugins
pom.xml
In dependencies
and build
add the following content, refresh and wait for all dependencies to be imported.
<!-- 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>
Write a configuration file
Spring Boot configuration file
I am used to using yml
the file format, you can right-click the default properties
file and change the suffix name toyml
Then fill in the following
# 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
Modify the MySQL and Redis configuration here
log configuration file
We use it log4j2
as our log framework. Although there are big loopholes that have been exposed recently, it is not a big problem for us. Students can just follow along and don't care about these security issues.
/resources
Create a new file in the directory log4j2.xml
and fill in the following content
<!-- 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>
Remember to modify the project name and log file storage path here, or you can choose the default
API Documentation Generate Plugin Configuration File
We use the API document generator smart-doc
as a project, the main advantage is that it is non-invasive, as long as the standard is written on the interface javadoc
, it is very convenient.
/resources
Create a new file in the directory smart-doc.json
and fill in the following content
// 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
}
If you want to change here, it is recommended to refer to the official documents.
Design package structure
Here is a reference to the one sent by Yang Ge’s official account, which has been changed a little according to my understanding
Write a Java configuration class
Annotate the program entry@EnableCaching
Add this annotation to enable caching
@EnableCaching
@SpringBootApplication
public class SpringBootExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootExampleApplication.class, args);
}
}
Configure Mybatis-Plus
One thing to say, this thing is very fragrant, anyway, I really smell it
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;
}
}
Configure cross-domain
Because it is generally written about front-end and back-end separation projects, cross-domain processing is required
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("*");
}
}
Configure Redis
Make the necessary configuration, here I am still stuck in the following afternoon, I don’t understand it very well
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;
}
}
Configure the Sa-Token interceptor
The Java permission authentication framework we use is very lightweight, and the official website documents are relatively complete
package com.wjl.example.config;
@Configuration
public class SaIntercepConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaAnnotationInterceptor())
.addPathPatterns("/**");
}
}
Unified backend return format
What SpringBoot provides cannot meet our needs, we need to encapsulate the returned results and configure them ourselves
return code definition
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;
}
}
The return result is uniformly packaged
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;
}
}
Unified processing of returned results
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);
}
}
Global exception interception
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());
}
}
Import tool class
RedisUtil for operating Redis
Only part of it is put here, this tool class is relatively long, you can see the source code for complete
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;
}
}
}
Tools for connecting to COS
I generally tend to use Tencent Cloud, so here is Tencent Cloud's COS object storage, Alibaba Cloud OSS is the same, just follow the official documentation.
Here you can first define global constants, information about COS storage buckets, note that these information cannot be uploaded to GitHub , I didn’t pay attention last time, just git push
took a , and Tencent Cloud immediately called me to say that the key was leaked, so be careful .
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);
}
}
Realize the authority authentication function
Here we use RBAC authentication mode
write entity
Here we choose to use Spring Data JPA as the ORM framework to define five entities 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;
}
Write the corresponding ORM layer and Service layer according to the entity
I put it together here, because the single code is relatively small
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());
}
}
Implement the authority authentication interface of Sa-Token
According to the official documentation, as long as the two methods in StpInterface
the interface
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;
}
}
I will not write about the login registration interface.
Implement Redis cache function
Entity and Object Mapping
Let's define an Todo
entity , and use Mybatis annotations for object mapping, don't forget to create the table first
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 integrates Redis caching mechanism
I will paste the code directly here, because I am not very familiar with this now
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;
}
}
Write unit tests
Here TodoService
is the unit test for
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);
}
}
Here RedisUtil
is the unit test for
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"));
}
}
Personally, I think the unit test coverage is still very wide.
-over-
The example project has been open source: wlonestar/spring-boot-example: a project that gradually integrates major components in the process of learning spring boot (github.com)