From f60ee2df3d5655d580c47ad211dc4592be30b1b5 Mon Sep 17 00:00:00 2001 From: ziin Date: Fri, 14 Nov 2025 15:48:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=96=B0=E5=A2=9E=E8=8B=B9?= =?UTF-8?q?=E6=9E=9C=20App=20Store=20=E8=AE=A2=E9=98=85=E7=A5=A8=E6=8D=AE?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E4=B8=8E=E5=90=91=E9=87=8F=E5=AD=98=E5=82=A8?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 Apple App Store Server Library,完成票据验证、续订、退款通知全套流程 - 新增 AppleReceiptController / AppleReceiptService 及相关配置类,支持沙箱与生产环境双端点 - 向量存储接口升级:EmbedSaveReq 封装向量与业务实体,QdrantVectorService 改为 JSON 字符串载荷并补全异常处理 - 补充 Apple 根证书与订阅密钥资源文件,pom 与 yml 增加对应依赖与配置 --- pom.xml | 6 + .../java/com/yolo/keyborad/MyApplication.java | 3 + .../keyborad/config/AppleAppStoreConfig.java | 107 ++++++++++ .../config/AppleAppStoreProperties.java | 20 ++ .../controller/AppleReceiptController.java | 27 +++ .../keyborad/controller/DemoController.java | 9 +- .../dto/AppleReceiptValidationResult.java | 30 +++ .../yolo/keyborad/model/dto/EmbedSaveReq.java | 20 ++ .../keyborad/model/entity/RecordItem.java | 19 ++ .../keyborad/service/AppleReceiptService.java | 13 ++ .../service/impl/AppleReceiptServiceImpl.java | 202 ++++++++++++++++++ .../service/impl/QdrantVectorService.java | 57 +++-- src/main/resources/AppleRootCA-G2.cer | Bin 0 -> 1430 bytes src/main/resources/AppleRootCA-G3.cer | Bin 0 -> 583 bytes .../resources/SubscriptionKey_Y7TF7BV74G.p8 | 6 + src/main/resources/application-dev.yml | 21 +- 16 files changed, 514 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/yolo/keyborad/config/AppleAppStoreConfig.java create mode 100644 src/main/java/com/yolo/keyborad/config/AppleAppStoreProperties.java create mode 100644 src/main/java/com/yolo/keyborad/controller/AppleReceiptController.java create mode 100644 src/main/java/com/yolo/keyborad/model/dto/AppleReceiptValidationResult.java create mode 100644 src/main/java/com/yolo/keyborad/model/dto/EmbedSaveReq.java create mode 100644 src/main/java/com/yolo/keyborad/model/entity/RecordItem.java create mode 100644 src/main/java/com/yolo/keyborad/service/AppleReceiptService.java create mode 100644 src/main/java/com/yolo/keyborad/service/impl/AppleReceiptServiceImpl.java create mode 100644 src/main/resources/AppleRootCA-G2.cer create mode 100644 src/main/resources/AppleRootCA-G3.cer create mode 100644 src/main/resources/SubscriptionKey_Y7TF7BV74G.p8 diff --git a/pom.xml b/pom.xml index 99e9c31..d214bf7 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,12 @@ grpc-netty-shaded 1.65.1 + + + com.apple.itunes.storekit + app-store-server-library + 3.6.0 + diff --git a/src/main/java/com/yolo/keyborad/MyApplication.java b/src/main/java/com/yolo/keyborad/MyApplication.java index 7137577..de23dc7 100644 --- a/src/main/java/com/yolo/keyborad/MyApplication.java +++ b/src/main/java/com/yolo/keyborad/MyApplication.java @@ -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); diff --git a/src/main/java/com/yolo/keyborad/config/AppleAppStoreConfig.java b/src/main/java/com/yolo/keyborad/config/AppleAppStoreConfig.java new file mode 100644 index 0000000..a7fe265 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/config/AppleAppStoreConfig.java @@ -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 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(); + } +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/config/AppleAppStoreProperties.java b/src/main/java/com/yolo/keyborad/config/AppleAppStoreProperties.java new file mode 100644 index 0000000..22f92f5 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/config/AppleAppStoreProperties.java @@ -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 rootCertificates; + +} diff --git a/src/main/java/com/yolo/keyborad/controller/AppleReceiptController.java b/src/main/java/com/yolo/keyborad/controller/AppleReceiptController.java new file mode 100644 index 0000000..90e436a --- /dev/null +++ b/src/main/java/com/yolo/keyborad/controller/AppleReceiptController.java @@ -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 body) { + String receipt = body.get("receipt"); + return appleReceiptService.validateReceipt(receipt); + } +} diff --git a/src/main/java/com/yolo/keyborad/controller/DemoController.java b/src/main/java/com/yolo/keyborad/controller/DemoController.java index 3970f9d..909834a 100644 --- a/src/main/java/com/yolo/keyborad/controller/DemoController.java +++ b/src/main/java/com/yolo/keyborad/controller/DemoController.java @@ -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 testSaveEmbed( @RequestBody List userInput) throws Exception { - qdrantVectorService.upsertPoint(1L, userInput, null); + public BaseResponse testSaveEmbed(@RequestBody EmbedSaveReq embedSaveReq) { + qdrantVectorService.upsertPoint(embedSaveReq.getRecordItem().getId() + , embedSaveReq.getVector() + , JSONUtil.toJsonStr(embedSaveReq.getRecordItem())); return ResultUtils.success(true); } } diff --git a/src/main/java/com/yolo/keyborad/model/dto/AppleReceiptValidationResult.java b/src/main/java/com/yolo/keyborad/model/dto/AppleReceiptValidationResult.java new file mode 100644 index 0000000..1d20e99 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/dto/AppleReceiptValidationResult.java @@ -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 productIds; + + private Instant purchaseDate; + private Instant expiresDate; + + private Environment environment; + +} diff --git a/src/main/java/com/yolo/keyborad/model/dto/EmbedSaveReq.java b/src/main/java/com/yolo/keyborad/model/dto/EmbedSaveReq.java new file mode 100644 index 0000000..4cadcd5 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/dto/EmbedSaveReq.java @@ -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 vector; + + private RecordItem recordItem; +} diff --git a/src/main/java/com/yolo/keyborad/model/entity/RecordItem.java b/src/main/java/com/yolo/keyborad/model/entity/RecordItem.java new file mode 100644 index 0000000..945f30a --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/entity/RecordItem.java @@ -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; +} diff --git a/src/main/java/com/yolo/keyborad/service/AppleReceiptService.java b/src/main/java/com/yolo/keyborad/service/AppleReceiptService.java new file mode 100644 index 0000000..24c30c5 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/service/AppleReceiptService.java @@ -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); +} diff --git a/src/main/java/com/yolo/keyborad/service/impl/AppleReceiptServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/AppleReceiptServiceImpl.java new file mode 100644 index 0000000..6754eee --- /dev/null +++ b/src/main/java/com/yolo/keyborad/service/impl/AppleReceiptServiceImpl.java @@ -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 应用内购买收据验证服务实现类 + *

