From 9170f93d6778c572ab9b34720c628c04c56bce8a Mon Sep 17 00:00:00 2001 From: ziin Date: Thu, 13 Nov 2025 22:02:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=96=B0=E5=A2=9E=E5=90=91?= =?UTF-8?q?=E9=87=8F=E6=95=B0=E6=8D=AE=E5=BA=93=E4=B8=8EApple=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 45 +++++++++- .../com/yolo/keyborad/config/LLMConfig.java | 20 +++++ .../keyborad/config/QdrantClientConfig.java | 33 +++++++ .../keyborad/config/SaTokenConfigure.java | 4 +- .../keyborad/controller/DemoController.java | 36 +++++++- .../keyborad/controller/UserController.java | 7 +- .../keyborad/model/dto/AppleLoginReq.java | 12 +++ .../keyborad/model/dto/IosPayVerifyReq.java | 21 +++++ .../service/{impl => }/IAppleService.java | 2 +- .../service/impl/AppleServiceImpl.java | 1 + .../service/impl/QdrantVectorService.java | 60 +++++++++++++ .../com/yolo/keyborad/utils/ApplePayUtil.java | 85 +++++++++++++++++++ .../com/yolo/keyborad/utils/ProtoUtils.java | 32 +++++++ src/main/resources/application-dev.yml | 1 + 14 files changed, 346 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/yolo/keyborad/config/QdrantClientConfig.java create mode 100644 src/main/java/com/yolo/keyborad/model/dto/AppleLoginReq.java create mode 100644 src/main/java/com/yolo/keyborad/model/dto/IosPayVerifyReq.java rename src/main/java/com/yolo/keyborad/service/{impl => }/IAppleService.java (85%) create mode 100644 src/main/java/com/yolo/keyborad/service/impl/QdrantVectorService.java create mode 100644 src/main/java/com/yolo/keyborad/utils/ApplePayUtil.java create mode 100644 src/main/java/com/yolo/keyborad/utils/ProtoUtils.java diff --git a/pom.xml b/pom.xml index 53f7084..99e9c31 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,50 @@ org.springframework.boot spring-boot-starter-web - + + + io.qdrant + client + 1.15.0 + + + guava + com.google.guava + + + + + com.google.guava + guava + 33.2.0-jre + + + com.google.protobuf + protobuf-java + 4.28.2 + + + + io.grpc + grpc-api + 1.65.1 + + + + + io.grpc + grpc-stub + 1.65.1 + + + + + io.grpc + grpc-netty-shaded + 1.65.1 + + + diff --git a/src/main/java/com/yolo/keyborad/config/LLMConfig.java b/src/main/java/com/yolo/keyborad/config/LLMConfig.java index bf82e90..4f4363c 100644 --- a/src/main/java/com/yolo/keyborad/config/LLMConfig.java +++ b/src/main/java/com/yolo/keyborad/config/LLMConfig.java @@ -3,12 +3,17 @@ package com.yolo.keyborad.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.document.MetadataMode; import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.ai.openai.OpenAiEmbeddingModel; +import org.springframework.ai.openai.OpenAiEmbeddingOptions; import org.springframework.ai.openai.api.OpenAiApi; +import org.springframework.ai.retry.RetryUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + /* * @author: ziin * @date: 2025/11/11 20:37 @@ -39,4 +44,19 @@ public class LLMConfig { .build()) .build(); } + + + + @Bean + public OpenAiEmbeddingModel embeddingModel() { + return new OpenAiEmbeddingModel( + this.openAiApi(), + MetadataMode.EMBED, + OpenAiEmbeddingOptions.builder() + .model("qwen/qwen3-embedding-8b") + .user("user-6") + .build(), + RetryUtils.DEFAULT_RETRY_TEMPLATE); + } + } diff --git a/src/main/java/com/yolo/keyborad/config/QdrantClientConfig.java b/src/main/java/com/yolo/keyborad/config/QdrantClientConfig.java new file mode 100644 index 0000000..bb0269d --- /dev/null +++ b/src/main/java/com/yolo/keyborad/config/QdrantClientConfig.java @@ -0,0 +1,33 @@ +package com.yolo.keyborad.config; + +import io.qdrant.client.QdrantClient; +import io.qdrant.client.QdrantGrpcClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/* + * @author: ziin + * @date: 2025/11/13 20:28 + */ +@Configuration +public class QdrantClientConfig { + + private final String qdrantHost = "b0c7f1ee-0eb9-469e-83e0-654249d9bd04.us-east4-0.gcp.cloud.qdrant.io"; + + private final Integer qdrantPort = 6334; + + private final String apiKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3MiOiJtIn0.HX_GxjXCrnhw2DQbMnMFzvDeaHbmNpI2tj2hoUjkvVU"; + + @Bean + public QdrantClient qdrantClient() { + return new QdrantClient( + QdrantGrpcClient.newBuilder( + qdrantHost, + qdrantPort, + true + ) + .withApiKey(apiKey) + .build() + ); + } +} diff --git a/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java b/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java index 13f730b..c78f059 100644 --- a/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java +++ b/src/main/java/com/yolo/keyborad/config/SaTokenConfigure.java @@ -36,7 +36,9 @@ public class SaTokenConfigure implements WebMvcConfigurer { "/demo/test", "/error", "/demo/talk", - "/user/appleLogin" + "/user/appleLogin", + "/demo/embed", + "/demo/testSaveEmbed" }; } @Bean diff --git a/src/main/java/com/yolo/keyborad/controller/DemoController.java b/src/main/java/com/yolo/keyborad/controller/DemoController.java index af7a24e..3970f9d 100644 --- a/src/main/java/com/yolo/keyborad/controller/DemoController.java +++ b/src/main/java/com/yolo/keyborad/controller/DemoController.java @@ -1,23 +1,26 @@ package com.yolo.keyborad.controller; -import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.yolo.keyborad.common.BaseResponse; -import com.yolo.keyborad.common.ErrorCode; import com.yolo.keyborad.common.ResultUtils; -import com.yolo.keyborad.exception.BusinessException; import com.yolo.keyborad.model.dto.IosPayVerifyReq; +import com.yolo.keyborad.service.impl.QdrantVectorService; +import io.qdrant.client.QdrantClient; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.embedding.Embedding; +import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; +import java.util.List; + /* * @author: ziin * @date: 2025/10/28 20:42 @@ -32,6 +35,14 @@ public class DemoController { @Resource private ChatClient client; + @Resource + private OpenAiEmbeddingModel embeddingModel; + + @Resource + private QdrantVectorService qdrantVectorService; + + + @GetMapping("/test") @Operation(summary = "测试接口", description = "测试接口") public BaseResponse testDemo(){ @@ -53,9 +64,26 @@ public class DemoController { .content(); } + @PostMapping("/embed") + @Operation(summary = "测试向量接口", description = "测试向量接口") + @Parameter(name = "userInput",required = true,description = "测试向量接口",example = "you are so cute!") + public BaseResponse testEmbed(@DefaultValue("you are so cute!") @RequestBody List userInput){ + EmbeddingResponse response = embeddingModel.embedForResponse(userInput); + return ResultUtils.success(response.getResult()); + } + + @Operation(summary = "IOS内购凭证校验", description = "IOS内购凭证校验") public BaseResponse iosPay(@RequestBody IosPayVerifyReq req) { return null; } + + @PostMapping("/testSaveEmbed") + @Operation(summary = "测试存储向量接口", description = "测试存储向量接口") + @Parameter(name = "userInput",required = true,description = "测试存储向量接口") + public BaseResponse testSaveEmbed( @RequestBody List userInput) throws Exception { + qdrantVectorService.upsertPoint(1L, userInput, null); + return ResultUtils.success(true); + } } diff --git a/src/main/java/com/yolo/keyborad/controller/UserController.java b/src/main/java/com/yolo/keyborad/controller/UserController.java index c418a93..b9de52b 100644 --- a/src/main/java/com/yolo/keyborad/controller/UserController.java +++ b/src/main/java/com/yolo/keyborad/controller/UserController.java @@ -1,22 +1,17 @@ package com.yolo.keyborad.controller; -import com.alibaba.fastjson.JSON; import com.yolo.keyborad.common.BaseResponse; import com.yolo.keyborad.common.ResultUtils; import com.yolo.keyborad.model.dto.AppleLoginReq; -import com.yolo.keyborad.service.impl.IAppleService; +import com.yolo.keyborad.service.IAppleService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.BeanUtils; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.Objects; - /** * 用户前端控制器 * diff --git a/src/main/java/com/yolo/keyborad/model/dto/AppleLoginReq.java b/src/main/java/com/yolo/keyborad/model/dto/AppleLoginReq.java new file mode 100644 index 0000000..75b9a12 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/dto/AppleLoginReq.java @@ -0,0 +1,12 @@ +package com.yolo.keyborad.model.dto; + +import lombok.Data; + +/* + * @author: ziin + * @date: 2025/11/13 16:15 + */ +@Data +public class AppleLoginReq { + private String identityToken; +} diff --git a/src/main/java/com/yolo/keyborad/model/dto/IosPayVerifyReq.java b/src/main/java/com/yolo/keyborad/model/dto/IosPayVerifyReq.java new file mode 100644 index 0000000..fcf8b05 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/dto/IosPayVerifyReq.java @@ -0,0 +1,21 @@ +package com.yolo.keyborad.model.dto; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class IosPayVerifyReq { + + @Schema(description = "商家订单id") + private String orderId; + + @Schema(description = "用户id") + private String userId; + + @Schema(description = "验证凭据") + private String receiptDate; + + @Schema(description = "ios选项值") + private String productId; +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/service/impl/IAppleService.java b/src/main/java/com/yolo/keyborad/service/IAppleService.java similarity index 85% rename from src/main/java/com/yolo/keyborad/service/impl/IAppleService.java rename to src/main/java/com/yolo/keyborad/service/IAppleService.java index 2263812..536418e 100644 --- a/src/main/java/com/yolo/keyborad/service/impl/IAppleService.java +++ b/src/main/java/com/yolo/keyborad/service/IAppleService.java @@ -1,4 +1,4 @@ -package com.yolo.keyborad.service.impl; +package com.yolo.keyborad.service; /** * Apple相关API diff --git a/src/main/java/com/yolo/keyborad/service/impl/AppleServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/AppleServiceImpl.java index 5b8b59a..dd41ff7 100644 --- a/src/main/java/com/yolo/keyborad/service/impl/AppleServiceImpl.java +++ b/src/main/java/com/yolo/keyborad/service/impl/AppleServiceImpl.java @@ -3,6 +3,7 @@ package com.yolo.keyborad.service.impl; import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.yolo.keyborad.service.IAppleService; import io.jsonwebtoken.*; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/yolo/keyborad/service/impl/QdrantVectorService.java b/src/main/java/com/yolo/keyborad/service/impl/QdrantVectorService.java new file mode 100644 index 0000000..9c353a2 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/service/impl/QdrantVectorService.java @@ -0,0 +1,60 @@ +package com.yolo.keyborad.service.impl; + +import com.yolo.keyborad.utils.ProtoUtils; +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 org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +import static io.qdrant.client.PointIdFactory.id; +import static io.qdrant.client.ValueFactory.value; +import static io.qdrant.client.VectorsFactory.vectors; + + +@Service +public class QdrantVectorService { + + @Resource + private QdrantClient qdrantClient; + + private static final String COLLECTION_NAME = "test_document"; + + /** + * 插入/更新一条向量数据 + * + * @param id 向量ID(可以是 Long / String,自行约定) + * @param vector 向量(和 collection 中定义的 size 一致) + * @param payload 额外信息,例如原文、标题、userId 等 + */ + public void upsertPoint(long id, List vector, Map payload) throws Exception { + + + + // 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(); + + } +} diff --git a/src/main/java/com/yolo/keyborad/utils/ApplePayUtil.java b/src/main/java/com/yolo/keyborad/utils/ApplePayUtil.java new file mode 100644 index 0000000..f65e2fe --- /dev/null +++ b/src/main/java/com/yolo/keyborad/utils/ApplePayUtil.java @@ -0,0 +1,85 @@ +package com.yolo.keyborad.utils; + +import javax.net.ssl.*; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Locale; + + +public class ApplePayUtil { + + private static class TrustAnyTrustManager implements X509TrustManager { + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } + + private static final String url_sandbox = "https://sandbox.itunes.apple.com/verifyReceipt"; + private static final String url_verify = "https://buy.itunes.apple.com/verifyReceipt"; + + /** + * 苹果服务器验证 + * + * @param receipt 账单 + * @return null 或返回结果 沙盒 https://sandbox.itunes.apple.com/verifyReceipt + * @url 要验证的地址 + */ + public static String buyAppVerify(String receipt, int type) throws Exception { + //环境判断 线上/开发环境用不同的请求链接 + String url = ""; + if (type == 0) { + url = url_sandbox; //沙盒测试 + } else { + url = url_verify; //线上测试 + } + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom()); + URL console = new URL(url); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", "text/json"); + conn.setRequestProperty("Proxy-Connection", "Keep-Alive"); + conn.setDoInput(true); + conn.setDoOutput(true); + BufferedOutputStream hurlBufOus = new BufferedOutputStream(conn.getOutputStream()); + //拼成固定的格式传给平台 + String str = String.format(Locale.CHINA, "{\"receipt-data\":\"" + receipt + "\"}"); + hurlBufOus.write(str.getBytes()); + hurlBufOus.flush(); + + InputStream is = conn.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + String line = null; + StringBuffer sb = new StringBuffer(); + while ((line = reader.readLine()) != null) { + sb.append(line); + } + + return sb.toString(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/utils/ProtoUtils.java b/src/main/java/com/yolo/keyborad/utils/ProtoUtils.java new file mode 100644 index 0000000..1fa5d53 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/utils/ProtoUtils.java @@ -0,0 +1,32 @@ +package com.yolo.keyborad.utils; + +import com.google.protobuf.Struct; +import com.google.protobuf.Value; + +import java.util.Map; + +public class ProtoUtils { + + public static Struct mapToStruct(Map map) { + Struct.Builder structBuilder = Struct.newBuilder(); + map.forEach((key, value) -> structBuilder.putFields(key, toValue(value))); + return structBuilder.build(); + } + + private static Value toValue(Object obj) { + Value.Builder valueBuilder = Value.newBuilder(); + + if (obj instanceof String) { + valueBuilder.setStringValue((String) obj); + } else if (obj instanceof Number) { + valueBuilder.setNumberValue(((Number) obj).doubleValue()); + } else if (obj instanceof Boolean) { + valueBuilder.setBoolValue((Boolean) obj); + } else { + // 复杂类型你自己扩展,也可以转 string 存 + valueBuilder.setStringValue(obj.toString()); + } + + return valueBuilder.build(); + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 6efee5e..41da143 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -5,6 +5,7 @@ spring: username: root password: 123asd + knife4j: enable: true openapi: