feat(erp-open): add Weixin publish lane and platform rename

Introduce WEIXIN platform handling for external goods upsert with dedicated sphPopAuth validation and result fields, and add a placeholder Sph publish service behind external.sph.publish-enabled. Rename EnumShopType WEI to WEIXIN and update related references to keep internal channel behavior consistent.

Made-with: Cursor
This commit is contained in:
huangyujie 2026-04-16 14:03:53 +08:00
parent 3b5ba130d5
commit a4e3421a10
17 changed files with 253 additions and 12 deletions

View File

@ -13,6 +13,7 @@ import cn.qihangerp.erp.support.ExternalGoodsRequestLogSupport;
import cn.qihangerp.service.external.ExternalGoodsAppService; import cn.qihangerp.service.external.ExternalGoodsAppService;
import cn.qihangerp.service.external.ExternalGoodsPddQuantityUpdateAppService; import cn.qihangerp.service.external.ExternalGoodsPddQuantityUpdateAppService;
import cn.qihangerp.service.external.pdd.ExternalPddProperties; import cn.qihangerp.service.external.pdd.ExternalPddProperties;
import cn.qihangerp.service.external.sph.ExternalSphProperties;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -43,6 +44,7 @@ public class ExternalGoodsController extends BaseController {
private final ExternalGoodsAppService externalGoodsAppService; private final ExternalGoodsAppService externalGoodsAppService;
private final ExternalGoodsPddQuantityUpdateAppService externalGoodsPddQuantityUpdateAppService; private final ExternalGoodsPddQuantityUpdateAppService externalGoodsPddQuantityUpdateAppService;
private final ExternalPddProperties externalPddProperties; private final ExternalPddProperties externalPddProperties;
private final ExternalSphProperties externalSphProperties;
private final ExternalGoodsApiLogProperties goodsApiLogProperties; private final ExternalGoodsApiLogProperties goodsApiLogProperties;
@PostMapping("/upsert") @PostMapping("/upsert")
@ -106,6 +108,17 @@ public class ExternalGoodsController extends BaseController {
} }
} }
if (EnumShopType.WEIXIN.equals(platform) && externalSphProperties.isPublishEnabled()) {
if (req.getSphPopAuth() == null) {
return AjaxResult.error("参数错误:开启微信视频号小店发布时 sphPopAuth 不能为空");
}
var sAuth = req.getSphPopAuth();
if (!StringUtils.hasText(sAuth.getAppKey()) || !StringUtils.hasText(sAuth.getAppSecret())
|| !StringUtils.hasText(sAuth.getAccessToken())) {
return AjaxResult.error("参数错误sphPopAuth 需提供 appKey、appSecret、accessToken");
}
}
ExternalGoodsUpsertResultVo vo = externalGoodsAppService.upsertGoods(req); ExternalGoodsUpsertResultVo vo = externalGoodsAppService.upsertGoods(req);
log.info("[external/goods/upsert] response shopId={} outGoodsId={} erpGoodsId={} platform={}", log.info("[external/goods/upsert] response shopId={} outGoodsId={} erpGoodsId={} platform={}",
req.getShopId(), req.getOutGoodsId(), vo.getGoodsId(), req.getPlatform()); req.getShopId(), req.getOutGoodsId(), vo.getGoodsId(), req.getPlatform());
@ -115,6 +128,13 @@ public class ExternalGoodsController extends BaseController {
vo.getPddPublishSuccess(), vo.getPddPublishSuccess(),
truncateLog(vo.getPddPublishMessage(), 800)); truncateLog(vo.getPddPublishMessage(), 800));
} }
if (EnumShopType.WEIXIN.equals(platform)) {
log.info("[external/goods/upsert] sphPublish attempted={} success={} message={} sphProductId={}",
vo.getSphPublishAttempted(),
vo.getSphPublishSuccess(),
truncateLog(vo.getSphPublishMessage(), 800),
vo.getSphProductId());
}
return AjaxResult.success(vo); return AjaxResult.success(vo);
} }

View File

