refactor(core): 重构Google Play订阅与商品接口逻辑
This commit is contained in:
@@ -89,7 +89,8 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
"/google-play/rtdn",
|
||||
"/appVersions/checkUpdate",
|
||||
"/appVersions/checkUpdate",
|
||||
"/character/detailWithNotLogin"
|
||||
"/character/detailWithNotLogin",
|
||||
"/apple/validate-receipt"
|
||||
};
|
||||
}
|
||||
@Bean
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 不匹配");
|
||||
}
|
||||
|
||||
@@ -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 写入交易流水
|
||||
|
||||
@@ -15,6 +15,8 @@ public class GooglePlayPurchaseSnapshot {
|
||||
|
||||
private String productType;
|
||||
|
||||
private String basePlanId;
|
||||
|
||||
private String purchaseToken;
|
||||
|
||||
private String orderKey;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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 返回不一致");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,7 +73,7 @@ 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();
|
||||
|
||||
@@ -86,6 +82,11 @@ public class KeyboardProductItemsServiceImpl extends ServiceImpl<KeyboardProduct
|
||||
query.eq(KeyboardProductItems::getType, type);
|
||||
}
|
||||
|
||||
// 根据平台过滤商品
|
||||
if (platform != null && !platform.isBlank()) {
|
||||
query.eq(KeyboardProductItems::getPlatform, platform);
|
||||
}
|
||||
|
||||
// 执行查询,按ID升序排列
|
||||
java.util.List<KeyboardProductItems> items = query
|
||||
.orderByAsc(KeyboardProductItems::getId)
|
||||
|
||||
Reference in New Issue
Block a user