溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么編寫API接口

發(fā)布時間:2021-10-21 11:09:27 來源:億速云 閱讀:188 作者:iii 欄目:編程語言

這篇文章主要講解了“怎么編寫API接口”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么編寫API接口”吧!

思考

  1. 業(yè)務(wù)接口通常都包含最基礎(chǔ)的CRUD操作,怎么盡可能的統(tǒng)一這部分接口?

  2. API接口部分會使用到HttpServletRequestHttpServletResponse里的數(shù)據(jù),如何方便的獲取到這兩個對象?

  3. 接口的參數(shù)有時通過單個參數(shù)傳遞,有時會通過對象傳遞,如何方便的檢驗這些參數(shù),而不用頻繁的使用if判斷去甄別臨界值或者判空?

帶著這些疑問,我們一步步去解決.

整體類圖

怎么編寫API接口

統(tǒng)一基礎(chǔ)接口路徑

在前一篇文章中有提到過,如何用正確的姿勢使用不同的協(xié)議,GET,POST,PUT,PATCH,DELETE這五種協(xié)議在日常CRUD開發(fā)中很常用.不同的業(yè)務(wù)基本都會羅列出這些API接口,那么我們能不能嘗試把他們抽離成接口,然后讓不同的業(yè)務(wù)控制層去實現(xiàn)這個接口,從而規(guī)范基礎(chǔ)的接口路徑呢?理論存在,實踐開始

實踐

v1版本

先定義一個普通的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)化.

v2版本

相信大家對泛型都不陌生.我們這里也可用泛型去優(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)完成.

統(tǒng)一的參數(shù)效驗

實踐

人總是不斷追求完美的,我也不例外,上面的v2版本雖然把基礎(chǔ)接口都統(tǒng)一了,但是看到這么多字段需要效驗也是愛不起來啊.比如在新增時,用戶的昵稱不可為空.用戶的年齡最少也要是一歲等等效驗再一次充斥著我的代碼,看著滿滿的if判斷,我得想辦法優(yōu)化一番.

v3版本

我們先引入一個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é)論

  1. 校驗注解可以疊加使用

  2. 在預(yù)設(shè)校驗注解滿足不了的時候可以自定義注解

  3. 可用分組實現(xiàn)不同業(yè)務(wù)需求,不同的校驗方式

  4. 拋出的校驗信息可自行設(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());
    }
}
v4版本

API接口部分會使用到HttpServletRequestHttpServletResponse里的數(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)注!

向AI問一下細(xì)節(jié)

免責(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)容。

api
AI