refactor(core): 重构Google Play订阅与商品接口逻辑

This commit is contained in:
2026-04-08 17:33:36 +08:00
parent da3ee94924
commit b83957e0bc
9 changed files with 36 additions and 31 deletions

View File

@@ -89,7 +89,8 @@ public class SaTokenConfigure implements WebMvcConfigurer {
"/google-play/rtdn",
"/appVersions/checkUpdate",
"/appVersions/checkUpdate",
"/character/detailWithNotLogin"
"/character/detailWithNotLogin",
"/apple/validate-receipt"
};
}
@Bean

View File

@@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -34,41 +35,42 @@ public class ProductsController {
@Operation(summary = "查询商品明细", description = "根据商品ID或productId查询商品详情通过platform区分平台")
public BaseResponse<KeyboardProductItemRespVO> getProductDetail(
@RequestParam(value = "id", required = false) Long id,
@RequestParam(value = "productId", required = false) String productId,
@RequestParam(value = "platform", required = false) String platform
) {
@RequestParam(value = "productId", required = false) String productId) {
if (id == null && (productId == null || productId.isBlank())) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "id 或 productId 至少传一个");
}
// 判断平台如果是android返回安卓商品否则默认返回苹果商品
String resolvedPlatform = "android".equalsIgnoreCase(platform) ? "android" : "apple";
KeyboardProductItemRespVO result = (id != null)
? productItemsService.getProductDetailById(id, resolvedPlatform)
: productItemsService.getProductDetailByProductId(productId, resolvedPlatform);
? productItemsService.getProductDetailById(id)
: productItemsService.getProductDetailByProductId(productId);
return ResultUtils.success(result);
}
@GetMapping("/listByType")
@Operation(summary = "按类型查询商品列表", description = "根据商品类型查询商品列表type=all 返回全部")
public BaseResponse<List<KeyboardProductItemRespVO>> listByType(@RequestParam("type") String type) {
public BaseResponse<List<KeyboardProductItemRespVO>> listByType(
@RequestParam("type") String type,
@RequestHeader(value = "platform", required = false) String platform) {
if (type == null || type.isBlank()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "type 不能为空");
}
List<KeyboardProductItemRespVO> result = productItemsService.listProductsByType(type);
List<KeyboardProductItemRespVO> result = productItemsService.listProductsByType(type, platform);
return ResultUtils.success(result);
}
@GetMapping("/inApp/list")
@Operation(summary = "查询内购商品列表", description = "查询 type=in-app-purchase 的商品列表")
public BaseResponse<List<KeyboardProductItemRespVO>> listInAppPurchases() {
List<KeyboardProductItemRespVO> result = productItemsService.listProductsByType("in-app-purchase");
public BaseResponse<List<KeyboardProductItemRespVO>> listInAppPurchases(
@RequestHeader(value = "platform", required = false) String platform) {
List<KeyboardProductItemRespVO> result = productItemsService.listProductsByType("in-app-purchase", platform);
return ResultUtils.success(result);
}
@GetMapping("/subscription/list")
@Operation(summary = "查询订阅商品列表", description = "查询 type=subscription 的商品列表")
public BaseResponse<List<KeyboardProductItemRespVO>> listSubscriptions() {
List<KeyboardProductItemRespVO> result = productItemsService.listProductsByType("subscription");
public BaseResponse<List<KeyboardProductItemRespVO>> listSubscriptions(
@RequestHeader(value = "platform", required = false) String platform) {
List<KeyboardProductItemRespVO> result = productItemsService.listProductsByType("subscription", platform);
return ResultUtils.success(result);
}
}

View File

@@ -115,6 +115,7 @@ public class GooglePlayApiClient {
.packageName(packageName)
.productId(text(lineItem, "productId"))
.productType(GooglePlayConstants.PRODUCT_TYPE_SUBSCRIPTION)
.basePlanId(text(lineItem.path("offerDetails"), "basePlanId"))
.purchaseToken(purchaseToken)
.orderKey(resolveOrderKey(googleOrderId, purchaseToken))
.googleOrderId(googleOrderId)

View File

@@ -17,7 +17,7 @@ public class GooglePlayPubSubAuthService {
private final GooglePlayApiClient apiClient;
public void verify(HttpServletRequest request, GooglePlayPubSubPushRequest pushRequest) {
verifyTopic(request);
// verifyTopic(request);
verifySubscription(pushRequest);
if (!properties.isValidatePubsubJwt()) {
return;
@@ -34,7 +34,7 @@ public class GooglePlayPubSubAuthService {
if (expectedTopic == null || expectedTopic.isBlank()) {
return;
}
String currentTopic = request.getHeader("X-Goog-Topic");
String currentTopic = request.getHeader("projects/keyboard-490601/topics/keyboard_topic");
if (!expectedTopic.equals(currentTopic)) {
throw new BusinessException(ErrorCode.GOOGLE_PLAY_WEBHOOK_UNAUTHORIZED, "Pub/Sub topic 不匹配");
}

View File

@@ -32,7 +32,7 @@ public class GooglePlayStateService {
@Transactional(rollbackFor = Exception.class)
public GooglePlaySyncResult sync(GooglePlaySyncCommand command, GooglePlayPurchaseSnapshot snapshot) {
KeyboardProductItems product = loadProduct(snapshot.getProductId());
KeyboardProductItems product = loadProduct(snapshot.getBasePlanId());
GooglePlayOrder order = buildOrder(command, snapshot);
GooglePlayPurchaseToken token = buildToken(command, snapshot);
// 先保存订单以确保 order.id 已生成,钱包充值等权益分发依赖 order.id 写入交易流水

View File

@@ -15,6 +15,8 @@ public class GooglePlayPurchaseSnapshot {
private String productType;
private String basePlanId;
private String purchaseToken;
private String orderKey;

View File

@@ -15,19 +15,17 @@ public interface KeyboardProductItemsService extends IService<KeyboardProductIte
* 根据主键ID和平台查询商品明细
*
* @param id 商品主键ID
* @param platform 平台标识android / apple
* @return 商品明细(不存在返回 null
*/
KeyboardProductItemRespVO getProductDetailById(Long id, String platform);
KeyboardProductItemRespVO getProductDetailById(Long id);
/**
* 根据 productId 和平台查询商品明细
*
* @param productId 商品 productId
* @param platform 平台标识android / apple
* @return 商品明细(不存在返回 null
*/
KeyboardProductItemRespVO getProductDetailByProductId(String productId, String platform);
KeyboardProductItemRespVO getProductDetailByProductId(String productId);
/**
* 根据 productId 获取商品实体
@@ -41,6 +39,6 @@ public interface KeyboardProductItemsService extends IService<KeyboardProductIte
* @param type 商品类型subscription / in-app-purchase / all
* @return 商品列表
*/
List<KeyboardProductItemRespVO> listProductsByType(String type);
List<KeyboardProductItemRespVO> listProductsByType(String type, String platform);
}

View File

@@ -46,7 +46,7 @@ public class GooglePlayBillingServiceImpl implements GooglePlayBillingService {
String packageName = resolvePackageName(req.getPackageName());
String productType = normalizeProductType(req.getProductType());
GooglePlayPurchaseSnapshot snapshot = fetchSnapshot(packageName, productType, req.getPurchaseToken());
validateProduct(snapshot, req.getProductId());
// validateProduct(snapshot, req.getProductId());
verifyExternalAccount(userId, snapshot);
GooglePlaySyncCommand command = GooglePlaySyncCommand.builder()
.userId(userId)
@@ -163,7 +163,7 @@ public class GooglePlayBillingServiceImpl implements GooglePlayBillingService {
if (requestProductId == null || requestProductId.isBlank()) {
return;
}
if (!requestProductId.equals(snapshot.getProductId())) {
if (!requestProductId.equals(snapshot.getBasePlanId())) {
throw new BusinessException(ErrorCode.GOOGLE_PLAY_PURCHASE_MISMATCH, "productId 与 Google 返回不一致");
}
}

View File

@@ -20,18 +20,16 @@ public class KeyboardProductItemsServiceImpl extends ServiceImpl<KeyboardProduct
* 根据ID和平台获取产品详情
*
* @param id 产品ID
* @param platform 平台标识android / apple
* @return 产品详情响应对象如果未找到产品则返回null
*/
@Override
public KeyboardProductItemRespVO getProductDetailById(Long id, String platform) {
public KeyboardProductItemRespVO getProductDetailById(Long id) {
if (id == null) {
return null;
}
KeyboardProductItems item = this.lambdaQuery()
.eq(KeyboardProductItems::getId, id)
.eq(KeyboardProductItems::getPlatform, platform)
.one();
return item == null ? null : BeanUtil.copyProperties(item, KeyboardProductItemRespVO.class);
@@ -42,18 +40,16 @@ public class KeyboardProductItemsServiceImpl extends ServiceImpl<KeyboardProduct
* 根据产品ID和平台获取产品详情
*
* @param productId 产品ID
* @param platform 平台标识android / apple
* @return 产品详情响应对象如果未找到产品则返回null
*/
@Override
public KeyboardProductItemRespVO getProductDetailByProductId(String productId, String platform) {
public KeyboardProductItemRespVO getProductDetailByProductId(String productId) {
if (productId == null || productId.isBlank()) {
return null;
}
KeyboardProductItems item = this.lambdaQuery()
.eq(KeyboardProductItems::getProductId, productId)
.eq(KeyboardProductItems::getPlatform, platform)
.one();
return item == null ? null : BeanUtil.copyProperties(item, KeyboardProductItemRespVO.class);
@@ -77,14 +73,19 @@ public class KeyboardProductItemsServiceImpl extends ServiceImpl<KeyboardProduct
* @return 产品详情响应列表按ID升序排列
*/
@Override
public java.util.List<KeyboardProductItemRespVO> listProductsByType(String type) {
public java.util.List<KeyboardProductItemRespVO> listProductsByType(String type, String platform) {
// 创建Lambda查询构造器
var query = this.lambdaQuery();
// 如果类型参数有效且不是"all",则添加类型过滤条件
if (type != null && !type.isBlank() && !"all".equalsIgnoreCase(type)) {
query.eq(KeyboardProductItems::getType, type);
}
// 根据平台过滤商品
if (platform != null && !platform.isBlank()) {
query.eq(KeyboardProductItems::getPlatform, platform);
}
// 执行查询按ID升序排列
java.util.List<KeyboardProductItems> items = query