一、项目要求
- 实验环境:Idea+mysql+JDK+Tomcat+Maven
- 将上一周个人作业用 RESTful 接口实现;(上周的SpringBoot+Mybatis+CRUD项目)
- 配置统一响应体;
- 配置Swagger,生成API自动文档;
- 对 RESTful 接口用Postman进行测试,并将测试结果截图;
二、RESTful风格
1、前后端分离
随着互联网技术的发展和移动应用的广泛应用,要求前端开发必须与后端开发分离,实施工程化开发模式。Ajax技术使所有服务器端数据都可以通过异步交互获取。只要能从后台取得一个规范定义的RESTful风格接口,前后两端就可以并行完成项目开发任务。网页有网页的处理方式,APP有APP的处理方式,但无论哪种前端,所需的数据基本相同,后端仅需开发一套逻辑对外提供数据即可。
2、RESTful风格
统一的接口是RESTful风格的核心内容。RESTful定义了Web API接口的设计风格,非常适用于前后端分离的应用模式中。RESTful接口约束包含的内容有资源识别、请求动作和响应信息,即通过URL表明要操作的资源,通过请求方法表明要执行的操作,通过返回的状态码表明这次请求的结果。
3、设计规范
- 协议:统一使用HTTPs协议或者HTTP协议其中一种
- 域名:应该尽量将API部署在专用域名之下,如 https://xxx.xxx.com;
- 版本:应该将API的版本号放入URL,如http://www.example.com/app/1.1/foo
- 路径:只能有名词,不能有动词,而且所用的名词往往与数据库的表名对应,名词应该用复数
- HTTP动词:GET(SELECT)、POST(INSERT)、PUT(UPDATE)、DELETE(DELETE)
- 过滤信息:补充字段
- 状态码:
- 返回结果:code+msg+data(常用)
三、SpringBoot热部署
修改测试代码时,无需手动重启项目
四、统一格式的响应体
在前后端分离架构下,后端设计成 RESTful API的数据接口。接口中有时返回数据,有时又没有,还有的会出错,也就是返回结果不一致,客户端调用时非常不方便。实际开发中,一般设置统一响应体返回值格式,通过修改响应返回的JSON数据,让其带上一些固有的字段,如下所示:
{
"code": 600,
"msg": "success",
"data": {
"id": 1,
"uname": "cc"
"password":111
}
}
五、统一异常处理
六、Swagger—API 接口文档自动生成工具
在实际开发中,常用Swagger-API接口文档自动生成工具,帮助项目自动生成和维护接口文档。Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格
的 Web 服务,它具有以下特点:
- 及时性:接口变更后,Api文档同步自动更新;
- 规范性:遵守RESTful风格,保证接口的规范性,如接口的地址,请求方式,参
数及响应格式和错误信息; - 一致性:接口信息一致,不会因开发人员拿到的文档版本不一致而导致分歧;
- 可测性:直接在接口文档上进行测试,可以在线测试Api接口,方便理解业务。
在pom.xml中加入Swagger依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
@Api注解:标记当前Controller的功能
@ApiOperation注解:用来标记一个方法的作用
@ApiImplicitParam注解:用来描述一个参数
七、项目完整代码
pom.xml
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>week11_20201000000</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>week11_20201000000</name>
<description>week11_20201000000</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**.*</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/myschool?serverTimezone=Hongkong?characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 密码
mvc:
pathmatch:
matching-strategy: ant_path_matcher
mybatis:
mapper-locations: classpath:com/exmaple/mapper/*.xml #指定sql配置文件的位置
type-aliases-package: com.example.pojo #指定实体类所在的包名
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #输出SQL命令
Student.java
package com.example.pojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor //自动生成无参构造函数
@AllArgsConstructor
@ApiModel(description = "学生字段")
public class Student {
@ApiModelProperty(value = "学生id",name = "id",example = "20201001111")
private Long id;
@ApiModelProperty(value = "学生名",name = "sname",example = "张三")
private String sname;
@ApiModelProperty(value = "专业",name = "dept",example = "软件工程")
private String dept;
@ApiModelProperty(value = "年龄",name = "age",example = "20")
private int age;
}
StudentMapper.java
package com.example.mapper;
import com.example.pojo.Student;
import java.util.List;
import java.util.Map;
public interface StudentMapper {
//1
public List<Student> getAllStudentMap();
//2
public Student getStudentById(Long id);
//3更新用户信息
public int updateStudentByDynam(Map<Object,Object> mp);
//4
public int addStudent(Student student);
//5
public int deleteStudentById(Long id);
}
StudentMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.StudentMapper">
<!--1 -->
<select id="getAllStudentMap" resultType="Student">
SELECT * FROM student
</select>
<!--2 -->
<select id="getStudentById" resultType="Student" parameterType="Long">
SELECT * FROM student WHERE id=#{0}
</select>
<!--3 -->
<update id="updateStudentByDynam" parameterType="Map">
update student
<set>
<if test="sname!=null">
sname=#{sname},
</if>
<if test="dept!=null">
dept=#{dept},
</if>
<if test="age!=null">
age=#{age},
</if>
id=#{id}
</set>
where id=#{id}
</update>
<!--4 -->
<insert id="addStudent" parameterType="Student">
INSERT INTO student SET sname=#{sname},dept=#{dept},age=#{age}
</insert>
<!--5 -->
<delete id="deleteStudentById" parameterType="Long">
DELETE FROM student
where id=#{id}
</delete>
</mapper>
StudentService.java
package com.example.service;
import com.example.pojo.Student;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public interface StudentService {
//1
public List<Student> getAllStudentMap();
//2
public Student getStudentById(Long id);
//3更新用户信息
public int updateStudentByDynam(Map<Object,Object> mp);
//4
public int addStudent(Student student);
//5
public int deleteStudentById(Long id);
}
StudentServiceImpl.java
package com.example.service.impl;
import com.example.mapper.StudentMapper;
import com.example.pojo.Student;
import com.example.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
//根据id查询
@Override
public Student getStudentById(Long id){
return studentMapper.getStudentById(id);
}
//根据id修改用户信息
@Override
public int updateStudentByDynam(Map<Object,Object> mp) {
return studentMapper.updateStudentByDynam(mp);
}
@Override
public int deleteStudentById(Long id){
return studentMapper.deleteStudentById(id);
}
//查询用户
public List<Student> getAllStudentMap() {
return studentMapper.getAllStudentMap();
}
public int addStudent(Student student) {
return studentMapper.addStudent(student);
}
}
StudentController.java
package com.example.controller;
import com.example.exception.NotAllowedRegException;
import com.example.pojo.Student;
import com.example.service.StudentService;
import com.example.util.Response;
import com.example.util.ResponseResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.ibatis.annotations.Update;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@RestController //=@ResponseBody+@Controller
@Api(tags = "学生管理相关接口")
public class StudentController {
@Autowired
private StudentService studentService;
// 查找所有学生信息
@GetMapping("/student")
@ApiOperation("查询所有学生信息")
public ResponseResult<List<Student>> selectstudentList(){
List<Student> studentList = studentService.getAllStudentMap();
return Response.createOkResp(studentList);
}
//根据学生id查找对应学生信息
@GetMapping("/student/{id}")
@ApiOperation("根据id查询学生信息")
public ResponseResult<Student> selectStudentById(@PathVariable("id") Long id) {
Student student = studentService.getStudentById(id);
return Response.createOkResp(student);
}
//ok
@PostMapping("/student")
@ApiOperation("增加学生信息")
public ResponseResult<Student> addStudent(Student student) {
try {
studentService.addStudent(student);
return Response.createOkResp("添加成功");
} catch (Exception e) {
return Response.createFailResp("添加失败");
}
}
@PutMapping("/student")
@ApiOperation("更新学生信息")
public ResponseResult<Student> updateStudentByDynam(@RequestBody Map<Object,Object> mp){
System.out.println("mp: ");
System.out.println(mp);
int row = studentService.updateStudentByDynam(mp);
System.out.println(row);
try {
studentService.updateStudentByDynam(mp);
return Response.createOkResp("更新成功");
} catch (Exception e) {
return Response.createFailResp("更新失败");
}
}
// 根据id删除学生记录 okk
@DeleteMapping("/student/{id}")
@ApiOperation("删除学生信息")
public ResponseResult<Student> deleteStudentById(@PathVariable("id") Long id){
studentService.deleteStudentById(id);
return Response.createOkResp();
}
@PostMapping("/studentException")
@ApiOperation("异常处理学生信息")
public ResponseResult addStudentException(Student student) throws NotAllowedRegException {
//如果添加的用户名是"",则抛出异常
if(student.getSname().equals("张三"))
throw new NotAllowedRegException();
studentService.addStudent(student);
return Response.createOkResp();
}
}
GlobalExceptionHandle.java
package com.example.controller;
import com.example.exception.NotAllowedRegException;
import com.example.util.Response;
import com.example.util.ResponseResult;
import com.example.util.StatusCode;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**定义一个全局处理异常类,来统一处理各种异常
*
*/
//@RestController+@ControllerAdvice=@RestControllerAdvice
@RestControllerAdvice
public class GlobalExceptionHandler {
//处理异常的方法1. 并确定接收的是哪种类型的异常
@ExceptionHandler(Exception.class)
public ResponseResult exceptionHandler(Exception e)
{
// 捕获到某个指定的异常,比如是 NotAllowedRegException 类型
if(e instanceof NotAllowedRegException )
{
//处理结束后 还是要返回统一相应结果类
return Response.createFailResp(StatusCode.NOT_ALLOWRD_REG.code,"异常:当前用户不允许注册");
}
else
{
//处理其它类型的异常 可以进入到不同的分支
return Response.createFailResp();
}
}
/*
@ExceptionHandler(NullPointerException.class)
public ResponseResult exceptionHandler(NullPointerException e) {
}
*/
}
SwaggerConfig.java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
/**
* Swagger配置类
*/
@Configuration
public class SwaggerConfig {
public static final String SWAGGER_SCAN_BASE_PACKAGE = "com.example";
public static final String VERSION = "1.0.0";
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage(SWAGGER_SCAN_BASE_PACKAGE))
.paths(PathSelectors.any()) // 可以根据url路径设置哪些请求加入文档,忽略哪些请求
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("我的第一个项目") //设置文档的标题
.description("*** API 接口文档") // 设置文档的描述
.version(VERSION) // 设置文档的版本
.contact(new Contact("****", "", "***@qq.com"))
.termsOfServiceUrl("http://***.***.***") // 配置服务网站,
.build();
}
}
NotAllowedRegException.java
package com.example.exception;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*自定义一个异常类:不能注册
*/
@Data
@NoArgsConstructor
public class NotAllowedRegException extends Exception {
private int code;
private String message;
public NotAllowedRegException(String message)
{
super(message);
}
}
Response.java
package com.example.util;
/**
* 定义不同情景下,各种响应体返回的具体值
*
*/
public class Response {
private static String SUCCESS="success";
private static String FAIL="fail";
//创建不同场景下的返回结果对象
//1.成功执行,没有要返回的数据
public static <T> ResponseResult<T> createOkResp()
{
return new ResponseResult<T>(StatusCode.SUCCESS.code,SUCCESS,null);
}
//2.成功执行,需要返回数据
public static <T> ResponseResult<T> createOkResp(T data)
{
return new ResponseResult<T>(StatusCode.SUCCESS.code,SUCCESS,data);
}
//3.成功执行,需要返回消息和数据
public static <T> ResponseResult<T> createOkResp(String msg, T data)
{
return new ResponseResult<T>(StatusCode.SUCCESS.code,msg,data);
}
//4.成功执行,需要消息参数,无数据
public static <T> ResponseResult<T> createOkResp(String msg)
{
return new ResponseResult<T>(StatusCode.SUCCESS.code,msg,null);
}
//1.失败执行
public static <T> ResponseResult<T> createFailResp()
{
return new ResponseResult<T>(StatusCode.SERVER_ERROR.code,FAIL,null);
}
//2.失败执行
public static <T> ResponseResult<T> createFailResp(String msg)
{
return new ResponseResult<T>(500,msg,null);
}
//3.其它失败执行
public static <T> ResponseResult<T> createFailResp(int code, String msg)
{
return new ResponseResult<T>(code,msg,null);
}
//4.其他执行
public static <T> ResponseResult<T> createResp(int code, String msg, T data)
{
return new ResponseResult<T>(code,msg,data);
}
}
ResponseResult.java
package com.example.util;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 定义响应结果的统一格式,这里定义响应结果由三个要素构成
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseResult<T> {
//1.状态码
private int code;
//2.消息
private String msg;
//3.返回数据
private T data;
}
StatusCode.java
package com.example.util;
/**
*封装各种状态码
*/
public enum StatusCode {
//定义枚举项,并调用构造函数
//http定义好的状态码
SUCCESS(200),
SERVER_ERROR(500),
URL_NOT_FOUND(404),
//自定义的状态码
NOT_ALLOWRD_REG(1001);
//定义成员变量
public int code;
//构造方法
private StatusCode(int code)
{
this.code=code;
}
}
Week1120201000000Application.java
package com.example;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@EnableSwagger2
@SpringBootApplication
@MapperScan(basePackages ="com.example.mapper")
public class Week1120201000000Application {
public static void main(String[] args) {
SpringApplication.run(Week1120201000000Application.class, args);
}
}
Week1120201000000ApplicationTests.java
package com.example.week11_20201000000;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Week1120201000000ApplicationTests {
@Test
void contextLoads() {
}
}
八、运行测试
1、查询所有学生
GET
http://localhost:8080/student
2、根据id查询学生信息
GET
http://localhost:8080/student/20201001111
3、增加学生信息
POST
http://localhost:8080/student
4、更新学生信息
PUT
http://localhost:8080/student
5、删除学生信息
DELETE
http://localhost:8080/student/20201005590
6、异常处理学生信息(如果添加的用户名是"张三",则抛出异常)
POST
http://localhost:8080/studentException
7、启动项目,访问API在线文档页面
访问:http://localhost:8080/swagger-ui.html,即可看到接口文档信息