@ -76,7 +76,7 @@
// } else if (mqMessage.getShopType().getIndex() == EnumShopType.OFFLINE.getIndex()) { // } else if (mqMessage.getShopType().getIndex() == EnumShopType.OFFLINE.getIndex()) {
// log.info("订单消息OFFLINE"); // log.info("订单消息OFFLINE");
// orderService.offlineOrderMessage(mqMessage.getKeyId()); // orderService.offlineOrderMessage(mqMessage.getKeyId());
// } else if (mqMessage.getShopType().getIndex() == EnumShopType.WEI.getIndex()) { // } else if (mqMessage.getShopType().getIndex() == EnumShopType.WEIXIN.getIndex()) {
// log.info("订单消息WEI"); // log.info("订单消息WEI");
// JSONObject jsonObject = openApiService.getWeiOrderDetail(mqMessage.getKeyId()); // JSONObject jsonObject = openApiService.getWeiOrderDetail(mqMessage.getKeyId());
// if (jsonObject.getInteger("code") != 200 || jsonObject.getJSONObject("data") == null) { // if (jsonObject.getInteger("code") != 200 || jsonObject.getJSONObject("data") == null) {
@ -132,7 +132,7 @@
// //
// JSONObject refundDetail = jsonObject.getJSONObject("data"); // JSONObject refundDetail = jsonObject.getJSONObject("data");
// refundService.douRefundMessage(mqMessage.getKeyId(), refundDetail); // refundService.douRefundMessage(mqMessage.getKeyId(), refundDetail);
// } else if (mqMessage.getShopType().getIndex() == EnumShopType.WEI.getIndex()) { // } else if (mqMessage.getShopType().getIndex() == EnumShopType.WEIXIN.getIndex()) {
// log.info("退款消息WEI"); // log.info("退款消息WEI");
// JSONObject jsonObject = openApiService.getWeiRefundDetail(mqMessage.getKeyId()); // JSONObject jsonObject = openApiService.getWeiRefundDetail(mqMessage.getKeyId());
// if (jsonObject.getInteger("code") != 200 || jsonObject.getJSONObject("data") == null) { // if (jsonObject.getInteger("code") != 200 || jsonObject.getJSONObject("data") == null) {

View File

@ -44,7 +44,7 @@
// } else if(vo.getShopType().getIndex() == EnumShopType.DOU.getIndex()) { // } else if(vo.getShopType().getIndex() == EnumShopType.DOU.getIndex()) {
// logger.info("Kafka订单消息DOU"+vo.getKeyId()); // logger.info("Kafka订单消息DOU"+vo.getKeyId());
//// orderService.douOrderMessage(vo.getKeyId()); //// orderService.douOrderMessage(vo.getKeyId());
// } else if(vo.getShopType().getIndex() == EnumShopType.WEI.getIndex()) { // } else if(vo.getShopType().getIndex() == EnumShopType.WEIXIN.getIndex()) {
// logger.info("Kafka订单消息WEI"+vo.getKeyId()); // logger.info("Kafka订单消息WEI"+vo.getKeyId());
//// orderService.weiOrderMessage(vo.getKeyId()); //// orderService.weiOrderMessage(vo.getKeyId());
// } // }
@ -70,7 +70,7 @@
// } else if(vo.getShopType().getIndex() == EnumShopType.DOU.getIndex()) { // } else if(vo.getShopType().getIndex() == EnumShopType.DOU.getIndex()) {
// logger.info("Kafka售后消息DOU"+vo.getKeyId()); // logger.info("Kafka售后消息DOU"+vo.getKeyId());
//// refundService.douRefundMessage(vo.getKeyId()); //// refundService.douRefundMessage(vo.getKeyId());
// } else if(vo.getShopType().getIndex() == EnumShopType.WEI.getIndex()) { // } else if(vo.getShopType().getIndex() == EnumShopType.WEIXIN.getIndex()) {
// logger.info("Kafka售后消息WEI"+vo.getKeyId()); // logger.info("Kafka售后消息WEI"+vo.getKeyId());
//// refundService.weiRefundMessage(vo.getKeyId()); //// refundService.weiRefundMessage(vo.getKeyId());
// } // }

View File

