您好,登錄后才能下訂單哦!
這篇文章主要講解了“怎么編寫API接口”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么編寫API接口”吧!
業(yè)務(wù)接口通常都包含最基礎(chǔ)的CRUD操作,怎么盡可能的統(tǒng)一這部分接口?
API接口部分會使用到HttpServletRequest
和HttpServletResponse
里的數(shù)據(jù),如何方便的獲取到這兩個對象?
接口的參數(shù)有時通過單個參數(shù)傳遞,有時會通過對象傳遞,如何方便的檢驗這些參數(shù),而不用頻繁的使用if判斷去甄別臨界值或者判空?
帶著這些疑問,我們一步步去解決.
在前一篇文章中有提到過,如何用正確的姿勢使用不同的協(xié)議,GET,POST,PUT,PATCH,DELETE
這五種協(xié)議在日常CRUD開發(fā)中很常用.不同的業(yè)務(wù)基本都會羅列出這些API接口,那么我們能不能嘗試把他們抽離成接口,然后讓不同的業(yè)務(wù)控制層去實現(xiàn)這個接口,從而規(guī)范基礎(chǔ)的接口路徑呢?理論存在,實踐開始
先定義一個普通的java接口類,寫入四個方法,分別對應(yīng)增刪查改四種操作. 注:PATCH接口修改單個屬性值,因為不同業(yè)務(wù)中字段存在太大差異,所以她比較適合單獨實現(xiàn),這個接口中就不涵蓋這個接口了.
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); }
相信大家看到這五個接口一目了然,這不就是增刪查改加上一個分頁接口么.有了這個接口我們編寫某種業(yè)務(wù)的控制層不就簡單了么,直接實現(xiàn)他,然后再實現(xiàn)不同的邏輯,完美.
//這里以用戶相關(guān)業(yè)務(wù)和User用戶相關(guān)業(yè)務(wù)為例 //教師業(yè)務(wù)控制層 @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; } } //教師業(yè)務(wù)控制層,跟上面一樣的操作,這里節(jié)省篇幅,先省略....
通過postMan測試,分別用teacher和user前綴就能調(diào)用各自業(yè)務(wù)的接口,簡單的增刪查改統(tǒng)一路徑便實現(xiàn)了.但是這也會出現(xiàn)一個缺陷,因為我們都是用Map接收的,map鍵值對因為不確定字段,后期如果不debug是很難維護(hù)的,甚至連字段掉了也可以調(diào)通,不便于排查,其次如果我們用Mybatis操作時,還需要轉(zhuǎn)換為對象,雖然統(tǒng)一了接口路徑,但是后面的操作還是很繁瑣,所以我們還是得進(jìn)一步優(yōu)化.
相信大家對泛型都不陌生.我們這里也可用泛型去優(yōu)化.因為搜索分頁需要攜帶頁碼,排序等等,我們把他作為兩個泛型去實現(xiàn).
//需要注意一點小細(xì)節(jié),因為這五個接口的路徑協(xié)議都是可以統(tǒng)一的,所以我們在定義接口的時候就把后面拼接的路徑寫在接口中. public interface BaseCrud<T, S> { /** * @description: 分頁查詢接口 * @author: chenyunxuan * @updateTime: 2020/12/18 11:40 上午 */ @GetMapping R selectList(@ModelAttribute S s); /** * @description: 根據(jù)id查詢單條數(shù)據(jù) * @author: chenyunxuan * @updateTime: 2020/12/18 11:44 上午 */ @GetMapping("{id}") R selectOne(@PathVariable String id); /** * @description: 新增單條數(shù)據(jù) * @author: chenyunxuan * @updateTime: 2020/12/18 1:39 下午 */ @PostMapping R add(@RequestBody T t); /** * @description: 修改單條數(shù)據(jù) * @author: chenyunxuan * @updateTime: 2020/12/18 1:39 下午 */ @PutMapping("{id}") R upp(@PathVariable String id, @RequestBody T t); /** * @description: 刪除單條數(shù)據(jù) * @author: chenyunxuan * @updateTime: 2020/12/18 1:40 下午 */ @DeleteMapping("{id}") R del(@PathVariable String id); }
定義兩個不同的業(yè)務(wù)實體類.分別用于分頁和新增修改.
@Data //對應(yīng)泛型T,用于新增修改 public class User { private String mobile; private String name; private String email; private Integer age; private LocalDateTime birthday; } @Data //對應(yīng)泛型S,用于搜索 public class UserSearch { private Integer pageNum; private String mobile; private String name; private String email; }
接下來是業(yè)務(wù)控制層實現(xiàn).
@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版本又方便了不少,優(yōu)化了傳輸對象,不同業(yè)務(wù)可以創(chuàng)建不同對象進(jìn)行傳輸.有一個小細(xì)節(jié),方法上的@ResponseBody被我省略了,因為@RestController里已經(jīng)包含了@ResponseBody注解.至此一個通用的API接口統(tǒng)一demo已經(jīng)完成.
人總是不斷追求完美的,我也不例外,上面的v2版本雖然把基礎(chǔ)接口都統(tǒng)一了,但是看到這么多字段需要效驗也是愛不起來啊.比如在新增時,用戶的昵稱不可為空.用戶的年齡最少也要是一歲等等效驗再一次充斥著我的代碼,看著滿滿的if判斷,我得想辦法優(yōu)化一番.
我們先引入一個spring的參數(shù)檢驗組件validation
,該組件可以用注解很方便的校驗入?yún)?如果異常也可以直接通過捕捉相應(yīng)的異常信息拋出.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
下面列舉一些常用的驗證約束注解
@Null 被注解的元素必須為null
@NotNull 被注解的元素必須不為null
@AssertTure 被注解的元素必須為ture
@AssertFalse 被注解的元素必須為false
@Min(value) 被注解的元素必須是數(shù)字且必須大于等于指定值
@Max(value) 被注解的元素必須是數(shù)字且必須小于等于指定值
@DecimalMin(value) 被注解的元素必須是數(shù)字且必須大于等于指定值
@DecimalMax(value) 被注解的元素必須是數(shù)字且必須小于等于指定值
@Size(max, min) 被注解的元素必須在指定的范圍內(nèi)
@Digits(integer, fraction) 被注解的元素必須是數(shù)字且其值必須在給定的范圍內(nèi)
@Past 被注解的元素必須是一個過去的日期
@Future 被注解的元素必須是一個將來的日期
@Pattern(value) 被注解的元素必須符合給定正則表達(dá)式
@Email 被注解的元素必須是Email地址
@Length(min, max) 被注解的元素長度必須在指定的范圍內(nèi)
@NotEmpty 被注解的元素必須不為空,空字符串也不可以
@Range 被注解的元素(可以是數(shù)字或者表示數(shù)字的字符串)必須在給定的范圍內(nèi)
@URL 被注解的元素必須是URL
@Valid 對實體類進(jìn)行校驗
接下來我們開始改造我們的接口,BaseCrud中用到實體的地方可以加一下注解
/** * @description: 分頁查詢接口 * @author: chenyunxuan * @updateTime: 2020/12/18 11:40 上午 */ @GetMapping R selectList(@Validated @ModelAttribute S s); /** * @description: 新增單條數(shù)據(jù) * @author: chenyunxuan * @updateTime: 2020/12/18 1:39 下午 */ @PostMapping R add(@Validated @RequestBody T t); /** * @description: 修改單條數(shù)據(jù) * @author: chenyunxuan * @updateTime: 2020/12/18 1:39 下午 */ @PutMapping("{id}") R upp(@PathVariable String id, @Validated @RequestBody T t);
同時我們的實體類也要做相應(yīng)的改造,加入你想要的校驗注解
public class User { /** * @description: 自定義參數(shù)效驗(電話號碼校驗) * @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") //不可為空且必須是在系統(tǒng)時間之前 @NotNull @Past private LocalDateTime birthday; }
由上面的代碼我們可以得出以下結(jié)論
校驗注解可以疊加使用
在預(yù)設(shè)校驗注解滿足不了的時候可以自定義注解
可用分組實現(xiàn)不同業(yè)務(wù)需求,不同的校驗方式
拋出的校驗信息可自行設(shè)置
預(yù)設(shè)的校驗有時是不可以滿足業(yè)務(wù)校驗要求的,比如電話號碼校驗,身份證校驗等等.好在validation也想到了這部分需求,提供了ConstraintValidator接口可自定義匹配規(guī)則.
首先我們得自定義一個注解以及一個校驗規(guī)則類
@Documented // 指定真正實現(xiàn)校驗規(guī)則的類 @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接口使用了泛型,需要指定兩個參數(shù),第一個自定義注解類,第二個為需要校驗的數(shù)據(jù)類型。 public class MobileValidator implements ConstraintValidator<MobileVail, String> { //這里是具體的匹配規(guī)則 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) { //實現(xiàn)驗證方法 if ( value == null || value.length() == 0 ) { return false; } Matcher m = PHONE_PATTERN.matcher(value); return m.matches(); } }
做完這步后只需要在需要驗證的字段上加上@MobileVail就可以愉快的使用了
看v3實現(xiàn)的代碼在驗證校驗name和mobile屬性時候,我們用到了分組功能,這是為了不同的業(yè)務(wù)場景采用不同的校驗方式.
...... @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 {}
完成這一步后只需要在不同業(yè)務(wù)中加入不同的組就會把校驗按組隔離開,比如上面用戶的例子,在新增數(shù)據(jù)時,我們需要校驗電話號碼的正確性而修改的時候則不需要驗證,在修改的時候我們需要驗證用戶的昵稱長度,新增的時候不需要驗證,我們只需要在@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后,如果不自定義捕捉異常,拋出的異常信息很詳細(xì),給客戶端提示不友好,所以我們需要攔截這部分異常,自定義拋出message.這需要用到上篇文章提到的統(tǒng)一異常攔截類.
@ControllerAdvice @Log4j2 public class GlobalExceptionHandler { ...... /** * @description: JSON傳值出現(xiàn)異常(對應(yīng)@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: 單個參數(shù)參數(shù)異常(對應(yīng)單個參數(shù)傳值錯誤) * @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參數(shù)異常(對應(yīng)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():隨機返回一個對象屬性的異常信息。如果要一次性返回所有對象屬性異常信息,則調(diào)用ex.getAllErrors() FieldError fieldError = e.getFieldError(); StringBuilder sb = new StringBuilder(); sb.append(fieldError.getDefaultMessage()); // 生成返回結(jié)果 log.error("BindException requestURI:{} paramName:{} msg:{}", req.getRequestURI(), e.getObjectName(), fieldError.getDefaultMessage()); return ResultUtil.error(400, fieldError.getDefaultMessage()); } }
API接口部分會使用到HttpServletRequest
和HttpServletResponse
里的數(shù)據(jù),如何方便的獲取到這兩個對象,常規(guī)做法就是每次用到的時候加在對應(yīng)的控制層方法入?yún)⒗?
public R selectOne(String id, HttpServletRequest request) { return null; }
這樣在切面打印入?yún)⒌臅r候用多出一個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接口”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對怎么編寫API接口這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。