From 8ee3af153a1b91d62f47c3c65d699b13771297e4 Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Sat, 22 Aug 2026 12:45:22 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=8F=B7=E5=95=86=E5=93=81=E9=A2=86=E5=9F=9F=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../channel/api/WxChannelGiftService.java | 102 +++++++++++++++ .../api/WxChannelLimitedDiscountService.java | 51 ++++++++ .../api/WxChannelProductAssistantService.java | 7 ++ .../api/WxChannelProductStockService.java | 45 +++++++ .../weixin/channel/api/WxChannelService.java | 28 +++++ .../api/impl/BaseWxChannelServiceImpl.java | 29 ++++- .../api/impl/WxChannelGiftServiceImpl.java | 104 ++++++++++++++++ .../WxChannelLimitedDiscountServiceImpl.java | 58 +++++++++ .../WxChannelProductAssistantServiceImpl.java | 16 +++ .../api/impl/WxChannelProductServiceImpl.java | 105 +++++----------- .../WxChannelProductStockServiceImpl.java | 52 ++++++++ .../api/impl/WxChannelServiceImplTest.java | 116 ++++++++++++++++++ 12 files changed, 639 insertions(+), 74 deletions(-) create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java create mode 100644 weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java new file mode 100644 index 000000000..23655e190 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelGiftService.java @@ -0,0 +1,102 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductGetResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductListParam; +import me.chanjar.weixin.channel.bean.product.GiftProductListResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店赠品与买赠活动服务。 + */ +public interface WxChannelGiftService { + + /** + * 添加非卖商品。 + * + * @param info 赠品信息 + * @return 添加赠品响应 + * @throws WxErrorException 异常 + */ + GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException; + + /** + * 更新非卖商品。 + * + * @param info 赠品信息 + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException; + + /** + * 在售商品转赠品。 + * + * @param productId 商品ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException; + + /** + * 获取赠品。 + * + * @param productId 赠品商品ID + * @return 赠品详情响应 + * @throws WxErrorException 异常 + */ + GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException; + + /** + * 获取赠品列表。 + * + * @param param 查询参数 + * @return 赠品列表 + * @throws WxErrorException 异常 + */ + GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException; + + /** + * 更新赠品库存。 + * + * @param productId 赠品商品ID + * @param skuId 赠品sku_id + * @param diffType 修改类型 1增加 2减少 3设置 + * @param num 增加、减少或者设置的库存值 + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateGiftStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException; + + /** + * 创建赠品活动。 + * + * @param info 活动信息 + * @return 创建赠品活动响应 + * @throws WxErrorException 异常 + */ + GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException; + + /** + * 删除赠品活动。 + * + * @param activityId 活动ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException; + + /** + * 停止赠品活动。 + * + * @param activityId 活动ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java new file mode 100644 index 000000000..d51d06fd2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.channel.api; + +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店限时抢购服务。 + */ +public interface WxChannelLimitedDiscountService { + + /** + * 添加限时抢购任务。 + * + * @param param 限时抢购任务 + * @return 添加任务响应 + * @throws WxErrorException 异常 + */ + LimitTaskAddResponse addLimitTask(LimitTaskParam param) throws WxErrorException; + + /** + * 拉取限时抢购任务列表。 + * + * @param pageSize 每页数量 + * @param nextKey 翻页上下文 + * @param status 抢购活动状态 + * @return 任务列表响应 + * @throws WxErrorException 异常 + */ + LimitTaskListResponse listLimitTask(Integer pageSize, String nextKey, Integer status) throws WxErrorException; + + /** + * 停止限时抢购任务。 + * + * @param taskId 限时抢购任务ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse stopLimitTask(String taskId) throws WxErrorException; + + /** + * 删除限时抢购任务。 + * + * @param taskId 限时抢购任务ID + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java new file mode 100644 index 000000000..ad5f912c6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java @@ -0,0 +1,7 @@ +package me.chanjar.weixin.channel.api; + +/** + * 微信小店商品辅助功能服务。 + */ +public interface WxChannelProductAssistantService { +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java new file mode 100644 index 000000000..6955de5e6 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java @@ -0,0 +1,45 @@ +package me.chanjar.weixin.channel.api; + +import java.util.List; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockResponse; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店商品库存服务。 + */ +public interface WxChannelProductStockService { + + /** + * 更新商品库存。 + * + * @param productId 商品ID + * @param skuId 商品sku_id + * @param diffType 修改类型 1增加 2减少 3设置 + * @param num 增加、减少或者设置的库存值 + * @return 操作响应 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException; + + /** + * 获取商品实时库存。 + * + * @param productId 商品ID + * @param skuId 商品sku_id + * @return 库存响应 + * @throws WxErrorException 异常 + */ + SkuStockResponse getSkuStock(String productId, String skuId) throws WxErrorException; + + /** + * 批量获取库存信息。 + * + * @param productIds 商品ID列表,单次请求不超过50个 + * @return 库存信息 + * @throws WxErrorException 异常 + */ + SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java index 64c21408c..772158785 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java @@ -35,6 +35,34 @@ public interface WxChannelService extends BaseWxChannelService { */ WxChannelProductService getProductService(); + /** + * 赠品与买赠活动服务 + * + * @return 赠品与买赠活动服务 + */ + WxChannelGiftService getGiftService(); + + /** + * 限时抢购服务 + * + * @return 限时抢购服务 + */ + WxChannelLimitedDiscountService getLimitedDiscountService(); + + /** + * 商品库存服务 + * + * @return 商品库存服务 + */ + WxChannelProductStockService getProductStockService(); + + /** + * 商品辅助功能服务 + * + * @return 商品辅助功能服务 + */ + WxChannelProductAssistantService getProductAssistantService(); + /** * 仓库服务 * diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java index fd432e972..b167af0d4 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/BaseWxChannelServiceImpl.java @@ -37,7 +37,14 @@ public abstract class BaseWxChannelServiceImpl implements WxChannelService private final WxChannelBasicService basicService = new WxChannelBasicServiceImpl(this); private final WxChannelCategoryService categoryService = new WxChannelCategoryServiceImpl(this); private final WxChannelBrandService brandService = new WxChannelBrandServiceImpl(this); - private final WxChannelProductService productService = new WxChannelProductServiceImpl(this); + private final WxChannelGiftService giftService = new WxChannelGiftServiceImpl(this); + private final WxChannelLimitedDiscountService limitedDiscountService = + new WxChannelLimitedDiscountServiceImpl(this); + private final WxChannelProductStockService productStockService = new WxChannelProductStockServiceImpl(this); + private final WxChannelProductAssistantService productAssistantService = + new WxChannelProductAssistantServiceImpl(this); + private final WxChannelProductService productService = new WxChannelProductServiceImpl( + this, giftService, limitedDiscountService, productStockService); private final WxChannelWarehouseService warehouseService = new WxChannelWarehouseServiceImpl(this); private final WxChannelOrderService orderService = new WxChannelOrderServiceImpl(this); private final WxChannelAfterSaleService afterSaleService = new WxChannelAfterSaleServiceImpl(this); @@ -336,6 +343,26 @@ public WxChannelProductService getProductService() { return productService; } + @Override + public WxChannelGiftService getGiftService() { + return giftService; + } + + @Override + public WxChannelLimitedDiscountService getLimitedDiscountService() { + return limitedDiscountService; + } + + @Override + public WxChannelProductStockService getProductStockService() { + return productStockService; + } + + @Override + public WxChannelProductAssistantService getProductAssistantService() { + return productAssistantService; + } + @Override public WxChannelWarehouseService getWarehouseService() { return warehouseService; diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java new file mode 100644 index 000000000..5d6a16bdc --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelGiftServiceImpl.java @@ -0,0 +1,104 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_ADD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_DELETE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_STOP_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ADD_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_GET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_LIST_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ON_SALE_SET_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_STOCK_UPDATE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_UPDATE_URL; + +import me.chanjar.weixin.channel.api.WxChannelGiftService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityAddParam; +import me.chanjar.weixin.channel.bean.product.GiftActivityAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftActivityInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductGetResponse; +import me.chanjar.weixin.channel.bean.product.GiftProductInfo; +import me.chanjar.weixin.channel.bean.product.GiftProductListParam; +import me.chanjar.weixin.channel.bean.product.GiftProductListResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockParam; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店赠品与买赠活动服务实现。 + */ +public class WxChannelGiftServiceImpl implements WxChannelGiftService { + + private final BaseWxChannelServiceImpl shopService; + + public WxChannelGiftServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(info); + String resJson = shopService.post(GIFT_PRODUCT_ADD_URL, reqJson); + return ResponseUtils.decode(resJson, GiftProductAddResponse.class); + } + + @Override + public WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException { + String reqJson = JsonUtils.encode(info); + String resJson = shopService.post(GIFT_PRODUCT_UPDATE_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException { + String reqJson = "{\"product_id\":\"" + productId + "\"}"; + String resJson = shopService.post(GIFT_PRODUCT_ON_SALE_SET_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException { + String reqJson = "{\"product_id\":\"" + productId + "\"}"; + String resJson = shopService.post(GIFT_PRODUCT_GET_URL, reqJson); + return ResponseUtils.decode(resJson, GiftProductGetResponse.class); + } + + @Override + public GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(GIFT_PRODUCT_LIST_URL, reqJson); + return ResponseUtils.decode(resJson, GiftProductListResponse.class); + } + + @Override + public WxChannelBaseResponse updateGiftStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException { + SkuStockParam param = new SkuStockParam(productId, skuId, diffType, num); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(GIFT_PRODUCT_STOCK_UPDATE_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException { + GiftActivityAddParam param = new GiftActivityAddParam(info); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(GIFT_ACTIVITY_ADD_URL, reqJson); + return ResponseUtils.decode(resJson, GiftActivityAddResponse.class); + } + + @Override + public WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException { + String reqJson = "{\"activity_id\":\"" + activityId + "\"}"; + String resJson = shopService.post(GIFT_ACTIVITY_DELETE_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException { + String reqJson = "{\"activity_id\":\"" + activityId + "\"}"; + String resJson = shopService.post(GIFT_ACTIVITY_STOP_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java new file mode 100644 index 000000000..e9eb5857b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java @@ -0,0 +1,58 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.ADD_LIMIT_TASK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.DELETE_LIMIT_TASK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.LIST_LIMIT_TASK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.STOP_LIMIT_TASK_URL; + +import me.chanjar.weixin.channel.api.WxChannelLimitedDiscountService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskListParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse; +import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店限时抢购服务实现。 + */ +public class WxChannelLimitedDiscountServiceImpl implements WxChannelLimitedDiscountService { + + private final BaseWxChannelServiceImpl shopService; + + public WxChannelLimitedDiscountServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public LimitTaskAddResponse addLimitTask(LimitTaskParam param) throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(ADD_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, LimitTaskAddResponse.class); + } + + @Override + public LimitTaskListResponse listLimitTask(Integer pageSize, String nextKey, Integer status) + throws WxErrorException { + LimitTaskListParam param = new LimitTaskListParam(pageSize, nextKey, status); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(LIST_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, LimitTaskListResponse.class); + } + + @Override + public WxChannelBaseResponse stopLimitTask(String taskId) throws WxErrorException { + String reqJson = "{\"task_id\": \"" + taskId + "\"}"; + String resJson = shopService.post(STOP_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException { + String reqJson = "{\"task_id\": \"" + taskId + "\"}"; + String resJson = shopService.post(DELETE_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java new file mode 100644 index 000000000..d8be9995b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java @@ -0,0 +1,16 @@ +package me.chanjar.weixin.channel.api.impl; + +import me.chanjar.weixin.channel.api.WxChannelProductAssistantService; + +/** + * 微信小店商品辅助功能服务实现。 + */ +public class WxChannelProductAssistantServiceImpl implements WxChannelProductAssistantService { + + /** 微信商店服务 */ + private final BaseWxChannelServiceImpl shopService; + + public WxChannelProductAssistantServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java index b3974e0aa..a7fc91c84 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductServiceImpl.java @@ -1,44 +1,29 @@ package me.chanjar.weixin.channel.api.impl; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.ADD_LIMIT_TASK_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.CANCEL_AUDIT_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.DELETE_LIMIT_TASK_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_ADD_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_DELETE_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_ACTIVITY_STOP_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ADD_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_GET_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_LIST_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_ON_SALE_SET_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_STOCK_UPDATE_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.GIFT_PRODUCT_UPDATE_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.LIST_LIMIT_TASK_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_ADD_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_AUDIT_FREE_UPDATE_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_DELISTING_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_DEL_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_BATCH_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_H5URL_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_LISTING_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_LIST_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_QRCODE_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_TAGLINK_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_STOCK_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_URL; -import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.STOP_LIMIT_TASK_URL; import java.util.List; import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.channel.api.WxChannelGiftService; +import me.chanjar.weixin.channel.api.WxChannelLimitedDiscountService; import me.chanjar.weixin.channel.api.WxChannelProductService; +import me.chanjar.weixin.channel.api.WxChannelProductStockService; import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse; -import me.chanjar.weixin.channel.bean.limit.LimitTaskListParam; import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse; import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; -import me.chanjar.weixin.channel.bean.product.GiftActivityAddParam; import me.chanjar.weixin.channel.bean.product.GiftActivityAddResponse; import me.chanjar.weixin.channel.bean.product.GiftActivityInfo; import me.chanjar.weixin.channel.bean.product.GiftProductAddResponse; @@ -46,9 +31,7 @@ import me.chanjar.weixin.channel.bean.product.GiftProductInfo; import me.chanjar.weixin.channel.bean.product.GiftProductListParam; import me.chanjar.weixin.channel.bean.product.GiftProductListResponse; -import me.chanjar.weixin.channel.bean.product.SkuStockBatchParam; import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; -import me.chanjar.weixin.channel.bean.product.SkuStockParam; import me.chanjar.weixin.channel.bean.product.SkuStockResponse; import me.chanjar.weixin.channel.bean.product.SpuFastInfo; import me.chanjar.weixin.channel.bean.product.SpuGetResponse; @@ -74,9 +57,22 @@ public class WxChannelProductServiceImpl implements WxChannelProductService { /** 微信商店服务 */ private final BaseWxChannelServiceImpl shopService; + private final WxChannelGiftService giftService; + private final WxChannelLimitedDiscountService limitedDiscountService; + private final WxChannelProductStockService productStockService; public WxChannelProductServiceImpl(BaseWxChannelServiceImpl shopService) { + this(shopService, new WxChannelGiftServiceImpl(shopService), + new WxChannelLimitedDiscountServiceImpl(shopService), new WxChannelProductStockServiceImpl(shopService)); + } + + WxChannelProductServiceImpl(BaseWxChannelServiceImpl shopService, WxChannelGiftService giftService, + WxChannelLimitedDiscountService limitedDiscountService, + WxChannelProductStockService productStockService) { this.shopService = shopService; + this.giftService = giftService; + this.limitedDiscountService = limitedDiscountService; + this.productStockService = productStockService; } @Override @@ -117,10 +113,7 @@ public WxChannelBaseResponse updateProductAuditFree(SpuFastInfo info) throws WxE @Override public WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffType, Integer num) throws WxErrorException { - SkuStockParam param = new SkuStockParam(productId, skuId, diffType, num); - String reqJson = JsonUtils.encode(param); - String resJson = shopService.post(SPU_UPDATE_STOCK_URL, reqJson); - return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + return productStockService.updateStock(productId, skuId, diffType, num); } /** @@ -194,17 +187,12 @@ public WxChannelBaseResponse downProduct(String productId) throws WxErrorExcepti @Override public SkuStockResponse getSkuStock(String productId, String skuId) throws WxErrorException { - String reqJson = "{\"product_id\":\"" + productId + "\",\"sku_id\":\"" + skuId + "\"}"; - String resJson = shopService.post(SPU_GET_STOCK_URL, reqJson); - return ResponseUtils.decode(resJson, SkuStockResponse.class); + return productStockService.getSkuStock(productId, skuId); } @Override public SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException { - SkuStockBatchParam param = new SkuStockBatchParam(productIds); - String reqJson = JsonUtils.encode(param); - String resJson = shopService.post(SPU_GET_STOCK_BATCH_URL, reqJson); - return ResponseUtils.decode(resJson, SkuStockBatchResponse.class); + return productStockService.getSkuStockBatch(productIds); } @Override @@ -230,97 +218,68 @@ public ProductTagLinkResponse getProductTagLink(String productId) throws WxError @Override public GiftProductAddResponse addGiftProduct(GiftProductInfo info) throws WxErrorException { - String reqJson = JsonUtils.encode(info); - String resJson = shopService.post(GIFT_PRODUCT_ADD_URL, reqJson); - return ResponseUtils.decode(resJson, GiftProductAddResponse.class); + return giftService.addGiftProduct(info); } @Override public WxChannelBaseResponse updateGiftProduct(GiftProductInfo info) throws WxErrorException { - String reqJson = JsonUtils.encode(info); - String resJson = shopService.post(GIFT_PRODUCT_UPDATE_URL, reqJson); - return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + return giftService.updateGiftProduct(info); } @Override public WxChannelBaseResponse setProductAsGift(String productId) throws WxErrorException { - String reqJson = "{\"product_id\":\"" + productId + "\"}"; - String resJson = shopService.post(GIFT_PRODUCT_ON_SALE_SET_URL, reqJson); - return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + return giftService.setProductAsGift(productId); } @Override public GiftProductGetResponse getGiftProduct(String productId) throws WxErrorException { - String reqJson = "{\"product_id\":\"" + productId + "\"}"; - String resJson = shopService.post(GIFT_PRODUCT_GET_URL, reqJson); - return ResponseUtils.decode(resJson, GiftProductGetResponse.class); + return giftService.getGiftProduct(productId); } @Override public GiftProductListResponse listGiftProduct(GiftProductListParam param) throws WxErrorException { - String reqJson = JsonUtils.encode(param); - String resJson = shopService.post(GIFT_PRODUCT_LIST_URL, reqJson); - return ResponseUtils.decode(resJson, GiftProductListResponse.class); + return giftService.listGiftProduct(param); } @Override public WxChannelBaseResponse updateGiftStock(String productId, String skuId, Integer diffType, Integer num) throws WxErrorException { - SkuStockParam param = new SkuStockParam(productId, skuId, diffType, num); - String reqJson = JsonUtils.encode(param); - String resJson = shopService.post(GIFT_PRODUCT_STOCK_UPDATE_URL, reqJson); - return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + return giftService.updateGiftStock(productId, skuId, diffType, num); } @Override public GiftActivityAddResponse addGiftActivity(GiftActivityInfo info) throws WxErrorException { - GiftActivityAddParam param = new GiftActivityAddParam(info); - String reqJson = JsonUtils.encode(param); - String resJson = shopService.post(GIFT_ACTIVITY_ADD_URL, reqJson); - return ResponseUtils.decode(resJson, GiftActivityAddResponse.class); + return giftService.addGiftActivity(info); } @Override public WxChannelBaseResponse deleteGiftActivity(String activityId) throws WxErrorException { - String reqJson = "{\"activity_id\":\"" + activityId + "\"}"; - String resJson = shopService.post(GIFT_ACTIVITY_DELETE_URL, reqJson); - return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + return giftService.deleteGiftActivity(activityId); } @Override public WxChannelBaseResponse stopGiftActivity(String activityId) throws WxErrorException { - String reqJson = "{\"activity_id\":\"" + activityId + "\"}"; - String resJson = shopService.post(GIFT_ACTIVITY_STOP_URL, reqJson); - return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + return giftService.stopGiftActivity(activityId); } @Override public LimitTaskAddResponse addLimitTask(LimitTaskParam param) throws WxErrorException { - String reqJson = JsonUtils.encode(param); - String resJson = shopService.post(ADD_LIMIT_TASK_URL, reqJson); - return ResponseUtils.decode(resJson, LimitTaskAddResponse.class); + return limitedDiscountService.addLimitTask(param); } @Override public LimitTaskListResponse listLimitTask(Integer pageSize, String nextKey, Integer status) throws WxErrorException { - LimitTaskListParam param = new LimitTaskListParam(pageSize, nextKey, status); - String reqJson = JsonUtils.encode(param); - String resJson = shopService.post(LIST_LIMIT_TASK_URL, reqJson); - return ResponseUtils.decode(resJson, LimitTaskListResponse.class); + return limitedDiscountService.listLimitTask(pageSize, nextKey, status); } @Override public WxChannelBaseResponse stopLimitTask(String taskId) throws WxErrorException { - String reqJson = "{\"task_id\": \"" + taskId + "\"}"; - String resJson = shopService.post(STOP_LIMIT_TASK_URL, reqJson); - return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + return limitedDiscountService.stopLimitTask(taskId); } @Override public WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException { - String reqJson = "{\"task_id\": \"" + taskId + "\"}"; - String resJson = shopService.post(DELETE_LIMIT_TASK_URL, reqJson); - return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + return limitedDiscountService.deleteLimitTask(taskId); } } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java new file mode 100644 index 000000000..a45f5de09 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.channel.api.impl; + +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_BATCH_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_STOCK_URL; + +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelProductStockService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchParam; +import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; +import me.chanjar.weixin.channel.bean.product.SkuStockParam; +import me.chanjar.weixin.channel.bean.product.SkuStockResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信小店商品库存服务实现。 + */ +public class WxChannelProductStockServiceImpl implements WxChannelProductStockService { + + private final BaseWxChannelServiceImpl shopService; + + public WxChannelProductStockServiceImpl(BaseWxChannelServiceImpl shopService) { + this.shopService = shopService; + } + + @Override + public WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffType, Integer num) + throws WxErrorException { + SkuStockParam param = new SkuStockParam(productId, skuId, diffType, num); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(SPU_UPDATE_STOCK_URL, reqJson); + return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); + } + + @Override + public SkuStockResponse getSkuStock(String productId, String skuId) throws WxErrorException { + String reqJson = "{\"product_id\":\"" + productId + "\",\"sku_id\":\"" + skuId + "\"}"; + String resJson = shopService.post(SPU_GET_STOCK_URL, reqJson); + return ResponseUtils.decode(resJson, SkuStockResponse.class); + } + + @Override + public SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException { + SkuStockBatchParam param = new SkuStockBatchParam(productIds); + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(SPU_GET_STOCK_BATCH_URL, reqJson); + return ResponseUtils.decode(resJson, SkuStockBatchResponse.class); + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java new file mode 100644 index 000000000..1b85e6442 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java @@ -0,0 +1,116 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.List; +import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; +import me.chanjar.weixin.channel.bean.product.GiftProductInfo; +import org.testng.annotations.Test; + +/** + * Verifies domain service accessors and product service compatibility delegates. + */ +public class WxChannelServiceImplTest { + + @Test + public void shouldExposeProductDomainServicesAndKeepProductServiceRoutesCompatible() throws Exception { + CapturingChannelService channelService = new CapturingChannelService(); + assertNotNull(channelService.getGiftService()); + assertNotNull(channelService.getLimitedDiscountService()); + assertNotNull(channelService.getProductStockService()); + assertNotNull(channelService.getProductAssistantService()); + + assertSameRequest(channelService, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getGiftService().addGiftProduct(new GiftProductInfo()); + } + }, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getProductService().addGiftProduct(new GiftProductInfo()); + } + }); + assertSameRequest(channelService, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getLimitedDiscountService().addLimitTask(new LimitTaskParam()); + } + }, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getProductService().addLimitTask(new LimitTaskParam()); + } + }); + assertSameRequest(channelService, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getProductStockService().updateStock("product-id", "sku-id", 1, 2); + } + }, new RequestCall() { + @Override + public void call() throws Exception { + channelService.getProductService().updateStock("product-id", "sku-id", 1, 2); + } + }); + } + + private void assertSameRequest(CapturingChannelService channelService, RequestCall domainCall, + RequestCall compatibilityCall) throws Exception { + channelService.clearRequests(); + domainCall.call(); + List domainRequests = channelService.getRequests(); + channelService.clearRequests(); + compatibilityCall.call(); + assertEquals(channelService.getRequests(), domainRequests); + } + + private interface RequestCall { + void call() throws Exception; + } + + private static class CapturingChannelService extends WxChannelServiceImpl { + private final List requests = new ArrayList<>(); + + @Override + public String post(String url, String postData) { + this.requests.add(new Request(url, postData)); + return "{\"errcode\":0}"; + } + + private List getRequests() { + return new ArrayList<>(this.requests); + } + + private void clearRequests() { + this.requests.clear(); + } + } + + private static class Request { + private final String url; + private final String json; + + private Request(String url, String json) { + this.url = url; + this.json = json; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Request)) { + return false; + } + Request request = (Request) other; + return this.url.equals(request.url) && this.json.equals(request.json); + } + + @Override + public int hashCode() { + return 31 * this.url.hashCode() + this.json.hashCode(); + } + } +} From cf5d0ff1a6300e1ad0bd264a776b872d898417ae Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Sat, 22 Aug 2026 12:55:09 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=8F=B7=E9=A2=86=E5=9F=9F=E6=9C=8D=E5=8A=A1=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7=E4=B8=8E=E6=B5=8B=E8=AF=95=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- weixin-java-channel/pom.xml | 1 + .../weixin/channel/api/WxChannelService.java | 16 ++- .../api/impl/WxChannelServiceImplTest.java | 106 ++++++++++++------ .../src/test/resources/testng.xml | 5 + 4 files changed, 90 insertions(+), 38 deletions(-) diff --git a/weixin-java-channel/pom.xml b/weixin-java-channel/pom.xml index 10417674f..754290049 100644 --- a/weixin-java-channel/pom.xml +++ b/weixin-java-channel/pom.xml @@ -133,6 +133,7 @@ org.apache.maven.plugins maven-surefire-plugin + false src/test/resources/testng.xml diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java index 772158785..52cc924bc 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelService.java @@ -40,28 +40,36 @@ public interface WxChannelService extends BaseWxChannelService { * * @return 赠品与买赠活动服务 */ - WxChannelGiftService getGiftService(); + default WxChannelGiftService getGiftService() { + throw new UnsupportedOperationException("Gift service is not supported by this implementation"); + } /** * 限时抢购服务 * * @return 限时抢购服务 */ - WxChannelLimitedDiscountService getLimitedDiscountService(); + default WxChannelLimitedDiscountService getLimitedDiscountService() { + throw new UnsupportedOperationException("Limited discount service is not supported by this implementation"); + } /** * 商品库存服务 * * @return 商品库存服务 */ - WxChannelProductStockService getProductStockService(); + default WxChannelProductStockService getProductStockService() { + throw new UnsupportedOperationException("Product stock service is not supported by this implementation"); + } /** * 商品辅助功能服务 * * @return 商品辅助功能服务 */ - WxChannelProductAssistantService getProductAssistantService(); + default WxChannelProductAssistantService getProductAssistantService() { + throw new UnsupportedOperationException("Product assistant service is not supported by this implementation"); + } /** * 仓库服务 diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java index 1b85e6442..a4bb5db6c 100644 --- a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelServiceImplTest.java @@ -2,10 +2,15 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; import java.util.List; import me.chanjar.weixin.channel.api.WxChannelService; +import me.chanjar.weixin.channel.bean.limit.LimitSku; import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; import me.chanjar.weixin.channel.bean.product.GiftProductInfo; import org.testng.annotations.Test; @@ -16,56 +21,102 @@ public class WxChannelServiceImplTest { @Test - public void shouldExposeProductDomainServicesAndKeepProductServiceRoutesCompatible() throws Exception { + public void shouldKeepNewDomainServiceAccessorsCompatibleWithExistingImplementations() throws Exception { + assertDefaultMethod("getGiftService"); + assertDefaultMethod("getLimitedDiscountService"); + assertDefaultMethod("getProductStockService"); + assertDefaultMethod("getProductAssistantService"); + } + + @Test + public void shouldExposeProductDomainServices() { CapturingChannelService channelService = new CapturingChannelService(); assertNotNull(channelService.getGiftService()); assertNotNull(channelService.getLimitedDiscountService()); assertNotNull(channelService.getProductStockService()); assertNotNull(channelService.getProductAssistantService()); + } + + @Test + public void shouldRouteGiftProductCallsExactlyOnce() throws Exception { + CapturingChannelService channelService = new CapturingChannelService(); + GiftProductInfo info = new GiftProductInfo(); + info.setListing(1); - assertSameRequest(channelService, new RequestCall() { + assertRequest(channelService, new RequestCall() { @Override public void call() throws Exception { - channelService.getGiftService().addGiftProduct(new GiftProductInfo()); + channelService.getGiftService().addGiftProduct(info); } - }, new RequestCall() { + }, "https://api.weixin.qq.com/channels/ec/product/gift/add", "{\"listing\":1}"); + assertRequest(channelService, new RequestCall() { @Override public void call() throws Exception { - channelService.getProductService().addGiftProduct(new GiftProductInfo()); + channelService.getProductService().addGiftProduct(info); } - }); - assertSameRequest(channelService, new RequestCall() { + }, "https://api.weixin.qq.com/channels/ec/product/gift/add", "{\"listing\":1}"); + } + + @Test + public void shouldRouteLimitedDiscountCallsExactlyOnce() throws Exception { + CapturingChannelService channelService = new CapturingChannelService(); + LimitTaskParam param = new LimitTaskParam(); + param.setProductId("product-id"); + param.setStartTime(new Date(1_000)); + param.setEndTime(new Date(2_000)); + param.setSkus(Arrays.asList(new LimitSku("sku-id", 100, 2))); + + assertRequest(channelService, new RequestCall() { @Override public void call() throws Exception { - channelService.getLimitedDiscountService().addLimitTask(new LimitTaskParam()); + channelService.getLimitedDiscountService().addLimitTask(param); } - }, new RequestCall() { + }, "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/add", + "{\"product_id\":\"product-id\",\"start_time\":1000,\"end_time\":2000," + + "\"limited_discount_skus\":[{\"sku_id\":\"sku-id\",\"sale_price\":100,\"sale_stock\":2}]}"); + assertRequest(channelService, new RequestCall() { @Override public void call() throws Exception { - channelService.getProductService().addLimitTask(new LimitTaskParam()); + channelService.getProductService().addLimitTask(param); } - }); - assertSameRequest(channelService, new RequestCall() { + }, "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/add", + "{\"product_id\":\"product-id\",\"start_time\":1000,\"end_time\":2000," + + "\"limited_discount_skus\":[{\"sku_id\":\"sku-id\",\"sale_price\":100,\"sale_stock\":2}]}"); + } + + @Test + public void shouldRouteStockCallsExactlyOnce() throws Exception { + CapturingChannelService channelService = new CapturingChannelService(); + + assertRequest(channelService, new RequestCall() { @Override public void call() throws Exception { channelService.getProductStockService().updateStock("product-id", "sku-id", 1, 2); } - }, new RequestCall() { + }, "https://api.weixin.qq.com/channels/ec/product/stock/update", + "{\"product_id\":\"product-id\",\"sku_id\":\"sku-id\",\"diff_type\":1,\"num\":2}"); + assertRequest(channelService, new RequestCall() { @Override public void call() throws Exception { channelService.getProductService().updateStock("product-id", "sku-id", 1, 2); } - }); + }, "https://api.weixin.qq.com/channels/ec/product/stock/update", + "{\"product_id\":\"product-id\",\"sku_id\":\"sku-id\",\"diff_type\":1,\"num\":2}"); } - private void assertSameRequest(CapturingChannelService channelService, RequestCall domainCall, - RequestCall compatibilityCall) throws Exception { - channelService.clearRequests(); - domainCall.call(); - List domainRequests = channelService.getRequests(); + private void assertDefaultMethod(String methodName) throws Exception { + Method method = WxChannelService.class.getMethod(methodName); + assertTrue(method.isDefault(), methodName + " must remain compatible with existing implementations"); + } + + private void assertRequest(CapturingChannelService channelService, RequestCall call, + String expectedUrl, String expectedJson) throws Exception { channelService.clearRequests(); - compatibilityCall.call(); - assertEquals(channelService.getRequests(), domainRequests); + call.call(); + List requests = channelService.getRequests(); + assertEquals(requests.size(), 1); + assertEquals(requests.get(0).url, expectedUrl); + assertEquals(requests.get(0).json, expectedJson); } private interface RequestCall { @@ -99,18 +150,5 @@ private Request(String url, String json) { this.json = json; } - @Override - public boolean equals(Object other) { - if (!(other instanceof Request)) { - return false; - } - Request request = (Request) other; - return this.url.equals(request.url) && this.json.equals(request.json); - } - - @Override - public int hashCode() { - return 31 * this.url.hashCode() + this.json.hashCode(); - } } } diff --git a/weixin-java-channel/src/test/resources/testng.xml b/weixin-java-channel/src/test/resources/testng.xml index 819ebcf5f..66e92f6cf 100644 --- a/weixin-java-channel/src/test/resources/testng.xml +++ b/weixin-java-channel/src/test/resources/testng.xml @@ -16,4 +16,9 @@ + + + + + From 93e06292076f6b180cca72b3c6344f88fbae3560 Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Sat, 22 Aug 2026 12:59:37 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat(channel):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=99=90=E6=97=B6=E6=8A=A2=E8=B4=AD=E6=9B=B4=E6=96=B0=E5=92=8C?= =?UTF-8?q?=E5=BA=93=E5=AD=98=E6=B5=81=E6=B0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/WxChannelLimitedDiscountService.java | 11 +++ .../api/WxChannelProductStockService.java | 11 +++ .../WxChannelLimitedDiscountServiceImpl.java | 10 +++ .../WxChannelProductStockServiceImpl.java | 10 +++ .../channel/bean/limit/LimitSkuUpdate.java | 32 ++++++++ .../bean/limit/LimitTaskUpdateParam.java | 41 ++++++++++ .../bean/limit/LimitTaskUpdateResponse.java | 26 ++++++ .../bean/product/stock/StockFlowExtInfo.java | 44 ++++++++++ .../bean/product/stock/StockFlowInfo.java | 44 ++++++++++ .../bean/product/stock/StockFlowParam.java | 57 +++++++++++++ .../bean/product/stock/StockFlowResponse.java | 48 +++++++++++ .../constant/WxChannelApiUrlConstants.java | 4 + ...ChannelLimitedDiscountServiceImplTest.java | 62 ++++++++++++++ .../WxChannelProductStockServiceImplTest.java | 81 +++++++++++++++++++ .../src/test/resources/testng.xml | 2 + 15 files changed, 483 insertions(+) create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowExtInfo.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java create mode 100644 weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java create mode 100644 weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java index d51d06fd2..40e6776f6 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelLimitedDiscountService.java @@ -4,6 +4,8 @@ import me.chanjar.weixin.channel.bean.limit.LimitTaskAddResponse; import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse; import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateResponse; import me.chanjar.weixin.common.error.WxErrorException; /** @@ -48,4 +50,13 @@ public interface WxChannelLimitedDiscountService { * @throws WxErrorException 异常 */ WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorException; + + /** + * 更新限时抢购任务。 + * + * @param param 更新任务参数 + * @return 更新任务响应 + * @throws WxErrorException 异常 + */ + LimitTaskUpdateResponse updateLimitTask(LimitTaskUpdateParam param) throws WxErrorException; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java index 6955de5e6..dc2fa4f58 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductStockService.java @@ -4,6 +4,8 @@ import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; import me.chanjar.weixin.channel.bean.product.SkuStockResponse; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowParam; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowResponse; import me.chanjar.weixin.common.error.WxErrorException; /** @@ -42,4 +44,13 @@ WxChannelBaseResponse updateStock(String productId, String skuId, Integer diffTy * @throws WxErrorException 异常 */ SkuStockBatchResponse getSkuStockBatch(List productIds) throws WxErrorException; + + /** + * 获取商品库存流水。 + * + * @param param 库存流水查询参数 + * @return 库存流水响应 + * @throws WxErrorException 异常 + */ + StockFlowResponse getStockFlow(StockFlowParam param) throws WxErrorException; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java index e9eb5857b..26cca4d40 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImpl.java @@ -4,6 +4,7 @@ import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.DELETE_LIMIT_TASK_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.LIST_LIMIT_TASK_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.STOP_LIMIT_TASK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.UPDATE_LIMIT_TASK_URL; import me.chanjar.weixin.channel.api.WxChannelLimitedDiscountService; import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; @@ -11,6 +12,8 @@ import me.chanjar.weixin.channel.bean.limit.LimitTaskListParam; import me.chanjar.weixin.channel.bean.limit.LimitTaskListResponse; import me.chanjar.weixin.channel.bean.limit.LimitTaskParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateResponse; import me.chanjar.weixin.channel.util.JsonUtils; import me.chanjar.weixin.channel.util.ResponseUtils; import me.chanjar.weixin.common.error.WxErrorException; @@ -55,4 +58,11 @@ public WxChannelBaseResponse deleteLimitTask(String taskId) throws WxErrorExcept String resJson = shopService.post(DELETE_LIMIT_TASK_URL, reqJson); return ResponseUtils.decode(resJson, WxChannelBaseResponse.class); } + + @Override + public LimitTaskUpdateResponse updateLimitTask(LimitTaskUpdateParam param) throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(UPDATE_LIMIT_TASK_URL, reqJson); + return ResponseUtils.decode(resJson, LimitTaskUpdateResponse.class); + } } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java index a45f5de09..1b3b37142 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImpl.java @@ -1,6 +1,7 @@ package me.chanjar.weixin.channel.api.impl; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_BATCH_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_FLOW_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_GET_STOCK_URL; import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.SPU_UPDATE_STOCK_URL; @@ -11,6 +12,8 @@ import me.chanjar.weixin.channel.bean.product.SkuStockBatchResponse; import me.chanjar.weixin.channel.bean.product.SkuStockParam; import me.chanjar.weixin.channel.bean.product.SkuStockResponse; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowParam; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowResponse; import me.chanjar.weixin.channel.util.JsonUtils; import me.chanjar.weixin.channel.util.ResponseUtils; import me.chanjar.weixin.common.error.WxErrorException; @@ -49,4 +52,11 @@ public SkuStockBatchResponse getSkuStockBatch(List productIds) throws Wx String resJson = shopService.post(SPU_GET_STOCK_BATCH_URL, reqJson); return ResponseUtils.decode(resJson, SkuStockBatchResponse.class); } + + @Override + public StockFlowResponse getStockFlow(StockFlowParam param) throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(SPU_GET_STOCK_FLOW_URL, reqJson); + return ResponseUtils.decode(resJson, StockFlowResponse.class); + } } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java new file mode 100644 index 000000000..98c14a402 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitSkuUpdate.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 限时抢购任务的 SKU 更新信息。 + */ +@Data +@NoArgsConstructor +public class LimitSkuUpdate implements Serializable { + + private static final long serialVersionUID = 4209672674401016015L; + + /** SKU 所属商品 ID。 */ + @JsonProperty("product_id") + private String productId; + + /** SKU ID。 */ + @JsonProperty("sku_id") + private String skuId; + + /** SKU 抢购价格,单位为分。 */ + @JsonProperty("sale_price") + private Integer salePrice; + + /** 参与抢购的商品库存。 */ + @JsonProperty("sale_stock") + private Integer saleStock; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java new file mode 100644 index 000000000..40c7650ce --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateParam.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 更新限时抢购任务请求参数。 + */ +@Data +@NoArgsConstructor +public class LimitTaskUpdateParam implements Serializable { + + private static final long serialVersionUID = 7277247203887803045L; + + /** 限时抢购任务 ID。 */ + @JsonProperty("task_id") + private String taskId; + + /** 当前活动状态:0 待开始,1 进行中。 */ + @JsonProperty("status") + private Integer status; + + /** 活动开始时间,秒级时间戳。 */ + @JsonProperty("start_time") + private Long startTime; + + /** 活动结束时间,秒级时间戳。 */ + @JsonProperty("end_time") + private Long endTime; + + /** 活动名称。 */ + @JsonProperty("title") + private String title; + + /** SKU 抢购信息列表。 */ + @JsonProperty("limited_discount_skus") + private List skus; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java new file mode 100644 index 000000000..73afc8247 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/limit/LimitTaskUpdateResponse.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.channel.bean.limit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 更新限时抢购任务响应。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class LimitTaskUpdateResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 4429517792042527433L; + + /** 限时抢购任务 ID。 */ + @JsonProperty("task_id") + private String taskId; + + /** 活动名称。 */ + @JsonProperty("title") + private String title; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowExtInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowExtInfo.java new file mode 100644 index 000000000..c08e35c47 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowExtInfo.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.product.stock; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 库存流水额外信息。 + */ +@Data +@NoArgsConstructor +public class StockFlowExtInfo implements Serializable { + + private static final long serialVersionUID = 1170328051641116647L; + + /** 归还的源库存子类型。 */ + @JsonProperty("unmove_from_stock_sub_type") + private Integer unmoveFromStockSubType; + + /** 分配的目标库存子类型。 */ + @JsonProperty("move_to_stock_sub_type") + private Integer moveToStockSubType; + + /** 操作来源。 */ + @JsonProperty("upload_source") + private Integer uploadSource; + + /** 订单 ID。 */ + @JsonProperty("order_id") + private String orderId; + + /** 区域仓库 ID。 */ + @JsonProperty("out_warehouse_id") + private String outWarehouseId; + + /** 限时抢购任务 ID。 */ + @JsonProperty("limited_discount_id") + private String limitedDiscountId; + + /** 达人的视频号 finder_id。 */ + @JsonProperty("finder_id") + private String finderId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java new file mode 100644 index 000000000..a15b0018e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowInfo.java @@ -0,0 +1,44 @@ +package me.chanjar.weixin.channel.bean.product.stock; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 库存流水信息。 + */ +@Data +@NoArgsConstructor +public class StockFlowInfo implements Serializable { + + private static final long serialVersionUID = 4094168882102603379L; + + /** 操作数量。 */ + @JsonProperty("amount") + private Integer amount; + + /** 操作前数量。 */ + @JsonProperty("beginning_amount") + private Integer beginningAmount; + + /** 操作后数量。 */ + @JsonProperty("ending_amount") + private Integer endingAmount; + + /** 库存子类型。 */ + @JsonProperty("stock_sub_type") + private Integer stockSubType; + + /** 库存事件类型。 */ + @JsonProperty("op_type") + private Integer opType; + + /** 流水发生时间,秒级时间戳。 */ + @JsonProperty("update_time") + private Long updateTime; + + /** 额外信息。 */ + @JsonProperty("ext_info") + private StockFlowExtInfo extInfo; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java new file mode 100644 index 000000000..cacd2ca3e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowParam.java @@ -0,0 +1,57 @@ +package me.chanjar.weixin.channel.bean.product.stock; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 获取库存流水请求参数。 + */ +@Data +@NoArgsConstructor +public class StockFlowParam implements Serializable { + + private static final long serialVersionUID = -7882480822919984178L; + + /** 内部商品 ID。 */ + @JsonProperty("product_id") + private String productId; + + /** 内部 SKU ID。 */ + @JsonProperty("sku_id") + private String skuId; + + /** 库存类型。 */ + @JsonProperty("stock_type") + private Integer stockType; + + /** 达人的视频号 finder_id。 */ + @JsonProperty("finder_id") + private String finderId; + + /** 查询开始时间,秒级时间戳。 */ + @JsonProperty("begin_time") + private Long beginTime; + + /** 查询结束时间,秒级时间戳。 */ + @JsonProperty("end_time") + private Long endTime; + + /** 库存事件类型列表。 */ + @JsonProperty("op_type_list") + private List opTypeList; + + /** 每页数量。 */ + @JsonProperty("page_size") + private Integer pageSize; + + /** 上次请求返回的翻页上下文。 */ + @JsonProperty("next_key") + private String nextKey; + + /** 库存类型 ID。 */ + @JsonProperty("stock_type_id") + private String stockTypeId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java new file mode 100644 index 000000000..4f779b64a --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/stock/StockFlowResponse.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.channel.bean.product.stock; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 获取库存流水响应。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class StockFlowResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7420844779570799705L; + + /** 本次翻页的上下文。 */ + private String nextKey; + + /** 库存流水。 */ + private List stockFlowInfoList; + + @JsonProperty("data") + private void unpackData(StockFlowData data) { + if (data == null) { + return; + } + this.nextKey = data.getNextKey(); + this.stockFlowInfoList = data.getStockFlowInfoList(); + } + + @Data + @NoArgsConstructor + private static class StockFlowData implements Serializable { + + private static final long serialVersionUID = -5455751387420196045L; + + @JsonProperty("next_key") + private String nextKey; + + @JsonProperty("stock_flow_info_list") + private List stockFlowInfoList; + } +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java index b095589bf..41fd5d009 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java @@ -166,6 +166,8 @@ public interface Spu { String SPU_GET_STOCK_BATCH_URL = "https://api.weixin.qq.com/channels/ec/product/stock/batchget"; /** 更新商品库存 */ String SPU_UPDATE_STOCK_URL = "https://api.weixin.qq.com/channels/ec/product/stock/update"; + /** 获取库存流水 */ + String SPU_GET_STOCK_FLOW_URL = "https://api.weixin.qq.com/channels/ec/product/stock/getflow"; /** 添加非卖商品 */ String GIFT_PRODUCT_ADD_URL = "https://api.weixin.qq.com/channels/ec/product/gift/add"; /** 更新非卖商品 */ @@ -192,6 +194,8 @@ public interface Spu { String STOP_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/stop"; /** 删除限时抢购任务 */ String DELETE_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/delete"; + /** 更新限时抢购任务 */ + String UPDATE_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/update"; } /** 区域仓库 */ diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java new file mode 100644 index 000000000..2ee7c5b70 --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelLimitedDiscountServiceImplTest.java @@ -0,0 +1,62 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; + +import java.util.Arrays; +import me.chanjar.weixin.channel.bean.limit.LimitSkuUpdate; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateParam; +import me.chanjar.weixin.channel.bean.limit.LimitTaskUpdateResponse; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Test; + +/** + * Tests for {@link WxChannelLimitedDiscountServiceImpl}. + */ +public class WxChannelLimitedDiscountServiceImplTest { + + @Test + public void shouldUpdateLimitedDiscountTaskAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"," + + "\"task_id\":\"task-id\",\"title\":\"updated title\"}"; + LimitSkuUpdate sku = new LimitSkuUpdate(); + sku.setProductId("product-id"); + sku.setSkuId("sku-id"); + sku.setSalePrice(2888); + sku.setSaleStock(5); + LimitTaskUpdateParam param = new LimitTaskUpdateParam(); + param.setTaskId("task-id"); + param.setStatus(0); + param.setStartTime(1_700_000_000L); + param.setEndTime(1_700_003_600L); + param.setTitle("updated title"); + param.setSkus(Arrays.asList(sku)); + + LimitTaskUpdateResponse response = channelService.getLimitedDiscountService().updateLimitTask(param); + + assertEquals(channelService.url, + "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/update"); + assertEquals(channelService.request, + "{\"task_id\":\"task-id\",\"status\":0,\"start_time\":1700000000," + + "\"end_time\":1700003600,\"title\":\"updated title\"," + + "\"limited_discount_skus\":[{\"product_id\":\"product-id\"," + + "\"sku_id\":\"sku-id\",\"sale_price\":2888,\"sale_stock\":5}]}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getErrMsg(), "ok"); + assertEquals(response.getTaskId(), "task-id"); + assertEquals(response.getTitle(), "updated title"); + } + + private static class CapturingChannelService extends WxChannelServiceImpl { + private String url; + private String request; + private String response; + + @Override + public String post(String url, String postData) { + this.url = url; + this.request = postData; + return this.response; + } + } +} diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java new file mode 100644 index 000000000..86c26043f --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductStockServiceImplTest.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; + +import java.util.Arrays; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowInfo; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowParam; +import me.chanjar.weixin.channel.bean.product.stock.StockFlowResponse; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Test; + +/** + * Tests for {@link WxChannelProductStockServiceImpl}. + */ +public class WxChannelProductStockServiceImplTest { + + @Test + public void shouldGetStockFlowAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\",\"data\":{" + + "\"stock_flow_info_list\":[{\"amount\":300,\"beginning_amount\":842," + + "\"ending_amount\":542,\"stock_sub_type\":1,\"op_type\":6," + + "\"update_time\":1689735682,\"ext_info\":{\"unmove_from_stock_sub_type\":3," + + "\"move_to_stock_sub_type\":4," + + "\"upload_source\":2,\"order_id\":\"order-id\"," + + "\"out_warehouse_id\":\"warehouse-id\"," + + "\"limited_discount_id\":\"discount-id\",\"finder_id\":\"finder-id\"}}]," + + "\"next_key\":\"next-page\"}}"; + StockFlowParam param = new StockFlowParam(); + param.setProductId("product-id"); + param.setSkuId("sku-id"); + param.setStockType(1); + param.setFinderId("finder-id"); + param.setBeginTime(1_689_218_360L); + param.setEndTime(1_689_736_760L); + param.setOpTypeList(Arrays.asList(1, 2)); + param.setPageSize(10); + param.setNextKey("current-page"); + param.setStockTypeId("stock-type-id"); + + StockFlowResponse response = channelService.getProductStockService().getStockFlow(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/stock/getflow"); + assertEquals(channelService.request, + "{\"product_id\":\"product-id\",\"sku_id\":\"sku-id\",\"stock_type\":1," + + "\"finder_id\":\"finder-id\",\"begin_time\":1689218360," + + "\"end_time\":1689736760,\"op_type_list\":[1,2],\"page_size\":10," + + "\"next_key\":\"current-page\",\"stock_type_id\":\"stock-type-id\"}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getErrMsg(), "ok"); + assertEquals(response.getNextKey(), "next-page"); + assertEquals(response.getStockFlowInfoList().size(), 1); + StockFlowInfo flow = response.getStockFlowInfoList().get(0); + assertEquals(flow.getAmount(), Integer.valueOf(300)); + assertEquals(flow.getBeginningAmount(), Integer.valueOf(842)); + assertEquals(flow.getEndingAmount(), Integer.valueOf(542)); + assertEquals(flow.getStockSubType(), Integer.valueOf(1)); + assertEquals(flow.getOpType(), Integer.valueOf(6)); + assertEquals(flow.getUpdateTime(), Long.valueOf(1_689_735_682L)); + assertEquals(flow.getExtInfo().getUnmoveFromStockSubType(), Integer.valueOf(3)); + assertEquals(flow.getExtInfo().getMoveToStockSubType(), Integer.valueOf(4)); + assertEquals(flow.getExtInfo().getUploadSource(), Integer.valueOf(2)); + assertEquals(flow.getExtInfo().getOrderId(), "order-id"); + assertEquals(flow.getExtInfo().getOutWarehouseId(), "warehouse-id"); + assertEquals(flow.getExtInfo().getLimitedDiscountId(), "discount-id"); + assertEquals(flow.getExtInfo().getFinderId(), "finder-id"); + } + + private static class CapturingChannelService extends WxChannelServiceImpl { + private String url; + private String request; + private String response; + + @Override + public String post(String url, String postData) { + this.url = url; + this.request = postData; + return this.response; + } + } +} diff --git a/weixin-java-channel/src/test/resources/testng.xml b/weixin-java-channel/src/test/resources/testng.xml index 66e92f6cf..77ace72b7 100644 --- a/weixin-java-channel/src/test/resources/testng.xml +++ b/weixin-java-channel/src/test/resources/testng.xml @@ -19,6 +19,8 @@ + + From 0f7ed5ce543da3f9d086ba36acc63d5c347cd3ce Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Sat, 22 Aug 2026 18:47:52 +0800 Subject: [PATCH 4/4] =?UTF-8?q?feat(channel):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=95=86=E5=93=81=E8=BE=85=E5=8A=A9=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/WxChannelProductAssistantService.java | 70 ++++++++ .../WxChannelProductAssistantServiceImpl.java | 61 +++++++ .../assistant/BeginTimingSaleParam.java | 24 +++ .../assistant/CancelTimingSaleParam.java | 20 +++ .../assistant/CategoryPreCheckParam.java | 20 +++ .../assistant/CategoryPreCheckResponse.java | 27 +++ .../product/assistant/ExternalAttribute.java | 24 +++ .../ExternalProductMappingNewParam.java | 41 +++++ .../ExternalProductMappingNewResponse.java | 23 +++ .../ExternalProductMappingParam.java | 32 ++++ .../ExternalProductMappingResponse.java | 35 ++++ .../assistant/ProductBrandRecommendParam.java | 33 ++++ .../ProductBrandRecommendResponse.java | 30 ++++ .../constant/WxChannelApiUrlConstants.java | 13 ++ ...hannelProductAssistantServiceImplTest.java | 169 ++++++++++++++++++ .../src/test/resources/testng.xml | 1 + 16 files changed, 623 insertions(+) create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/BeginTimingSaleParam.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java create mode 100644 weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java create mode 100644 weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java index ad5f912c6..2f39249f0 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/WxChannelProductAssistantService.java @@ -1,7 +1,77 @@ package me.chanjar.weixin.channel.api; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.assistant.BeginTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CancelTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendResponse; +import me.chanjar.weixin.common.error.WxErrorException; + /** * 微信小店商品辅助功能服务。 */ public interface WxChannelProductAssistantService { + + /** + * 发品前校验。 + * + * @param param 校验参数 + * @return 校验结果 + * @throws WxErrorException 异常 + */ + CategoryPreCheckResponse categoryPreCheck(CategoryPreCheckParam param) throws WxErrorException; + + /** + * 获取商品品牌推荐。 + * + * @param param 推荐参数 + * @return 推荐结果 + * @throws WxErrorException 异常 + */ + ProductBrandRecommendResponse getProductBrandRecommend(ProductBrandRecommendParam param) + throws WxErrorException; + + /** + * 获取站内外商品属性映射。 + * + * @param param 映射参数 + * @return 映射结果 + * @throws WxErrorException 异常 + */ + ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param) + throws WxErrorException; + + /** + * 获取商品属性映射及推荐。 + * + * @param param 映射参数 + * @return 映射结果 + * @throws WxErrorException 异常 + */ + ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param) + throws WxErrorException; + + /** + * 将定时开售商品改为立即开售。 + * + * @param param 开售参数 + * @return 操作结果 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse beginTimingSale(BeginTimingSaleParam param) throws WxErrorException; + + /** + * 取消商品定时开售。 + * + * @param param 取消参数 + * @return 操作结果 + * @throws WxErrorException 异常 + */ + WxChannelBaseResponse cancelTimingSale(CancelTimingSaleParam param) throws WxErrorException; } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java index d8be9995b..7f1de84d0 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImpl.java @@ -1,6 +1,27 @@ package me.chanjar.weixin.channel.api.impl; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.BEGIN_TIMING_SALE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.CANCEL_TIMING_SALE_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.CATEGORY_PRE_CHECK_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.EXTERNAL_PRODUCT_MAPPING_NEW_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.EXTERNAL_PRODUCT_MAPPING_URL; +import static me.chanjar.weixin.channel.constant.WxChannelApiUrlConstants.Spu.PRODUCT_BRAND_RECOMMEND_URL; + import me.chanjar.weixin.channel.api.WxChannelProductAssistantService; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.assistant.BeginTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CancelTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendResponse; +import me.chanjar.weixin.channel.util.JsonUtils; +import me.chanjar.weixin.channel.util.ResponseUtils; +import me.chanjar.weixin.common.error.WxErrorException; /** * 微信小店商品辅助功能服务实现。 @@ -13,4 +34,44 @@ public class WxChannelProductAssistantServiceImpl implements WxChannelProductAss public WxChannelProductAssistantServiceImpl(BaseWxChannelServiceImpl shopService) { this.shopService = shopService; } + + @Override + public CategoryPreCheckResponse categoryPreCheck(CategoryPreCheckParam param) throws WxErrorException { + return post(CATEGORY_PRE_CHECK_URL, param, CategoryPreCheckResponse.class); + } + + @Override + public ProductBrandRecommendResponse getProductBrandRecommend(ProductBrandRecommendParam param) + throws WxErrorException { + return post(PRODUCT_BRAND_RECOMMEND_URL, param, ProductBrandRecommendResponse.class); + } + + @Override + public ExternalProductMappingResponse externalProductMapping(ExternalProductMappingParam param) + throws WxErrorException { + return post(EXTERNAL_PRODUCT_MAPPING_URL, param, ExternalProductMappingResponse.class); + } + + @Override + public ExternalProductMappingNewResponse externalProductMappingNew(ExternalProductMappingNewParam param) + throws WxErrorException { + return post(EXTERNAL_PRODUCT_MAPPING_NEW_URL, param, ExternalProductMappingNewResponse.class); + } + + @Override + public WxChannelBaseResponse beginTimingSale(BeginTimingSaleParam param) throws WxErrorException { + return post(BEGIN_TIMING_SALE_URL, param, WxChannelBaseResponse.class); + } + + @Override + public WxChannelBaseResponse cancelTimingSale(CancelTimingSaleParam param) throws WxErrorException { + return post(CANCEL_TIMING_SALE_URL, param, WxChannelBaseResponse.class); + } + + private T post(String url, Object param, Class responseType) + throws WxErrorException { + String reqJson = JsonUtils.encode(param); + String resJson = shopService.post(url, reqJson); + return ResponseUtils.decode(resJson, responseType); + } } diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/BeginTimingSaleParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/BeginTimingSaleParam.java new file mode 100644 index 000000000..2a624ec43 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/BeginTimingSaleParam.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品立即开售参数。 + */ +@Data +@NoArgsConstructor +public class BeginTimingSaleParam implements Serializable { + + private static final long serialVersionUID = -1525220756273987016L; + + /** 商品 ID。 */ + @JsonProperty("product_id") + private String productId; + + /** 定时开售任务 ID。 */ + @JsonProperty("task_id") + private String taskId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java new file mode 100644 index 000000000..25980a148 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CancelTimingSaleParam.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 取消商品开售参数。 + */ +@Data +@NoArgsConstructor +public class CancelTimingSaleParam implements Serializable { + + private static final long serialVersionUID = -3750831026611057323L; + + /** 商品 ID。 */ + @JsonProperty("product_id") + private String productId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java new file mode 100644 index 000000000..ee9306666 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckParam.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 发品前校验参数。 + */ +@Data +@NoArgsConstructor +public class CategoryPreCheckParam implements Serializable { + + private static final long serialVersionUID = 3616569394767815856L; + + /** 叶子类目 ID,不传时只校验店铺相关条件。 */ + @JsonProperty("cat_id") + private Long catId; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java new file mode 100644 index 000000000..8559f2b0e --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/CategoryPreCheckResponse.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 发品前校验响应。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class CategoryPreCheckResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = 8912798390684239592L; + + /** 是否全部校验通过。 */ + @JsonProperty("all_pass") + private Boolean allPass; + + /** 校验不通过的原因。 */ + @JsonProperty("fail_reasons") + private List failReasons; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java new file mode 100644 index 000000000..e173a85d0 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalAttribute.java @@ -0,0 +1,24 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品属性键值对。 + */ +@Data +@NoArgsConstructor +public class ExternalAttribute implements Serializable { + + private static final long serialVersionUID = -8639178782951125101L; + + /** 属性名。 */ + @JsonProperty("key") + private String key; + + /** 属性值。 */ + @JsonProperty("value") + private String value; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java new file mode 100644 index 000000000..89e0be65c --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewParam.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品属性映射及推荐参数。 + */ +@Data +@NoArgsConstructor +public class ExternalProductMappingNewParam implements Serializable { + + private static final long serialVersionUID = -4942505655791636645L; + + /** 叶子类目 ID。 */ + @JsonProperty("cat_id") + private Long catId; + + /** 外部商品类目名称。 */ + @JsonProperty("external_category_name") + private String externalCategoryName; + + /** 商品主图,至少一张。 */ + @JsonProperty("head_imgs") + private List headImgs; + + /** 商品详情图。 */ + @JsonProperty("detail_imgs") + private List detailImgs; + + /** 商品标题。 */ + @JsonProperty("title") + private String title; + + /** 外部商品属性列表。 */ + @JsonProperty("external_attributes") + private List externalAttributes; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java new file mode 100644 index 000000000..87b09e03b --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingNewResponse.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品属性映射及推荐响应。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ExternalProductMappingNewResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -6192580254142696913L; + + /** 映射属性结果。 */ + @JsonProperty("attributes") + private List attributes; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java new file mode 100644 index 000000000..c31e0f0f2 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingParam.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 站内外商品属性映射参数。 + */ +@Data +@NoArgsConstructor +public class ExternalProductMappingParam implements Serializable { + + private static final long serialVersionUID = 1944528166283981889L; + + /** 叶子类目 ID。 */ + @JsonProperty("cat_id") + private Long catId; + + /** 外部商品属性名。 */ + @JsonProperty("external_attribute_name") + private String externalAttributeName; + + /** 外部商品属性值。 */ + @JsonProperty("external_attribute_value") + private String externalAttributeValue; + + /** 外部商品类目名称。 */ + @JsonProperty("external_category_name") + private String externalCategoryName; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java new file mode 100644 index 000000000..e97d4a94d --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ExternalProductMappingResponse.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 站内外商品属性映射响应。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ExternalProductMappingResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -2267639791023044849L; + + /** 外部商品属性名。 */ + @JsonProperty("external_attribute_name") + private String externalAttributeName; + + /** 外部商品属性值。 */ + @JsonProperty("external_attribute_value") + private String externalAttributeValue; + + /** 内部商品属性名。 */ + @JsonProperty("internal_attribute_name") + private String internalAttributeName; + + /** 内部商品属性值。 */ + @JsonProperty("internal_attribute_value") + private List internalAttributeValue; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java new file mode 100644 index 000000000..36a47bfc4 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendParam.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 商品品牌推荐参数。 + */ +@Data +@NoArgsConstructor +public class ProductBrandRecommendParam implements Serializable { + + private static final long serialVersionUID = 4516219198778673928L; + + /** 叶子类目 ID。 */ + @JsonProperty("cat_id") + private Long catId; + + /** 商品主图,至少一张。 */ + @JsonProperty("head_imgs") + private List headImgs; + + /** 商品详情图。 */ + @JsonProperty("detail_imgs") + private List detailImgs; + + /** 商品标题。 */ + @JsonProperty("title") + private String title; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java new file mode 100644 index 000000000..b7c008108 --- /dev/null +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/bean/product/assistant/ProductBrandRecommendResponse.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.channel.bean.product.assistant; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; + +/** + * 商品品牌推荐响应。 + */ +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ProductBrandRecommendResponse extends WxChannelBaseResponse { + + private static final long serialVersionUID = -7903894941180639923L; + + /** 品牌 ID。 */ + @JsonProperty("brand_id") + private Long brandId; + + /** 品牌中文名称。 */ + @JsonProperty("brand_name_chinese") + private String brandNameChinese; + + /** 品牌英文名称。 */ + @JsonProperty("brand_name_english") + private String brandNameEnglish; +} diff --git a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java index 41fd5d009..92ece47b2 100644 --- a/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java +++ b/weixin-java-channel/src/main/java/me/chanjar/weixin/channel/constant/WxChannelApiUrlConstants.java @@ -196,6 +196,19 @@ public interface Spu { String DELETE_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/delete"; /** 更新限时抢购任务 */ String UPDATE_LIMIT_TASK_URL = "https://api.weixin.qq.com/channels/ec/product/limiteddiscounttask/update"; + /** 发品前校验 */ + String CATEGORY_PRE_CHECK_URL = "https://api.weixin.qq.com/channels/ec/product/categoryprecheck"; + /** 商品品牌推荐 */ + String PRODUCT_BRAND_RECOMMEND_URL = "https://api.weixin.qq.com/channels/ec/product/productbrandrecommend"; + /** 站内外商品属性映射 */ + String EXTERNAL_PRODUCT_MAPPING_URL = "https://api.weixin.qq.com/channels/ec/product/externalproductmapping"; + /** 商品属性映射及推荐 */ + String EXTERNAL_PRODUCT_MAPPING_NEW_URL = + "https://api.weixin.qq.com/channels/ec/product/externalproductmappingnew"; + /** 商品立即开售 */ + String BEGIN_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/begintimingsale"; + /** 取消商品开售 */ + String CANCEL_TIMING_SALE_URL = "https://api.weixin.qq.com/channels/ec/product/canceltimingsale"; } /** 区域仓库 */ diff --git a/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java new file mode 100644 index 000000000..7c19b137f --- /dev/null +++ b/weixin-java-channel/src/test/java/me/chanjar/weixin/channel/api/impl/WxChannelProductAssistantServiceImplTest.java @@ -0,0 +1,169 @@ +package me.chanjar.weixin.channel.api.impl; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; +import me.chanjar.weixin.channel.bean.base.WxChannelBaseResponse; +import me.chanjar.weixin.channel.bean.product.assistant.BeginTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CancelTimingSaleParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckParam; +import me.chanjar.weixin.channel.bean.product.assistant.CategoryPreCheckResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalAttribute; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingNewResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingParam; +import me.chanjar.weixin.channel.bean.product.assistant.ExternalProductMappingResponse; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendParam; +import me.chanjar.weixin.channel.bean.product.assistant.ProductBrandRecommendResponse; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.Test; + +/** + * Tests for {@link WxChannelProductAssistantServiceImpl}. + */ +public class WxChannelProductAssistantServiceImplTest { + + @Test + public void shouldPreCheckCategoryAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"," + + "\"all_pass\":false,\"fail_reasons\":[\"保证金不足\"]}"; + CategoryPreCheckParam param = new CategoryPreCheckParam(); + param.setCatId(6261L); + + CategoryPreCheckResponse response = channelService.getProductAssistantService().categoryPreCheck(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/categoryprecheck"); + assertEquals(channelService.request, "{\"cat_id\":6261}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getErrMsg(), "ok"); + assertEquals(response.getAllPass(), Boolean.FALSE); + assertEquals(response.getFailReasons(), Arrays.asList("保证金不足")); + } + + @Test + public void shouldRecommendProductBrandAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\",\"brand_id\":2100000000," + + "\"brand_name_chinese\":\"品牌\",\"brand_name_english\":\"brand\"}"; + ProductBrandRecommendParam param = new ProductBrandRecommendParam(); + param.setCatId(6000L); + param.setHeadImgs(Arrays.asList("https://example.com/head.jpg")); + param.setDetailImgs(Arrays.asList("https://example.com/detail.jpg")); + param.setTitle("测试商品"); + + ProductBrandRecommendResponse response = + channelService.getProductAssistantService().getProductBrandRecommend(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/productbrandrecommend"); + assertEquals(channelService.request, + "{\"cat_id\":6000,\"head_imgs\":[\"https://example.com/head.jpg\"]," + + "\"detail_imgs\":[\"https://example.com/detail.jpg\"],\"title\":\"测试商品\"}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getErrMsg(), "ok"); + assertEquals(response.getBrandId(), Long.valueOf(2_100_000_000L)); + assertEquals(response.getBrandNameChinese(), "品牌"); + assertEquals(response.getBrandNameEnglish(), "brand"); + } + + @Test + public void shouldMapExternalProductAttributeAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"," + + "\"external_attribute_name\":\"帮面材质\",\"external_attribute_value\":\"塑胶\"," + + "\"internal_attribute_name\":\"鞋面材质\",\"internal_attribute_value\":[\"塑胶\"]}"; + ExternalProductMappingParam param = new ExternalProductMappingParam(); + param.setCatId(6261L); + param.setExternalAttributeName("帮面材质"); + param.setExternalAttributeValue("塑胶"); + param.setExternalCategoryName("母婴:童鞋:雨鞋"); + + ExternalProductMappingResponse response = + channelService.getProductAssistantService().externalProductMapping(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/externalproductmapping"); + assertEquals(channelService.request, + "{\"cat_id\":6261,\"external_attribute_name\":\"帮面材质\"," + + "\"external_attribute_value\":\"塑胶\",\"external_category_name\":\"母婴:童鞋:雨鞋\"}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getExternalAttributeName(), "帮面材质"); + assertEquals(response.getExternalAttributeValue(), "塑胶"); + assertEquals(response.getInternalAttributeName(), "鞋面材质"); + assertEquals(response.getInternalAttributeValue(), Arrays.asList("塑胶")); + } + + @Test + public void shouldMapMultipleExternalProductAttributesAndDecodeResponse() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"," + + "\"attributes\":[{\"key\":\"鞋面材质\",\"value\":\"塑胶\"}]}"; + ExternalAttribute externalAttribute = new ExternalAttribute(); + externalAttribute.setKey("帮面材质"); + externalAttribute.setValue("塑胶"); + ExternalProductMappingNewParam param = new ExternalProductMappingNewParam(); + param.setCatId(6000L); + param.setExternalCategoryName("母婴:童鞋:雨鞋"); + param.setHeadImgs(Arrays.asList("https://example.com/head.jpg")); + param.setDetailImgs(Arrays.asList("https://example.com/detail.jpg")); + param.setTitle("测试商品"); + param.setExternalAttributes(Arrays.asList(externalAttribute)); + + ExternalProductMappingNewResponse response = + channelService.getProductAssistantService().externalProductMappingNew(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/externalproductmappingnew"); + assertEquals(channelService.request, + "{\"cat_id\":6000,\"external_category_name\":\"母婴:童鞋:雨鞋\"," + + "\"head_imgs\":[\"https://example.com/head.jpg\"]," + + "\"detail_imgs\":[\"https://example.com/detail.jpg\"],\"title\":\"测试商品\"," + + "\"external_attributes\":[{\"key\":\"帮面材质\",\"value\":\"塑胶\"}]}"); + assertEquals(response.getErrCode(), 0); + assertEquals(response.getAttributes().size(), 1); + assertEquals(response.getAttributes().get(0).getKey(), "鞋面材质"); + assertEquals(response.getAttributes().get(0).getValue(), "塑胶"); + } + + @Test + public void shouldBeginTimingSaleWithStringIdentifiers() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"}"; + BeginTimingSaleParam param = new BeginTimingSaleParam(); + param.setProductId("9007199254740993"); + param.setTaskId("000123456789"); + + WxChannelBaseResponse response = channelService.getProductAssistantService().beginTimingSale(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/begintimingsale"); + assertEquals(channelService.request, + "{\"product_id\":\"9007199254740993\",\"task_id\":\"000123456789\"}"); + assertTrue(response.isSuccess()); + } + + @Test + public void shouldCancelTimingSaleWithStringProductIdentifier() throws WxErrorException { + CapturingChannelService channelService = new CapturingChannelService(); + channelService.response = "{\"errcode\":0,\"errmsg\":\"ok\"}"; + CancelTimingSaleParam param = new CancelTimingSaleParam(); + param.setProductId("9007199254740993"); + + WxChannelBaseResponse response = channelService.getProductAssistantService().cancelTimingSale(param); + + assertEquals(channelService.url, "https://api.weixin.qq.com/channels/ec/product/canceltimingsale"); + assertEquals(channelService.request, "{\"product_id\":\"9007199254740993\"}"); + assertTrue(response.isSuccess()); + } + + private static class CapturingChannelService extends WxChannelServiceImpl { + private String url; + private String request; + private String response; + + @Override + public String post(String url, String postData) { + this.url = url; + this.request = postData; + return this.response; + } + } +} diff --git a/weixin-java-channel/src/test/resources/testng.xml b/weixin-java-channel/src/test/resources/testng.xml index 77ace72b7..caa7a2c54 100644 --- a/weixin-java-channel/src/test/resources/testng.xml +++ b/weixin-java-channel/src/test/resources/testng.xml @@ -21,6 +21,7 @@ +