统一前后端接口调用规范

在平时的前后端接口对接的过程中,前后端总是因为接口的实现方式不同需要花很长时间去对接,针对这种情况,我花了点时间把常用的几种调用方式总结成具体的代码,供大家参考,提高大家的工作效率。

后台代码采用java编写,使用了spring boot快速构建项目,前端网络层就用了最近比较流行的axios。也特别欢迎其他小伙伴针对这套接口编写其他语言的具体实现。这套代码我会上传到github:launcher

后台依赖:

//Guava,因为没有使用数据库存储数据,所以用了Guava缓存

    com.google.guava
    guava
    27.1-jre


//lombok

    org.projectlombok
    lombok
    1.18.6
    provided

java后台具体实现

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {

    /**
     * 创建一个空对象,避免找不到缓存抛异常
     */
    private static final User EMPTY = new User();

    /**
     * 构建一个本地缓存
     */
    private static final LoadingCache CACHE = CacheBuilder.newBuilder()
            //初始化100个
            .initialCapacity(100)
            //最大10000
            .maximumSize(10000)
            //30分钟没有读写操作数据就过期
            .expireAfterAccess(30, TimeUnit.MINUTES)
            .build(new CacheLoader() {
                //如果get()没有拿到缓存,直接点用load()加载缓存
                @Override
                public User load(Integer key) {
                    log.info("key:" + key);
                    return EMPTY;
                }

                /**
                 * 在调用getAll()的时候,如果没有找到缓存,就会调用loadAll()加载缓存
                 * @param keys
                 * @return
                 * @throws Exception
                 */
                @Override
                public Map loadAll(Iterable keys) throws Exception {
                    log.info(String.valueOf(keys));
                    return super.loadAll(keys);
                }
            });


    @Data
    static class User {
        /**
         * id
         */
        private Integer id;
        /**
         * 名字
         */
        private String name;
        /**
         * 性别
         */
        private Boolean isMan;
        /**
         * 头像名称
         */
        private String imageName;
    }

    /**
     * 获取所有的users
     *
     * @return
     */
    @GetMapping("/list")
    @ResponseBody
    public Collection users() {
        Map map = CACHE.asMap();
        return map.values();
    }

    /**
     * 添加user(设置头像)
     *
     * @param user
     * @param image
     * @return
     */
    @PostMapping("/addWithImage")
    @ResponseBody
    public User add(@RequestPart("user") User user, @RequestPart("image") MultipartFile image) {
        //拿到头像进行处理
        user.setImageName(image.getName());
        //缓存
        CACHE.put(user.getId(), user);
        return user;
    }

    /**
     * 第一种方式(推荐)
     * 添加user(不设置头像)
     *
     * @param user
     * @return
     */
    @PostMapping("/add")
    @ResponseBody
    public User add(@RequestPart("user") User user) {
        //缓存
        CACHE.put(user.getId(), user);
        return user;
    }

    /**
     * 第二种方式
     * 添加user(不设置头像)
     *
     * @param user
     * @return
     */
    @PostMapping("/v2/add")
    @ResponseBody
    public User add2(@RequestBody User user) {
        //缓存
        CACHE.put(user.getId(), user);
        return user;
    }

    /**
     * 修改user
     *
     * @param user
     * @return
     * @throws ExecutionException
     */
    @PutMapping("/update")
    @ResponseBody
    public User update(@RequestBody User user) throws ExecutionException {
        User u = CACHE.get(user.getId());
        u.setImageName(user.getImageName());
        u.setName(user.getName());
        u.setIsMan(user.getIsMan());
        //缓存
        CACHE.put(u.getId(), u);
        return user;
    }

    /**
     *
     * @param id
     * @return
     * @throws ExecutionException
     */
    @DeleteMapping("/{id}")
    @ResponseBody
    public User deleteUser(@PathVariable("id") Integer id) throws ExecutionException {
        User user = CACHE.get(id);
        CACHE.invalidate(id);
        return user;
    }
}

js前端依赖

//axios

针对axios做了一套个性化封装,最终得到产物:launcher.js

