# base-app
**Repository Path**: redoing/base-app
## Basic Information
- **Project Name**: base-app
- **Description**: 记录Spring Boot 学习过程中遇到的各种通用处理方法
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-03-22
- **Last Updated**: 2022-09-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# base-app:基础应用
### 参考文档
## 问题描述
在学习Spring Boot过程中,经常遇到一些通用的步骤和处理方法。每次新建一个Spring Boot工程时都要重新配置一遍,因此萌生了编写一个base-app基础应用的想法,其目的有以下:
* 记录Spring Boot 学习过程中各种配置的方法
* 加深已经学习过的常用的配置的印象
* 形成一个完整的学习体系和应用快照
## 需求
本着记录的想法,该应用需要涉及到学习的各个流程,文档随开发进行更新。
- 拥有完整的开发记录
- 开发过程中详细的配置细节和注释
## 概要设计
根据需求,该项目采用Spring Boot + myBatis-Plus+MySQL的设计,使用Git作为版本管理,项目托管在[ gitee base-app](https://gitee.com/zzhangleiz/base-app.git)
```sh
mkdir base-app
cd base-app
git init
touch README.md
git add README.md
git commit -m "first commit"
git remote add origin https://gitee.com/zzhangleiz/base-app.git
git push -u origin "master"
```
## 详细设计
### MyBatis-Plus 使用
#### pom.xml 配置
- 添加MyBatis-Pluas依赖
```xml
com.baomidou
mybatis-plus-boot-starter
3.5.1
```
#### application.yml 配置
- 配置MySQL链接和驱动
```yml
spring:
application:
name: BASE_APP
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
url: jdbc:mysql://localhost:3306/os_development
```
- 注意,使用MyBatis/MyBatis-Plus时需要将dao或者mapper包注册到启动类上
```java
@MapperScan(value = {"com.example.baseapp.business.mapper"})
@ComponentScan(value = {"com.example.baseapp.dao"})
//或者使用以下,并在对呀mapper类上加上@Mapper注解
@MapperScan(basePackages = {"com.biz1", "com.group2"}, annotationClass = Mapper.class)
```
### 使用druid数据库连接池
druid[官方文档](https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter)
#### pom.xml 配置
- 添加pom.xml依赖
```xml
com.alibaba
druid-spring-boot-starter
1.2.8
com.alibaba
druid
1.2.8
```
#### application.yml 配置
- 使用starter,配置druid数据库连接池
```yml
druid:
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 600000
timeBetweenEvictionRunsMillis: 600000
minEvictableIdleTimeMillis: 300000
validation-query:
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
# 配置监控统计拦截器filters,去掉h后监控界面sql无法统计,‘wall’用于防火墙
filters: stat,wall,slf4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true,druid.stat.slowSqlMillis=500
stat-view-servlet:
# 需要账号密码才能访问控制台,默认为root
login-username: admin
login-password: admin
# 是否启用StatViewServlet默认值true
enabled: true
# 访问路径为/druid时,跳转到StatViewServlet
url-pattern: /druid/*
# 是否能够重置数据
reset-enable: false
# IP白名单
allow: 127.0.0.1
# IP黑名单(共同存在时,deny优先于allow)
deny:
```
#### 使用druid依赖需要创建配置
```java
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druid() {
return new DruidDataSource();
}
//配置Druid的监控
//1、配置一个管理后台的Servlet
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map initParams = new HashMap<>();
initParams.put("loginUsername", "admin");
initParams.put("loginPassword", "123456");
initParams.put("allow", "");//默认就是允许所有访问
initParams.put("deny", "192.168.15.21");
bean.setInitParameters(initParams);
return bean;
}
//2、配置一个web监控的filter
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*");
bean.setInitParameters(initParams);
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
```
配置完成后访问http://ip:port/druid/spring.html查看是否成功!
### 日志使用
使用logback作为日志输出,具体配置如下:
#### application.yml 配置
- 使用 logback-spring.xml
```yml
logging:
config: classpath:logback-spring.xml
```
- 调整日志级别为info
```yml
logging:
level:
root: info
#调整具体包日志级别
com:
example: debug
```
#### logback-spring.xml 文件
- logback-spring.xml文件内容
```xml
logback
debug
${CONSOLE_LOG_PATTERN}
UTF-8
${log.path}/web_debug.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/web-debug-%d{yyyy-MM-dd}.%i.log
100MB
15
debug
ACCEPT
DENY
${log.path}/web_info.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/web-info-%d{yyyy-MM-dd}.%i.log
100MB
15
info
ACCEPT
DENY
${log.path}/web_warn.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/web-warn-%d{yyyy-MM-dd}.%i.log
100MB
15
warn
ACCEPT
DENY
${log.path}/web_error.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/web-error-%d{yyyy-MM-dd}.%i.log
100MB
15
ERROR
ACCEPT
DENY
```
### 统一返回格式
该部分参考[ java日知录-SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!](https://javadaily.cn/post/2022012758/2fed6f3dba49/)
#### 定义返回标准格式
一个标准的返回格式应该包含3部分:
1. status 状态值:由后端统一定义各种返回结果的状态码
2. message 描述:本次接口调用的结果描述
3. data 数据:本次返回的数据
4. timestamp: 接口调用时间等
```json
{
"status":"000000",
"message":"操作成功",
"data":"hello world"
}
```
#### 定义返回对象
```java
package com.example.baseapp.common;
import com.example.baseapp.common.enums.ResponseCode;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* Result class
*
* @author zhangl
* @date 2022/3/23 11:42
*/
@Slf4j
@Data
public class Result {
private String status;
private String message;
private T responseData;
private long timestamp;
public Result() {
this.timestamp = System.currentTimeMillis();
}
public static Result success(T data) {
Result result = new Result<>();
result.setResponseData(data);
result.setStatus(ResponseCode.RC000000.getCode());
result.setMessage(ResponseCode.RC000000.getMessage());
log.info("Result :[{}]", result);
return result;
}
public static Result fail(String code, String message) {
Result result = new Result<>();
result.setStatus(code);
result.setMessage(message);
log.info("Result :[{}]", result);
return result;
}
}
```
#### 定义返回状态码
```java
package com.example.baseapp.common.enums;
/**
* ResponeCode enum
*
* @author zhangl
* @date 2022/3/23 11:47
*/
public enum ResponseCode {
/**操作成功**/
RC000000("000000", "操作成功"),
/**操作失败**/
RC999999("999999", "操作失败"),
/**服务限流**/
RC200("200", "服务开启限流保护,请稍后再试!"),
/**服务降级**/
RC201("201", "服务开启降级保护,请稍后再试!"),
/**热点参数限流**/
RC202("202", "热点参数限流,请稍后再试!"),
/**系统规则不满足**/
RC203("203", "系统规则不满足要求,请稍后再试!"),
/**授权规则不通过**/
RC204("204", "授权规则不通过,请稍后再试!"),
/**access_denied**/
RC403("403", "无访问权限,请联系管理员授予权限"),
/**access_denied**/
RC401("401", "匿名用户访问无权限资源时的异常"),
/**服务异常**/
RC500("500", "系统异常,请稍后重试"),
INVALID_TOKEN("2001", "访问令牌不合法"),
ACCESS_DENIED("2003", "没有权限访问该资源"),
CLIENT_AUTHENTICATION_FAILED("1001", "客户端认证失败"),
USERNAME_OR_PASSWORD_ERROR("1002", "用户名或密码错误"),
UNSUPPORTED_GRANT_TYPE("1003", "不支持的认证模式");
private final String code;
private final String message;
ResponseCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
```
#### 一般实现方法
##### 测试
```java
@GetMapping("hello")
public Result hello(){
return Result.success("HELLO WORLD !");
}
```
返回结果:
```json
{
"status": "000000",
"message": "操作成功",
"responseData": "HELLO WORLD !",
"timestamp": 1648021474934
}
```
#### 高级实现方式
借助SpringBoot提供的`ResponseBodyAdvice`实现高级的实现机制
> ResponseBodyAdvice的作用:拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等等。
##### 编写`ResponseBodyAdvice`实现类
```java
/**
* ResponseAdvice class
*
* @author zhangl
* @date 2022/3/23 15:52
*/
@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice