您好,登錄后才能下訂單哦!
這篇文章主要講解了“怎么編寫API接口”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“怎么編寫API接口”吧!
業務接口通常都包含最基礎的CRUD操作,怎么盡可能的統一這部分接口?
API接口部分會使用到HttpServletRequest
和HttpServletResponse
里的數據,如何方便的獲取到這兩個對象?
接口的參數有時通過單個參數傳遞,有時會通過對象傳遞,如何方便的檢驗這些參數,而不用頻繁的使用if判斷去甄別臨界值或者判空?
帶著這些疑問,我們一步步去解決.
在前一篇文章中有提到過,如何用正確的姿勢使用不同的協議,GET,POST,PUT,PATCH,DELETE
這五種協議在日常CRUD開發中很常用.不同的業務基本都會羅列出這些API接口,那么我們能不能嘗試把他們抽離成接口,然后讓不同的業務控制層去實現這個接口,從而規范基礎的接口路徑呢?理論存在,實踐開始
先定義一個普通的java接口類,寫入四個方法,分別對應增刪查改四種操作. 注:PATCH接口修改單個屬性值,因為不同業務中字段存在太大差異,所以她比較適合單獨實現,這個接口中就不涵蓋這個接口了.
public interface BaseCrud { R selectList(@ModelAttribute Map s); R selectOne(@PathVariable String id); R add(@RequestBody Map t); R upp(@PathVariable String id, @RequestBody Map t); R del(@PathVariable String id); }
相信大家看到這五個接口一目了然,這不就是增刪查改加上一個分頁接口么.有了這個接口我們編寫某種業務的控制層不就簡單了么,直接實現他,然后再實現不同的邏輯,完美.
//這里以用戶相關業務和User用戶相關業務為例 //教師業務控制層 @Controller @RequestMapping("user") public class UserController implements BaseCrud { @Override @GetMapping @ResponseBody public R selectList(Map s) { return null; } @Override @GetMapping("{id}") @ResponseBody public R selectOne(String id) { return null; } @Override @PostMapping @ResponseBody public R add(Map t) { return null; } @Override @PutMapping("{id}") @ResponseBody public R upp(String id, Map t) { return null; } @Override @DeleteMapping("{id}") @ResponseBody public R del(String id) { return null; } } //教師業務控制層,跟上面一樣的操作,這里節省篇幅,先省略....
通過postMan測試,分別用teacher和user前綴就能調用各自業務的接口,簡單的增刪查改統一路徑便實現了.但是這也會出現一個缺陷,因為我們都是用Map接收的,map鍵值對因為不確定字段,后期如果不debug是很難維護的,甚至連字段掉了也可以調通,不便于排查,其次如果我們用Mybatis操作時,還需要轉換為對象,雖然統一了接口路徑,但是后面的操作還是很繁瑣,所以我們還是得進一步優化.
相信大家對泛型都不陌生.我們這里也可用泛型去優化.因為搜索分頁需要攜帶頁碼,排序等等,我們把他作為兩個泛型去實現.
//需要注意一點小細節,因為這五個接口的路徑協議都是可以統一的,所以我們在定義接口的時候就把后面拼接的路徑寫在接口中. public interface BaseCrud<T, S> { /** * @description: 分頁查詢接口 * @author: chenyunxuan * @updateTime: 2020/12/18 11:40 上午 */ @GetMapping R selectList(@ModelAttribute S s); /** * @description: 根據id查詢單條數據 * @author: chenyunxuan * @updateTime: 2020/12/18 11:44 上午 */ @GetMapping("{id}") R selectOne(@PathVariable String id); /** * @description: 新增單條數據 * @author: chenyunxuan * @updateTime: 2020/12/18 1:39 下午 */ @PostMapping R add(@RequestBody T t); /** * @description: 修改單條數據 * @author: chenyunxuan * @updateTime: 2020/12/18 1:39 下午 */ @PutMapping("{id}") R upp(@PathVariable String id, @RequestBody T t); /** * @description: 刪除單條數據 * @author: chenyunxuan * @updateTime: 2020/12/18 1:40 下午 */ @DeleteMapping("{id}") R del(@PathVariable String id); }
定義兩個不同的業務實體類.分別用于分頁和新增修改.
@Data //對應泛型T,用于新增修改 public class User { private String mobile; private String name; private String email; private Integer age; private LocalDateTime birthday; } @Data //對應泛型S,用于搜索 public class UserSearch { private Integer pageNum; private String mobile; private String name; private String email; }
接下來是業務控制層實現.
@RestController @RequestMapping("teacher") public class UserController implements BaseCrud<User, UserSearch> { @Override public R selectList(UserSearch userSearch) { return null; } @Override public R selectOne(String id) { return null; } @Override public R add(User user) { return null; } @Override public R upp(String id, User user) { return null; } @Override public R del(String id) { return null; } }
可以看到這個版本比V1版本又方便了不少,優化了傳輸對象,不同業務可以創建不同對象進行傳輸.有一個小細節,方法上的@ResponseBody被我省略了,因為@RestController里已經包含了@ResponseBody注解.至此一個通用的API接口統一demo已經完成.
人總是不斷追求完美的,我也不例外,上面的v2版本雖然把基礎接口都統一了,但是看到這么多字段需要效驗也是愛不起來啊.比如在新增時,用戶的昵稱不可為空.用戶的年齡最少也要是一歲等等效驗再一次充斥著我的代碼,看著滿滿的if判斷,我得想辦法優化一番.
我們先引入一個spring的參數檢驗組件validation
,該組件可以用注解很方便的校驗入參,如果異常也可以直接通過捕捉相應的異常信息拋出.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
下面列舉一些常用的驗證約束注解
@Null 被注解的元素必須為null
@NotNull 被注解的元素必須不為null
@AssertTure 被注解的元素必須為ture
@AssertFalse 被注解的元素必須為false
@Min(value) 被注解的元素必須是數字且必須大于等于指定值
@Max(value) 被注解的元素必須是數字且必須小于等于指定值
@DecimalMin(value) 被注解的元素必須是數字且必須大于等于指定值
@DecimalMax(value) 被注解的元素必須是數字且必須小于等于指定值
@Size(max, min) 被注解的元素必須在指定的范圍內
@Digits(integer, fraction) 被注解的元素必須是數字且其值必須在給定的范圍內
@Past 被注解的元素必須是一個過去的日期
@Future 被注解的元素必須是一個將來的日期
@Pattern(value) 被注解的元素必須符合給定正則表達式
@Email 被注解的元素必須是Email地址
@Length(min, max) 被注解的元素長度必須在指定的范圍內
@NotEmpty 被注解的元素必須不為空,空字符串也不可以
@Range 被注解的元素(可以是數字或者表示數字的字符串)必須在給定的范圍內
@URL 被注解的元素必須是URL
@Valid 對實體類進行校驗
接下來我們開始改造我們的接口,BaseCrud中用到實體的地方可以加一下注解
/** * @description: 分頁查詢接口 * @author: chenyunxuan * @updateTime: 2020/12/18 11:40 上午 */ @GetMapping R selectList(@Validated @ModelAttribute S s); /** * @description: 新增單條數據 * @author: chenyunxuan * @updateTime: 2020/12/18 1:39 下午 */ @PostMapping R add(@Validated @RequestBody T t); /** * @description: 修改單條數據 * @author: chenyunxuan * @updateTime: 2020/12/18 1:39 下午 */ @PutMapping("{id}") R upp(@PathVariable String id, @Validated @RequestBody T t);
同時我們的實體類也要做相應的改造,加入你想要的校驗注解
public class User { /** * @description: 自定義參數效驗(電話號碼校驗) * @author: chenyunxuan * @updateTime: 2019-12-18 17:30 */ @MobileVail(groups = {Add.class}) private String mobile; //用戶名稱最短兩位,最長30位 //這里的group分組后面會介紹其作用 @Size(min = 2, max = 30, groups = {Upp.class}) private String name; /** * @description: 自定義錯誤信息 * @author: chenyunxuan * @updateTime: 2019-12-18 17:30 */ //校驗注解都可以自定義message,可配合異常攔截返回你想要message @NotEmpty(message = "自定義錯誤信息,Email不能為空") @Email private String email; @NotNull @Min(18) @Max(100) private Integer age; @DateTimeFormat(pattern = "MM/dd/yyyy") //不可為空且必須是在系統時間之前 @NotNull @Past private LocalDateTime birthday; }
由上面的代碼我們可以得出以下結論
校驗注解可以疊加使用
在預設校驗注解滿足不了的時候可以自定義注解
可用分組實現不同業務需求,不同的校驗方式
拋出的校驗信息可自行設置
預設的校驗有時是不可以滿足業務校驗要求的,比如電話號碼校驗,身份證校驗等等.好在validation也想到了這部分需求,提供了ConstraintValidator接口可自定義匹配規則.
首先我們得自定義一個注解以及一個校驗規則類
@Documented // 指定真正實現校驗規則的類 @Constraint(validatedBy = MobileValidator.class) @Target( { ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface MobileVail { String message() default "不是正確的手機號碼"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; @Target({ElementType.METHOD,ElementType.FIELD,ElementType.PACKAGE}) @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { MobileVail[] value(); } } //ConstraintValidator接口使用了泛型,需要指定兩個參數,第一個自定義注解類,第二個為需要校驗的數據類型。 public class MobileValidator implements ConstraintValidator<MobileVail, String> { //這里是具體的匹配規則 private static final Pattern PHONE_PATTERN = Pattern.compile( "^((13[0-9])|(15[^4])|(18[0,2,3,5-9])|(17[0-8])|(147))\\d{8}$" ); @Override public void initialize(MobileVail constraintAnnotation) { } @Override public boolean isValid(String value, ConstraintValidatorContext context) { //實現驗證方法 if ( value == null || value.length() == 0 ) { return false; } Matcher m = PHONE_PATTERN.matcher(value); return m.matches(); } }
做完這步后只需要在需要驗證的字段上加上@MobileVail就可以愉快的使用了
看v3實現的代碼在驗證校驗name和mobile屬性時候,我們用到了分組功能,這是為了不同的業務場景采用不同的校驗方式.
...... @MobileVail(groups = {Add.class}) private String mobile; @Size(min = 2, max = 30, groups = {Upp.class}) private String name; ......
Add和Upp類定義比較簡單,一個空的注解類就OK
public @interface Add {} public @interface Upp {}
完成這一步后只需要在不同業務中加入不同的組就會把校驗按組隔離開,比如上面用戶的例子,在新增數據時,我們需要校驗電話號碼的正確性而修改的時候則不需要驗證,在修改的時候我們需要驗證用戶的昵稱長度,新增的時候不需要驗證,我們只需要在@Validated中注明他的分組即可.
@PostMapping R add(@Validated(value = Add.class) @RequestBody T t); @PutMapping("{id}") R upp(@PathVariable String id, @Validated(value = Upp.class) @RequestBody T t);
在使用validation后,如果不自定義捕捉異常,拋出的異常信息很詳細,給客戶端提示不友好,所以我們需要攔截這部分異常,自定義拋出message.這需要用到上篇文章提到的統一異常攔截類.
@ControllerAdvice @Log4j2 public class GlobalExceptionHandler { ...... /** * @description: JSON傳值出現異常(對應@RequestBody傳值錯誤) * @author: chenyunxuan * @date: 2019-12-18 16:37 * @version: 1.0.0 * @updateTime: 2019-12-18 16:37 */ @ExceptionHandler(value = MethodArgumentNotValidException.class) @ResponseBody public R handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) { BindingResult bindingResult = e.getBindingResult(); StringBuilder sb = new StringBuilder(); sb.append("url="); sb.append(req.getRequestURI().replace("/", "")); sb.append(","); for (FieldError fieldError : bindingResult.getFieldErrors()) { sb.append("field="); sb.append(fieldError.getObjectName()); sb.append("."); sb.append(fieldError.getField()); sb.append(",error="); sb.append(fieldError.getDefaultMessage()); sb.append(";"); } String msg = sb.toString(); log.error(String.format("MethodArgumentNotValidException RequestURI:%s msg:%s", req.getRequestURI(), msg), e); return ResultUtil.error(400, bindingResult.getFieldError().getDefaultMessage()); } /** * @title: 單個參數參數異常(對應單個參數傳值錯誤) * @author: chenyunxuan * @date: 2019-12-18 16:37 * @version: 1.0.0 * @updateTime: 2019-12-18 16:37 */ @ExceptionHandler(value = ConstraintViolationException.class) @ResponseBody public R handleMethodArgumentNotValidException(HttpServletRequest req, ConstraintViolationException e) { log.error(String.format("ConstraintViolationException RequestURI:%s", req.getRequestURI()), e); return ResultUtil.error(400, e.getMessage()); } /** * @title: 提交FORM參數異常(對應form表單傳值錯誤) * @author: chenyunxuan * @date: 2019-12-18 16:41 * @version: 1.0.0 * @updateTime: 2019-12-18 16:41 */ @ExceptionHandler(value = BindException.class) @ResponseBody public R handleBindException(HttpServletRequest req, BindException e) throws BindException { // ex.getFieldError():隨機返回一個對象屬性的異常信息。如果要一次性返回所有對象屬性異常信息,則調用ex.getAllErrors() FieldError fieldError = e.getFieldError(); StringBuilder sb = new StringBuilder(); sb.append(fieldError.getDefaultMessage()); // 生成返回結果 log.error("BindException requestURI:{} paramName:{} msg:{}", req.getRequestURI(), e.getObjectName(), fieldError.getDefaultMessage()); return ResultUtil.error(400, fieldError.getDefaultMessage()); } }
API接口部分會使用到HttpServletRequest
和HttpServletResponse
里的數據,如何方便的獲取到這兩個對象,常規做法就是每次用到的時候加在對應的控制層方法入參里.
public R selectOne(String id, HttpServletRequest request) { return null; }
這樣在切面打印入參的時候用多出一個request對象,多處用到的話也要一搜索全都是相同HttpServletRequest對象,這里我選擇用一個抽象類去注入這兩個對象
/** * @description: 控制層基類 * @author: chenyunxuan * @updateTime: 2020/12/18 3:36 下午 */ public abstract class BaseController { @Autowired protected HttpServletRequest request; @Autowired protected HttpServletResponse response; }
然后在控制層繼承這個抽象類,就可以直接在方法里愉快的使用request和response對象了
public class UserController extends BaseController implements BaseCrud<User, UserSearch> { @Override public R selectList(UserSearch userSearch) { request.getRequestURI(); return null; } }
感謝各位的閱讀,以上就是“怎么編寫API接口”的內容了,經過本文的學習后,相信大家對怎么編寫API接口這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。