4000-520-616
欢迎来到免疫在线!(蚂蚁淘生物旗下平台)  请登录 |  免费注册 |  询价篮
主营:原厂直采,平行进口,授权代理(蚂蚁淘为您服务)
咨询热线电话
4000-520-616
当前位置: 首页 > 新闻动态 >
新闻详情
Springboot如何设计出优雅的后端(API)接口(一)_我是你..._CSDN博客
来自 : CSDN技术社区 发布时间:2021-03-25

项目背景

现在绝大部分项目都是采用前后端分离的模式 对于前端来说 后端如果能有一个规范的、优雅的设计的api模式 那么前端的开发将会事倍功半 同时对于后端来说 统一的格式也有利于后期的维护和扩展 其实主要是甩手的时候 不至于下一个人看不懂 。 。
一个后端的接口分为四个部分 接口地址 url 、接口请求方式 get、post等 、请求数据 request 、响应数据 response 。下面我们就从这四个部分分析需要的注意的点。
后续我会把项目上传到github上 感兴趣的同学可以帮忙点个爱心。
附上csdn的下载地址 springboot优雅后端接口demo

需要思考的问题 统一返回接口格式。全局异常处理。参数常规非空校验。通用的分页对象。日志统一格式。常用工具类。接口文档。接口的安全。 开始

这个是采用的springboot配置项目 我先附上需要依赖的包

 properties  java.version 1.8 /java.version  spring-mybatis.version 1.3.2 /spring-mybatis.version  spring-druid.version 1.1.10 /spring-druid.version  spring-jdbc.version 2.0.6 /spring-jdbc.version  mysql-connector.version 8.0.16 /mysql-connector.version  commons-lang.version 3.8.1 /commons-lang.version  fastjson.version 1.2.51 /fastjson.version  jwt.version 3.4.0 /jwt.version  page-helper.version 1.2.7 /page-helper.version  /properties  dependencies  dependency  groupId org.apache.commons /groupId  artifactId commons-lang3 /artifactId  version ${commons-lang.version} /version  /dependency  dependency  groupId com.alibaba /groupId  artifactId fastjson /artifactId  version ${fastjson.version} /version  /dependency  !-- mysql connector --  dependency  groupId mysql /groupId  artifactId mysql-connector-java /artifactId  version ${mysql-connector.version} /version  /dependency  !-- Mybatis --  dependency  groupId org.mybatis.spring.boot /groupId  artifactId mybatis-spring-boot-starter /artifactId  version ${spring-mybatis.version} /version  /dependency  !-- druid数据库连接池 --  dependency  groupId com.alibaba /groupId  artifactId druid-spring-boot-starter /artifactId  version 1.1.10 /version  /dependency  dependency  groupId org.springframework.boot /groupId  artifactId spring-boot-starter-web /artifactId  version 2.0.6.RELEASE /version  /dependency  !-- swagger--  dependency  groupId io.springfox /groupId  artifactId springfox-swagger2 /artifactId  version 2.8.0 /version  /dependency  dependency  groupId io.springfox /groupId  artifactId springfox-swagger-ui /artifactId  version 2.8.0 /version  /dependency  dependency  groupId com.squareup.okhttp3 /groupId  artifactId okhttp /artifactId  version 3.11.0 /version  /dependency  dependency  groupId com.aliyun.api.gateway /groupId  artifactId sdk-core-java /artifactId  version 1.1.0 /version  /dependency  dependency  groupId org.apache.shiro /groupId  artifactId shiro-spring /artifactId  version 1.3.2 /version  /dependency  !--JWT--  dependency  groupId com.auth0 /groupId  artifactId java-jwt /artifactId  version ${jwt.version} /version  /dependency  dependency  groupId com.github.pagehelper /groupId  artifactId pagehelper-spring-boot-starter /artifactId  version ${page-helper.version} /version  /dependency  dependency  groupId org.springframework.boot /groupId  artifactId spring-boot-starter-test /artifactId  scope test /scope  /dependency  /dependencies 

