SpringBoot integrates common components

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.xmlIn dependenciesand buildadd 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 ymlthe file format, you can right-click the default propertiesfile 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 log4j2as 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.

/resourcesCreate a new file in the directory log4j2.xmland 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-docas 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.

/resourcesCreate a new file in the directory smart-doc.jsonand 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

GitHub - wjl-lab/spring-boot-example: A project that gradually integrates major components in the process of learning spring boot

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 pushtook 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 StpInterfacethe 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 Todoentity , 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 TodoServiceis 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 RedisUtilis 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)

Guess you like

Origin blog.csdn.net/wji15/article/details/126650219