From 5755c293dfea25f16b30b8afb145aaaa4cf08f3a Mon Sep 17 00:00:00 2001 From: dragon Date: Sat, 23 Oct 2021 23:23:18 +0800 Subject: [PATCH] =?UTF-8?q?:new:=20#2356=20=E3=80=90=E5=85=AC=E4=BC=97?= =?UTF-8?q?=E5=8F=B7=E3=80=91=E6=96=B0=E5=A2=9E=E8=8D=89=E7=A8=BF=E7=AE=B1?= =?UTF-8?q?=E5=92=8C=E5=8F=91=E5=B8=83=E7=9B=B8=E5=85=B3=E7=9A=84=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weixin/mp/api/WxMpDraftService.java | 127 ++++++++++++++++++ .../weixin/mp/api/WxMpFreePublishService.java | 113 ++++++++++++++++ .../me/chanjar/weixin/mp/api/WxMpService.java | 28 ++++ .../mp/api/impl/BaseWxMpServiceImpl.java | 25 +++- .../mp/api/impl/WxMpDraftServiceImpl.java | 84 ++++++++++++ .../api/impl/WxMpFreePublishServiceImpl.java | 72 ++++++++++ .../weixin/mp/bean/draft/WxMpAddDraft.java | 44 ++++++ .../mp/bean/draft/WxMpDraftArticles.java | 81 +++++++++++ .../weixin/mp/bean/draft/WxMpDraftInfo.java | 44 ++++++ .../weixin/mp/bean/draft/WxMpDraftItem.java | 33 +++++ .../weixin/mp/bean/draft/WxMpDraftList.java | 41 ++++++ .../weixin/mp/bean/draft/WxMpUpdateDraft.java | 55 ++++++++ .../freepublish/WxMpFreePublishArticles.java | 85 ++++++++++++ .../bean/freepublish/WxMpFreePublishInfo.java | 44 ++++++ .../bean/freepublish/WxMpFreePublishItem.java | 35 +++++ .../bean/freepublish/WxMpFreePublishList.java | 41 ++++++ .../freepublish/WxMpFreePublishStatus.java | 56 ++++++++ .../chanjar/weixin/mp/enums/WxMpApiUrl.java | 89 +++++++++++- .../mp/api/impl/WxMpDraftServiceImplTest.java | 127 ++++++++++++++++++ .../impl/WxMpFreePublishServiceImplTest.java | 109 +++++++++++++++ 20 files changed, 1330 insertions(+), 3 deletions(-) create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDraftService.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpFreePublishService.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImpl.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpFreePublishServiceImpl.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpAddDraft.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftArticles.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftInfo.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftItem.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftList.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpUpdateDraft.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishArticles.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishInfo.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishItem.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishList.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishStatus.java create mode 100644 weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImplTest.java create mode 100644 weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpFreePublishServiceImplTest.java diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDraftService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDraftService.java new file mode 100644 index 000000000..3e38410d5 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDraftService.java @@ -0,0 +1,127 @@ +package me.chanjar.weixin.mp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.draft.WxMpAddDraft; +import me.chanjar.weixin.mp.bean.draft.WxMpDraftInfo; +import me.chanjar.weixin.mp.bean.draft.WxMpDraftList; +import me.chanjar.weixin.mp.bean.draft.WxMpUpdateDraft; + +/** + * 微信 草稿箱 接口. + * + * @author dragon + * @date 2021-10-22 + */ +public interface WxMpDraftService { + + /** + * 新建草稿 - 只有默认必填参数 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/draft/add?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Add_draft.html
+   * 
+ * + * @param title 标题 + * @param content 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS,涉及图片url必须来源 "上传图文消息内的图片获取URL"接口获取。外部图片url将被过滤。 + * @param thumbMediaId 图文消息的封面图片素材id(必须是永久MediaID) + * @throws WxErrorException . + */ + String addDraft(String title, String content, String thumbMediaId) throws WxErrorException; + + /** + * 新建草稿 - 完整参数 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/draft/add?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Add_draft.html
+   * 
+ * + * @param addDraft 新建草稿信息 + * @throws WxErrorException . + */ + String addDraft(WxMpAddDraft addDraft) throws WxErrorException; + + /** + * 修改草稿 - 完整参数 + * 正常情况下调用成功时,errcode将为0。错误时微信会返回错误码等信息,请根据错误码查询错误信息 + *
+   * 请求地址: POST https://api.weixin.qq.com/cgi-bin/draft/update?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Update_draft.html
+   * 
+ * + * @param updateDraftInfo 修改草稿信息 + * @throws WxErrorException . + */ + Boolean updateDraft(WxMpUpdateDraft updateDraftInfo) throws WxErrorException; + + /** + * 获取草稿信息 + * + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/draft/get?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Get_draft.html
+   * 
+ * + * @param mediaId 要获取的草稿的media_id + * @return 草稿信息 + * @throws WxErrorException . + */ + WxMpDraftInfo getDraft(String mediaId) throws WxErrorException; + + /** + * 删除草稿 + * 正常情况下调用成功时,errcode将为0。错误时微信会返回错误码等信息,请根据错误码查询错误信息。 + * 多次删除同一篇草稿,也返回 0. + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/draft/delete?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Delete_draft.html
+   * 
+ * + * @param mediaId 要删除的草稿的media_id + * @throws WxErrorException . + */ + Boolean delDraft(String mediaId) throws WxErrorException; + + /** + * 获取草稿列表 + * + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Get_draft_list.html
+   * 
+ * + * @param offset 分页页数,从0开始 从全部素材的该偏移位置开始返回,0表示从第一个素材返回 + * @param count 每页数量 返回素材的数量,取值在1到20之间 + * @param noContent 1 表示不返回 content 字段,0 表示正常返回,默认为 0 + * @return 草稿信息列表 + * @throws WxErrorException . + */ + WxMpDraftList listDraft(int offset, int count, int noContent) throws WxErrorException; + + /** + * 获取草稿列表 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Get_draft_list.html
+   * 
+ * + * @param offset 分页页数,从0开始 从全部素材的该偏移位置开始返回,0表示从第一个素材返回 + * @param count 每页数量 返回素材的数量,取值在1到20之间 + * @return + * @throws WxErrorException + */ + WxMpDraftList listDraft(int offset, int count) throws WxErrorException; + + /** + * 获取草稿数量 + * 开发者可以根据本接口来获取草稿的总数。此接口只统计数量,不返回草稿的具体内容。 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/draft/count?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Count_drafts.html
+   * 
+ * + * @return 草稿的总数 + * @throws WxErrorException . + */ + Long countDraft() throws WxErrorException; + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpFreePublishService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpFreePublishService.java new file mode 100644 index 000000000..c69594279 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpFreePublishService.java @@ -0,0 +1,113 @@ +package me.chanjar.weixin.mp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishInfo; +import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishList; +import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishStatus; + +/** + * 微信 发布能力 接口. + * + * @author dragon + * @date 2021-10-23 + */ +public interface WxMpFreePublishService { + + /** + * 发布接口 - 只有默认必填参数 + * 开发者需要先将图文素材以草稿的形式保存(见“草稿箱/新建草稿”,如需从已保存的草稿中选择,见“草稿箱/获取草稿列表”),选择要发布的草稿 media_id 进行发布 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Publish/Publish.html
+   * 
+ * + * @param mediaId 要发布的草稿的media_id + * @throws WxErrorException . + */ + String submit(String mediaId) throws WxErrorException; + + /** + * 发布状态轮询接口 + * 开发者可以尝试通过下面的发布状态轮询接口获知发布情况。 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/freepublish/get?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Publish/Get_status.html
+   * 
+ * + * @param publishId 发布任务id + * @throws WxErrorException . + */ + WxMpFreePublishStatus getPushStatus(String publishId) throws WxErrorException; + + /** + * 删除发布 + * 发布成功之后,随时可以通过该接口删除。此操作不可逆,请谨慎操作。 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/freepublish/delete?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Publish/Delete_posts.html
+   * 
+ * + * @param articleId 成功发布时返回的 article_id + * @param index 要删除的文章在图文消息中的位置,第一篇编号为1,该字段不填或填0会删除全部文章 + * @throws WxErrorException . + */ + Boolean deletePush(String articleId, Integer index) throws WxErrorException; + + /** + * 删除发布 - 此条发布的所有内容,不指定文章编号 + * 发布成功之后,随时可以通过该接口删除。此操作不可逆,请谨慎操作。 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/freepublish/delete?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Publish/Delete_posts.html
+   * 
+ * + * @param articleId 成功发布时返回的 article_id + * @throws WxErrorException . + */ + Boolean deletePushAllArticle(String articleId) throws WxErrorException; + + /** + * 通过 article_id 获取已发布文章 + * 开发者可以通过 article_id 获取已发布的图文信息。 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/freepublish/getarticle?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Publish/Get_article_from_id.html
+   * 
+ * + * @param articleId 要获取的草稿的article_id + * @return 已发布文章信息 + * @throws WxErrorException . + */ + WxMpFreePublishInfo getArticleFromId(String articleId) throws WxErrorException; + + /** + * 获取成功发布列表 - 支持选择是否返回:图文消息的具体内容 + * + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Publish/Get_publication_records.html
+   * 
+ * + * @param offset 分页页数,从0开始 从全部素材的该偏移位置开始返回,0表示从第一个素材返回 + * @param count 每页数量 返回素材的数量,取值在1到20之间 + * @param noContent 1 表示不返回 content 字段,0 表示正常返回,默认为 0 + * @return 草稿信息列表 + * @throws WxErrorException . + */ + WxMpFreePublishList getPublicationRecords(int offset, int count, int noContent) throws WxErrorException; + + /** + * 获取成功发布列表 - 默认返回 图文消息的具体内容 + *
+   * 请求地址:POST https://api.weixin.qq.com/cgi-bin/draft/batchget?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Publish/Get_publication_records.html
+   * 
+ * + * @param offset 分页页数,从0开始 从全部素材的该偏移位置开始返回,0表示从第一个素材返回 + * @param count 每页数量 返回素材的数量,取值在1到20之间 + * @return + * @throws WxErrorException + */ + WxMpFreePublishList getPublicationRecords(int offset, int count) throws WxErrorException; + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java index 6b6e30b7f..fbe9e2d43 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java @@ -567,6 +567,20 @@ public interface WxMpService extends WxService { */ WxMpReimburseInvoiceService getReimburseInvoiceService(); + /** + * 返回草稿箱相关接口 + * + * @return WxMpDraftService + */ + WxMpDraftService getDraftService(); + + /** + * 返回发布能力接口 + * + * @return WxMpFreePublishService + */ + WxMpFreePublishService getFreePublishService(); + /** * . * @@ -818,4 +832,18 @@ public interface WxMpService extends WxService { * @param merchantInvoiceService the merchant invoice service */ void setMerchantInvoiceService(WxMpMerchantInvoiceService merchantInvoiceService); + + /** + * Sets draft service. + * + * @param draftService the draft service + */ + void setDraftService(WxMpDraftService draftService); + + /** + * Sets free publish service. + * + * @param freePublishService the free publish service + */ + void setFreePublishService(WxMpFreePublishService freePublishService); } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java index 2b8d9bfd3..d11499bd4 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java @@ -25,7 +25,11 @@ import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.DataUtils; import me.chanjar.weixin.common.util.RandomUtils; import me.chanjar.weixin.common.util.crypto.SHA1; -import me.chanjar.weixin.common.util.http.*; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; +import me.chanjar.weixin.common.util.http.URIUtil; import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.common.util.json.WxGsonBuilder; import me.chanjar.weixin.mp.api.*; @@ -42,7 +46,16 @@ import java.io.IOException; import java.util.Map; import java.util.concurrent.locks.Lock; -import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.*; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.CLEAR_QUOTA_URL; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.FETCH_SHORTEN_URL; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.GEN_SHORTEN_URL; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.GET_CALLBACK_IP_URL; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.GET_CURRENT_AUTOREPLY_INFO_URL; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.GET_TICKET_URL; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.NETCHECK_URL; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.QRCONNECT_URL; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.SEMANTIC_SEMPROXY_SEARCH_URL; +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.SHORTURL_API_URL; /** * 基础实现类. @@ -146,6 +159,14 @@ public abstract class BaseWxMpServiceImpl implements WxMpService, RequestH @Setter private WxMpReimburseInvoiceService reimburseInvoiceService = new WxMpReimburseInvoiceServiceImpl(this); + @Getter + @Setter + private WxMpDraftService draftService = new WxMpDraftServiceImpl(this); + + @Getter + @Setter + private WxMpFreePublishService freePublishService = new WxMpFreePublishServiceImpl(this); + private Map configStorageMap; private int retrySleepMillis = 1000; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImpl.java new file mode 100644 index 000000000..96ff9f70b --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImpl.java @@ -0,0 +1,84 @@ +package me.chanjar.weixin.mp.api.impl; + +import lombok.AllArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.mp.api.WxMpDraftService; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.draft.WxMpAddDraft; +import me.chanjar.weixin.mp.bean.draft.WxMpDraftArticles; +import me.chanjar.weixin.mp.bean.draft.WxMpDraftInfo; +import me.chanjar.weixin.mp.bean.draft.WxMpDraftList; +import me.chanjar.weixin.mp.bean.draft.WxMpUpdateDraft; +import me.chanjar.weixin.mp.enums.WxMpApiUrl; + +import java.util.ArrayList; +import java.util.List; + +/** + * 草稿箱能力-service实现类. + * + * @author dragon + * @date 2021-10-22 + */ +@AllArgsConstructor +public class WxMpDraftServiceImpl implements WxMpDraftService { + + private static final String MEDIA_ID = "media_id"; + private static final String ERRCODE_SUCCESS = "0"; + private static final String ERRCODE = "errcode"; + private final WxMpService mpService; + + @Override + public String addDraft(String title, String content, String thumbMediaId) throws WxErrorException { + List draftArticleList = new ArrayList<>(); + WxMpDraftArticles draftArticle = WxMpDraftArticles.builder() + .title(title).content(content).thumbMediaId(thumbMediaId).build(); + WxMpAddDraft addDraft = WxMpAddDraft.builder().articles(draftArticleList).build(); + draftArticleList.add(draftArticle); + return addDraft(addDraft); + } + + @Override + public String addDraft(WxMpAddDraft addDraft) throws WxErrorException { + String json = this.mpService.post(WxMpApiUrl.Draft.ADD_DRAFT, addDraft); + return GsonParser.parse(json).get(MEDIA_ID).toString(); + } + + @Override + public Boolean updateDraft(WxMpUpdateDraft updateDraftInfo) throws WxErrorException { + String json = this.mpService.post(WxMpApiUrl.Draft.UPDATE_DRAFT, updateDraftInfo); + return GsonParser.parse(json).get(ERRCODE).toString().equals(ERRCODE_SUCCESS); + } + + @Override + public WxMpDraftInfo getDraft(String mediaId) throws WxErrorException { + return WxMpDraftInfo.fromJson(this.mpService.post(WxMpApiUrl.Draft.GET_DRAFT, + GsonHelper.buildJsonObject(MEDIA_ID, mediaId))); + } + + @Override + public Boolean delDraft(String mediaId) throws WxErrorException { + String json = this.mpService.post(WxMpApiUrl.Draft.DEL_DRAFT, + GsonHelper.buildJsonObject(MEDIA_ID, mediaId)); + return GsonParser.parse(json).get(ERRCODE).toString().equals(ERRCODE_SUCCESS); + } + + @Override + public WxMpDraftList listDraft(int offset, int count, int noContent) throws WxErrorException { + return WxMpDraftList.fromJson(this.mpService.post(WxMpApiUrl.Draft.LIST_DRAFT, + GsonHelper.buildJsonObject("offset", offset, "count", count, "no_content", noContent))); + } + + @Override + public WxMpDraftList listDraft(int offset, int count) throws WxErrorException { + return listDraft(offset, count, 0); + } + + @Override + public Long countDraft() throws WxErrorException { + String json = this.mpService.get(WxMpApiUrl.Draft.COUNT_DRAFT, null); + return Long.valueOf(GsonParser.parse(json).get("total_count").toString()); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpFreePublishServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpFreePublishServiceImpl.java new file mode 100644 index 000000000..f8f9b3684 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpFreePublishServiceImpl.java @@ -0,0 +1,72 @@ +package me.chanjar.weixin.mp.api.impl; + +import lombok.AllArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.mp.api.WxMpFreePublishService; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishInfo; +import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishList; +import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishStatus; +import me.chanjar.weixin.mp.enums.WxMpApiUrl; + +/** + * 发布能力-service实现类. + * + * @author dragon + * @date 2021-10-23 + */ +@AllArgsConstructor +public class WxMpFreePublishServiceImpl implements WxMpFreePublishService { + + private static final String MEDIA_ID = "media_id"; + private static final String PUBLISH_ID = "publish_id"; + private static final String ARTICLE_ID = "article_id"; + private static final String ERRCODE_SUCCESS = "0"; + private static final String ERRCODE = "errcode"; + private final WxMpService mpService; + + @Override + public String submit(String mediaId) throws WxErrorException { + String json = this.mpService.post(WxMpApiUrl.FreePublish.SUBMIT, + GsonHelper.buildJsonObject(MEDIA_ID, mediaId)); + return GsonParser.parse(json).get(PUBLISH_ID).toString(); + } + + @Override + public WxMpFreePublishStatus getPushStatus(String publishId) throws WxErrorException { + return WxMpFreePublishStatus.fromJson(this.mpService.post(WxMpApiUrl.FreePublish.GET_PUSH_STATUS, + GsonHelper.buildJsonObject(PUBLISH_ID, publishId))); + } + + @Override + public Boolean deletePush(String articleId, Integer index) throws WxErrorException { + String json = this.mpService.post(WxMpApiUrl.FreePublish.DEL_PUSH, + GsonHelper.buildJsonObject(ARTICLE_ID, articleId, "index", index)); + return GsonParser.parse(json).get(ERRCODE).toString().equals(ERRCODE_SUCCESS); + } + + @Override + public Boolean deletePushAllArticle(String articleId) throws WxErrorException { + // index字段不填或填0会删除全部文章 + return deletePush(articleId, 0); + } + + @Override + public WxMpFreePublishInfo getArticleFromId(String articleId) throws WxErrorException { + return WxMpFreePublishInfo.fromJson(this.mpService.post(WxMpApiUrl.FreePublish.GET_ARTICLE, + GsonHelper.buildJsonObject(ARTICLE_ID, articleId))); + } + + @Override + public WxMpFreePublishList getPublicationRecords(int offset, int count, int noContent) throws WxErrorException { + return WxMpFreePublishList.fromJson(this.mpService.post(WxMpApiUrl.FreePublish.BATCH_GET, + GsonHelper.buildJsonObject("offset", offset, "count", count, "no_content", noContent))); + } + + @Override + public WxMpFreePublishList getPublicationRecords(int offset, int count) throws WxErrorException { + return getPublicationRecords(offset, count, 0); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpAddDraft.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpAddDraft.java new file mode 100644 index 000000000..400b228c0 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpAddDraft.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.mp.bean.draft; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import me.chanjar.weixin.common.bean.ToJson; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 草稿箱能力-新建草稿. + * + * @author dragon + * @date 2021-10-22 + */ +@Data +@Builder +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class WxMpAddDraft implements ToJson, Serializable { + private static final long serialVersionUID = 2481699972367293721L; + + /** + * 图文素材列表 + */ + @SerializedName("articles") + private List articles; + + public static WxMpAddDraft fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpAddDraft.class); + } + + @Override + public String toJson() { + return WxGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftArticles.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftArticles.java new file mode 100644 index 000000000..fdcff5c29 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftArticles.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.mp.bean.draft; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import me.chanjar.weixin.common.bean.ToJson; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * 草稿箱能力-图文素材文章实体. + * + * @author dragon + * @date 2021-10-22 + */ +@Data +@Builder +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class WxMpDraftArticles implements ToJson, Serializable { + /** + * 标题 + */ + @SerializedName("title") + private String title; + /** + * 作者 + */ + @SerializedName("author") + private String author; + /** + * 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空。如果本字段为没有填写,则默认抓取正文前54个字。 + */ + @SerializedName("digest") + private String digest; + /** + * 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS,涉及图片url必须来源 "上传图文消息内的图片获取URL"接口获取。外部图片url将被过滤。 + */ + @SerializedName("content") + private String content; + /** + * 图文消息的原文地址,即点击“阅读原文”后的URL + */ + @SerializedName("content_source_url") + private String contentSourceUrl; + /** + * 图文消息的封面图片素材id(必须是永久MediaID) + */ + @SerializedName("thumb_media_id") + private String thumbMediaId; + /** + * 是否显示封面,0为false,即不显示,1为true,即显示(默认) + */ + @SerializedName("show_cover_pic") + private Integer showCoverPic; + /** + * 是否打开评论,0不打开(默认),1打开 + */ + @SerializedName("need_open_comment") + private Integer needOpenComment; + /** + * 是否粉丝才可评论,0所有人可评论(默认),1粉丝才可评论 + */ + @SerializedName("only_fans_can_comment") + private Integer onlyFansCanComment; + + public static WxMpDraftArticles fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpDraftArticles.class); + } + + @Override + public String toJson() { + return WxGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftInfo.java new file mode 100644 index 000000000..92a0c928d --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftInfo.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.mp.bean.draft; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import me.chanjar.weixin.common.bean.ToJson; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 草稿箱能力-获取草稿详情. + * + * @author dragon + * @date 2021-10-22 + */ +@Data +@Builder +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class WxMpDraftInfo implements ToJson, Serializable { + private static final long serialVersionUID = 6111694033486314392L; + + /** + * 文章列表 + */ + @SerializedName("news_item") + private List newsItem; + + public static WxMpDraftInfo fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpDraftInfo.class); + } + + @Override + public String toJson() { + return WxGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftItem.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftItem.java new file mode 100644 index 000000000..05294cade --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftItem.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.mp.bean.draft; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * 一条草稿 + * + * @author dragon + * @date 2021-10-22 + */ +@Data +public class WxMpDraftItem implements Serializable { + private static final long serialVersionUID = 214696458030935146L; + + /** + * 图文消息的id + */ + @SerializedName("media_id") + private String mediaId; + /** + * 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS。 + */ + @SerializedName("content") + private WxMpDraftInfo content; + + public static WxMpDraftItem fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpDraftItem.class); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftList.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftList.java new file mode 100644 index 000000000..924ce4b04 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpDraftList.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.mp.bean.draft; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 草稿箱能力-获取草稿列表. + * + * @author dragon + * @date 2021-10-22 + */ +@Data +public class WxMpDraftList implements Serializable { + private static final long serialVersionUID = 7216822694952035295L; + + /** + * 草稿素材的总数 + */ + @SerializedName("total_count") + private Integer totalCount; + + /** + * 本次调用获取的素材的数量 + */ + @SerializedName("item_count") + private Integer itemCount; + + /** + * 所有草稿列表 + */ + @SerializedName("item") + private List items; + + public static WxMpDraftList fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpDraftList.class); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpUpdateDraft.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpUpdateDraft.java new file mode 100644 index 000000000..fa92a6239 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/draft/WxMpUpdateDraft.java @@ -0,0 +1,55 @@ +package me.chanjar.weixin.mp.bean.draft; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import me.chanjar.weixin.common.bean.ToJson; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * 草稿箱能力-修改草稿. + * + * @author dragon + * @date 2021-10-22 + */ +@Data +@Builder +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class WxMpUpdateDraft implements ToJson, Serializable { + + private static final long serialVersionUID = -8564521168423899915L; + /** + * 要修改的图文消息的id + */ + @SerializedName("media_id") + private String mediaId; + + /** + * 要更新的文章在图文消息中的位置(多图文消息时,此字段才有意义),第一篇为0 + */ + @SerializedName("index") + private Integer index; + + /** + * 图文素材列表 + */ + @SerializedName("articles") + private WxMpDraftArticles articles; + + public static WxMpUpdateDraft fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpUpdateDraft.class); + } + + @Override + public String toJson() { + return WxGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishArticles.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishArticles.java new file mode 100644 index 000000000..20f915f19 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishArticles.java @@ -0,0 +1,85 @@ +package me.chanjar.weixin.mp.bean.freepublish; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * 一条发布的图文记录 + * + * @author dragon + * @date 2021-10-23 + */ +@NoArgsConstructor +@Data +public class WxMpFreePublishArticles implements Serializable { + private static final long serialVersionUID = -6435229818150835883L; + + /** + * 标题 + */ + @SerializedName("title") + private String title; + /** + * 作者 + */ + @SerializedName("author") + private String author; + /** + * 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空。 + */ + @SerializedName("digest") + private String digest; + /** + * 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS + */ + @SerializedName("content") + private String content; + /** + * 图文消息的原文地址,即点击“阅读原文”后的URL + */ + @SerializedName("content_source_url") + private String contentSourceUrl; + /** + * 图文消息的封面图片素材id(一定是永久MediaID) + */ + @SerializedName("thumb_media_id") + private String thumbMediaId; + /** + * 是否显示封面,0为false,即不显示,1为true,即显示(默认) + */ + @SerializedName("show_cover_pic") + private Integer showCoverPic; + /** + * 是否打开评论,0不打开(默认),1打开 + */ + @SerializedName("need_open_comment") + private Integer needOpenComment; + /** + * 是否粉丝才可评论,0所有人可评论(默认),1粉丝才可评论 + */ + @SerializedName("only_fans_can_comment") + private Integer onlyFansCanComment; + + /* + * ===== 上面的参数,就是草稿箱的内容的字段,为了后续扩展,单独写一份==== + */ + + /** + * 草稿的临时链接 + */ + @SerializedName("url") + private String url; + /** + * 该图文是否被删除 + */ + @SerializedName("is_deleted") + private Boolean isDeleted; + + public static WxMpFreePublishArticles fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpFreePublishArticles.class); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishInfo.java new file mode 100644 index 000000000..79205aab9 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishInfo.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.mp.bean.freepublish; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import me.chanjar.weixin.common.bean.ToJson; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 发布能力-通过 article_id 获取已发布文章. + * + * @author dragon + * @date 2021-10-23 + */ +@Data +@Builder +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class WxMpFreePublishInfo implements ToJson, Serializable { + private static final long serialVersionUID = 3331288672996730705L; + + /** + * 文章列表 + */ + @SerializedName("news_item") + private List newsItem; + + public static WxMpFreePublishInfo fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpFreePublishInfo.class); + } + + @Override + public String toJson() { + return WxGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishItem.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishItem.java new file mode 100644 index 000000000..f32b34a9f --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishItem.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.mp.bean.freepublish; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * 发布列表的一条记录 + * + * @author dragon + * @date 2021-10-23 + */ +@NoArgsConstructor +@Data +public class WxMpFreePublishItem implements Serializable { + private static final long serialVersionUID = -6435229818150835883L; + + /** + * 成功发布的图文消息id + */ + @SerializedName("article_id") + private String articleId; + /** + * 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS。 + */ + @SerializedName("content") + private WxMpFreePublishInfo content; + + public static WxMpFreePublishItem fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpFreePublishItem.class); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishList.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishList.java new file mode 100644 index 000000000..c0c2e2dfb --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishList.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.mp.bean.freepublish; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 发布能力-获取成功发布列表. + * + * @author dragon + * @date 2021-10-22 + */ +@Data +public class WxMpFreePublishList implements Serializable { + private static final long serialVersionUID = 764054773431665250L; + + /** + * 成功发布素材的总数 + */ + @SerializedName("total_count") + private Integer totalCount; + + /** + * 本次调用获取的素材的数量 + */ + @SerializedName("item_count") + private Integer itemCount; + + /** + * 所有成功发布列表 + */ + @SerializedName("item") + private List items; + + public static WxMpFreePublishList fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpFreePublishList.class); + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishStatus.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishStatus.java new file mode 100644 index 000000000..573412396 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/freepublish/WxMpFreePublishStatus.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.mp.bean.freepublish; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import me.chanjar.weixin.common.bean.ToJson; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + * 发布能力-发布状态轮询接口,通过publishId返回 article_id(删除发布时需要用到). + * + * @author dragon + * @date 2021-10-23 + */ +@Data +@Builder +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +public class WxMpFreePublishStatus implements ToJson, Serializable { + + private static final long serialVersionUID = -7526369389476785732L; + private String publish_id; + private Integer publish_status; + private String article_id; + private ArticleDetail article_detail; + private List fail_idx; + + public static WxMpFreePublishStatus fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMpFreePublishStatus.class); + } + + @Override + public String toJson() { + return WxGsonBuilder.create().toJson(this); + } + + @NoArgsConstructor + @Data + public static class ArticleDetail { + private Integer count; + private List item; + + @NoArgsConstructor + @Data + public static class Item { + private Integer idx; + private String article_url; + } + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java index 8f6cfeea4..6ecf75754 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java @@ -5,7 +5,10 @@ import lombok.Getter; import me.chanjar.weixin.mp.config.WxMpConfigStorage; import me.chanjar.weixin.mp.config.WxMpHostConfig; -import static me.chanjar.weixin.mp.config.WxMpHostConfig.*; +import static me.chanjar.weixin.mp.config.WxMpHostConfig.API_DEFAULT_HOST_URL; +import static me.chanjar.weixin.mp.config.WxMpHostConfig.MP_DEFAULT_HOST_URL; +import static me.chanjar.weixin.mp.config.WxMpHostConfig.OPEN_DEFAULT_HOST_URL; +import static me.chanjar.weixin.mp.config.WxMpHostConfig.buildUrl; /** *
@@ -1374,6 +1377,90 @@ public interface WxMpApiUrl {
     ;
 
 
+    private final String prefix;
+    private final String path;
+
+  }
+
+  /**
+   * 草稿箱 能力:
+   * 新建草稿
+   * 获取草稿
+   * 删除草稿
+   * 修改草稿
+   * 获取草稿总数
+   * 获取草稿列表
+   * MP端开关(仅内测期间使用)- 上线后废弃,没实现,可以自己去公众号后台开启草稿箱
+   */
+  @AllArgsConstructor
+  @Getter
+  enum Draft implements WxMpApiUrl {
+
+    /**
+     * 新建草稿
+     */
+    ADD_DRAFT(API_DEFAULT_HOST_URL, "/cgi-bin/draft/add"),
+    /**
+     * 修改草稿
+     */
+    UPDATE_DRAFT(API_DEFAULT_HOST_URL, "/cgi-bin/draft/update"),
+    /**
+     * 获取草稿
+     */
+    GET_DRAFT(API_DEFAULT_HOST_URL, "/cgi-bin/draft/get"),
+    /**
+     * 删除草稿
+     */
+    DEL_DRAFT(API_DEFAULT_HOST_URL, "/cgi-bin/draft/delete"),
+    /**
+     * 获取草稿列表
+     */
+    LIST_DRAFT(API_DEFAULT_HOST_URL, "/cgi-bin/draft/batchget"),
+    /**
+     * 获取草稿总数
+     */
+    COUNT_DRAFT(API_DEFAULT_HOST_URL, "/cgi-bin/draft/count");
+
+    private final String prefix;
+    private final String path;
+
+  }
+
+  /**
+   * 发布能力:
+   * 发布接口
+   * 发布状态轮询接口
+   * 事件推送发布结果 -- 是回调,没实现
+   * 删除发布
+   * 通过 article_id 获取已发布文章
+   * 获取成功发布列表
+   */
+  @AllArgsConstructor
+  @Getter
+  enum FreePublish implements WxMpApiUrl {
+
+    /**
+     * 发布接口
+     */
+    SUBMIT(API_DEFAULT_HOST_URL, "/cgi-bin/freepublish/submit"),
+    /**
+     * 通过 article_id 获取已发布文章
+     */
+    GET_ARTICLE(API_DEFAULT_HOST_URL, "/cgi-bin/freepublish/getarticle"),
+    /**
+     * 发布状态轮询接口
+     */
+    GET_PUSH_STATUS(API_DEFAULT_HOST_URL, "/cgi-bin/freepublish/get"),
+    /**
+     * 删除发布
+     */
+    DEL_PUSH(API_DEFAULT_HOST_URL, "/cgi-bin/freepublish/delete"),
+    /**
+     * 获取成功发布列表
+     */
+    BATCH_GET(API_DEFAULT_HOST_URL, "/cgi-bin/freepublish/batchget")
+    ;
+
     private final String prefix;
     private final String path;
 
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImplTest.java
new file mode 100644
index 000000000..193580a9f
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpDraftServiceImplTest.java
@@ -0,0 +1,127 @@
+package me.chanjar.weixin.mp.api.impl;
+
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.test.ApiTestModule;
+import me.chanjar.weixin.mp.bean.draft.WxMpAddDraft;
+import me.chanjar.weixin.mp.bean.draft.WxMpDraftArticles;
+import me.chanjar.weixin.mp.bean.draft.WxMpDraftInfo;
+import me.chanjar.weixin.mp.bean.draft.WxMpDraftList;
+import me.chanjar.weixin.mp.bean.draft.WxMpUpdateDraft;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * 草稿箱单元测试.
+ *
+ * @author dragon
+ * @date 2021-10-22
+ */
+@Guice(modules = ApiTestModule.class)
+public class WxMpDraftServiceImplTest {
+
+  /**
+   * 1.先上传一个永久图片素材:me.chanjar.weixin.mp.api.impl.WxMpMaterialServiceImplTest.testUploadMaterial
+   * 2.后续图文需要设置一个永久素材id
+   */
+  final String thumbMediaId = "zUUtT8ZYeXzZ4slFbtnAkh7Yd-f45DbFoF9ERzVC6s4";
+
+  /**
+   * 新增草稿后返回的id,后续查询、修改、删除,获取等需要使用
+   */
+  final String mediaId = "zUUtT8ZYeXzZ4slFbtnAkpgGKyqnTsjtUvMdVBRWJVk";
+
+  @Inject
+  protected WxMpService wxService;
+
+  @Test
+  public void testAddDraft() throws WxErrorException {
+    // {"mediaId":"zUUtT8ZYeXzZ4slFbtnAkh7Yd-f45DbFoF9ERzVC6s4","url":"http://mmbiz.qpic.cn/mmbiz_jpg/fLtyChQRfH84IyicNUbGt3l3IlHxJRibSFz7Tky0ibmzKykzVbo9tZGYhXQGJ2npFtDPbvPhKYxBz6JxkYIibTmUicQ/0?wx_fmt=jpeg"}
+    this.wxService.getDraftService().addDraft("标题", "图文消息的具体内容", thumbMediaId);
+    // 【响应数据】:{"media_id":"zUUtT8ZYeXzZ4slFbtnAks-nZeGiPQmwvhShTh72CqM","item":[]}
+  }
+
+  @Test
+  public void testAddGuide_another() throws WxErrorException {
+    List draftArticleList = new ArrayList<>();
+    WxMpDraftArticles draftArticle = WxMpDraftArticles.builder()
+      .title("新建草稿-对象形式")
+      .author("dragon")
+      .digest("图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空")
+      .content("图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS")
+      .contentSourceUrl("https://github.com/Wechat-Group/WxJava")
+      .thumbMediaId(thumbMediaId)
+      // 显示封面、打开评论、所有人可评论
+      .showCoverPic(1).needOpenComment(1).onlyFansCanComment(0)
+      .build();
+    draftArticleList.add(draftArticle);
+
+    WxMpAddDraft addDraft = WxMpAddDraft.builder().articles(draftArticleList).build();
+    String mediaId = this.wxService.getDraftService().addDraft(addDraft);
+    // 【响应数据】:{"media_id":"zUUtT8ZYeXzZ4slFbtnAkpgGKyqnTsjtUvMdVBRWJVk","item":[]}
+    assertThat(mediaId).isNotNull();
+  }
+
+  @Test
+  public void testGetDraft() throws WxErrorException {
+    final WxMpDraftInfo draftInfo = this.wxService.getDraftService().getDraft(mediaId);
+    assertThat(draftInfo).isNotNull();
+    // 【响应数据】:{"news_item":[{"title":"标题","author":"","digest":"图文消息的具体内容","content":"图文消息的具体内容","content_source_url":"","thumb_media_id":"zUUtT8ZYeXzZ4slFbtnAkh7Yd-f45DbFoF9ERzVC6s4","show_cover_pic":1,"url":"http:\/\/mp.weixin.qq.com\/s?__biz=Mzk0OTI5MzM1OQ==&mid=100000006&idx=1&sn=89903965ae5ebd6014903c7c5ca34daa&chksm=435bd946742c5050d18da32289904db5ede8bbd157d181438231a1762b85030419b3c0ed4c00#rd","thumb_url":"http:\/\/mmbiz.qpic.cn\/mmbiz_jpg\/fLtyChQRfH84IyicNUbGt3l3IlHxJRibSFz7Tky0ibmzKykzVbo9tZGYhXQGJ2npFtDPbvPhKYxBz6JxkYIibTmUicQ\/0?wx_fmt=jpeg","need_open_comment":0,"only_fans_can_comment":0}],"create_time":1634886802,"update_time":1634886802}
+  }
+
+  @Test
+  public void testUpdateDraft() throws WxErrorException {
+    WxMpDraftArticles draftArticles = WxMpDraftArticles.builder()
+      .title("新标题").content("新图文消息的具体内容").thumbMediaId(thumbMediaId).build();
+    WxMpUpdateDraft updateDraft = WxMpUpdateDraft.builder()
+      .mediaId(mediaId)
+      .index(0)
+      .articles(draftArticles)
+      .build();
+    Boolean updateDraftResult = this.wxService.getDraftService().updateDraft(updateDraft);
+    // assertThat(updateDraftResult).isTrue();
+    assertThat(updateDraftResult).isTrue();
+  }
+
+  @Test
+  public void testDelDraft() throws WxErrorException {
+    Boolean delDraftResult = this.wxService.getDraftService().delDraft(mediaId);
+    // 【响应数据】:{"errcode":0,"errmsg":"ok"}
+    assertThat(delDraftResult).isTrue();
+  }
+
+  @Test
+  public void testListDraft() throws WxErrorException {
+    WxMpDraftList draftList = this.wxService.getDraftService().listDraft(0, 10);
+    /*
+    【响应数据】:{"item":[{"media_id":"zUUtT8ZYeXzZ4slFbtnAks-nZeGiPQmwvhShTh72CqM",
+    "content":{
+      "news_item":
+      [
+        {"title":"标题","author":"","digest":"图文消息的具体内容","content":"图文消息的具体内容",
+        "content_source_url":"","thumb_media_id":"zUUtT8ZYeXzZ4slFbtnAkh7Yd-f45DbFoF9ERzVC6s4",
+        "show_cover_pic":1,"url":"http:\/\/mp.weixin.qq.com\/s?__biz=Mzk0OTI5MzM1?wx_fmt=jpeg",
+        "need_open_comment":0,"only_fans_can_comment":0}],
+        "create_time":1634886802,"update_time":1634886802},"update_time":1634886802}
+      ]
+    ,"total_count":1,"item_count":1}
+
+    */
+    assertThat(draftList).isNotNull();
+  }
+
+  @Test
+  public void testCountDraft() throws WxErrorException {
+    Long countDraft = this.wxService.getDraftService().countDraft();
+    // 【响应数据】:{"total_count":1}
+    assertThat(countDraft).isNotNull();
+  }
+
+}
+
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpFreePublishServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpFreePublishServiceImplTest.java
new file mode 100644
index 000000000..ff5cd0e5d
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpFreePublishServiceImplTest.java
@@ -0,0 +1,109 @@
+package me.chanjar.weixin.mp.api.impl;
+
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.test.ApiTestModule;
+import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishInfo;
+import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishList;
+import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishStatus;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * 发布能力-单元测试.
+ *
+ * @author dragon
+ * @date 2021-10-23
+ */
+@Guice(modules = ApiTestModule.class)
+public class WxMpFreePublishServiceImplTest {
+
+  /**
+   * 新增草稿后返回的id,发布需要使用
+   */
+  final String mediaId = "HKVdzjkDfooMqBqJtvSs2EEeRAJaM33gJgkii_JDOHg";
+
+  /**
+   * 发布后的id,后续查询等需要使用
+   */
+  final String publishId = "2650177669";
+
+  /**
+   * 图文的 article_id,后续查询图文详情、删除发布内容 需要使用
+   * 要根据 publishId 来获取 article_id
+   *
+   * @see this.testGetPushStatus
+   */
+  final String articleId = "zjMKVd1g66BkEkpetwml4ElbDdniE8JeI2Ec324Sjqg";
+
+  @Inject
+  protected WxMpService wxService;
+
+  @Test
+  public void testSubmit() throws WxErrorException {
+    String submit = this.wxService.getFreePublishService().submit(mediaId);
+    assertThat(submit).isNotBlank();
+    // 【响应数据】:{"errcode":0,"errmsg":"ok","publish_id":2650177668}
+  }
+
+  @Test
+  public void testGetPushStatus() throws WxErrorException {
+    WxMpFreePublishStatus pushStatus = this.wxService.getFreePublishService().getPushStatus(publishId);
+    assertThat(pushStatus).isNotNull();
+    // 【响应数据】:{"publish_id":2650177668,"publish_status":0,"article_id":"zjMKVd1g66BkEkpetwml4J-4gNf4I1nsh-B-r_inemw",
+    // "article_detail":{"count":1,"item":
+    // [{"idx":1,"article_url":
+    // "https://mp.weixin.qq.com/s?__biz=MzAwMTE2MzA1xxxxxxxxxx"
+    // }]},"fail_idx":[]}
+    // article_url -> 已发布内容可被自定义菜单、自动回复、话题引用,也可用于公开传播
+  }
+
+  @Test
+  public void testGetArticleFromId() throws WxErrorException {
+    WxMpFreePublishInfo articleFromId = this.wxService.getFreePublishService().getArticleFromId(articleId);
+    assertThat(articleFromId).isNotNull();
+    /* 【响应数据】:{"news_item":[{"title":"欢迎你加入啊~ 这是我的第一条文字消息草稿","author":"","digest":"","content":"欢迎你加入啊~ 这是我的第一条文字消息草稿",
+    "content_source_url":"","thumb_media_id":"","show_cover_pic":0,"url":"http:\/\/mp.weixin.qq
+    .com\/s?__biz=MzAwMTE2MzA1Mg==&mid=2650177668","thumb_url":"","need_open_comment":1,"only_fans_can_comment":1,"is_deleted":false}],
+    "create_time":1634961670,"update_time":1634961672}
+     */
+  }
+
+  @Test
+  public void testDelPush() throws WxErrorException {
+    Boolean deletePush = this.wxService.getFreePublishService().deletePush(articleId, 0);
+    // 【响应数据】:{"errcode":0,"errmsg":"ok"}
+    assertThat(deletePush).isTrue();
+  }
+
+  @Test
+  public void testDeletePushAllArticle() throws WxErrorException {
+    Boolean deletePush = this.wxService.getFreePublishService().deletePushAllArticle(articleId);
+    // 【响应数据】:{"errcode":0,"errmsg":"ok"}
+    assertThat(deletePush).isTrue();
+  }
+
+  @Test
+  public void testGetPublicationRecords() throws WxErrorException {
+    WxMpFreePublishList publicationRecords = this.wxService.getFreePublishService().getPublicationRecords(0, 10, 0);
+    /*
+    【响应数据】:
+    {"item":[{"article_id":"zjMKVd1g66BkEkpetwml4BOSzatuEYNY3TFhCc0kSIE","content":{"news_item":[
+    {"title":"新建草稿-对象形式","author":"dragon","digest":"图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空",
+    "content":"图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS",
+    "content_source_url":"https:\/\/github.com\/Wechat-Group\/WxJava","thumb_media_id":"HKVdzjkDfooMqBqJtvSs2Ajz2v6L_vtGhyyr_mqKcPU",
+    "show_cover_pic":1,"url":"http:\/\/mp.weixin.qq.com\/s?__biz=MzAwMTE2MzA1Mg==&mid=26501776710e5adb91#rd",
+    "thumb_url":"http:\/\/mmbiz.qpic.cn\/mmbiz_jpg\/0QSAUfroWrUmxHthQ\/0?wx_fmt=jpeg",
+    "need_open_comment":1,"only_fans_can_comment":0,"is_deleted":false}],
+    "create_time":1634976306,"update_time":1634976318}}
+    ]
+    ,"total_count":1,"item_count":1}
+    */
+    assertThat(publicationRecords).isNotNull();
+  }
+
+}
+