+ * 负责验证苹果应用内购买的收据,包括一次性购买和订阅的验证逻辑 + * 使用 Apple App Store Server API 进行收据验证和交易信息获取 + *

+ */ +@Slf4j +@Service +public class AppleReceiptServiceImpl implements AppleReceiptService { + + /** + * App Store 服务器 API 客户端 + *

+ * 用于调用 Apple App Store Server API 获取交易信息 + *

+ */ + private final AppStoreServerAPIClient client; + + /** + * 签名数据验证器 + *

+ * 用于验证和解码 Apple 返回的 JWS 签名数据 + *

+ */ + private final SignedDataVerifier signedDataVerifier; + + /** + * 收据工具类 + *

+ * 用于解析应用收据内容,提取交易 ID 等信息 + *

+ */ + private final ReceiptUtility receiptUtility; + + /** + * 构造函数 + *

+ * 通过构造函数注入所需的依赖组件 + *

+ * @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 应用内购买收据 + *

+ * 执行完整的收据验证流程,包括解析收据、获取交易信息、验证签名和业务逻辑校验 + *

+ * @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"); + } + } + + /** + * 构建苹果收据验证结果对象 + *

+ * 根据交易有效性和交易负载构建详细的验证结果 + *

+ * @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; + } + + /** + * 创建无效的验证结果 + *

+ * 快速创建一个表示验证失败的结果对象 + *

+ * @param reason 失败原因 + * @return 标记为无效的验证结果对象 + */ + private AppleReceiptValidationResult invalid(String reason) { + AppleReceiptValidationResult r = new AppleReceiptValidationResult(); + r.setValid(false); + r.setReason(reason); + return r; + } + + /** + * 检查交易是否仍然有效 + *

+ * 根据交易负载中的信息判断交易是否处于有效状态 + * 1. 已撤销/退款的交易无效 + * 2. 有过期时间的订阅类交易检查是否已过期 + * 3. 一次性购买交易只要未撤销则认为有效 + *

+ * @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; + } +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/service/impl/QdrantVectorService.java b/src/main/java/com/yolo/keyborad/service/impl/QdrantVectorService.java index 9c353a2..1a575eb 100644 --- a/src/main/java/com/yolo/keyborad/service/impl/QdrantVectorService.java +++ b/src/main/java/com/yolo/keyborad/service/impl/QdrantVectorService.java @@ -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 vector, Map payload) throws Exception { + public void upsertPoint(long id, List 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); +// } - - // 1. 确保 collection 存在(没有就创建一次即可) - qdrantClient.createCollectionAsync( - COLLECTION_NAME, - Collections.VectorParams.newBuilder() - .setSize(vector.size()) // 向量维度 - .setDistance(Collections.Distance.Cosine) // 相似度度量 - .build() - ).get(); // 简单起见直接 get(),生产建议在启动时提前创建好 - - - qdrantClient.upsertAsync( - COLLECTION_NAME, - List.of( - Points.PointStruct.newBuilder() - .setId(id(id)) - .setVectors(vectors(vector)) - .putAllPayload(Map.of("payload",value("testInfo"))) - .build() - ) - ).get(); + try { + qdrantClient.upsertAsync( + COLLECTION_NAME, + List.of( + Points.PointStruct.newBuilder() + .setId(id(id)) + .setVectors(vectors(vector)) + .putAllPayload(Map.of("payload",value(payload))) + .build() + ) + ).get(); + } catch (InterruptedException | ExecutionException e) { + log.error("upsert point 失败", e); + throw new BusinessException(ErrorCode.OPERATION_ERROR); + } } } diff --git a/src/main/resources/AppleRootCA-G2.cer b/src/main/resources/AppleRootCA-G2.cer new file mode 100644 index 0000000000000000000000000000000000000000..739b8141312801fc4e88396bf4366075e2711173 GIT binary patch literal 1430 zcmXqLVx45r#9Xz2nTe5!iG%UM)2+?viys*9vTOAw3^$fWUI6;DJ!c3vT26E!Oh9(9k#s-EKh6V@Moh?3yZ7ptT%{oZtmbE?IX_gbDkv37-N%oe}J zzP5HeiJHX2V)W^R!|$N@`QHwFe7wtbLBLg^?oW5`O>9&C@O4{}9^)$g`tk5`oHG2U-jvVF(JFAtQy>@PC8|Gm+-ur_evqs<>KYls!+pFOkI zyw>maT8D@Bjeoz}a@j5VxYux+X5^hik_|o4CC79w3k0jliufP7(wJt`o^Grz!truT zd5cLk8RGj7?_MQ5Sw1#If5q{d-@GFGL~T_}KFzvQ>X@dqh4C8q`U&%RUM2mQ z9cjbyXqw5BL}ue|2zV0PvXSiivJZh)`o?5)W*}w22NK{1 z39tY&9UF2g175L?oeT7jmWF>&3sQpoaUAA37lOmwM#Iu zvEs(Zt*#Cgt5+O2_q#UPJmcH*-|LSZ_I@L8rY0QHHl=9OE2g*IxleeOO*jx$J&Y#WKm>kMqyI%SzoNwLx|7a?QVA>=u=J?w@!2U}iCt+`i2!(e>BKPs%5fi}#0h8g95*leD~AbVWzkY72)$XN3NX zKK@{S-@NGbnHkUaFWU19&c)8#u)Swc2lF=;T`x`$uV~zj- literal 0 HcmV?d00001 diff --git a/src/main/resources/AppleRootCA-G3.cer b/src/main/resources/AppleRootCA-G3.cer new file mode 100644 index 0000000000000000000000000000000000000000..228bfa39cbd5acfe53fb9d196e3c1bbbd28649f8 GIT binary patch literal 583 zcmXqLVsbWUVm!HknTe5!i9`43pN>mMy{8&*v2kd%d7QIlVP-Z+HbQ8gAnH8xlJyL^4LCu9Y{E>T!3J{TyoM$QCdLMa7KWAvW>Mn2#+FE2`Z=V` zK!A-M?0+UkHdgIM76v8eBnFllFZFF5ik7^ctW?w}EOS?2>c^vt{R;1hh~4CSx{Ot; zJf%9`&*JiK8JDf~U*)$MCB>e6*%Iw<;4c`(@HZlYXX#gd9ba~L;nG{vr%%r}jCrd) zw_3sa#?FwNaWj`#1#%fKb~11^i`@ju(^Y`3K= md8u*U