RESTfulAPI设计
实现这些接口的步骤如下
- 创建spring boot工程,按需导入坐标
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.kt</groupId> <artifactId>girl</artifactId> <version>0.0.1-SNAPSHOT</version> <name>girl</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.3.7.RELEASE</spring-boot.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!--参数校验--> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.19.Final</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.7.RELEASE</version> <configuration> <mainClass>cn.kt.girl.GirlApplication</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
- 编写相应的配置文件
# 应用服务 WEB 访问端口 server.port=8081 # 数据库驱动: spring.datasource.driver-class-name= com.mysql.jdbc.Driver # 数据源名称 spring.datasource.name=dbgirl # 数据库连接地址 spring.datasource.url=jdbc:mysql://localhost:3306/dbgirl?serverTimezone=UTC # 数据库用户名&密码: spring.datasource.username=root spring.datasource.password=root # 配置jpa,根据JavaBean自动建表 spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true
- 编写JavaBean实体类,使用springboot jpa自动创表
/** * @author tao * @date 2021-01-23 18:05 * 概要: */ @Entity public class Girl { //主键 @Id //自增 @GeneratedValue private Integer id; private String cupSize; //设置验证条件 @Min(value = 18, message = "未成年少女禁止入内") private Integer age; public Girl() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getCupSize() { return cupSize; } public void setCupSize(String cupSize) { this.cupSize = cupSize; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "Girl{" + "id=" + id + ", cupSize='" + cupSize + '\'' + ", age=" + age + '}'; } }
- 编写jap的 dao层
public interface GirlRepository extends JpaRepository<Girl,Integer> { public List<Girl> findByAge(Integer age); }
- 编写controller层
package cn.kt.girl.controller; import cn.kt.girl.domain.Girl; import cn.kt.girl.repository.GirlRepository; import cn.kt.girl.service.GirlService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; import java.util.Optional; /** * @author tao * @date 2021-01-23 17:54 * 概要: */ @RestController public class GirlController { @Autowired private GirlRepository girlRepository; @Autowired private GirlService girlService; /** * 获取数据列表 * * @author :tao * @date :Created in 2021-01-23 18:50 * @param: : * @return: */ @GetMapping("/girls") @ResponseBody public List<Girl> girlList() { return girlRepository.findAll(); } /** * 添加一条数据 * * @author :tao * @date :Created in 2021-01-23 18:51 * @param: : * @return: */ @PostMapping("girls") public Girl girlAdd(@Valid Girl girl, BindingResult bindingResult) { //表单参数验证 if (bindingResult.hasErrors()) { System.out.println(bindingResult.getFieldError().getDefaultMessage()); return null; } girl.setAge(girl.getAge()); girl.setCupSize(girl.getCupSize()); return girlRepository.save(girl); } //查询一个女生 @GetMapping("/girls/{id}") public Girl girlFindOne(@PathVariable("id") Integer id) { Optional<Girl> girl = girlRepository.findById(id); return girl.get(); } //更新一个女生 @PutMapping("/girls/{id}") public Girl girlUpdate(@PathVariable("id") Integer id, @RequestParam("cutSize") String cutSize, @RequestParam("age") Integer age) { Girl girl = new Girl(); girl.setId(id); girl.setCupSize(cutSize); girl.setAge(age); return girlRepository.save(girl); } //删除一个女生 @DeleteMapping("/girls/{id}") public void girlDelete(@PathVariable("id") Integer id) { girlRepository.deleteById(id); } //按年龄查询 //查询一个女生 @GetMapping("/girls/age/{age}") public List<Girl> girlFindByAge(@PathVariable("age") Integer age) { List<Girl> girls = girlRepository.findByAge(age); return girls; } //事务回滚测试 @GetMapping("/girls/two") public void insertTwo() { girlService.insertTwo(); } }
Controller的使用
SpringMVC中使用Controller需要配合ResponseBody来返回json格式,springboot4后只需配置RestController就能实现返回json
Controller获取参数的注解
简单的事务处理
业务需求:当插入两条数据时,插入第一条数据时成功,插入第二条数据时出现了问题,需求时保证两条数据必须同时插入,或者同时回滚不插入。
实现案例如下:
* Service层
@Service public class GirlService { @Autowired private GirlRepository girlRepository; //事务回滚,要么全部成功,要么全部失败,保证事务的一致性 @Transactional public void insertTwo() { Girl girlA = new Girl(); girlA.setCupSize("A"); girlA.setAge(18); girlRepository.save(girlA); Girl girlB = new Girl(); girlB.setCupSize("B"); girlB.setAge(19); //设置错误 int a = 1 / 0; girlRepository.save(girlB); } }
- Controller层
//事务回滚测试 @GetMapping("/girls/two") public void insertTwo() { girlService.insertTwo(); }
在类上或者方法上使用@Transactiona注解
SpringBoot表单验证
SpringBoot提供了强大的表单验证功能实现。即校验用户提交的数据的合理性的,比如是否为空了,年龄必须是不小于18 ,是否是纯数字等等。
导入坐标
<!--参数校验--> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.19.Final</version> </dependency>
表单验证示例:
需求是前台传的数据验证年龄是否满18岁
1. 在JavaBean实体类上对应的属性上加上验证注解@注解(message="提示信息")
验证限制 说明 @Null 限制只能为null @NotNull 限制必须不为null @AssertFalse 限制必须为false @AssertTrue 限制必须为true @DecimalMax(value) 限制必须为一个不大于指定值的数字 @DecimalMin(value) 限制必须为一个不小于指定值的数字 @Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction @Future 限制必须是一个将来的日期 @Max(value) 限制必须为一个不大于指定值的数字 @Min(value) 限制必须为一个不小于指定值的数字 @Pattern(value) 限制必须符合指定的正则表达式 @Size(max,min) 限制字符长度必须在min到max之间 @Past 验证注解的元素值(日期类型)比当前时间早 @NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) @NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 @Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
示例如下
@Entity public class Girl { //主键 @Id //自增 @GeneratedValue private Integer id; @NotEmpty(message="cupSize不能为空!") private String cupSize; //设置验证条件 @Min(value = 18, message = "未成年少女禁止入内") private Integer age; public Girl() { }
- 编写Controller层
@PostMapping("girls") public Girl girlAdd(@Valid Girl girl, BindingResult bindingResult) { //表单参数验证 //如果有错误 if (bindingResult.hasErrors()) { System.out.println(bindingResult.getFieldError().getDefaultMessage()); return null; } girl.setAge(girl.getAge()); girl.setCupSize(girl.getCupSize()); return girlRepository.save(girl); }
在接收前台参数前加@Valid注解,并且使用BindingResult对象获取验证结果,bindingResult.hasErrors()表示验证出错。bindingResult.getFieldError().getDefaultMessage()获取JavaBean上注解中Message的信息。
- 测试数据
后台输出
AOP统一处理请求日志
什么时面向切面
示例:使用AOP记录每一个http请求
- 导入依赖坐标
<!--aop切面编程--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
- 编写切面的执行逻辑类
HttpAspect.java
* 对GirlController的所有方法都进行拦截
* 获取url、method、ip、类方法、参数
* 记录日志
* 获取调用http接口后返回的数据
package cn.kt.girl.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.mvc.condition.RequestConditionHolder; import javax.servlet.http.HttpServletRequest; /** * @author tao * @date 2021-01-23 21:35 * 概要: */ @Aspect @Component public class HttpAspect { //定义spring自带的slf4j日志对象 private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class); /* * 第一种写法 */ //表示对GirlController的所有方法都进行拦截 /*@Before("execution(public * cn.kt.girl.controller.GirlController.*(..))") public void doBefore() { System.out.println("我执行前被拦截了"); } @After("execution(public * cn.kt.girl.controller.GirlController.*(..))") public void doAfter() { System.out.println("我执行后被拦截了"); }*/ /* * 第二种写法 */ //表示对GirlController的所有方法都进行拦截 @Pointcut("execution(public * cn.kt.girl.controller.GirlController.*(..))") public void log() { } @Before("log()") public void doBefore(JoinPoint joinPoint) { //使用JoinPoint获取相关类信息 //先获取HttpServletRequest对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //url logger.info("url={}", request.getRequestURL()); //method logger.info("method={}", request.getMethod()); //ip logger.info("ip={}", request.getRemoteAddr()); //类方法 logger.info("class_method={}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); //参数 logger.info("args={}", joinPoint.getArgs()); } @After("log()") public void doAfter() { //System.out.println("我执行后被拦截了"); logger.info("我执行后被拦截了"); } //获取调用http接口后返回的数据 @AfterReturning(returning = "object", pointcut = "log()") public void doAfterReturning(Object object) { logger.info("response={}", object); } }
执行结果如下
统一异常处理
日常开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管。这种方法对于程序员来说处理也比较麻烦,对客户来说也不太友好,所以我们希望既能方便程序员编写代码,不用过多的自己去处理各种异常编写重复的代码又能提升用户的体验,这时候全局异常处理就显得很重要也很便捷了,是一种不错的选择。
1. 统一结果返回与统一异常
- 建立一个工具包,再建一个专门用来返回结果的工具类ResultUtils.java,用来封装数据,返回我们想要的数据格式。
package cn.kt.girl.utils; import cn.kt.girl.domain.Result; /** * @author tao * @date 2021-01-23 23:30 * 概要:用来统一处理返回结果 */ public class ResultUtils { public static Result success(Object object) { Result result = new Result(); result.setCode(0); result.setMsg("成功"); result.setData(object); return result; } public static Result success() { return success(null); } public static Result error(Integer coad, String msg) { Result result = new Result(); result.setCode(coad); result.setMsg(msg); return result; } }
- 建立一个枚举包,在建立一个枚举类ResultEnums.java,用来统一枚举可能发生的异常和错误码。
枚举的基本用法参考
package cn.kt.girl.enums; /** * @author tao * @date 2021-01-24 0:35 * 概要: */ public enum ResultEnums { UNKNOW_ERROR(-1, "未知错误"), SUCCESS(0, "成功"), PRIMARY_SCHOOL(100, "你可能还在上小学"), MIDDLE_SCHOOL(101, "你可能在上初中"), ; private Integer code; private String msg; ResultEnums(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public String getMsg() { return msg; } }
2. 自定义异常类
为什么要编写自定义异常?
因为抛出Expection异常时,无法自定义错误码,只能传入异常处理信息,所以自定义类可以处理错误码和提示信息对应,甚至更多。
新建一个自定义异常包exception,再建一个自定义异常类GirlException.java
package cn.kt.girl.exception; import cn.kt.girl.enums.ResultEnums; /** * @author tao * @date 2021-01-24 0:22 * 概要:自己写的异常类 * 注意RuntimeException的异常会进行回滚,而直接继承Exception不会进行回滚 */ public class GirlExpection extends RuntimeException { private Integer code; public GirlExpection(ResultEnums resultEnums) { super(resultEnums.getMsg()); this.code = resultEnums.getCode(); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } }
注意RuntimeException的异常会进行回滚,而直接继承Exception不会进行回滚
3. 自定义一个全局异常处理类
用来全局处理各种异常,包括自己定义的异常和内部异常。这样可以简化不少代码,不用自己对每个异常都使用try,catch的方式来实现。
新建一个异常处理包handle,在里面建一个全局异常处理类ExceptionHandle.java
package cn.kt.girl.handle; import cn.kt.girl.aspect.HttpAspect; import cn.kt.girl.controller.GirlController; import cn.kt.girl.domain.Result; import cn.kt.girl.enums.ResultEnums; import cn.kt.girl.exception.GirlExpection; import cn.kt.girl.utils.ResultUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * @author tao * @date 2021-01-23 23:58 * 概要:捕获异常,处理异常,记录日志 */ @ControllerAdvice public class ExceptionHandle { private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class); @ExceptionHandler(value = Exception.class) @ResponseBody public Result handle(Exception e) { if (e instanceof GirlExpection) { GirlExpection girlExpection = (GirlExpection) e; return ResultUtils.error(girlExpection.getCode(), girlExpection.getMessage()); } else { logger.error("【系统异常】{}", e); return ResultUtils.error(ResultEnums.UNKNOW_ERROR.getCode(), ResultEnums.UNKNOW_ERROR.getMsg()); } } }
说明:
1、@ControllerAdvice注解是Spring3.2中新增的注解,可以理解为是Controller增强器,作用是给Controller控制器添加统一的操作或处理。可以用来指定作用范围:@ControllerAdvice(basePackages={"com.automvc", "com.test"}) ,如果不指定范围,@ControllerAdvice默认对所有的controller起作用。
具体作用可以参考:https://www.cnblogs.com/yanggb/p/10859907.html
2、@ExceptionHandler这个注解的功能是:自动捕获controller层出现的指定类型异常,并对该异常进行相应的异常处理.要求该方法必须要和出现问题的控制器在一个类中,才能生效。因此@ExceptionHandler和@ControllerAdvice经常结合使用,达到全局异常的捕获和处理。
3、每个方法上面加上一个 @ResponseBody的注解,用于将对象解析成json,方便前后端的交互,也可以使用 @ResponseBody放在异常类上面。
4. controller和service层代码测试
处理统一返回结果
/** * 添加一条数据 * * @author :tao * @date :Created in 2021-01-23 18:51 * @param: : * @return: */ @PostMapping("girls") public Result<Girl> girlAdd(@Valid Girl girl, BindingResult bindingResult) { //表单参数验证 //如果有错误 if (bindingResult.hasErrors()) { return ResultUtils.error(1, bindingResult.getFieldError().getDefaultMessage()); } return ResultUtils.success(girlRepository.save(girl)); }
表单验证如下
测试结果如下
处理统一返回异常
需求
* Service层代码
//根据不同年龄抛出异常 public void getAge(Integer id) throws Exception { Girl girl = girlRepository.findById(id).get(); Integer age = girl.getAge(); if (age < 10) { throw new GirlExpection(ResultEnums.PRIMARY_SCHOOL); } else if (age > 10 && age < 16) { throw new GirlExpection(ResultEnums.MIDDLE_SCHOOL); } }
- Controller层代码
@GetMapping("/girls/getAge/{id}") public void getAge(@PathVariable("id") Integer id) throws Exception { girlService.getAge(id); }
- 测试结果如下
如果发生其他错误
- 测试结果如下
案例代码下载
源码下载
链接:https://pan.baidu.com/s/1touXxe4RBI0Yl6c5VRl3xA
提取码:7qy1
目录结构