在平时的前后端接口对接的过程中,前后端总是因为接口的实现方式不同需要花很长时间去对接,针对这种情况,我花了点时间把常用的几种调用方式总结成具体的代码,供大家参考,提高大家的工作效率。
后台代码采用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 extends Integer> 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';
以便能成功地测试。
以上代码是我对于前后端接口调试的一个简单理解,避免前后端开发在接口对接上反复地不必要的沟通。有不同理解的小伙伴欢迎留言,大家一起讨论。