@ -77,6 +77,10 @@ public final class ExternalGoodsRequestLogSupport {
if (auth != null && !auth.isEmpty()) { if (auth != null && !auth.isEmpty()) {
maskPddPopAuth(auth); maskPddPopAuth(auth);
} }
JSONObject sphAuth = root.getJSONObject("sphPopAuth");
if (sphAuth != null && !sphAuth.isEmpty()) {
maskPddPopAuth(sphAuth);
}
return root.toJSONString(); return root.toJSONString();
} catch (Exception e) { } catch (Exception e) {
return "{\"_logSanitizeError\":true}"; return "{\"_logSanitizeError\":true}";

View File

@ -77,4 +77,8 @@ external:
DEFAULT: "303351846671360" DEFAULT: "303351846671360"
sku-overrides: [] sku-overrides: []
# 微信视频号小店platform=WEIXIN默认不调用渠道发品接入 OpenAPI 后再开启
sph:
publish-enabled: false
# 说明:对外商品接口见 temp/yunxi-erp-open-goods-upsert-api.mdNacos 对齐见 temp/erp-open-erp-api-nacos-yunxi-reference.md # 说明:对外商品接口见 temp/yunxi-erp-open-goods-upsert-api.mdNacos 对齐见 temp/erp-open-erp-api-nacos-yunxi-reference.md

View File

@ -29,7 +29,7 @@ public class WeiApiCommon {
if (shop == null) { if (shop == null) {
return ResultVo.error(HttpStatus.PARAMS_ERROR,"参数错误,没有找到店铺"); return ResultVo.error(HttpStatus.PARAMS_ERROR,"参数错误,没有找到店铺");
} }
if (shop.getType() != EnumShopType.WEI.getIndex()) { if (shop.getType() != EnumShopType.WEIXIN.getIndex()) {
return ResultVo.error(HttpStatus.PARAMS_ERROR, "参数错误,店铺不是微信小店店铺"); return ResultVo.error(HttpStatus.PARAMS_ERROR, "参数错误,店铺不是微信小店店铺");
} }
if(!StringUtils.hasText(shop.getAppKey())) { if(!StringUtils.hasText(shop.getAppKey())) {

View File

@ -34,7 +34,7 @@ public class WeiRefundController extends BaseController {
// TODO:需要优化消息格式 // TODO:需要优化消息格式
if(bo!=null && bo.getIds()!=null) { if(bo!=null && bo.getIds()!=null) {
for(String id: bo.getIds()) { for(String id: bo.getIds()) {
mqUtils.sendApiMessage(MqMessage.build(EnumShopType.WEI, MqType.REFUND_MESSAGE, id)); mqUtils.sendApiMessage(MqMessage.build(EnumShopType.WEIXIN, MqType.REFUND_MESSAGE, id));
} }
} }

View File

@ -13,7 +13,7 @@ public enum EnumShopType {
JDVC("京东自营", 280), JDVC("京东自营", 280),
PDD("拼多多", 300), PDD("拼多多", 300),
DOU("抖店", 400), DOU("抖店", 400),
WEI("视频号", 500), WEIXIN("视频号", 500),
KWAI("快手小店", 600), KWAI("快手小店", 600),
XHS("小红书", 700), XHS("小红书", 700),
OFFLINE("拼多多", 999) OFFLINE("拼多多", 999)

View File

@ -65,7 +65,7 @@
### 4.0 已确认口径(冻结) ### 4.0 已确认口径(冻结)
- `shopId`**全平台唯一**,可直接作为本服务店铺主键,不考虑跨平台撞号。 - `shopId`**全平台唯一**,可直接作为本服务店铺主键,不考虑跨平台撞号。
- `platform`:由外部系统显式传入,取值采用 **`PDD`/`TAO`/`JD`/`DOU`/`WEI`/`KWAI`/`XHS`**(后续平台新增再扩展枚举)。 - `platform`:由外部系统显式传入,取值采用 **`PDD`/`TAO`/`JD`/`DOU`/`WEIXIN`/`KWAI`/`XHS`**(后续平台新增再扩展枚举)。
- AK/SK**一套即可**(外部系统作为单一调用方)。 - AK/SK**一套即可**(外部系统作为单一调用方)。
- `X-Timestamp`**毫秒**ms - `X-Timestamp`**毫秒**ms
- `X-Signature`**Base64** 编码HMAC-SHA256 输出 bytes → Base64 - `X-Signature`**Base64** 编码HMAC-SHA256 输出 bytes → Base64

View File

@ -23,7 +23,7 @@ public class ExternalGoodsUpsertRequest {
private Long shopId; private Long shopId;
/** /**
* 目标平台枚举PDD/TAO/JD/DOU/WEI/KWAI/XHS EnumShopType 命名一致 * 目标平台枚举PDD/TAO/JD/DOU/WEIXIN/KWAI/XHS EnumShopType 命名一致
*/ */
private String platform; private String platform;
@ -149,6 +149,12 @@ public class ExternalGoodsUpsertRequest {
*/ */
private PddPopAuth pddPopAuth; private PddPopAuth pddPopAuth;
/**
* 微信视频号小店凭证 {@link PddPopAuth} 形态一致appId/secret + 店铺 access_token
* {@code platform=WEIXIN}{@link cn.qihangerp.common.enums.EnumShopType#WEIXIN} {@code external.sph.publish-enabled=true} <b>必填</b>
*/
private SphPopAuth sphPopAuth;
@Data @Data
public static class PddPopAuth { public static class PddPopAuth {
@JsonAlias({"app_key", "clientId", "client_id"}) @JsonAlias({"app_key", "clientId", "client_id"})
@ -165,6 +171,21 @@ public class ExternalGoodsUpsertRequest {
private String gatewayUrl; private String gatewayUrl;
} }
@Data
public static class SphPopAuth {
@JsonAlias({"app_key", "clientId", "client_id"})
private String appKey;
@JsonAlias({"app_secret", "clientSecret", "client_secret"})
private String appSecret;
@JsonAlias({"access_token", "token", "sessionKey", "session_key"})
private String accessToken;
@JsonAlias({"gateway_url", "channelsGatewayUrl"})
private String gatewayUrl;
}
@Data @Data
public static class AttributeItem { public static class AttributeItem {
private String code; private String code;

View File

@ -52,4 +52,19 @@ public class ExternalGoodsUpsertResultVo implements Serializable {
/** 是否通过 spec.id.get 自动生成了 sku 规格 */ /** 是否通过 spec.id.get 自动生成了 sku 规格 */
private Boolean pddSpecAutoResolved; private Boolean pddSpecAutoResolved;
private String pddAutoResolveDetail; private String pddAutoResolveDetail;
// ---- 微信视频号小店platform=WEIXIN----
/** 是否尝试调用微信侧发品 */
private Boolean sphPublishAttempted;
/** 微信发品是否成功(未尝试时为 null */
private Boolean sphPublishSuccess;
private String sphPublishMessage;
private String sphResponseSnippet;
/** 小店侧商品 ID字符串 */
private String sphProductId;
} }

View File

@ -0,0 +1,28 @@
package cn.qihangerp.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 微信视频号小店发品链路诊断 {@link PddPublishLaneResultVo} 对称字段独立避免语义混淆
*
* @author guochengyu
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SphPublishLaneResultVo implements Serializable {
private Boolean attempted;
private Boolean success;
private String message;
/** 渠道网关/适配层响应片段(脱敏前由服务端控制长度) */
private String responseSnippet;
/** 小店侧商品 ID字符串与微信返回形态一致 */
private String sphProductId;
}

View File

@ -210,7 +210,7 @@ public class WeiOrderServiceImpl extends ServiceImpl<WeiOrderMapper, WeiOrder>
} }
OOrder order = new OOrder(); OOrder order = new OOrder();
order.setOrderNum(weiOrder.getOrderId()); order.setOrderNum(weiOrder.getOrderId());
order.setShopType(EnumShopType.WEI.getIndex()); order.setShopType(EnumShopType.WEIXIN.getIndex());
order.setShopId(weiOrder.getShopId()); order.setShopId(weiOrder.getShopId());
// order.setShipType(confirmBo.getShipType()); // order.setShipType(confirmBo.getShipType());
order.setShipType(0); order.setShipType(0);
@ -273,7 +273,7 @@ public class WeiOrderServiceImpl extends ServiceImpl<WeiOrderMapper, WeiOrder>
oOrderItem.setOrderId(order.getId()); oOrderItem.setOrderId(order.getId());
oOrderItem.setOrderNum(order.getOrderNum()); oOrderItem.setOrderNum(order.getOrderNum());
oOrderItem.setSubOrderNum(order.getOrderNum()+"-"+item.getSkuId()); oOrderItem.setSubOrderNum(order.getOrderNum()+"-"+item.getSkuId());
oOrderItem.setShopType(EnumShopType.WEI.getIndex()); oOrderItem.setShopType(EnumShopType.WEIXIN.getIndex());
oOrderItem.setShopId(weiOrder.getShopId()); oOrderItem.setShopId(weiOrder.getShopId());
// 商品信息 // 商品信息
oOrderItem.setProductId(item.getProductId()); oOrderItem.setProductId(item.getProductId());

View File

@ -8,10 +8,12 @@ import cn.qihangerp.model.request.ExternalGoodsDelistRequest;
import cn.qihangerp.model.request.ExternalGoodsUpsertRequest; import cn.qihangerp.model.request.ExternalGoodsUpsertRequest;
import cn.qihangerp.model.vo.ExternalGoodsUpsertResultVo; import cn.qihangerp.model.vo.ExternalGoodsUpsertResultVo;
import cn.qihangerp.model.vo.PddPublishLaneResultVo; import cn.qihangerp.model.vo.PddPublishLaneResultVo;
import cn.qihangerp.model.vo.SphPublishLaneResultVo;
import cn.qihangerp.module.service.OGoodsService; import cn.qihangerp.module.service.OGoodsService;
import cn.qihangerp.module.service.OGoodsSkuService; import cn.qihangerp.module.service.OGoodsSkuService;
import cn.qihangerp.service.external.ExternalGoodsAppService; import cn.qihangerp.service.external.ExternalGoodsAppService;
import cn.qihangerp.service.external.pdd.ExternalPddPublishService; import cn.qihangerp.service.external.pdd.ExternalPddPublishService;
import cn.qihangerp.service.external.sph.ExternalSphPublishService;
import cn.qihangerp.service.external.pdd.OGoodsPddMappingPersistence; import cn.qihangerp.service.external.pdd.OGoodsPddMappingPersistence;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
@ -41,6 +43,7 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
private final OGoodsService goodsService; private final OGoodsService goodsService;
private final OGoodsSkuService skuService; private final OGoodsSkuService skuService;
private final ExternalPddPublishService externalPddPublishService; private final ExternalPddPublishService externalPddPublishService;
private final ExternalSphPublishService externalSphPublishService;
private final OGoodsPddMappingPersistence oGoodsPddMappingPersistence; private final OGoodsPddMappingPersistence oGoodsPddMappingPersistence;
@Override @Override
@ -111,7 +114,12 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
.pddCatRuleSnippet(null) .pddCatRuleSnippet(null)
.pddSpecAutoResolved(null) .pddSpecAutoResolved(null)
.pddAutoResolveDetail(null) .pddAutoResolveDetail(null)
.pddPublishLane(null); .pddPublishLane(null)
.sphPublishAttempted(false)
.sphPublishSuccess(null)
.sphPublishMessage(null)
.sphResponseSnippet(null)
.sphProductId(null);
if ("PDD".equalsIgnoreCase(req.getPlatform())) { if ("PDD".equalsIgnoreCase(req.getPlatform())) {
OGoods g = goodsService.getById(goodsId); OGoods g = goodsService.getById(goodsId);
@ -149,6 +157,36 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
out.pddPublishLane(lane.getPublishLane()); out.pddPublishLane(lane.getPublishLane());
} }
if ("WEIXIN".equalsIgnoreCase(req.getPlatform())) {
OGoods g = goodsService.getById(goodsId);
List<OGoodsSku> skuRows = skuService.list(new LambdaQueryWrapper<OGoodsSku>()
.eq(OGoodsSku::getGoodsId, goodsId));
Map<String, OGoodsSku> byOuter = new LinkedHashMap<>();
for (OGoodsSku row : skuRows) {
if (row != null && StringUtils.hasText(row.getOuterErpSkuId())) {
byOuter.putIfAbsent(row.getOuterErpSkuId(), row);
}
}
List<OGoodsSku> orderedSkus = new ArrayList<>();
if (!CollectionUtils.isEmpty(req.getSkus())) {
for (ExternalGoodsUpsertRequest.Sku sr : req.getSkus()) {
if (sr == null || !StringUtils.hasText(sr.getOutSkuId())) {
continue;
}
OGoodsSku row = byOuter.get(sr.getOutSkuId());
if (row != null) {
orderedSkus.add(row);
}
}
}
SphPublishLaneResultVo lane = externalSphPublishService.publish(g, orderedSkus, req);
out.sphPublishAttempted(Boolean.TRUE.equals(lane.getAttempted()));
out.sphPublishSuccess(lane.getSuccess());
out.sphPublishMessage(lane.getMessage());
out.sphResponseSnippet(lane.getResponseSnippet());
out.sphProductId(lane.getSphProductId());
}
ExternalGoodsUpsertResultVo vo = out.build(); ExternalGoodsUpsertResultVo vo = out.build();
if ("PDD".equalsIgnoreCase(req.getPlatform())) { if ("PDD".equalsIgnoreCase(req.getPlatform())) {
oGoodsPddMappingPersistence.persistAfterPddPublishSuccess(goodsId, req, vo); oGoodsPddMappingPersistence.persistAfterPddPublishSuccess(goodsId, req, vo);
@ -167,6 +205,16 @@ public class ExternalGoodsAppServiceImpl implements ExternalGoodsAppService {
vo.getPddSpecAutoResolved(), vo.getPddSpecAutoResolved(),
truncateForLog(vo.getPddAutoResolveDetail(), 300)); truncateForLog(vo.getPddAutoResolveDetail(), 300));
} }
if ("WEIXIN".equalsIgnoreCase(req.getPlatform())) {
log.info("[external/upsert] WEIXIN summary shopId={} outGoodsId={} erpGoodsId={} attempted={} success={} message={} sphProductId={}",
req.getShopId(),
req.getOutGoodsId(),
vo.getGoodsId(),
vo.getSphPublishAttempted(),
vo.getSphPublishSuccess(),
truncateForLog(vo.getSphPublishMessage(), 600),
vo.getSphProductId());
}
return vo; return vo;
} }

View File

@ -0,0 +1,12 @@
package cn.qihangerp.service.external.sph;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author guochengyu
*/
@Configuration
@EnableConfigurationProperties(ExternalSphProperties.class)
public class ExternalSphConfiguration {
}

View File

@ -0,0 +1,19 @@
package cn.qihangerp.service.external.sph;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 微信视频号小店platform=WEIXIN发品相关开关应用级密钥由调用方经 {@code sphPopAuth} 传入与拼多多一致
*
* @author guochengyu
*/
@Data
@ConfigurationProperties(prefix = "external.sph")
public class ExternalSphProperties {
/**
* 是否在 upsert 落库后尝试调用微信小店发品适配未接入前请保持 false避免误报失败
*/
private boolean publishEnabled = false;
}

View File

@ -0,0 +1,70 @@
package cn.qihangerp.service.external.sph;
import cn.qihangerp.model.entity.OGoods;
import cn.qihangerp.model.entity.OGoodsSku;
import cn.qihangerp.model.request.ExternalGoodsUpsertRequest;
import cn.qihangerp.model.vo.SphPublishLaneResultVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* 微信视频号小店发品凭证仅来自本次请求的 {@link ExternalGoodsUpsertRequest#getSphPopAuth()}
* <p>具体 HTTP/OpenAPI 接入在 {@code publishEnabled=true} 后实现关闭开关时不调用渠道与拼多多 {@link cn.qihangerp.service.external.pdd.ExternalPddPublishService} 行为对称</p>
*
* @author guochengyu
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ExternalSphPublishService {
private final ExternalSphProperties props;
/**
* 落库成功后可选调用微信侧发品未开启或未实现时返回 {@code attempted=false}不阻断 ERP 本地 upsert
*/
public SphPublishLaneResultVo publish(OGoods goods, List<OGoodsSku> skus, ExternalGoodsUpsertRequest req) {
if (!props.isPublishEnabled()) {
log.info("[SPH] publish skipped shopId={} outGoodsId={} reason=publish-disabled",
req != null ? req.getShopId() : null,
req != null ? req.getOutGoodsId() : null);
return SphPublishLaneResultVo.builder()
.attempted(false)
.success(null)
.message("external.sph.publish-enabled=false未调用微信视频号小店")
.build();
}
if (req == null) {
return SphPublishLaneResultVo.builder()
.attempted(false)
.success(false)
.message("请求参数不能为空")
.build();
}
ExternalGoodsUpsertRequest.SphPopAuth auth = req.getSphPopAuth();
if (auth == null || !StringUtils.hasText(auth.getAppKey()) || !StringUtils.hasText(auth.getAppSecret())
|| !StringUtils.hasText(auth.getAccessToken())) {
log.info("[SPH] publish skipped shopId={} outGoodsId={} reason=sphPopAuth-incomplete",
req.getShopId(), req.getOutGoodsId());
return SphPublishLaneResultVo.builder()
.attempted(false)
.success(false)
.message("微信小店凭证不完整:请在请求体 sphPopAuth 中传入 appKey、appSecret、accessToken")
.build();
}
// TODO: 接入微信视频号小店商品发布 OpenAPI填充 sphProductId/responseSnippet
log.warn("[SPH] publish-enabled=true 但发品适配尚未实现 shopId={} outGoodsId={} skuCount={}",
req.getShopId(), req.getOutGoodsId(), skus == null ? 0 : skus.size());
return SphPublishLaneResultVo.builder()
.attempted(true)
.success(false)
.message("微信视频号小店发品接口尚未接入(请关闭 external.sph.publish-enabled 或完成适配开发)")
.responseSnippet(null)
.sphProductId(null)
.build();
}
}