var LAUNCHER = function(){
    //请求方式
    var METHOD = {
        GET: 'GET',
        POST: 'POST',
        PUT: 'PUT',
        DELETE: 'DELETE',
        HEAD: 'HEAD',
        PATCH: 'PATCH',
        OPTIONS: 'OPTIONS'
    }

    //content-type
    var CONTENT_TYPE = {
        NAME: 'Content-Type',
        JSON: 'application/json;charset=UTF-8',
        FORM_DATA: 'multipart/form-data'
    }

    var BASE = {
        send: function(obj){
            //判断响应码是否正常,默认的
            function isOkResponse(status){
                return status >= 200 && status < 300;
            }

            axios.request({
                url: obj.url,
                //默认是get
                method: obj.method||METHOD.GET,
                baseURL: obj.baseURL,
                headers: obj.headers,
                params: obj.params,
                data: obj.data,
                //默认1000
                timeout: obj.timeout||1000,
                //默认json
                responseType: obj.responseType||'json',
                //上传事件
                onUploadProgress: obj.onUploadProgress||function(progressEvent){
                    //默认上传进度事件,可定义全局进度条
                    console.log('上传进度事件');
                },
                //定义可获得的http响应状态码
                validateStatus: obj.validateStatus||function(status){
                    return isOkResponse(status);
                }
            }).then(function(response){
                //处理响应头和响应数据
                function handleResponse(res,obj){
                    if(obj.handleResponse){
                        obj.handleResponse(res);
                    }
                    if(obj.handleResponseHeaders){
                        obj.handleResponseHeaders(res.headers);
                    }
                    if(obj.handleResponseData){
                        obj.handleResponseData(res.data);
                    }
                }
                //验证响应数据是否有效,全局验证
                function validateResponse(data){
                    return true;
                }

                //处理非正常业务数据
                function handleErrorResponse(res,obj){
                    if(obj.handleErrorResponse){
                        obj.handleErrorResponse(res);
                    }
                    if(obj.handleResponseHeaders){
                        obj.handleResponseHeaders(res.headers);
                    }
                    if(obj.handleErrorResponseData){
                        obj.handleErrorResponseData(res);
                    }
                }

                //处理非正常业务数据
                function handleErrorData(data){
                    //可以自行定义弹框或提醒
                    console.log(data);
                }
                //开始处理响应数据
                if(validateResponse(response)){
                    //自定义响应数据验证器
                    if(obj.validateResponse){
                        if(obj.validateResponse(response)){
                            //能调用这个方法,说明业务数据一定正确
                            handleResponse(response,obj);
                        }else{
                            //说明是非正常业务数据,比如响应code返回非正常状态
                            //如果有自定义处理非正常业务数据,就调用自定义处理器,否则调用全局处理器
                            if(obj.handleErrorResponse || obj.handleErrorResponseData){
                                handleErrorResponse(response,obj);
                            }else{
                                //调用全局处理非正常业务数据
                                handleErrorData(response.data);
                            }
                        }
                    }else{
                        //能调用这个方法,说明业务数据一定正确
                        handleResponse(response,obj);
                    }
                }
            }).catch(function(error){
                //默认全局处理error的方式
                function handleError(error){
                    //可以自行定义弹框或提醒
                    console.log(error);
                }
                //如果有自定义异常处理器,就调用自定义的处理器,否则调用全局处理器
                if (obj.handleError) {
                    obj.handleError(error);
                }else{
                    handleError(error);
                }
            });
        }
    }

    //抽离公共的参数
    function defaultHandleConfig(obj){
        return {
            url:obj.url,
            baseURL:obj.baseURL,
            headers:obj.headers,
            responseType:obj.responseType,
            //响应状态验证器
            validateStatus:obj.validateStatus,
            //响应数据验证器
            validateResponse:obj.validateResponse,
            handleResponse:obj.handleResponse,
            //处理响应头(正常业务数据)
            handleResponseHeaders:obj.handleResponseHeaders,
            //处理响应数据(正常业务数据)
            handleResponseData:obj.handleResponseData,
            //处理非正常响应数据(不符合validateResponseData的数据)
            handleErrorResponse:obj.handleErrorResponse,
            //处理非正常响应数据(不符合validateResponseData的数据)
            handleErrorResponseData:obj.handleErrorResponseData,
            //处理异常情况,不定义会调用全局处理器
            handleError:obj.handleError
        }
    }

    //设置headers
    function addHeader(headers,key,value){
        if(!headers){
            headers = {};
        }
        headers[key] = value;
        return headers;
    }

    //将data转换为json包装
    function handleData2Json(data){
        if(!!data){
            return JSON.stringify(data);
        }
        return data;
    }

    //将data转换为FormData包装
    function handleData2FormData(data){
        //判断是不是文件
        function isFile(file){
            return file instanceof File;
        }
        //创建一个FormData,用来包装数据
        var formData = new FormData();
        if(!!data){
            //抽出data的所有属性和值,放进FormData中
            for(var attr in data){
                var value = data[attr];
                if(isFile(value)){
                    formData.append(attr,value);
                }else if(typeof value === 'object'){
                    //针对{},[]类型,要使用Blob包装
                    formData.append(attr, new Blob([JSON.stringify(value)],{type:CONTENT_TYPE.JSON}));
                }else{
                    formData.append(attr,value);
                }
            }
        }
        return formData;
    }

    return {
        //对应所有get请求
        get: function(obj) {
            var config = defaultHandleConfig(obj);
            config.params = obj.params;
            BASE.send(config);
        },

        //对应spring mvc中的@RequestPart,支持文件上传
        postFormData: function(obj){
            var config = defaultHandleConfig(obj);
            config.method = METHOD.POST;
            config.headers = addHeader(obj.headers,CONTENT_TYPE.NAME,CONTENT_TYPE.FORM_DATA);
            config.data = handleData2FormData(obj.data);
            config.onUploadProgress = obj.onUploadProgress;
            BASE.send(config);
        },

        //对应spring mvc中的@RequestBody
        post: function(obj){
            var config = defaultHandleConfig(obj);
            config.method = METHOD.POST;
            config.headers = addHeader(obj.headers,CONTENT_TYPE.NAME,CONTENT_TYPE.JSON);
            config.data = handleData2Json(obj.data);
            BASE.send(config);

        },

        //和post一样,不过要保持幂等
        put: function(obj){
            var config = defaultHandleConfig(obj);
            config.method = METHOD.PUT
            config.headers = addHeader(obj.headers,CONTENT_TYPE.NAME,CONTENT_TYPE.JSON);
            config.data = handleData2Json(obj.data);
            BASE.send(config);
        },

        //删除
        delete: function(obj){
            var config = defaultHandleConfig(obj);
            config.method = METHOD.DELETE;
            config.params = obj.params;
            BASE.send(config);
        }
    };
}();

针对后台接口的测试页面






    hah


Hello World






调试过程中,为了避免跨域问题,利用了nginx的动静分离分离功能,小伙伴在代码下载后需要针对自己的环境修改

//小伙伴需要针对自己的环境修改URL_PREFIX
var URL_PREFIX = 'https://localhost';

以便能成功地测试。

以上代码是我对于前后端接口调试的一个简单理解,避免前后端开发在接口对接上反复地不必要的沟通。有不同理解的小伙伴欢迎留言,大家一起讨论。

你可能感兴趣的:(统一前后端接口调用规范)