顿时傻眼 一看这么多东西 怎么办 兄弟们不要慌 跟着我一步一步来。
首先第一步 我们先写一个简单的controller 先把项目运行起来。

 RestControllerpublic class UserController { GetMapping( /user ) public String getUserInfo( RequestParam( id ) Integer id) { return cj id;

启动项目 等一会儿将会出现success的提示语。。。。。。。。。。。。。。。
wait、wait自信过头了。。。。。。
\"在这里插入图片描述\"
我擦 忘了配置mysql的信息的 因为我maven中加入和mysql的驱动 我们配置上信息 重新启动

server: port: 8082 tomcat: uri-encoding: UTF-8 servlet: session: timeout: 600000 context-path: /cj-apispring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT 8 #应用名称 application: name: cj-api datasource: url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding utf8 useSSL true zeroDateTimeBehavior convertToNull allowMultiQueries true serverTimezone Asia/Shanghai username: root password: root123 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource druid: initialSize: 2 minIdle: 2 maxActive: 30

\"在这里插入图片描述\"
访问地址 http://127.0.0.1:8082/cj-api/user?id 1 出现如下图所示 则表明成功。
\"在这里插入图片描述\"
至此一个简单的接口就完成了 但是我们还需要很多流程去优化这个接口。

统一返回格式

常规而言 至少我是这么认为的 有意见的评论区见 。 后端和前端的数据交互的格式都是采用JSON格式 那么json格式的话 一般都会有固定的几个字段比如 code、data、msg。
code 一般是返回错误码 前端会根据这个错误码进行下一步操作。
data code成功之后 返回的具体数据 这里一般是个泛型。
msg 错误信息、成功信息 根据code来的 。

新建ResponseBean类
/** * 描述 统一返回前端的实体类 * author caojing * create 2020-11-27-13:56public class ResponseBean T { * 状态码 0 success,1 fail 3第一次登陆 ApiModelProperty( 状态码 0 success,1 fail,2 wait ) private int code 0; * 返回信息 ApiModelProperty( 返回信息 ) private String msg; * 返回的数据 ApiModelProperty( 返回数据 ) private T data; public ResponseBean() { public ResponseBean(int code, String msg, T data) { this.code code; this.msg msg; this.data data; public void buildSuccessResponse(T data) { this.code 0; this.data data; this.msg 成功 ; public void buildFailedResponse() { this.code 1; this.msg 失败 ; public void buildFailedResponse(String msg) { this.code 1; this.msg msg; public int getCode() { return code; public void setCode(int code) { this.code code; public String getMsg() { return msg; public void setMsg(String msg) { this.msg msg; public T getData() { return data; public void setData(T data) { this.data data;
新增一个userbean类
package com.cj.demo.bean.user; * 描述  * author caojing * create 2020-11-27-16:06public class UserBean { public UserBean(int id, String name, int age, String email) { this.id id; this.name name; this.age age; this.email email; private int id; private String name; private int age; private String email; public int getId() { return id; public void setId(int id) { this.id id; public String getName() { return name; public void setName(String name) { this.name name; public int getAge() { return age; public void setAge(int age) { this.age age; public String getEmail() { return email; public void setEmail(String email) { this.email email;
Controller类改为如下所示
 RestControllerpublic class UserController { GetMapping( /user ) public ResponseBean UserBean getUserInfo( RequestParam( id ) Integer id) { ResponseBean responseBean new ResponseBean(); responseBean.setCode(0); responseBean.setData(new UserBean(1, cj ,12, 106067690 qq.com ) ); responseBean.setMsg( 成功 ); return responseBean;

RestController这个注解其实就是 controller ResponseBody 会自动帮我们把实体类转化为json格式。启动项目 如下图所示
\"在这里插入图片描述\"
至此 任何接口的返回都必须遵守这个规则。
这里有个点 code可以不仅仅是0或者1 可以是自定义的一些错误码 这种好处呢就是好排查。看就错误码就大概知道哪边出错。但我本人喜欢只返回0或者1,为什么呢 因为tm的简单啊 我具体的错误内容我会放到msg里面 比如 用户名错误 什么的。

全局异常处理

我们再来看下刚才那个接口 让后台报错会怎样 如下所示
\"在这里插入图片描述\"
这种返回结果 根本一点都不友好。所以我们应该把后台的异常作统一处理 报错只能是后台报错 返回的接口 对于前端而言 必须还是刚才所说的格式。

RestControllerAdvice

附上全局异常处理类

package com.cj.demo.controller;import com.cj.demo.bean.ResponseBean;import org.apache.shiro.ShiroException;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authz.UnauthorizedException;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.http.HttpStatus;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseStatus;import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.servlet.http.HttpServletRequest; * author cj * 异常捕获 遵循restful风格 RestControllerAdvicepublic class ExceptionController { private Logger logger LoggerFactory.getLogger(ExceptionController.class);// /**// * 捕捉shiro的异常// */// ResponseStatus(HttpStatus.UNAUTHORIZED)// ExceptionHandler(ShiroException.class)// public ResponseBean handle401(ShiroException e) {// if (e instanceof UnauthorizedException) {// return Tools.buildResFail( 无对应权限 // } else if (e instanceof AuthenticationException) {// return Tools.buildResFail(e.getMessage());// return new ResponseBean(401, Shiro错误 e.getMessage(), null);// ResponseStatus(HttpStatus.UNAUTHORIZED)// ExceptionHandler(IllegalAccessException.class)// public ResponseBean handle403() {// return new ResponseBean(1, 非法访问 , null); * 捕捉其他所有异常 ExceptionHandler(Exception.class) ResponseStatus(HttpStatus.BAD_REQUEST) public ResponseBean globalException(HttpServletRequest request, Throwable ex) { logger.error( 异常 , ex); return new ResponseBean(1, ex.getMessage(), null); private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode (Integer) request.getAttribute( javax.servlet.error.status_code ); if (statusCode null) { return HttpStatus.INTERNAL_SERVER_ERROR; return HttpStatus.valueOf(statusCode);

注释的部分先忽略不计 毕竟我还没讲到shiro这块的 但是大家感兴趣的话可以去这边看下 SpringBoot2.0集成Shiro
重启项目 访问刚才的地址 如下图所示
\"在这里插入图片描述\"
美滋滋 这样不管后台怎么样保证了返回的数据格式的一致性。

接口参数常规校验 Valid

解决方案 采用Validator 注解进行参数校验。

在需要校验的参数上加上 NotNull注解。在controller参数中加上注解 Valid。
\"在这里插入图片描述\"
\"在这里插入图片描述\"
附录 校验对应的注解
\"在这里插入图片描述\"
结果展示
这里因为是post请求 不能直接采用web方式访问 我们只能使用postman访问 如下图所示
\"在这里插入图片描述\"
返回格式还是responseBean这种JSON格式 但美中不足的是 返回的msg内容太多了 其实我只需要msg中的 default message [用户姓名不能为空]]
如何处理呢
还记得我们上面讲的全局异常处理么 打开后台 看下后台异常报错
\"在这里插入图片描述\"
注意笔者标注红色部分 我们只需要对这种类型的异常进行特殊处理 获取到msg的消息内容 然后返回ResponseBean就行了 具体如下所示
 /** * 对参数校验的异常处理 * param e * return ExceptionHandler(MethodArgumentNotValidException.class) public ResponseBean MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { // 从异常对象中拿到ObjectError对象 ObjectError objectError e.getBindingResult().getAllErrors().get(0); // 然后提取错误提示信息进行返回 return new ResponseBean(1, objectError.getDefaultMessage(), null);

这个是加到 RestControllerAdvice注解的那个类里面。

通用的分页对象 利用pageHelper进行分页

分页也是我们在后端开发的时候经常遇到的一个功能 那么在不用这些第三方插件的前提下我们是如何操作的呢
第一步 一般是根据条件查询出对应的数据然后最后加上limit进行分页。
第二步 是同样的sql去除limit只查询出符合条件的总数。
常规是需要这2个sql就可以进行分页了。但这样的话sql会有重复的代码 一种解决方案是利用 sql /sql 来提取通用的内容。一种是借助第三方插件来实现。
第三方插件的话 我经常使用的是这个PageHelper 当然还有其他的 觉得不错的话 可以在下面评论区提出来。
PageHelper

导入maven包
如何你是导入的我上面的maven文件的话 这边就不需要导入了。
 dependency  groupId com.github.pagehelper /groupId  artifactId pagehelper-spring-boot-starter /artifactId  version ${page-helper.version} /version  /dependency 
请求中加入分页的参数 pageNum、pageSize
新建一个BasePageRequestVO(任何分页请求的实体类都要继承这个类)
package com.cj.demo.bean.request;import io.swagger.annotations.ApiModelProperty;import org.apache.commons.lang3.builder.ToStringBuilder;import org.apache.commons.lang3.builder.ToStringStyle; * 描述  * author caojing * create 2019-12-03-11:41public class BasePageRequestVO { 是否需要分页,默认需要 private Boolean enablePage true; private int pageNum; 每页M条数 private int pageSize; 是否需要统计总数,默认需要 private Boolean enableCount true; public Boolean getEnablePage() { return enablePage; public void setEnablePage(Boolean enablePage) { this.enablePage enablePage; public int getPageNum() { return pageNum; public void setPageNum(int pageNum) { this.pageNum pageNum; public int getPageSize() { return pageSize; public void setPageSize(int pageSize) { this.pageSize pageSize; public Boolean getEnableCount() { return enableCount; public void setEnableCount(Boolean enableCount) { this.enableCount enableCount; Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);

新建一个UserRequestVO类

package com.cj.demo.bean.request; * 描述  * author caojing * create 2020-12-02-14:29public class UserRequestVO extends BasePageRequestVO { private String name; public String getName() { return name; public void setName(String name) { this.name name; Override public String toString() { return super.toString();

UserController 增加如下代码

 PostMapping( /user/page ) public ResponseBean PageInfo List UserBean getUserInfoPage( RequestBody UserRequestVO userRequestVO) { PageHelper.startPage(userRequestVO.getPageNum(), userRequestVO.getPageSize()); List UserBean userBean userService.selectPage(); PageInfo pageInfo new PageInfo(userBean); pageInfo.setList(userBean); ResponseBean responseBean new ResponseBean(); responseBean.setData(pageInfo); responseBean.setCode(0); return responseBean;

我们可以看下mapper的内容

 select id selectPage resultMap BaseResultMap  select * from user /select 

并没有分页语句 其实上面controller最主要的一句话是 PageHelper.startPage()这句话后面要紧跟着我们的查询语句 这样就可以实现分页效果啦 可以看下控制台打印的sql语句
\"在这里插入图片描述\"
注意 本文不是介绍如何使用PageHelper 所以关于如何集成PageHelper的文明同以及PageHelper的用法大家可以出门右转百度。
其他
其实更为方便的第三方库的话 我推荐使用 Mybatis-plus这个库 我也是最近才接触到的 他对于单表的操作实在是太方便了 一句sql都不需要要写 而且也自带分页功能。感兴趣的同学可以去看看。那有些杠精儿会问楼主了 你都推荐了 你为啥不用啊 楼主是因为用pageHelper用习惯了 所以没用 开发嘛 哪个用的习惯 用的顺手就用哪个。

总结 统一的返回格式
这边需要一个统一的返回格式 方便前后端进行数据的共享和交互。全局异常处理
这里是为了在后台出现异常的时候 返回到前端的代码依旧是之前规定的数据格式 不然返回错误信息给前端 前端也无法进行判断。常规性的接口校验
采用是springboot自带的 NotNull之类的参数判断 方便对一些数据、非空字段进行判断。通用的分页请求对象
这里我是采用的pageHelper类 其实我是把请求分为2类
一类是普通的请求。
一类是分页的请求。
我这里只是对分页请求进行了简单的处理 因为分页都是需要2个共同的参数 pageNum、pageSize。所以形成一个basePageRequestVO这个类。
当然如果你业务逻辑都需要一个通用的请求参数 你也可以新建一个baseRequestVO 然后让其余的实体类都继承这个类。 结束语

其实写代码本来就是一个归纳总结的过程 整天复制粘贴的话 对自己而言其实没啥提升 我没有贬低复制粘贴这种做法 我自己也是复制粘贴 有现成的代码干嘛不用呢 但我希望是大家用脑子的复制粘贴 别复制粘贴过来 能运行就行。那这样的话 你永远都不会进步。

本文链接: http://benaglobal.immuno-online.com/view-749151.html

发布于 : 2021-03-25 阅读(0)