SpringBoot教程一RESTful API
现在项目普遍实行前后端分离,为了前后端人员更好地联调需要制定一套统一的接口规范
实现返回固定的JSON格式,如:
{
code:0,
message:'操作成功',
data:null
}
{
code:10001,
message:'参数错误',
data:null
}
1、Http的常用请求方法Method
GET 一搬用于获取数据
POST 用于提交数据
PUT 用于修改数据
DELETE 用于删除数据
2、Restful api 常用的几个注解
@RestController 一般用于Controoler类上
@ResponseBody 用了这个 RestController就没有必要在加ResponseBody了
@GetMapping 方法上
@PostMapping 方法上
@PutMapping 方法上
@DeleteMapping 方法上
@PostMapping和@PutMapping作用接近,都是用来向服务器提交信息。如果是新增(insert)信息,倾向于用@PostMapping,如果是更新(update)信息,倾向于用@PutMapping。
创建名为curd
的Spring工程
创建src/main/java/com.example.curd/controller/SiteController.java
文件,内容如下
package com.example.curd.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/site")
public class SiteController {
@GetMapping("/test")
public String test(){
return "site/test";
}
@GetMapping("/detail/{id}")
public Object detail(@PathVariable Integer id)
{
return "查看详情" id;
}
@PostMapping("/add")
public Object add(@RequestParam String name){
return "新增:" name;
}
@PutMapping("/update")
public Object update(@RequestParam Integer id,@RequestParam String name){
return "更新ID为" id "的名称为" name;
}
@DeleteMapping("/delete/{id}")
public Object delete(@PathVariable Integer id)
{
return "删除ID为" id "的记录";
}
}
VO、DTO、BO、DO、PO、POJO、Entity的概念、区别和应用
https://www.jianshu.com/p/e2dcde64b681
阿里巴巴Java开发手册中的DO、DTO、BO、AO、VO、POJO定义
返回码ResultCode类
创建src/main/java/com.example.curd/enums/ResultCode.java
文件,内容如下
package com.example.curd.enums;
public enum ResultCode {
SUCCESS(0,"成功"),
PARAMS_ERROR(10000,"参数错误"),//大类
PARAMS_IS_INVALID(10001,"参数无效"),
PARAMS_NOT_COMPLETE(10002,"参数不全"),
DATA_ERROR(20000,"数据错误"),//大类
DATA_NOT_FOUND(20001,"数据没找到"),
DATA_ALREADY_EXISTED(20002,"数据已存在"),
UNKNOWN_ERROR(99999,"未知错误");
private Integer code;
private String desc;
private ResultCode(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public Integer getCode(){
return code;
}
public String getDesc(){
return desc;
}
}
泛型相关
Result 类
创建src/main/java/com.example.curd/vo/Result.java
文件,内容如下
package com.example.curd.vo;
import java.io.Serializable;
public class Result implements Serializable {
private Integer code;
private String message;
private Object data;
public Result() {
}
public Result(Integer code, String message,Object data) {
this.setCode(code);
this.setMessage(message);
this.setData(data);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public String toString() {
return "Result{"
"code=" code
", message='" message '\''
", data=" data
'}';
}
}
创建src/main/java/com.example.curd/util/ResultUtil.java
文件,内容如下
package com.example.curd.util;
import com.example.curd.enums.ResultCode;
import com.example.curd.vo.*;
public class ResultUtil {
//基本用法1
public static Result create(Integer code, String message, Object data) {
return new Result(code,message,data);
}
//基本用法2
public static Result create(ResultCode resultCode,Object data) {
return create(resultCode.getCode(),resultCode.getDesc(),data);
}
//基本用法3
public static Result create(ResultCode resultCode) {
return create(resultCode,null);
}
//成功
public static Result success() {
return create(ResultCode.SUCCESS);
}
//成功
public static Result success(Object data) {
return create(ResultCode.SUCCESS,data);
}
//失败
public static Result fail(ResultCode resultCode) {
return create(resultCode);
}
//失败
public static Result fail(ResultCode resultCode,Object data) {
return create(resultCode,data);
}
}
用法SiteController.java
文件增加如下内容
@GetMapping("/test")
public Object test()
{
//return ResultUtil.success();
return ResultUtil.fail(ResultCode.DATA_ALREADY_EXISTED);
}
创建自定义异常类CustomException
,位置src/main/java/com.example.curd/exception/CustomException.java
package com.example.curd.exception;
import com.example.curd.vo.ResultCode;
public class CustomException extends RuntimeException {
public CustomException(Integer code, String message) {
this.code = code;
this.message = message;
}
public CustomException(ResultCode resultCode) {
this.code = resultCode.getCode();
this.message = resultCode.getDesc();
}
private Integer code;
private String message;
public Integer getCode() {
return code;
}
public CustomException setCode(Integer code) {
this.code = code;
return this;
}
public String getMessage() {
return message;
}
public CustomException setMessage(String message) {
this.message = message;
return this;
}
}
创建异常处理增强类
RestControllerAdvice作用
创建src/main/java/com.example.curd/advice/ExceptionHandlerAdvice.java
文件,内容如下
package com.example.curd.advice;
import com.example.curd.enums.ResultCode;
import com.example.curd.exception.CustomException;
import com.example.curd.util.ResultUtil;
import com.example.curd.vo.Result;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
@RestControllerAdvice
public class ExceptionHandlerAdvice {
/*
//应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器
@InitBinder
public void initBinder(WebDataBinder binder) {}
//把值绑定到Model中,使全局@RequestMapping可以获取到该值
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("author", "vastshine");
}
*/
//自定义异常(业务异常)捕捉处理
@ExceptionHandler(value = CustomException.class)
public Result customExceptionHandler(CustomException ex) {
return ResultUtil.create(ex.getCode(),ex.getMessage(),null);
}
//参数验证异常
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) {
return ResultUtil.create(ResultCode.PARAMS_ERROR,ex.getAllErrors());
}
//其他异常(全局异常)捕捉处理
@ExceptionHandler(value = Exception.class)
public Result globalExceptionHandler(Exception ex) {
return ResultUtil.create(ResultCode.UNKNOWN_ERROR,ex);
}
}
用法
@GetMapping("/test")
public Object test()
{
throw new CustomException(ResultCode.DATA_ALREADY_EXISTED);
}
使用AOP记录日志
日志组件使用slf4j,slf4j简介
slf4j本身不是一个日志实现库,而是一个日志库的抽象层,它必须依赖底层的日志库,SLF4J必须和其他的具体日志实现库配合才能正常运行
pom.xml
文件增加依赖
slf4j1.7.x之前的版本和slf4j2.x的版本改动较大,slf4j2.x需要java9以上
slf4j1.7.x引入如下依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
slf4j1.7.x会自动查找并绑定当前可用的日志实现库,而2.x版本则不再自动绑定需要连同实现库一起引入,如使用log4j
则如下引入依赖
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.3</version>
<scope>test</scope>
</dependency>
经测试使用slf4j2.x版本存在其他问题,所以这里继续使用1.x版本
SpringBoot默认日志实现库为logback
默认情况下,INFO级别将日志输出到控制台,不会写到日志文件。
用户可以自定义修改日志等级和输出方式。
logback日志级别从低到高分为ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF,定义于ch.qos.logback.classic.Level
类中。
默认情况下高级别会继承低级别的能力,
可以通过addtivity
参数改为false
关闭继承。
如果级别为WARN,则高于WARN的级别都会输出。
添加配置文件src/main/java/resources/logback-spring.xml
,内容如下
<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="true" scan="false" scanPeriod="60 seconds">
<contextName>logback-config</contextName>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 工程名,application配置中读取 -->
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<!-- 日志目录,application配置中读取 -->
<springProperty scope="context" name="loggerFolder" source="logger.folder"/>
<!-- 日志文件目录 -->
<property name="FILE_PATH" value="${loggerFolder:-build}"/>
<!-- 日志文件名称 -->
<property name="FILE_NAME" value="${FILE_PATH}/${springAppName}"/>
<!-- 彩色PATTERN -->
<property name="PATTERN" value="[%highlight(%-5level)][%green(%d{yyyy-MM-dd HH:mm:ss.SSS})][%boldMagenta(%class{39}.%M\\(%line\\))][%thread] -%msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<pattern>${PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 日志文件配置 -->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 写死文件名,会引起rollingPolicy设置无效 -->
<!-- <file>${FILE_NAME}.log</file> -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 循环滚动 -->
<fileNamePattern>${FILE_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保存最近10天 -->
<maxHistory>10</maxHistory>
<!-- 超过2G删除旧日志 -->
<totalSizeCap>2GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- org.springframework包下的类只打印warn级别以上的日志 -->
<logger name="org.springframework" level="warn"/>
<!-- com.example.curd包下的类只打印info级别上日志 -->
<!--<logger name="com.example.curd" level="info"/>-->
<!-- spring配置文件值:spring.profiles.active=dev则启动dev的设置 -->
<springProfile name="dev">
<root level="error">
<appender-ref ref="file"/>
</root>
<root level="warn">
<appender-ref ref="file"/>
</root>
<root level="info">
<appender-ref ref="console"/>
</root>
</springProfile>
<springProfile name="test">
<root level="warn">
<appender-ref ref="file"/>
</root>
<root level="info">
<appender-ref ref="console"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="error">
<appender-ref ref="file"/>
</root>
</springProfile>
</configuration>
需要注意的是上面<root level="warn">
节点和<root level="info">
的顺序不能搞反,级别高的先写
application.properties
内容如下
spring.application.name = cur
mybatis.configuration.map-underscore-to-camel-case = true
spring.jackson.date-format = yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone = GMT 8
server.address = localhost
server.port = 8081
logger.folder = logs
spring.profiles.active = dev
application.dev.properties
内容如下
logger.folder = logs_dev
server.address = localhost
server.port = 8081
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://192.168.1.1:3306/curd
spring.datasource.username = root
spring.datasource.password = 123456
application.prod.properties
内容如下
logger.folder = logs_prod
创建切面类src/main/java/com.example.curd/aspect/LogAspect.java
处理日志
package com.example.curd.aspect;
import com.example.curd.exception.CustomException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.*;
import org.slf4j.*;
import java.lang.reflect.Method;
@Aspect
@Component
public class LogAspect {
//全部拦截使用@Around(value = "execution(* com.example.curd..*.* (..))")
//注意:一般不使用全部拦截,尽可能减少拦截范围,因为可能会出现多个拦截器冲突的情况(如控制器又嵌套了拦截器)
//这里只拦截controller
private final String POINT_CUT = "execution(* com.example.curd.controller.*.* (..))";
//[@Aspect各种通知的执行顺序](https://blog.csdn.net/qq_41936224/article/details/107100175)
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
private static final ObjectMapper mapper = new ObjectMapper();
//前置通知
@Before(value = POINT_CUT)
public void before(JoinPoint joinPoint) {
System.out.println("处理@Before");
}
//环绕通知
@Around(value = POINT_CUT)
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("处理@Around");
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String name = method.getName();
Object[] args = joinPoint.getArgs();
Object object = joinPoint.getTarget();
Object result = joinPoint.proceed();
//只会输出到控制台
log.info("class :{} , method :{}, args :{} , result :{}", object.getClass().getName(),name,mapper.writeValueAsString(args),result);
return result;
}
//正常返回通知(无异常发生才会执行该通知)
@AfterReturning(value = POINT_CUT)
public void afterReturning(JoinPoint joinPoint) {
System.out.println("处理@AfterReturning");
}
//异常通知
@AfterThrowing(value = POINT_CUT,throwing="e")
public void afterThrowing(Throwable e) {
System.out.println("处理@AfterThrowing");
/*
if(e instanceof CustomException){
log.error("发生了自定义异常:",e);
}else{
log.error("发生了系统异常了:",e);
}
*/
//只记录全局异常(没作异常处理的异常)
if(e instanceof Exception){
log.error("发生了异常了:",e);
}
}
//后置通知
@After(value = POINT_CUT)
public void after(JoinPoint joinPoint) {
System.out.println("处理@After");
}
}
统一Response的返回格式
创建src/main/java/com.example.curd/advice/MyResponseBodyAdvice.java
,内容如下
package com.example.curd.advice;
import com.example.curd.util.ResultUtil;
import com.example.curd.vo.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
//return true;//一律通过;当controller返回String时会报错
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//异常发生时已包装过数据无需再次包装数据
if(body instanceof Result){
return body;
}
//包装数据统一格式
return ResultUtil.success(body);
}
}
测试
@GetMapping("/test")
public String logTest(){
System.out.println("测试LogAspect");
//return "ok";
throw new CustomException(ResultCode.DATA_ALREADY_EXISTED);
}
跨域处理
增加全局跨域处理类GlobalCorsConfiguration
package com.example.curd.configuration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.*;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class GlobalCorsConfiguration {
@Bean
public CorsConfiguration buildConfig() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern(CorsConfiguration.ALL);
config.setAllowCredentials(true);
config.addAllowedMethod(CorsConfiguration.ALL);
config.addAllowedHeader(CorsConfiguration.ALL);
config.addExposedHeader(CorsConfiguration.ALL);
return config;
}
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
//存在多个filter时此处设置CorsFilter的执行顺序
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
测试跨域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>axios测试跨域访问</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const instance = axios.create({
baseURL: 'http://localhost:8081/',
timeout: 2000,
headers: {'Access-Control-Allow-Origin': '*'}}
);
const res = instance.get('site/test');
//const res = instance.get('ad/list');
//const res = instance.post('ad/insert', { name: '' });
console.log(res.data);
</script>
</body>
</html>
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgbhhbi
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01