diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java
index f1be843827..044086496d 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxError.java
@@ -2,6 +2,7 @@
import lombok.Builder;
import lombok.Data;
+import lombok.NoArgsConstructor;
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import org.apache.commons.lang3.StringUtils;
@@ -17,6 +18,7 @@
* @author Daniel Qian & Binary Wang
*/
@Data
+@NoArgsConstructor
@Builder
public class WxError implements Serializable {
private static final long serialVersionUID = 7869786563361406291L;
@@ -39,6 +41,11 @@ public class WxError implements Serializable {
private String json;
+ public WxError(int errorCode, String errorMsg) {
+ this.errorCode = errorCode;
+ this.errorMsg = errorMsg;
+ }
+
public static WxError fromJson(String json) {
return fromJson(json, null);
}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaPrivacyService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaPrivacyService.java
new file mode 100644
index 0000000000..4bf78f53bc
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaPrivacyService.java
@@ -0,0 +1,65 @@
+package me.chanjar.weixin.open.api;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.open.bean.ma.privacy.GetPrivacySettingResult;
+import me.chanjar.weixin.open.bean.ma.privacy.SetPrivacySetting;
+import me.chanjar.weixin.open.bean.ma.privacy.UploadPrivacyFileResult;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * 微信第三方平台 小程序用户隐私保护指引接口
+ * https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/privacy_config/set_privacy_setting.html
+ *
+ * @author 广州跨界
+ */
+public interface WxOpenMaPrivacyService {
+
+ /**
+ * 1 设置小程序用户隐私保护指引
+ */
+ String OPEN_SET_PRIVACY_SETTING = "https://api.weixin.qq.com/cgi-bin/component/setprivacysetting";
+
+ /**
+ * 2 查询小程序用户隐私保护指引
+ */
+ String OPEN_GET_PRIVACY_SETTING = "https://api.weixin.qq.com/cgi-bin/component/getprivacysetting";
+
+ /**
+ * 3 上传小程序用户隐私保护指引文件
+ */
+ String OPEN_UPLOAD_PRIVACY_FILE = "https://api.weixin.qq.com/cgi-bin/component/uploadprivacyextfile";
+
+
+ /**
+ * 查询小程序用户隐私保护指引
+ * 文档地址:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/privacy_config/get_privacy_setting.html
+ *
+ * @param privacyVer 1表示现网版本,即,传1则该接口返回的内容是现网版本的;2表示开发版,即,传2则该接口返回的内容是开发版本的。默认是2。
+ * @return 查询结果
+ * @throws WxErrorException 如果出错,抛出此异常
+ */
+ GetPrivacySettingResult getPrivacySetting(@Nullable Integer privacyVer) throws WxErrorException;
+
+
+ /**
+ * 设置小程序用户隐私保护指引
+ * 文档地址:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/privacy_config/set_privacy_setting.html
+ *
+ * @param dto 参数对象
+ * @throws WxErrorException 如果出错,抛出此异常
+ */
+ void setPrivacySetting(SetPrivacySetting dto) throws WxErrorException;
+
+
+ /**
+ * 上传小程序用户隐私保护指引文件
+ * 本接口用于上传自定义的小程序的用户隐私保护指引
+ * 仅限文本文件, 限制文件大小为不超过100kb,否则会报错
+ * 文档地址:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/privacy_config/upload_privacy_exfile.html
+ *
+ * @param content 文本文件内容
+ * @return 上传结果
+ * @throws WxErrorException 如果出错,抛出此异常
+ */
+ UploadPrivacyFileResult uploadPrivacyFile(String content) throws WxErrorException;
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java
index cbb4b23783..f907ff9be6 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMaService.java
@@ -632,6 +632,13 @@ WxOpenMaDomainResult modifyDomain(String action, List requestDomains, Li
*/
WxOpenMaBasicService getBasicService();
+ /**
+ * 小程序用户隐私保护指引服务
+ *
+ * @return 小程序用户隐私保护指引服务
+ */
+ WxOpenMaPrivacyService getPrivacyService();
+
/**
* 小程序审核 提审素材上传接口
*
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaPrivacyServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaPrivacyServiceImpl.java
new file mode 100644
index 0000000000..f7deb523c6
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaPrivacyServiceImpl.java
@@ -0,0 +1,55 @@
+package me.chanjar.weixin.open.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.open.api.WxOpenMaPrivacyService;
+import me.chanjar.weixin.open.bean.ma.privacy.GetPrivacySettingResult;
+import me.chanjar.weixin.open.bean.ma.privacy.SetPrivacySetting;
+import me.chanjar.weixin.open.bean.ma.privacy.UploadPrivacyFileResult;
+import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信第三方平台 小程序用户隐私保护指引接口
+ * https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/privacy_config/set_privacy_setting.html
+ *
+ * @author 广州跨界
+ */
+@AllArgsConstructor
+public class WxOpenMaPrivacyServiceImpl implements WxOpenMaPrivacyService {
+
+ private final WxMaService wxMaService;
+
+
+ @Override
+ public GetPrivacySettingResult getPrivacySetting(@Nullable Integer privacyVer) throws WxErrorException {
+ Map params = new HashMap<>();
+ if (privacyVer != null) {
+ params.put("privacy_ver", privacyVer);
+ }
+ String json = wxMaService.post(OPEN_GET_PRIVACY_SETTING, params);
+ return WxOpenGsonBuilder.create().fromJson(json, GetPrivacySettingResult.class);
+ }
+
+ @Override
+ public void setPrivacySetting(SetPrivacySetting dto) throws WxErrorException {
+ wxMaService.post(OPEN_SET_PRIVACY_SETTING, dto);
+ }
+
+ @SneakyThrows
+ @Override
+ public UploadPrivacyFileResult uploadPrivacyFile(String content) throws WxErrorException {
+ // TODO 应实现通过InputStream上传的功能,一下代码暂时无法正常运行
+// ByteArrayInputStream data = new ByteArrayInputStream(content.getBytes("GBK"));
+// GenericUploadRequestExecutor executor = new GenericUploadRequestExecutor(wxMaService.getRequestHttp(), "POST", "file", "/temp.txt");
+// String json = wxMaService.execute(executor, OPEN_UPLOAD_PRIVACY_FILE, data);
+// return WxOpenGsonBuilder.create().fromJson(json, UploadPrivacyFileResult.class);
+ throw new WxErrorException(new WxError(5003, "暂未实现用户隐私指引内容上传"));
+ }
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
index 4fb637b393..7188a669c2 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
@@ -15,6 +15,7 @@
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.open.api.WxOpenComponentService;
import me.chanjar.weixin.open.api.WxOpenMaBasicService;
+import me.chanjar.weixin.open.api.WxOpenMaPrivacyService;
import me.chanjar.weixin.open.api.WxOpenMaService;
import me.chanjar.weixin.open.bean.ma.WxMaQrcodeParam;
import me.chanjar.weixin.open.bean.ma.WxMaScheme;
@@ -42,12 +43,15 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
private final String appId;
@Getter
private final WxOpenMaBasicService basicService;
+ @Getter
+ private final WxOpenMaPrivacyService privacyService;
public WxOpenMaServiceImpl(WxOpenComponentService wxOpenComponentService, String appId, WxMaConfig wxMaConfig) {
this.wxOpenComponentService = wxOpenComponentService;
this.appId = appId;
this.wxMaConfig = wxMaConfig;
this.basicService = new WxOpenMaBasicServiceImpl(this);
+ this.privacyService = new WxOpenMaPrivacyServiceImpl(this);
initHttp();
}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/GetPrivacySettingResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/GetPrivacySettingResult.java
new file mode 100644
index 0000000000..a3b0a6ef1b
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/GetPrivacySettingResult.java
@@ -0,0 +1,118 @@
+package me.chanjar.weixin.open.bean.ma.privacy;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.open.bean.result.WxOpenResult;
+
+import java.util.List;
+
+/**
+ * 查询小程序用户隐私保护指引 响应
+ *
+ * @author 广州跨界
+ */
+@Getter
+@Setter
+public class GetPrivacySettingResult extends WxOpenResult {
+
+ /**
+ * 代码是否存在, 0 不存在, 1 存在 。如果最近没有通过commit接口上传代码,则会出现 code_exist=0的情况。
+ */
+ @SerializedName("code_exist")
+ private Integer codeExist;
+
+ /**
+ * 代码检测出来的用户信息类型(privacy_key)
+ */
+ @SerializedName("privacy_list")
+ private List privacyList;
+
+ /**
+ * 要收集的用户信息配置
+ */
+ @SerializedName("setting_list")
+ private List settingList;
+
+ /**
+ * 更新时间
+ */
+ @SerializedName("update_time")
+ private Long updateTime;
+
+ /**
+ * 收集方(开发者)信息配置
+ */
+ @SerializedName("owner_setting")
+ private PrivacyOwnerSetting ownerSetting;
+
+ /**
+ * 收集方(开发者)信息配置
+ */
+ @SerializedName("privacy_desc")
+ private PrivacyDesc privacyDesc;
+
+
+ @Data
+ public static class Setting {
+
+ /**
+ * 官方的可选值参考下方说明;该字段也支持自定义
+ *
+ * @see PrivacyKeyEnum
+ * @see PrivacyKeyEnum#getKey()
+ */
+ @SerializedName("privacy_key")
+ private String privacyKey;
+
+ /**
+ * 请填写收集该信息的用途。例如privacy_key=Location(位置信息),那么privacy_text则填写收集位置信息的用途。
+ * 无需再带上“为了”或者“用于”这些字眼,小程序端的显示格式是为了xxx,因此开发者只需要直接填写用途即可。
+ */
+ @SerializedName("privacy_text")
+ private String privacyText;
+
+ /**
+ * 用户信息类型的中文名称
+ *
+ * @see PrivacyKeyEnum#getDesc() ()
+ */
+ @SerializedName("privacy_label")
+ private String privacyLabel;
+ }
+
+
+ @Data
+ public static class PrivacyDesc {
+
+ /**
+ * 用户信息类型
+ */
+ @SerializedName("privacy_desc_list")
+ private List privacyDescList;
+ }
+
+ @Data
+ public static class PrivacyDescItem {
+
+ /**
+ * 用户信息类型的英文key
+ *
+ * @see PrivacyKeyEnum
+ * @see PrivacyKeyEnum#getKey()
+ */
+ @SerializedName("privacy_key")
+ private String privacyKey;
+
+ /**
+ * 用户信息类型的中文描述
+ *
+ * @see PrivacyKeyEnum#getDesc()
+ */
+ @SerializedName("privacy_desc")
+ private String privacyDesc;
+ }
+
+
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/PrivacyKeyEnum.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/PrivacyKeyEnum.java
new file mode 100644
index 0000000000..c3d6af281a
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/PrivacyKeyEnum.java
@@ -0,0 +1,62 @@
+package me.chanjar.weixin.open.bean.ma.privacy;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 隐私key枚举
+ *
+ * @author 广州跨界
+ */
+@Getter
+@AllArgsConstructor
+public enum PrivacyKeyEnum {
+
+ USER_INFO("UserInfo", "用户信息(微信昵称、头像)"),
+
+ LOCATION("Location", "位置信息"),
+
+ ADDRESS("Address", "地址"),
+
+ INVOICE("Invoice", "发票信息"),
+
+ RUN_DATA("RunData", "微信运动数据"),
+
+ RECORD("Record", "麦克风"),
+
+ ALBUM("Album", "选中的照片或视频信息"),
+
+ CAMERA("Camera", "摄像头"),
+
+ PHONE_NUMBER("PhoneNumber", "手机号码"),
+
+ CONTACT("Contact", "通讯录(仅写入)权限"),
+
+ DEVICE_INFO("DeviceInfo", "设备信息"),
+
+ EXID_NUMBER("EXIDNumber", "身份证号码"),
+
+ EX_ORDER_INFO("EXOrderInfo", "订单信息"),
+
+ EX_USER_PUBLISH_CONTENT("EXUserPublishContent", "发布内容"),
+
+ EX_USER_FOLLOW_ACCT("EXUserFollowAcct", "所关注账号"),
+
+ EX_USER_OP_LOG("EXUserOpLog", "操作日志"),
+
+ ALBUM_WRITE_ONLY("AlbumWriteOnly", "相册(仅写入)权限"),
+
+ LICENSE_PLATE("LicensePlate", "车牌号"),
+
+ BLUE_TOOTH("BlueTooth", "蓝牙"),
+
+ CALENDAR_WRITE_ONLY("CalendarWriteOnly", "日历(仅写入)权限"),
+
+ EMAIL("Email", "邮箱"),
+
+ MESSAGE_FILE("MessageFile", "选中的文件"),
+ ;
+
+ private final String key;
+ private final String desc;
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/PrivacyOwnerSetting.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/PrivacyOwnerSetting.java
new file mode 100644
index 0000000000..a52d0588a9
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/PrivacyOwnerSetting.java
@@ -0,0 +1,65 @@
+package me.chanjar.weixin.open.bean.ma.privacy;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * 小程序用户隐私保护指引 收集方(开发者)信息配置
+ *
+ * @author 广州跨界
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PrivacyOwnerSetting {
+
+ /**
+ * 信息收集方(开发者)的邮箱地址,4种联系方式至少要填一种
+ */
+ @SerializedName("contact_email")
+ private String contactEmail;
+
+ /**
+ * 信息收集方(开发者)的手机号,4种联系方式至少要填一种
+ */
+ @SerializedName("contact_phone")
+ private String contactPhone;
+
+ /**
+ * 信息收集方(开发者)的qq号,4种联系方式至少要填一种
+ */
+ @SerializedName("contact_qq")
+ private String contactQq;
+
+ /**
+ * 信息收集方(开发者)的微信号,4种联系方式至少要填一种
+ */
+ @SerializedName("contact_weixin")
+ private String contactWeixin;
+
+ /**
+ * 如果开发者不使用微信提供的标准化用户隐私保护指引模板,也可以上传自定义的用户隐私保护指引,通过上传接口上传后可获取media_id
+ */
+ @SerializedName("ext_file_media_id")
+ private String extFileMediaId;
+
+ /**
+ * 通知方式,指的是当开发者收集信息有变动时,通过该方式通知用户。这里服务商需要按照实际情况填写,例如通过弹窗或者公告或者其他方式。
+ */
+ @NotNull
+ @SerializedName("notice_method")
+ private String noticeMethod;
+
+ /**
+ * 存储期限,指的是开发者收集用户信息存储多久。如果不填则展示为【开发者承诺,除法律法规另有规定,开发者对你的信息保存期限应当为实现处理目的所必要的最短时间】,
+ * 如果填请填数字+天,例如“30天”,否则会出现87072的报错。
+ */
+ @SerializedName("store_expire_timestamp")
+ private String storeExpireTimestamp;
+
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/SetPrivacySetting.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/SetPrivacySetting.java
new file mode 100644
index 0000000000..d74b86d45a
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/SetPrivacySetting.java
@@ -0,0 +1,68 @@
+package me.chanjar.weixin.open.bean.ma.privacy;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * 设置小程序用户隐私保护指引参数
+ *
+ * @author 广州跨界
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SetPrivacySetting {
+
+ /**
+ * 用户隐私保护指引的版本,1表示现网版本;2表示开发版。默认是2开发版。
+ */
+ @SerializedName("privacy_ver")
+ private Integer privacyVer;
+
+ /**
+ * 收集方(开发者)信息配置
+ */
+ @NotNull
+ @SerializedName("owner_setting")
+ private PrivacyOwnerSetting ownerSetting;
+
+ /**
+ * 要收集的用户信息配置
+ */
+ @NotNull
+ @SerializedName("setting_list")
+ private List settingList;
+
+
+ @Data
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class Setting {
+
+ /**
+ * 官方的可选值参考下方说明;该字段也支持自定义
+ *
+ * @see PrivacyKeyEnum
+ * @see PrivacyKeyEnum#getKey()
+ */
+ @NotNull
+ @SerializedName("privacy_key")
+ private String privacyKey;
+
+ /**
+ * 请填写收集该信息的用途。例如privacy_key=Location(位置信息),那么privacy_text则填写收集位置信息的用途。
+ * 无需再带上“为了”或者“用于”这些字眼,小程序端的显示格式是为了xxx,因此开发者只需要直接填写用途即可。
+ */
+ @NotNull
+ @SerializedName("privacy_text")
+ private String privacyText;
+ }
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/UploadPrivacyFileResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/UploadPrivacyFileResult.java
new file mode 100644
index 0000000000..d2fcfecc50
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/ma/privacy/UploadPrivacyFileResult.java
@@ -0,0 +1,22 @@
+package me.chanjar.weixin.open.bean.ma.privacy;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Getter;
+import lombok.Setter;
+import me.chanjar.weixin.open.bean.result.WxOpenResult;
+
+/**
+ * 上传小程序用户隐私保护指引文件 响应
+ *
+ * @author 广州跨界
+ */
+@Getter
+@Setter
+public class UploadPrivacyFileResult extends WxOpenResult {
+
+ /**
+ * 文件的media_id
+ */
+ @SerializedName("ext_file_media_id")
+ private String extFileMediaId;
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/GenericUploadRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/GenericUploadRequestExecutor.java
new file mode 100644
index 0000000000..3ef3fd2e1e
--- /dev/null
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/GenericUploadRequestExecutor.java
@@ -0,0 +1,184 @@
+package me.chanjar.weixin.open.executor;
+
+import jodd.http.HttpConnectionProvider;
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
+import lombok.Data;
+import me.chanjar.weixin.common.enums.WxType;
+import me.chanjar.weixin.common.error.WxError;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.http.RequestExecutor;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.ResponseHandler;
+import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler;
+import okhttp3.*;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.*;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.HttpMultipartMode;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+
+/**
+ * 通用的上传请求执行器
+ */
+public class GenericUploadRequestExecutor implements RequestExecutor {
+
+ private final Executor, ?> executor;
+
+ /**
+ * 构造通用执行器
+ *
+ * @param requestHttp http请求
+ * @param httpMethod http方法(POST PUT PATCH)
+ * @param paramName 参数名
+ * @param fileName 文件名
+ */
+ @SuppressWarnings("all")
+ public GenericUploadRequestExecutor(RequestHttp, ?> requestHttp, String httpMethod, String paramName, String fileName) {
+ switch (requestHttp.getRequestType()) {
+ case APACHE_HTTP:
+ executor = new ApacheExecutor();
+ break;
+ case OK_HTTP:
+ executor = new OkExecutor();
+ break;
+ case JODD_HTTP:
+ executor = new JoddExecutor();
+ break;
+ default:
+ throw new UnsupportedOperationException("使用了暂不支持的HTTP客户端:" + requestHttp.getRequestType());
+ }
+ executor.setRequestHttp((RequestHttp) requestHttp);
+ executor.setHttpMethod(httpMethod);
+ executor.setParamName(paramName);
+ executor.setFileName(fileName);
+ }
+
+ @Override
+ public String execute(String uri, InputStream data, WxType wxType) throws WxErrorException, IOException {
+ String json = executor.execute(uri, data, wxType);
+ WxError error = WxError.fromJson(json, wxType);
+ if (error.getErrorCode() != 0) {
+ throw new WxErrorException(error);
+ }
+ return json;
+ }
+
+ @Override
+ public void execute(String uri, InputStream data, ResponseHandler handler, WxType wxType) throws WxErrorException, IOException {
+ handler.handle(this.execute(uri, data, wxType));
+ }
+
+ /**
+ * 内部请求执行器
+ *
+ * @param http客户端
+ * @param http代理
+ */
+ @Data
+ public static abstract class Executor {
+
+ private RequestHttp requestHttp;
+ private String httpMethod;
+ private String paramName;
+ private String fileName;
+
+ public abstract String execute(String uri, InputStream data, WxType wxType) throws WxErrorException, IOException;
+ }
+
+ /**
+ * 阿帕奇执行器
+ */
+ public static class ApacheExecutor extends Executor {
+
+ @Override
+ public String execute(String uri, InputStream data, WxType wxType) throws WxErrorException, IOException {
+ HttpEntityEnclosingRequestBase bodyRequest;
+ switch (getHttpMethod()) {
+ case "POST":
+ bodyRequest = new HttpPost(uri);
+ break;
+ case "PUT":
+ bodyRequest = new HttpPut(uri);
+ break;
+ case "PATCH":
+ bodyRequest = new HttpPatch(uri);
+ break;
+ default:
+ throw new IllegalAccessError("不支持的请求方式:" + getHttpMethod());
+ }
+ if (getRequestHttp().getRequestHttpProxy() != null) {
+ RequestConfig config = RequestConfig.custom().setProxy(getRequestHttp().getRequestHttpProxy()).build();
+ bodyRequest.setConfig(config);
+ }
+
+ HttpEntity entity = MultipartEntityBuilder
+ .create()
+ .addBinaryBody(getParamName(), data, ContentType.create("multipart/form-data", StandardCharsets.UTF_8), getFileName())
+ .setMode(HttpMultipartMode.RFC6532)
+ .build();
+ bodyRequest.setEntity(entity);
+ bodyRequest.setHeader("Content-Type", ContentType.MULTIPART_FORM_DATA.toString());
+
+ try (CloseableHttpResponse response = getRequestHttp().getRequestHttpClient().execute(bodyRequest)) {
+ return Utf8ResponseHandler.INSTANCE.handleResponse(response);
+ } finally {
+ bodyRequest.releaseConnection();
+ }
+ }
+ }
+
+ /**
+ * ok执行器
+ */
+ public static class OkExecutor extends Executor {
+
+ @Override
+ public String execute(String uri, InputStream data, WxType wxType) throws WxErrorException, IOException {
+ OkHttpClient client = getRequestHttp().getRequestHttpClient();
+
+ byte[] bytes = data instanceof ByteArrayInputStream ? ((ByteArrayInputStream) data).readAllBytes() : IOUtils.toByteArray(data);
+ RequestBody body = new MultipartBody.Builder()
+ .setType(Objects.requireNonNull(MediaType.parse("multipart/form-data")))
+ .addFormDataPart("media", getFileName(), RequestBody.create(bytes, MediaType.parse("application/octet-stream")))
+ .build();
+
+ Request request = new Request.Builder().url(uri).method(getHttpMethod(), body).build();
+ Response response = client.newCall(request).execute();
+ return response.body().string();
+ }
+ }
+
+ /**
+ * jodd执行器
+ */
+ public static class JoddExecutor extends Executor {
+
+ @Override
+ public String execute(String uri, InputStream data, WxType wxType) throws WxErrorException, IOException {
+ HttpRequest request = HttpRequest.post(uri);
+ if (getRequestHttp().getRequestHttpProxy() != null) {
+ getRequestHttp().getRequestHttpClient().useProxy(getRequestHttp().getRequestHttpProxy());
+ }
+ request.withConnectionProvider(getRequestHttp().getRequestHttpClient());
+
+ byte[] bytes = data instanceof ByteArrayInputStream ? ((ByteArrayInputStream) data).readAllBytes() : IOUtils.toByteArray(data);
+ request.form(getParamName(), data);
+
+ HttpResponse response = request.send();
+ response.charset(StandardCharsets.UTF_8.name());
+ return response.bodyText();
+ }
+ }
+}