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(); + } + } +}