feat(core): 新增苹果 App Store 订阅票据校验与向量存储结构升级
- 引入 Apple App Store Server Library,完成票据验证、续订、退款通知全套流程 - 新增 AppleReceiptController / AppleReceiptService 及相关配置类,支持沙箱与生产环境双端点 - 向量存储接口升级:EmbedSaveReq 封装向量与业务实体,QdrantVectorService 改为 JSON 字符串载荷并补全异常处理 - 补充 Apple 根证书与订阅密钥资源文件,pom 与 yml 增加对应依赖与配置
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -97,6 +97,12 @@
|
||||
<artifactId>grpc-netty-shaded</artifactId>
|
||||
<version>1.65.1</version>
|
||||
</dependency>
|
||||
<!-- 苹果 IAP sdk -->
|
||||
<dependency>
|
||||
<groupId>com.apple.itunes.storekit</groupId>
|
||||
<artifactId>app-store-server-library</artifactId>
|
||||
<version>3.6.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.alibaba.cloud.ai</groupId>-->
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package com.yolo.keyborad;
|
||||
|
||||
import com.yolo.keyborad.config.AppleAppStoreProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.yolo.keyborad.mapper")
|
||||
@EnableConfigurationProperties(AppleAppStoreProperties.class)
|
||||
public class MyApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MyApplication.class, args);
|
||||
|
||||
107
src/main/java/com/yolo/keyborad/config/AppleAppStoreConfig.java
Normal file
107
src/main/java/com/yolo/keyborad/config/AppleAppStoreConfig.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package com.yolo.keyborad.config;
|
||||
|
||||
import com.apple.itunes.storekit.client.AppStoreServerAPIClient;
|
||||
import com.apple.itunes.storekit.migration.ReceiptUtility;
|
||||
import com.apple.itunes.storekit.model.Environment;
|
||||
import com.apple.itunes.storekit.verification.SignedDataVerifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Apple App Store 配置类
|
||||
* 用于配置与Apple App Store服务器API交互所需的各种组件
|
||||
*/
|
||||
@Configuration
|
||||
public class AppleAppStoreConfig {
|
||||
|
||||
// Apple App Store 属性配置
|
||||
private final AppleAppStoreProperties properties;
|
||||
// 资源加载器,用于加载证书和密钥文件
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param properties Apple App Store属性配置对象
|
||||
* @param resourceLoader Spring资源加载器
|
||||
*/
|
||||
public AppleAppStoreConfig(AppleAppStoreProperties properties,
|
||||
ResourceLoader resourceLoader) {
|
||||
this.properties = properties;
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建App Store服务器API客户端
|
||||
* 用于与Apple App Store服务器进行API交互
|
||||
*
|
||||
* @return AppStoreServerAPIClient实例
|
||||
* @throws Exception 当加载密钥文件或创建客户端失败时抛出
|
||||
*/
|
||||
@Bean
|
||||
public AppStoreServerAPIClient appStoreServerAPIClient() throws Exception {
|
||||
// 加载私钥文件
|
||||
Resource keyResource = resourceLoader.getResource(properties.getPrivateKeyPath());
|
||||
Path keyPath = keyResource.getFile().toPath();
|
||||
String encodedKey = Files.readString(keyPath);
|
||||
|
||||
// 获取环境配置(沙盒或生产)
|
||||
Environment env = Environment.valueOf(properties.getEnvironment());
|
||||
|
||||
// 创建并返回API客户端
|
||||
return new AppStoreServerAPIClient(
|
||||
encodedKey, // 私钥内容
|
||||
properties.getKeyId(), // 密钥ID
|
||||
properties.getIssuerId(), // 颁发者ID
|
||||
properties.getBundleId(), // 应用包ID
|
||||
env // 环境
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建签名数据验证器
|
||||
* 用于验证来自App Store的签名收据和通知
|
||||
*
|
||||
* @return SignedDataVerifier实例
|
||||
* @throws Exception 当加载根证书或创建验证器失败时抛出
|
||||
*/
|
||||
@Bean
|
||||
public SignedDataVerifier signedDataVerifier() throws Exception {
|
||||
// 加载所有根证书
|
||||
Set<InputStream> rootCAs = new HashSet<>();
|
||||
for (String path : properties.getRootCertificates()) {
|
||||
Resource resource = resourceLoader.getResource(path);
|
||||
rootCAs.add(resource.getInputStream());
|
||||
}
|
||||
|
||||
// 获取环境配置(沙盒或生产)
|
||||
Environment env = Environment.valueOf(properties.getEnvironment());
|
||||
|
||||
// 创建并返回签名数据验证器
|
||||
return new SignedDataVerifier(
|
||||
rootCAs, // 根证书集合
|
||||
properties.getBundleId(), // 应用包ID
|
||||
properties.getAppAppleId(), // 应用Apple ID(生产环境必须填写)
|
||||
env, // 环境
|
||||
true // 启用在线检查
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建收据工具类实例
|
||||
* 用于处理和解析App Store收据数据
|
||||
*
|
||||
* @return ReceiptUtility实例
|
||||
*/
|
||||
@Bean
|
||||
public ReceiptUtility receiptUtility() {
|
||||
return new ReceiptUtility();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.yolo.keyborad.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ConfigurationProperties(prefix = "apple.appstore")
|
||||
@Data
|
||||
public class AppleAppStoreProperties {
|
||||
|
||||
private String issuerId;
|
||||
private String keyId;
|
||||
private String bundleId;
|
||||
private Long appAppleId;
|
||||
private String privateKeyPath;
|
||||
private String environment; // SANDBOX / PRODUCTION
|
||||
private List<String> rootCertificates;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.yolo.keyborad.controller;
|
||||
|
||||
import com.yolo.keyborad.model.dto.AppleReceiptValidationResult;
|
||||
import com.yolo.keyborad.service.AppleReceiptService;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/apple")
|
||||
public class AppleReceiptController {
|
||||
|
||||
private final AppleReceiptService appleReceiptService;
|
||||
|
||||
public AppleReceiptController(AppleReceiptService appleReceiptService) {
|
||||
this.appleReceiptService = appleReceiptService;
|
||||
}
|
||||
|
||||
@PostMapping("/validate-receipt")
|
||||
public AppleReceiptValidationResult validateReceipt(@RequestBody Map<String, String> body) {
|
||||
String receipt = body.get("receipt");
|
||||
return appleReceiptService.validateReceipt(receipt);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.yolo.keyborad.controller;
|
||||
|
||||
import cn.hutool.json.JSON;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.yolo.keyborad.common.BaseResponse;
|
||||
import com.yolo.keyborad.common.ResultUtils;
|
||||
|
||||
import com.yolo.keyborad.model.dto.EmbedSaveReq;
|
||||
import com.yolo.keyborad.model.dto.IosPayVerifyReq;
|
||||
import com.yolo.keyborad.service.impl.QdrantVectorService;
|
||||
import io.qdrant.client.QdrantClient;
|
||||
@@ -82,8 +85,10 @@ public class DemoController {
|
||||
@PostMapping("/testSaveEmbed")
|
||||
@Operation(summary = "测试存储向量接口", description = "测试存储向量接口")
|
||||
@Parameter(name = "userInput",required = true,description = "测试存储向量接口")
|
||||
public BaseResponse<Boolean> testSaveEmbed( @RequestBody List<Float> userInput) throws Exception {
|
||||
qdrantVectorService.upsertPoint(1L, userInput, null);
|
||||
public BaseResponse<Boolean> testSaveEmbed(@RequestBody EmbedSaveReq embedSaveReq) {
|
||||
qdrantVectorService.upsertPoint(embedSaveReq.getRecordItem().getId()
|
||||
, embedSaveReq.getVector()
|
||||
, JSONUtil.toJsonStr(embedSaveReq.getRecordItem()));
|
||||
return ResultUtils.success(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.yolo.keyborad.model.dto;
|
||||
|
||||
import com.apple.itunes.storekit.model.Environment;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AppleReceiptValidationResult {
|
||||
|
||||
private boolean valid;
|
||||
private String reason;
|
||||
|
||||
private String transactionId;
|
||||
private String originalTransactionId;
|
||||
private List<String> productIds;
|
||||
|
||||
private Instant purchaseDate;
|
||||
private Instant expiresDate;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
}
|
||||
20
src/main/java/com/yolo/keyborad/model/dto/EmbedSaveReq.java
Normal file
20
src/main/java/com/yolo/keyborad/model/dto/EmbedSaveReq.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.yolo.keyborad.model.dto;
|
||||
|
||||
import com.yolo.keyborad.model.entity.RecordItem;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/11/14 13:21
|
||||
*/
|
||||
@Data
|
||||
public class EmbedSaveReq {
|
||||
|
||||
|
||||
|
||||
private List<Float> vector;
|
||||
|
||||
private RecordItem recordItem;
|
||||
}
|
||||
19
src/main/java/com/yolo/keyborad/model/entity/RecordItem.java
Normal file
19
src/main/java/com/yolo/keyborad/model/entity/RecordItem.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.yolo.keyborad.model.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/11/14 13:22
|
||||
*/
|
||||
@Data
|
||||
public class RecordItem {
|
||||
|
||||
private Long id;
|
||||
|
||||
private String document;
|
||||
|
||||
private String content;
|
||||
|
||||
private Integer frequency;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.yolo.keyborad.service;
|
||||
|
||||
import com.yolo.keyborad.model.dto.AppleReceiptValidationResult;
|
||||
|
||||
public interface AppleReceiptService {
|
||||
|
||||
/**
|
||||
* 验证 base64 app receipt 是否有效,并返回解析结果。
|
||||
*
|
||||
* @param appReceipt Base64 的 app receipt(以 MI... 开头那串)
|
||||
*/
|
||||
AppleReceiptValidationResult validateReceipt(String appReceipt);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package com.yolo.keyborad.service.impl;
|
||||
|
||||
import com.apple.itunes.storekit.client.APIException;
|
||||
import com.apple.itunes.storekit.client.AppStoreServerAPIClient;
|
||||
import com.apple.itunes.storekit.migration.ReceiptUtility;
|
||||
import com.apple.itunes.storekit.model.Environment;
|
||||
import com.apple.itunes.storekit.model.JWSTransactionDecodedPayload;
|
||||
import com.apple.itunes.storekit.model.TransactionInfoResponse;
|
||||
import com.apple.itunes.storekit.verification.SignedDataVerifier;
|
||||
import com.apple.itunes.storekit.verification.VerificationException;
|
||||
import com.yolo.keyborad.model.dto.AppleReceiptValidationResult;
|
||||
import com.yolo.keyborad.service.AppleReceiptService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Apple 应用内购买收据验证服务实现类
|
||||
* <p>
|
||||
* 负责验证苹果应用内购买的收据,包括一次性购买和订阅的验证逻辑
|
||||
* 使用 Apple App Store Server API 进行收据验证和交易信息获取
|
||||
* </p>
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AppleReceiptServiceImpl implements AppleReceiptService {
|
||||
|
||||
/**
|
||||
* App Store 服务器 API 客户端
|
||||
* <p>
|
||||
* 用于调用 Apple App Store Server API 获取交易信息
|
||||
* </p>
|
||||
*/
|
||||
private final AppStoreServerAPIClient client;
|
||||
|
||||
/**
|
||||
* 签名数据验证器
|
||||
* <p>
|
||||
* 用于验证和解码 Apple 返回的 JWS 签名数据
|
||||
* </p>
|
||||
*/
|
||||
private final SignedDataVerifier signedDataVerifier;
|
||||
|
||||
/**
|
||||
* 收据工具类
|
||||
* <p>
|
||||
* 用于解析应用收据内容,提取交易 ID 等信息
|
||||
* </p>
|
||||
*/
|
||||
private final ReceiptUtility receiptUtility;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* <p>
|
||||
* 通过构造函数注入所需的依赖组件
|
||||
* </p>
|
||||
* @param client App Store 服务器 API 客户端
|
||||
* @param signedDataVerifier 签名数据验证器
|
||||
* @param receiptUtility 收据工具类
|
||||
*/
|
||||
public AppleReceiptServiceImpl(AppStoreServerAPIClient client,
|
||||
SignedDataVerifier signedDataVerifier,
|
||||
ReceiptUtility receiptUtility) {
|
||||
this.client = client;
|
||||
this.signedDataVerifier = signedDataVerifier;
|
||||
this.receiptUtility = receiptUtility;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证 Apple 应用内购买收据
|
||||
* <p>
|
||||
* 执行完整的收据验证流程,包括解析收据、获取交易信息、验证签名和业务逻辑校验
|
||||
* </p>
|
||||
* @param appReceipt Base64 编码的应用内购买收据
|
||||
* @return 验证结果对象,包含验证状态和交易信息
|
||||
*/
|
||||
@Override
|
||||
public AppleReceiptValidationResult validateReceipt(String appReceipt) {
|
||||
// 检查收据是否为空
|
||||
if (appReceipt == null || appReceipt.isBlank()) {
|
||||
return invalid("empty_receipt");
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 从收据里解析出 transactionId(不做验证,只是解析 ASN.1)
|
||||
String transactionId = receiptUtility.extractTransactionIdFromAppReceipt(appReceipt);
|
||||
if (transactionId == null) {
|
||||
return invalid("no_transaction_id_in_receipt");
|
||||
}
|
||||
|
||||
// 2. 调用 App Store Server API 获取单笔交易信息
|
||||
TransactionInfoResponse infoResponse = client.getTransactionInfo(transactionId);
|
||||
|
||||
String signedTransactionInfo = infoResponse.getSignedTransactionInfo();
|
||||
if (signedTransactionInfo == null) {
|
||||
return invalid("no_signed_transaction_info");
|
||||
}
|
||||
|
||||
// 3. 使用 SignedDataVerifier 验证 JWS 并解码 payload
|
||||
JWSTransactionDecodedPayload payload =
|
||||
signedDataVerifier.verifyAndDecodeTransaction(signedTransactionInfo);
|
||||
|
||||
// 4. 执行业务校验:检查交易是否仍然有效
|
||||
boolean stillActive = isTransactionActive(payload);
|
||||
|
||||
// 构建并返回验证结果
|
||||
return getAppleReceiptValidationResult(stillActive, payload);
|
||||
} catch (VerificationException e) {
|
||||
// 验证异常处理
|
||||
log.warn("Apple receipt verification failed", e);
|
||||
return invalid("verification_exception:" + e.getStatus());
|
||||
} catch (APIException e) {
|
||||
// API 异常处理,可以根据错误码进一步处理(如环境切换)
|
||||
// 4040010 表示订单不存在,可能是环境不匹配导致
|
||||
long code = e.getApiError().errorCode();
|
||||
log.warn("App Store API error, code={}", code, e);
|
||||
return invalid("api_exception:" + code);
|
||||
} catch (Exception e) {
|
||||
// 其他未预期异常处理
|
||||
log.error("Unexpected error when validating Apple receipt", e);
|
||||
return invalid("unexpected_error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建苹果收据验证结果对象
|
||||
* <p>
|
||||
* 根据交易有效性和交易负载构建详细的验证结果
|
||||
* </p>
|
||||
* @param stillActive 交易是否仍然有效
|
||||
* @param payload 解码后的交易负载
|
||||
* @return 包含详细信息的验证结果对象
|
||||
*/
|
||||
private static AppleReceiptValidationResult getAppleReceiptValidationResult(boolean stillActive, JWSTransactionDecodedPayload payload) {
|
||||
AppleReceiptValidationResult result = new AppleReceiptValidationResult();
|
||||
// 设置验证状态和原因
|
||||
result.setValid(stillActive);
|
||||
result.setReason(stillActive ? "ok" : "expired_or_revoked");
|
||||
|
||||
// 设置交易相关信息
|
||||
result.setTransactionId(payload.getTransactionId());
|
||||
result.setOriginalTransactionId(payload.getOriginalTransactionId());
|
||||
result.setProductIds(List.of(payload.getProductId()));
|
||||
|
||||
// 设置时间信息
|
||||
if (payload.getPurchaseDate() != null) {
|
||||
result.setPurchaseDate(Instant.ofEpochMilli(payload.getPurchaseDate()));
|
||||
}
|
||||
if (payload.getExpiresDate() != null) {
|
||||
result.setExpiresDate(Instant.ofEpochMilli(payload.getExpiresDate()));
|
||||
}
|
||||
|
||||
// 设置环境信息
|
||||
Environment env = payload.getEnvironment();
|
||||
result.setEnvironment(env);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建无效的验证结果
|
||||
* <p>
|
||||
* 快速创建一个表示验证失败的结果对象
|
||||
* </p>
|
||||
* @param reason 失败原因
|
||||
* @return 标记为无效的验证结果对象
|
||||
*/
|
||||
private AppleReceiptValidationResult invalid(String reason) {
|
||||
AppleReceiptValidationResult r = new AppleReceiptValidationResult();
|
||||
r.setValid(false);
|
||||
r.setReason(reason);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查交易是否仍然有效
|
||||
* <p>
|
||||
* 根据交易负载中的信息判断交易是否处于有效状态
|
||||
* 1. 已撤销/退款的交易无效
|
||||
* 2. 有过期时间的订阅类交易检查是否已过期
|
||||
* 3. 一次性购买交易只要未撤销则认为有效
|
||||
* </p>
|
||||
* @param payload 解码后的交易负载
|
||||
* @return 交易是否有效
|
||||
*/
|
||||
private boolean isTransactionActive(JWSTransactionDecodedPayload payload) {
|
||||
// 已撤销/退款的可以直接认为无效
|
||||
if (payload.getRevocationDate() != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 有 expiresDate 的一般是订阅(自动续订等),过期就无效
|
||||
if (payload.getExpiresDate() != null) {
|
||||
long now = System.currentTimeMillis();
|
||||
return now < payload.getExpiresDate();
|
||||
}
|
||||
|
||||
// 没有 expiresDate 的一次性内购,只要能通过验签 + 没撤销,就认为是有效购买
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
package com.yolo.keyborad.service.impl;
|
||||
|
||||
import com.yolo.keyborad.utils.ProtoUtils;
|
||||
import com.yolo.keyborad.common.ErrorCode;
|
||||
import com.yolo.keyborad.exception.BusinessException;
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.grpc.Collections;
|
||||
import io.qdrant.client.grpc.JsonWithInt;
|
||||
import io.qdrant.client.grpc.Points;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static io.qdrant.client.PointIdFactory.id;
|
||||
import static io.qdrant.client.ValueFactory.value;
|
||||
@@ -17,6 +20,7 @@ import static io.qdrant.client.VectorsFactory.vectors;
|
||||
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class QdrantVectorService {
|
||||
|
||||
@Resource
|
||||
@@ -31,30 +35,37 @@ public class QdrantVectorService {
|
||||
* @param vector 向量(和 collection 中定义的 size 一致)
|
||||
* @param payload 额外信息,例如原文、标题、userId 等
|
||||
*/
|
||||
public void upsertPoint(long id, List<Float> vector, Map<String, JsonWithInt.Value> payload) throws Exception {
|
||||
|
||||
|
||||
|
||||
// 1. 确保 collection 存在(没有就创建一次即可)
|
||||
qdrantClient.createCollectionAsync(
|
||||
COLLECTION_NAME,
|
||||
Collections.VectorParams.newBuilder()
|
||||
.setSize(vector.size()) // 向量维度
|
||||
.setDistance(Collections.Distance.Cosine) // 相似度度量
|
||||
.build()
|
||||
).get(); // 简单起见直接 get(),生产建议在启动时提前创建好
|
||||
public void upsertPoint(long id, List<Float> vector,String payload){
|
||||
|
||||
// // 1. 确保 collection 存在(没有就创建一次即可)
|
||||
// try {
|
||||
// qdrantClient.createCollectionAsync(
|
||||
// COLLECTION_NAME,
|
||||
// Collections.VectorParams.newBuilder()
|
||||
// .setSize(vector.size()) // 向量维度
|
||||
// .setDistance(Collections.Distance.Cosine) // 相似度度量
|
||||
// .build()
|
||||
// ).get(); // 简单起见直接 get(),生产建议在启动时提前创建好
|
||||
// } catch (InterruptedException | ExecutionException e) {
|
||||
// log.error("创建 collection 失败", e);
|
||||
// throw new BusinessException(ErrorCode.OPERATION_ERROR);
|
||||
// }
|
||||
|
||||
try {
|
||||
qdrantClient.upsertAsync(
|
||||
COLLECTION_NAME,
|
||||
List.of(
|
||||
Points.PointStruct.newBuilder()
|
||||
.setId(id(id))
|
||||
.setVectors(vectors(vector))
|
||||
.putAllPayload(Map.of("payload",value("testInfo")))
|
||||
.putAllPayload(Map.of("payload",value(payload)))
|
||||
.build()
|
||||
)
|
||||
).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
log.error("upsert point 失败", e);
|
||||
throw new BusinessException(ErrorCode.OPERATION_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/main/resources/AppleRootCA-G2.cer
Normal file
BIN
src/main/resources/AppleRootCA-G2.cer
Normal file
Binary file not shown.
BIN
src/main/resources/AppleRootCA-G3.cer
Normal file
BIN
src/main/resources/AppleRootCA-G3.cer
Normal file
Binary file not shown.
6
src/main/resources/SubscriptionKey_Y7TF7BV74G.p8
Normal file
6
src/main/resources/SubscriptionKey_Y7TF7BV74G.p8
Normal file
@@ -0,0 +1,6 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg3YPU71xkC9jamBjy
|
||||
HhI7dvQrwmK5MstIOEiwdPVSuvqgCgYIKoZIzj0DAQehRANCAATKNsGPJmrKtAda
|
||||
byQwaaV6ODuiV1zX6JW9eNDS/1WJRqDEHCUG44kTaWyczCid4pQdzGumnbBoOxA8
|
||||
0SaFtsTR
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -16,3 +16,22 @@ knife4j:
|
||||
api-rule: package
|
||||
api-rule-resources:
|
||||
- com.yolo.keyborad.controller
|
||||
|
||||
apple:
|
||||
appstore:
|
||||
issuer-id: "178b442e-b7be-4526-bd13-ab293d019df0"
|
||||
key-id: "Y7TF7BV74G"
|
||||
bundle-id: "com.loveKey.nyx"
|
||||
# app 在 App Store 的 Apple ID(数值),生产环境必填
|
||||
app-apple-id: 1234567890
|
||||
|
||||
# p8 私钥文件路径(你可以放在 resources 下)
|
||||
private-key-path: "classpath:SubscriptionKey_Y7TF7BV74G.p8"
|
||||
|
||||
# SANDBOX 或 PRODUCTION
|
||||
environment: "SANDBOX"
|
||||
|
||||
# 根证书路径(从 Apple PKI 下载)
|
||||
root-certificates:
|
||||
- "classpath:AppleRootCA-G2.cer"
|
||||
- "classpath:AppleRootCA-G3.cer"
|
||||
Reference in New Issue
Block a user