导航
导读
- 再接上文,框架已经搭建好,本篇对接口对接进行详细说明,直进主题。
gitee代码地址
- https://gitee.com/zhaifengxi/zhai-docking-alipay-open
案例:支付宝图片上传
- 目前对接支付宝接口,问题比较多的就是支付宝图片上传接口,针对这个接口进行具体详细说明,如果这个接口搞定了,其他的都不是问题。
支付宝开放接口文档解读
一、图片上传文档地址
- https://opendocs.alipay.com/apis/01ea4t
二、公共请求参数
- 作为支付宝的公共参数,里面的参数基本都是固定的,把因为环境不同的参数提取到配置文件中,剩余可以定义为常量。
提炼字段关键信息:图片二进制字节流,最小50K,最大为5M,支持png/bmp/gif/jpg/jpeg格式
三、请求参数
- 具体业务请求,根据业务传参。
四、公共响应参数
- 作为支付宝的公共响应参数,被区分为网关和业务两种返回码,作为对接方不区分网关还是业务可以通过封装进行合并。
五、响应参数
- 具体业务响应结果,根据业务处理。
六、请求示例
- 非常好的示例,直接粘贴下来按要求传好参数即可调用,支付宝的SDK封装的非常奶思,很值得其他开放平台借鉴学习。
七、响应示例
- 接口调用之后的成功情况下的返回结果。
八、异常示例
- 接口调用之后的失败情况下的返回结果。这里需要特别注意是支付宝网关失败还是业务失败,如果是网关失败,业务失败没有值。
九、业务错误码
- 统一的业务错误码,不能完全描述清楚具体场景的问题,仅供参考,具体问题还得去找支付宝的技术支持沟通确认。
十、SDK对接文档
- 地址:https://opendocs.alipay.com/open/54/cyz7do
- 服务端对接两种方式:1.服务端 SDK(新版) 2.服务端 SDK(老版)
- 服务端 SDK(新版):1.链式编程 2.代码简洁 3.参考资料少 4.部分接口找不到
- 服务端 SDK(老版):1.相对新版代码臃肿 2.接口都可以找到,故选用此方式进行对接。
核心代码
一、支付宝基础服务层
@Slf4j
@Service
public class BaseAlipayApiImageSerImpl implements BaseAlipayApiImageSer {
@Autowired
AlipayApiProperties alipayApiProp;
@Autowired
BizLogSer bizLogSer;
@SneakyThrows
@Override
public BaseAlipayImageUploadOut upload(BaseAlipayImageUploadIn in) {
/** 操作:调用支付宝接口 */
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConstant.COMMON_SERVER_URL, alipayApiProp.getAppid(), alipayApiProp.getPrivateKey(), AlipayConstant.COMMON_FORMAT, AlipayConstant.COMMON_CHARSET, alipayApiProp.getPublicKey(), AlipayConstant.COMMON_SIGN_TYPE);
AlipayOpenSpImageUploadRequest request = new AlipayOpenSpImageUploadRequest();
request.setImageContent(new FileItem(in.getImageFile()));
AlipayOpenSpImageUploadResponse response = alipayClient.execute(request);
/** 操作:记录业务日志 */
bizLogSer.save(new BizLogSave(new BizLogDataTemp(in, response), AlipayConstant.IMAGE_UPLOAD_LOG));
/** 操作:结果转换 */
BaseAlipayImageUploadOut result = ObjectUtil.copy(response, BaseAlipayImageUploadOut.class);
BaseAlipayCommonOut commonOut = result.commonOut(response);
ObjectUtil.copy(commonOut, result);
return result;
}
- 通过Base接口层进行和具体业务的解耦。
- 通过封装,简化代码。
- 通过灵活配置,方便不同环境的切换。
- 配合Slf4j + mongodb关键日志存储,方便快速捕获定位线上问题。
BaseAlipayApiImageSerImpl.java 详情:https://gitee.com/zhaifengxi/zhai-docking-alipay-open/blob/master/src/main/java/zhai/docking/alipay/service/base/alipay/api/BaseAlipayApiImageSerImpl.java
二、业务层处理
- api层(controller层),也就是restful接口,对系统提供后端接口支持。api层对入参做基本校验,校验根据支付宝接口文档要求编写。
@RestController
@RequestMapping("/api/alipay/img")
public class AlipayImgCtr {
@Autowired
AlipayImgSer alipayImgSer;
@RequestMapping(path = "/upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity upload(@RequestParam(name = "file") MultipartFile file) {
/** 校验:是否为空 */
if (file.isEmpty()) {
return ResponseEntity.ok(Result.failure(ResultConstant.FAIL_SYS_NO_IN_ERROR_CODE, ResultConstant.FAIL_SYS_NO_IN_ERROR_MSG));
}
/** 校验:文件类型 */
String fileType = FileUtil.getFileType(FileUtil.getFileName(file));
if (StringUtil.isBlank(fileType)) {
return ResponseEntity.ok(Result.failure(ResultConstant.FAIL_SYS_DEF_CODE, "该文件没有文件类型"));
}
if (!FileUtil.isFileType(fileType, FileUtil.FILE_TYPE_JPG, FileUtil.FILE_TYPE_JPEG)) {
return ResponseEntity.ok(Result.failure(ResultConstant.FAIL_BIZ_DEF_CODE, StringUtil.join("不支持上传该文件格式:", fileType)));
}
/** 校验:文件大小 */
boolean minSize = FileUtil.checkMinSize(file.getSize(), 50, FileUtil.UNIT_KB);
if (!minSize) {
return ResponseEntity.ok(Result.failure(ResultConstant.FAIL_BIZ_DEF_CODE, "文件大小不能小于50KB"));
}
boolean maxSize = FileUtil.checkMaxSize(file.getSize(), 5, FileUtil.UNIT_MB);
if (!maxSize) {
return ResponseEntity.ok(Result.failure(ResultConstant.FAIL_BIZ_DEF_CODE, "文件大小不能大于5MB"));
}
Result result = alipayImgSer.upload(file);
return ResponseEntity.ok().body(result);
}
}
AlipayImgCtr.java 详情:https://gitee.com/zhaifengxi/zhai-docking-alipay-open/blob/master/src/main/java/zhai/docking/alipay/api/AlipayImgCtr.java
- service层提供业务服务支持。
@Service
public class AlipayImgSerImpl implements AlipayImgSer {
@Autowired
BaseAlipayApiImageSer baseAlipayApiImageSer;
@Autowired
SnowflakeUtil snowflakeUtil;
@SneakyThrows
@Override
public Result upload(MultipartFile in) {
/** 操作:重命名文件名 */
String imageName = StringUtil.join(snowflakeUtil.nextId(), StringConstant.POINT, FileUtil.getFileType(FileUtil.getFileName(in)));
File file = new File(imageName);
FileUtil.copyInputStreamToFile(in.getInputStream(), file);
BaseAlipayImageUploadOut upload = null;
try {
/** 操作:上送-支付宝图片接口 */
BaseAlipayImageUploadIn uploadIn = new BaseAlipayImageUploadIn();
uploadIn.setImageFile(file);
upload = baseAlipayApiImageSer.upload(uploadIn);
} finally {
if (file.exists()) {
file.delete(); // 删除临时文件
}
}
/** 结果:转换 */
if (!upload.isSuccess()) {
log.info(StringUtil.join(BUSINESS_NAME, LogConstant.SERVICE, METHOD_UPLOAD, LogConstant.RESULT, LogConstant.FAIL, upload.getCode(), upload.getMsg()));
return Result.failure(upload.getCode(), upload.getMsg());
}
AlipayImageUploadOut result = new AlipayImageUploadOut();
result.setImgCode(upload.getImageId());
return Result.success(result);
}
}
AlipayImgSerImpl.java 详情:https://gitee.com/zhaifengxi/zhai-docking-alipay-open/blob/master/src/main/java/zhai/docking/alipay/service/biz/AlipayImgSerImpl.java
- 以上核心代码就是完整的一套从后端接口到调用具体支付宝接口的核心代码。
避坑指南
- 上面的一整套代码已经躲避了支付宝开放接口的坑,下面讲解下这些坑。
一、未知的错误码:FILE_SIZE_MIN_LIMIT (文件大小低于范围,请检查文件类型的参数大小)
- 首先支付宝接口文档要求:图片二进制字节流,最小50K,最大为5M,支持png/bmp/gif/jpg/jpeg格式,代码也是完全按照要求编写的。
- 我们测试了1M的图片依然不行,然后联系了技术支持,问题依然没解决,最后上升的支付宝研发,期间花费了非常多的时间。
- 支付宝研发回复:50K-5M,JPEG格式,不会报错。
- 原来是接口文档给的格式除了jpg和jpeg两个格式以外,其他格式容易报该错误。所以做了用户图片上传类型的限制为 jpg和jpeg。关键代码如下:
if (!FileUtil.isFileType(fileType, FileUtil.FILE_TYPE_JPG, FileUtil.FILE_TYPE_JPEG)) {
return ResponseEntity.ok(Result.failure(ResultConstant.FAIL_BIZ_DEF_CODE, StringUtil.join("不支持上传该文件格式:", fileType)));
}
二、一大长串html报错
- 截选报错日志:
ERROR sdk.biz.err - 2021-01-19 14:34:04^_^<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>支付宝 - 网上支付 安全快速!</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<meta http-equiv="x-ua-compatible" content="ie=7" />
<meta name="description" content="中国最大的第三方电子支付服务提供商" />
...
<li>支付宝版权所有 2004-2021 ALIPAY.COM</li>
</ul>
<div id="ServerNum">openapi-49-14424</div>
</div>
<!--footer ending-->
- 支付宝研发回复:大概率就是网络问题。
- 最后发现原因是调用支付宝图片上传入参:“imageFile”:“210119143401000217bmp”
- 原因:例如图片名 2020.01.01.bmp,但是重命名文件名的时候变成210119143401000217bmp,少了个点,推送给支付宝就报一长串html。关键代码如下
/** 操作:重命名文件名 */
String imageName = StringUtil.join(snowflakeUtil.nextId(), StringConstant.POINT, FileUtil.getFileType(FileUtil.getFileName(in)));
支付宝报错特点:ERROR sdk.biz.err
三、文件名,目录名或卷标语法不正确
- 现象:上传文件名包含中文的图片就会报错
- 原因:MultipartFile 转 File 时文件名???,所以报如上错误。
/** MultipartFile 转 File */
File file = new File(imageName);
图片文件名:你好_12313.jpg
后端接收:???_12313.jpg
- 解决办法:重命名文件名,利用雪花算法生成不重复的随机ID。
/** 操作:重命名文件名 */
String imageName = StringUtil.join(snowflakeUtil.nextId(), StringConstant.POINT, FileUtil.getFileType(FileUtil.getFileName(in)));
四、The field file exceeds its maximum permitted size of 1048576 bytes.
- 具体报错
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field file exceeds its maximum permitted size of 1048576 bytes.
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:111)
at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:85)
at org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:79)
- 原因:上传的图片超过默认文件大小了。
- 解决办法:修改配置图片大小。
servlet: # 文件上传配置
multipart:
max-file-size: 10MB
max-request-size: 10MB
最后
- 本篇主要通过 “案例:支付宝图片上传”,来完整串联如何开发对接支付宝开放接口,通过对具体文档、核心代码、避坑指南来进行详细分析,希望能给你一些参考。下篇文章针对支付宝开放接口回调通知展开细致详细的描述。原创不易,希望大家多多支持。