# 基于springboot的web项目最佳实践
+ [web](#web)
+ [单元测试](#test)
+ [actuator应用监控](#actuator)
+ [lombok](#lombok)
+ [baseEntity](#baseEntity)
+ [统一响应返回值](#result)
+ [异常](#exception)
+ [数据校验](#validation)
+ [日志](#log)
+ [swagger](#swagger)
+ [数据库连接池](#datasource)
+ [spring jdbc](#jdbc)
+ [jpa](#jpa)
+ [redis](#redis)
+ [spring cache](#springcache)
+ [mogodb](#mogodb)
+ [mybatis](#mybatis)
+ [spring security](#security)
+ [项目上下文](#ContextHolder)
+ [单点登录](#sso)
+ [邮件](#mail)
+ [maven](#maven)
+ [总结](#总结)
`springboot` 可以说是现在做`javaweb`开发最火的技术,我在基于`springboot`搭建项目的过程中,踩过不少坑,发现整合框架时并非仅仅引入`starter` 那么简单。
要做到简单,易用,扩展性更好,还需做不少二次封装,于是便写了个基于`springboot`的web项目脚手架,对一些常用的框架进行整合,并进行了简单的二次封装。
项目名`baymax`取自动画片超能陆战队里面的大白,大白是一个医护充气机器人,希望这个项目你能像大白一样贴心,可以减少你的工作量。
**github** https://github.com/zhaoguhong/baymax
## <span id="web">web</span>
web模块是开发web项目必不可少的一个模块
maven 依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
```
对于前后端分离项目,推荐直接使用``@RestController``注解
需要注意的是,**不建议直接用RequstMapping注解并且不指定方法类型的写法**,推荐使用`GetMaping`或者`PostMaping`之类的注解
```java
@SpringBootApplication
@RestController
public class BaymaxApplication {
public static void main(String[] args) {
SpringApplication.run(BaymaxApplication.class, args);
}
@GetMapping("/test")
public String test() {
return "hello baymax";
}
}
```
## <span id="test">单元测试</span>
spring 对单元测试也提供了很好的支持
maven 依赖
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
```
添加 `@RunWith(SpringRunner.class)` 和 `@SpringBootTest` 即可进行测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class WebTest {
}
```
对于`Controller`层的接口,可以直接用`MockMvc`进行测试
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class WebTest {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@Test
public void testValidation() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/test"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.content().string("hello baymax"));
}
}
```
## <span id="actuator">actuator应用监控</span>
actuator 是 spring 提供的应用监控功能,常用的配置项如下
```
# actuator端口 默认应用端口
management.server.port=8082
# 加载所有的端点 默认只加载 info,health
management.endpoints.web.exposure.include=*
# actuator路径前缀,默认 /actuator
management.endpoints.web.base-path=/actuator
```
## <span id="lombok">lombok</span>
lombok可以在编译期生成对应的java代码,使代码看起来更简洁,同时减少开发工作量
用lombok后的实体类
```java
@Data
public class Demo {
private Long id;
private String userName;
private Integer age;
}
```
需要注意,`@Data` 包含 `@ToString、@Getter、@Setter、@EqualsAndHashCode、@RequiredArgsConstructor`,**RequiredArgsConstructor 并不是无参构造**,无参构造的注解是`NoArgsConstructor`
`RequiredArgsConstructor` 会生成 会生成一个包含常量(final),和标识了@NotNull的变量 的构造方法
## <span id="baseEntity">baseEntity</span>
把表中的基础字段抽离出来一个BaseEntity,所有的实体类都继承该类
```java
/**
* 实体类基础类
*/
@Data
public abstract class BaseEntity implements Serializable {
/**
* 主键id
*/
private Long id;
/**
* 创建人
*/
private Long createdBy;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新人
*/
private Long updatedBy;
/**
* 更新时间
*/
private Date updatedTime;
/**
* 是否删除
*/
private Integer isDeleted;
}
```
## <span id="result">统一响应返回值</span>
前后端分离项目基本上都是ajax调用,所以封装一个统一的返回对象有利于前端统一处理
```java
/**
* 用于 ajax 请求的响应工具类
*/
@Data
public class ResponseResult<T> {
// 未登录
public static final String UN_LOGIN_CODE = "401";
// 操作失败
public static final String ERROR_CODE = "400";
// 服务器内部执行错误
public static final String UNKNOWN_ERROR_CODE = "500";
// 操作成功
public static final String SUCCESS_CODE = "200";
// 响应信息
private String msg;
// 响应code
private String code;
// 操作成功,响应数据
private T data;
public ResponseResult(String code, String msg, T data) {
this.msg = msg;
this.code = code;
this.data = data;
}
}
```
返回给前端的值用`ResponseResult`包装一下
```java
/**
* 测试成功的 ResponseResult
*/
@GetMapping("/successResult")
public ResponseResult<List<Demo>> test() {
List<Demo> demos = demoMapper.getDemos();
return ResponseResult.success(demos);
}
/**
* 测试失败的 ResponseResult
*/
@GetMapping("/errorResult")
public ResponseResult<List<Demo>> demo() {
return ResponseResult.error("操作失败");
}
```
### ResponseEntity
spring其实封装了ResponseEntity 处理响应,ResponseEntity 包含 状态码,头部信息,响应体 三部分
```java
/**
* 测试请求成功
* @return
*/
@GetMapping("/responseEntity")
public ResponseEntity<String> responseEntity() {
return ResponseEntity.ok("请求成功");
}
/**
* 测试服务器内部错误
* @return
*/
@GetMapping("/InternalServerError")
public ResponseEntity<String> responseEntityerror() {
return new ResponseEntity<>("出错了", HttpStatus.INTERNAL_SERVER_ERROR);
}
```
## <span id="exception">异常</span>
### 自定义异常体系
为了方便异常处理,定义一套异常体系,BaymaxException 做为所有自定义异常的父类
```java
// 项目所有自定义异常的父类
public class BaymaxException extends RuntimeException
// 业务异常 该异常的信息会返回给用户
public class BusinessException extends BaymaxException
// 用户未登录异常
public class NoneLoginException extends BaymaxException
```
### 全局异常处理
对所有的异常处理后再返回给前端
```java
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
/**
* 业务异常
*/
@ExceptionHandler(value = {BusinessException.class})
public ResponseResult<?> handleBusinessException(BusinessException ex) {
String msg = ex.getMessage();
if (StringUtils.isBlank(msg)) {
msg = "操作失败";
}
return ResponseResult.error(msg);
}
/**
* 处理未登录异常
*/
@ExceptionHandler(value = {NoneLoginException.class})
public ResponseResult<?> handleNoneLoginException(NoneLoginException ex) {
return ResponseResult.unLogin();
}
```
### 异常持久化
对于未知的异常,保存到数据库,方便后续排错
需要说明是的,如果项目访问量比较大,推荐用 ELK 这种成熟的日志分析系统,不推荐日志保存到关系型数据库
```java
@Autowired
private ExceptionLogMapper exceptionLogMapper;
/**
* 处理未知的错误
*/