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: