├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── funai.sql ├── mdImg ├── assistant-1.png ├── assistant-2.png ├── chat-1.png ├── chat-2.png ├── chat-3.png ├── chat-4.png ├── contact-1.jpg ├── expert-2.png ├── game-1.png ├── game-2.png ├── game-3.png ├── game-4.png ├── mmexport1684888774983.jpg ├── pdf-1.png ├── pdf-2.png ├── pdf-3.png ├── pinecone-1.png ├── prompt-1.png ├── text2Img-1.png ├── text2Img-2.png ├── text2Img-3.png ├── trans-1.png ├── trans-2.png ├── user-1.png ├── user-2.png ├── user-3.png ├── user-4.png └── wechat-pay.png ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── gzhu │ │ └── funai │ │ ├── FunAiApplication.java │ │ ├── api │ │ ├── baidu │ │ │ ├── constant │ │ │ │ └── BaiDuConst.java │ │ │ └── util │ │ │ │ └── VoiceTool.java │ │ ├── mengwang │ │ │ └── SmsComponent.java │ │ ├── openai │ │ │ ├── ChatGPTApi.java │ │ │ ├── constant │ │ │ │ └── OpenAIConst.java │ │ │ ├── enums │ │ │ │ ├── OpenAiRespError.java │ │ │ │ └── Role.java │ │ │ ├── req │ │ │ │ ├── ChatGPTReq.java │ │ │ │ ├── ContextMessage.java │ │ │ │ └── EmbeddingReq.java │ │ │ └── resp │ │ │ │ ├── BillingUsage.java │ │ │ │ ├── ChatGPTResp.java │ │ │ │ ├── ChoiceMessages.java │ │ │ │ ├── CreditGrantsResp.java │ │ │ │ ├── Datum.java │ │ │ │ ├── Embedding.java │ │ │ │ ├── EmbeddingResp.java │ │ │ │ ├── Grants.java │ │ │ │ └── Usage.java │ │ └── pinecone │ │ │ ├── PineconeApi.java │ │ │ ├── req │ │ │ ├── PineconeDeleteReq.java │ │ │ ├── PineconeInsertReq.java │ │ │ ├── PineconeQueryReq.java │ │ │ └── PineconeVectorsReq.java │ │ │ └── resp │ │ │ ├── PineconeQueryDownResp.java │ │ │ └── PineconeQueryResp.java │ │ ├── config │ │ ├── FunAiConfig.java │ │ ├── MyWebMvcConfigurer.java │ │ ├── RedisConfig.java │ │ └── SwaggerConfig.java │ │ ├── controller │ │ ├── AdminController.java │ │ ├── ChatController.java │ │ ├── FileChatController.java │ │ ├── PromptController.java │ │ ├── StreamChatController.java │ │ ├── TranslationController.java │ │ └── UserController.java │ │ ├── dto │ │ ├── AddSessionRequest.java │ │ ├── ChangeUserLevelRequest.java │ │ ├── ChatWithFileRequest.java │ │ ├── OneShotChatRequest.java │ │ ├── PromptQueryRequest.java │ │ ├── RenameSessionRequest.java │ │ ├── SendCodeRequest.java │ │ ├── SessionChatRequest.java │ │ ├── StartGameStreamSessionRequest.java │ │ ├── StreamOneShotChatRequest.java │ │ ├── StreamSessionChatRequest.java │ │ ├── TranslationRequest.java │ │ ├── UserAdviceRequest.java │ │ ├── UserApiKeyRequest.java │ │ ├── UserListRequest.java │ │ ├── UserLockRequest.java │ │ ├── UserLoginRequest.java │ │ ├── UserRegisterRequest.java │ │ └── UserResetPasswordRequest.java │ │ ├── entity │ │ ├── AdminApiKeyEntity.java │ │ ├── DataSqlEntity.java │ │ ├── PromptEntity.java │ │ ├── SessionChatRecordEntity.java │ │ ├── UserAdvicesEntity.java │ │ ├── UserApiKeyEntity.java │ │ ├── UserEntity.java │ │ ├── UserLoginRecord.java │ │ └── UserSessionEntity.java │ │ ├── enums │ │ ├── ApiType.java │ │ ├── LoginType.java │ │ ├── Prompt.java │ │ ├── PromptTarget.java │ │ ├── PromptType.java │ │ ├── SessionType.java │ │ └── UserLevel.java │ │ ├── exception │ │ ├── BaseException.java │ │ ├── EmailException.java │ │ ├── PhoneException.java │ │ ├── SessionNameRepeatException.java │ │ └── UsernameException.java │ │ ├── filter │ │ ├── ChannelFilter.java │ │ └── CorsFilter.java │ │ ├── global │ │ ├── GlobalExceptionHandler.java │ │ └── constant │ │ │ ├── GlobalConstant.java │ │ │ └── TimeInterval.java │ │ ├── handler │ │ ├── LoginHandler.java │ │ └── impl │ │ │ ├── NormalLoginHandler.java │ │ │ └── VisitorLoginHandler.java │ │ ├── interceptor │ │ ├── AccessLimitInterceptor.java │ │ ├── AdminOperateInterceptor.java │ │ ├── UserChatLimitInterceptor.java │ │ ├── UserFileUploadLimitInterceptor.java │ │ └── UserLoginInterceptor.java │ │ ├── mapper │ │ ├── AdminApiKeyMapper.java │ │ ├── PromptMapper.java │ │ ├── SessionChatRecordMapper.java │ │ ├── UserAdvicesMapper.java │ │ ├── UserApiKeyMapper.java │ │ ├── UserLoginRecordMapper.java │ │ ├── UserMapper.java │ │ ├── UserSessionMapper.java │ │ └── xml │ │ │ ├── UserApiKeyMapper.xml │ │ │ └── UserMapper.xml │ │ ├── redis │ │ ├── AdminApiKeyRedisHelper.java │ │ ├── ChatRedisHelper.java │ │ └── RedisKeys.java │ │ ├── service │ │ ├── AdminApiKeyService.java │ │ ├── ChatService.java │ │ ├── FileChatService.java │ │ ├── FileService.java │ │ ├── PromptService.java │ │ ├── SessionChatRecordService.java │ │ ├── UserAdvicesService.java │ │ ├── UserApiKeyService.java │ │ ├── UserLoginRecordService.java │ │ ├── UserService.java │ │ ├── UserSessionService.java │ │ ├── helper │ │ │ └── ExpertChatHelper.java │ │ └── impl │ │ │ ├── AdminApiKeyServiceImpl.java │ │ │ ├── ChatServiceImpl.java │ │ │ ├── FileChatServiceImpl.java │ │ │ ├── FileServiceImpl.java │ │ │ ├── PromptServiceImpl.java │ │ │ ├── SessionChatRecordServiceImpl.java │ │ │ ├── UserAdvicesServiceImpl.java │ │ │ ├── UserApiKeyServiceImpl.java │ │ │ ├── UserLoginRecordServiceImpl.java │ │ │ ├── UserServiceImpl.java │ │ │ └── UserSessionServiceImpl.java │ │ ├── session │ │ └── LoginSession.java │ │ ├── sse │ │ ├── OpenAIOneShotChatSSEListener.java │ │ └── OpenAISessionChatSSEListener.java │ │ └── utils │ │ ├── AbstractUUIDGenerator.java │ │ ├── BytesHelper.java │ │ ├── DateTimeFormatterUtil.java │ │ ├── HttpClientUtil.java │ │ ├── JwtUtil.java │ │ ├── MilvusClientUtil.java │ │ ├── OkHttpClientUtil.java │ │ ├── PasswordEncoderUtil.java │ │ ├── RecursiveCharacterTextSplitter.java │ │ ├── RequestWrapper.java │ │ ├── ResponseWrapper.java │ │ ├── ResultCode.java │ │ ├── ReturnResult.java │ │ ├── SequentialUuidHexGenerator.java │ │ ├── SnowflakeIdGenerator.java │ │ ├── SpringUtil.java │ │ └── VerificationCodeGenerator.java └── resources │ ├── application.properties │ └── logback-spring.xml └── test └── java └── com └── gzhu └── funai ├── TestChatGPT.java ├── TestPinecone.java ├── api └── openai │ └── TestChatGPTApi.java ├── redis └── TestChatRedisHelper.java ├── service ├── TestAdminApiKeyService.java ├── TestChatService.java ├── TestFileChatService.java ├── TestPromptService.java ├── TestUserAdviceService.java ├── TestUserApiKeyService.java └── TestUserSessionService.java └── utils └── SnowflakeIdGeneratorTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /mdImg/assistant-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/assistant-1.png -------------------------------------------------------------------------------- /mdImg/assistant-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/assistant-2.png -------------------------------------------------------------------------------- /mdImg/chat-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/chat-1.png -------------------------------------------------------------------------------- /mdImg/chat-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/chat-2.png -------------------------------------------------------------------------------- /mdImg/chat-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/chat-3.png -------------------------------------------------------------------------------- /mdImg/chat-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/chat-4.png -------------------------------------------------------------------------------- /mdImg/contact-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/contact-1.jpg -------------------------------------------------------------------------------- /mdImg/expert-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/expert-2.png -------------------------------------------------------------------------------- /mdImg/game-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/game-1.png -------------------------------------------------------------------------------- /mdImg/game-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/game-2.png -------------------------------------------------------------------------------- /mdImg/game-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/game-3.png -------------------------------------------------------------------------------- /mdImg/game-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/game-4.png -------------------------------------------------------------------------------- /mdImg/mmexport1684888774983.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/mmexport1684888774983.jpg -------------------------------------------------------------------------------- /mdImg/pdf-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/pdf-1.png -------------------------------------------------------------------------------- /mdImg/pdf-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/pdf-2.png -------------------------------------------------------------------------------- /mdImg/pdf-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/pdf-3.png -------------------------------------------------------------------------------- /mdImg/pinecone-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/pinecone-1.png -------------------------------------------------------------------------------- /mdImg/prompt-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/prompt-1.png -------------------------------------------------------------------------------- /mdImg/text2Img-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/text2Img-1.png -------------------------------------------------------------------------------- /mdImg/text2Img-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/text2Img-2.png -------------------------------------------------------------------------------- /mdImg/text2Img-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/text2Img-3.png -------------------------------------------------------------------------------- /mdImg/trans-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/trans-1.png -------------------------------------------------------------------------------- /mdImg/trans-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/trans-2.png -------------------------------------------------------------------------------- /mdImg/user-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/user-1.png -------------------------------------------------------------------------------- /mdImg/user-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/user-2.png -------------------------------------------------------------------------------- /mdImg/user-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/user-3.png -------------------------------------------------------------------------------- /mdImg/user-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/user-4.png -------------------------------------------------------------------------------- /mdImg/wechat-pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huangPengL/ChatGPT-Java-FunAi/aabd503de9a42eec1505f9450434ad1f32ad9e43/mdImg/wechat-pay.png -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/FunAiApplication.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | 8 | 9 | @EnableScheduling 10 | @SpringBootApplication(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class }) 11 | @MapperScan("com.gzhu.funai.mapper") 12 | public class FunAiApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(FunAiApplication.class, args); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/baidu/constant/BaiDuConst.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.baidu.constant; 2 | 3 | /** 4 | * @Author :wuxiaodong 5 | * @Date: 2023/4/10 16:21 6 | * @Description: 7 | */ 8 | public class BaiDuConst { 9 | 10 | /** 11 | * 接入百度语音API需要填写,否则可以不填 12 | * TODO 13 | */ 14 | public static final String APP_ID = ""; 15 | 16 | public static final String API_KEY = ""; 17 | 18 | public static final String SECRET_KEY = ""; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/baidu/util/VoiceTool.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.baidu.util; 2 | 3 | import com.baidu.aip.speech.AipSpeech; 4 | import com.baidu.aip.speech.TtsResponse; 5 | import com.baidu.aip.util.Util; 6 | import com.gzhu.funai.api.baidu.constant.BaiDuConst; 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | import java.io.IOException; 10 | 11 | /** 12 | * @Author :wuxiaodong 13 | * @Date: 2023/4/9 22:05 14 | * @Description: 15 | */ 16 | public class VoiceTool { 17 | 18 | // 初始化一个AipSpeech 19 | public static final AipSpeech client = new AipSpeech(BaiDuConst.APP_ID, BaiDuConst.API_KEY, BaiDuConst.SECRET_KEY); 20 | /** 21 | * 语音识别 22 | * @return 23 | */ 24 | public static String SpeechRecognition(byte[] data) throws JSONException { 25 | 26 | // 可选:设置网络连接参数 27 | client.setConnectionTimeoutInMillis(2000); 28 | client.setSocketTimeoutInMillis(60000); 29 | 30 | // 调用接口 31 | JSONObject asrRes2 = client.asr(data, "wav", 16000, null); 32 | return asrRes2.toString(2); 33 | } 34 | 35 | /** 36 | * 语音合成 37 | * @param text 文字内容 38 | * @param path 合成语音生成路径,pcm格式 39 | * @return 40 | */ 41 | public static void SpeechSynthesis(String text, String path) { 42 | /* 43 | 最长的长度 44 | */ 45 | int maxLength = 1024; 46 | if (text.getBytes().length >= maxLength) { 47 | return ; 48 | } 49 | // 初始化一个AipSpeech 50 | AipSpeech client = new AipSpeech(BaiDuConst.APP_ID, BaiDuConst.API_KEY, BaiDuConst.SECRET_KEY); 51 | 52 | // 可选:设置网络连接参数 53 | client.setConnectionTimeoutInMillis(2000); 54 | client.setSocketTimeoutInMillis(60000); 55 | 56 | // 可选:设置代理服务器地址, http和socket二选一,或者均不设置 57 | // client.setHttpProxy("proxy_host", proxy_port); // 设置http代理 58 | // client.setSocketProxy("proxy_host", proxy_port); // 设置socket代理 59 | 60 | // 调用接口 61 | TtsResponse res = client.synthesis(text, "zh", 1, null); 62 | byte[] data = res.getData(); 63 | if (data != null) { 64 | try { 65 | Util.writeBytesToFileSystem(data, path); 66 | } catch (IOException e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/mengwang/SmsComponent.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.mengwang; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.gzhu.funai.utils.HttpClientUtil; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.net.URLEncoder; 12 | 13 | /** 14 | * @Author :wuxiaodong 15 | * @Date: 2023/2/24 17:02 16 | * @Description:短信发送工具类 17 | */ 18 | @Component 19 | @Data 20 | @Slf4j 21 | public class SmsComponent { 22 | @Value("${sms.apikey}") 23 | private String apiKey; 24 | @Value("${sms.url}") 25 | private String url; 26 | 27 | public boolean send(String phone,String code) throws Exception { 28 | String content = "您的验证码是:%s,两分钟内有效!(FunAI智能应用平台)"; 29 | content = URLEncoder.encode(String.format(content,code), "GBK"); 30 | String body ="apikey="+apiKey+"&mobile="+phone+"&content="+content; 31 | String result = HttpClientUtil.post(url,body,"application/x-www-form-urlencoded","GBK", 10000, 10000); 32 | JSONObject jsonObject = JSON.parseObject(result); 33 | return (Integer)(jsonObject.get("result")) == 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/constant/OpenAIConst.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.constant; 2 | 3 | /** 4 | * @Author: huangpenglong 5 | * @Date: 2023/3/28 0:11 6 | */ 7 | public class OpenAIConst { 8 | public static final String HOST = "https://api.openai.com/"; 9 | public static final String CHATGPT_MAPPING = "v1/chat/completions"; 10 | /** 11 | * 查询普通账单 12 | */ 13 | public static final String CREDIT_GRANTS_MAPPING = "/dashboard/billing/credit_grants"; 14 | 15 | /** 16 | * 查询是否订阅,总额度 17 | */ 18 | public static final String SUBSCRIPTION_MAPPING = "/v1/dashboard/billing/subscription"; 19 | 20 | /** 21 | * 查询使用量(跨度不能超过100天) 22 | */ 23 | public static final String USAGE_MAPPING = 24 | "/v1/dashboard/billing/usage?start_date=%s&end_date=%s"; 25 | 26 | 27 | public static final String EMBEDDING_MAPPING = "v1/embeddings"; 28 | 29 | /** 30 | * 模型选择 31 | */ 32 | public static final String MODEL_NAME_CHATGPT_3_5 = "gpt-3.5-turbo"; 33 | public static final String MODEL_NAME_CHATGPT_4 = "gpt-4"; 34 | 35 | public static final int MAX_TOKENS = 4000; 36 | 37 | private OpenAIConst(){} 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/enums/OpenAiRespError.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.enums; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * @Author: huangpenglong 9 | * @Date: 2023/3/28 0:23 10 | */ 11 | 12 | 13 | public enum OpenAiRespError { 14 | //官方的错误码列表:https://platform.openai.com/docs/guides/error-codes/api-errors 15 | OPENAI_BAD_REQUEST_ERROR(400, "错误的请求"), 16 | OPENAI_AUTHENTICATION_ERROR(401, "身份验证无效/提供的 API 密钥不正确"), 17 | OPENAI_FORBIDDEN(403, "禁止访问"), 18 | OPENAI_LIMIT_ERROR(429 , "达到请求的速率限制/您超出了当前配额,请检查您的计划和帐单详细信息/发动机当前过载,请稍后重试"), 19 | OPENAI_SERVER_ERROR(500, "服务器在处理您的请求时出错"), 20 | 21 | 22 | OPENAI_APIKEY_EXPIRED(1000, "APIKey过期") 23 | ; 24 | 25 | public final int code; 26 | public final String msg; 27 | 28 | private static final Map MAP = Arrays.stream(values()) 29 | .collect(Collectors.toMap(item->item.code, item->item)); 30 | 31 | public static boolean contain(Integer code){ 32 | return MAP.containsKey(code); 33 | } 34 | public static OpenAiRespError get(Integer code){ 35 | return MAP.get(code); 36 | } 37 | OpenAiRespError(int code, String msg) { 38 | this.code = code; 39 | this.msg = msg; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/enums/Role.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.enums; 2 | 3 | /** 4 | * @Author: huangpenglong 5 | * @Date: 2023/3/9 11:09 6 | */ 7 | public enum Role { 8 | SYSTEM("system"), 9 | USER("user"), 10 | ASSISTANT("assistant"); 11 | 12 | public final String name; 13 | 14 | Role(String name){ 15 | this.name = name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/req/ChatGPTReq.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.req; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @Author: huangpenglong 10 | * @Date: 2023/3/9 10:41 11 | */ 12 | 13 | @Builder 14 | @Data 15 | public class ChatGPTReq{ 16 | @Builder.Default 17 | private String model = "gpt-3.5-turbo"; // 模型名字 18 | private List messages; // 区分上下文,实现多轮对话x 19 | @Builder.Default 20 | private String user = "xxx"; // 用户的唯一标识符,可以帮助 OpenAI 监控和检测滥用行为 21 | @Builder.Default 22 | private Double temperature = 0.8; // 控制结果的随机性,值在[0,1]之间,越大表示回复越具有不确定性 23 | @Builder.Default 24 | private Boolean stream = false; // 是否流式输出 25 | private List stop; // 停止输出 26 | @Builder.Default 27 | private Integer max_tokens = 500; // 回复最大的字符数 28 | @Builder.Default 29 | private Double frequency_penalty = 0.0; // [-2,2]之间,该值越大则更倾向于产生不同的内容 30 | @Builder.Default 31 | private Double presence_penalty = 0.0; // [-2,2]之间,该值越大则更倾向于产生不同的内容 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/req/ContextMessage.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.req; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Author: huangpenglong 7 | * @Date: 2023/3/9 10:42 8 | */ 9 | 10 | @Data 11 | public class ContextMessage { 12 | /** 13 | * 对话角色,三种形式:system(初始化)、user、assistant 14 | */ 15 | private String role; 16 | private String content; 17 | 18 | public ContextMessage() { 19 | } 20 | 21 | public ContextMessage(String role, String content) { 22 | this.role = role; 23 | this.content = content; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/req/EmbeddingReq.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.req; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author zxw 10 | * @Desriiption: 文本编码请求 11 | */ 12 | @Builder 13 | @Data 14 | public class EmbeddingReq { 15 | 16 | @Builder.Default 17 | /** 18 | * 模型名字 19 | */ 20 | private String model = "text-embedding-ada-002"; 21 | 22 | private List input; 23 | 24 | /** 25 | * 用户的唯一标识符,可以帮助 OpenAI 监控和检测滥用行为 26 | */ 27 | @Builder.Default 28 | private String user = "xxx"; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/resp/BillingUsage.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.resp; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import java.math.BigDecimal; 9 | import java.time.LocalDate; 10 | 11 | /** 12 | * @Author: huangpenglong 13 | * @Date: 2023/4/26 9:01 14 | */ 15 | 16 | @Data 17 | @ToString 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | public class BillingUsage { 21 | private BigDecimal totalAmount; 22 | 23 | private BigDecimal totalUsage; 24 | 25 | /** 26 | * 过期时间,windows时间戳 27 | */ 28 | private LocalDate expiredTime; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/resp/ChatGPTResp.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.resp; 2 | 3 | import lombok.Data; 4 | import org.springframework.util.CollectionUtils; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @Author: huangpenglong 10 | * @Date: 2023/3/9 15:56 11 | */ 12 | 13 | @Data 14 | public class ChatGPTResp { 15 | private String id; 16 | private String object; 17 | private Long created; 18 | private String model; // 模型名字 19 | private Usage usage; // 当次请求使用的tokens 20 | private List choices; // ChatGPT返回的结果列表,一般仅有1个返回,所以获取数据只需choices.get(0) 21 | 22 | public String getMessage(){ 23 | if(!CollectionUtils.isEmpty(choices)){ 24 | return choices.get(0).getMessage().getContent(); 25 | } 26 | return ""; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/resp/ChoiceMessages.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.resp; 2 | 3 | import com.gzhu.funai.api.openai.req.ContextMessage; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | 7 | /** 8 | * @Author: huangpenglong 9 | * @Date: 2023/3/9 15:59 10 | */ 11 | 12 | @Data 13 | @ToString 14 | public class ChoiceMessages { 15 | private ContextMessage message; // ChatGPT返回的内容 16 | private String finish_reason; // stop 表示内容返回完毕 17 | private ContextMessage delta; // 流式输出返回的内容 18 | private Integer index; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/resp/CreditGrantsResp.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.resp; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | 7 | import java.math.BigDecimal; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/3/28 8:55 12 | */ 13 | 14 | @Data 15 | @ToString 16 | public class CreditGrantsResp { 17 | private String object; 18 | /** 19 | * 总金额:美元 20 | */ 21 | @JsonProperty("total_granted") 22 | private BigDecimal totalGranted; 23 | /** 24 | * 总使用金额:美元 25 | */ 26 | @JsonProperty("total_used") 27 | private BigDecimal totalUsed; 28 | /** 29 | * 总剩余金额:美元 30 | */ 31 | @JsonProperty("total_available") 32 | private BigDecimal totalAvailable; 33 | /** 34 | * 余额明细 35 | */ 36 | private Grants grants; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/resp/Datum.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.resp; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | 6 | import java.math.BigDecimal; 7 | 8 | /** 9 | * @Author: huangpenglong 10 | * @Date: 2023/3/28 8:55 11 | */ 12 | @Data 13 | public class Datum { 14 | private String object; 15 | private String id; 16 | /** 17 | * 赠送金额:美元 18 | */ 19 | @JsonProperty("grant_amount") 20 | private BigDecimal grantAmount; 21 | /** 22 | * 使用金额:美元 23 | */ 24 | @JsonProperty("used_amount") 25 | private BigDecimal usedAmount; 26 | /** 27 | * 生效时间戳 28 | */ 29 | @JsonProperty("effective_at") 30 | private Long effectiveAt; 31 | /** 32 | * 过期时间戳 33 | */ 34 | @JsonProperty("expires_at") 35 | private Long expiresAt; 36 | 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/resp/Embedding.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.resp; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author zxw 9 | * @Desriiption: 10 | */ 11 | @Data 12 | public class Embedding { 13 | 14 | String object; 15 | 16 | List embedding; 17 | 18 | Integer index; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/resp/EmbeddingResp.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.resp; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author zxw 9 | * @Desriiption: 文本编码结果 10 | */ 11 | @Data 12 | public class EmbeddingResp { 13 | 14 | String model; 15 | 16 | String object; 17 | 18 | List data; 19 | 20 | Usage usage; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/resp/Grants.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.resp; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Data; 5 | import org.springframework.util.CollectionUtils; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/3/28 8:55 12 | */ 13 | @Data 14 | public class Grants { 15 | private String object; 16 | @JsonProperty("data") 17 | private List data; 18 | 19 | public Datum getActualData(){ 20 | if(CollectionUtils.isEmpty(data)){ 21 | return null; 22 | } 23 | return data.get(0); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/openai/resp/Usage.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai.resp; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Author: huangpenglong 7 | * @Date: 2023/3/9 16:01 8 | */ 9 | 10 | @Data 11 | public class Usage { 12 | private Integer prompt_tokens; // 请求的tokens 13 | private Integer completion_tokens; // 响应的tokens 14 | private Integer total_tokens; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/pinecone/PineconeApi.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.pinecone; 2 | 3 | import cn.hutool.http.ContentType; 4 | import cn.hutool.json.JSONUtil; 5 | import com.gzhu.funai.api.pinecone.req.PineconeDeleteReq; 6 | import com.gzhu.funai.api.pinecone.req.PineconeInsertReq; 7 | import com.gzhu.funai.api.pinecone.req.PineconeQueryReq; 8 | import com.gzhu.funai.api.pinecone.resp.PineconeQueryResp; 9 | import com.gzhu.funai.exception.BaseException; 10 | import com.gzhu.funai.utils.OkHttpClientUtil; 11 | import lombok.extern.slf4j.Slf4j; 12 | import okhttp3.MediaType; 13 | import okhttp3.Request; 14 | import okhttp3.RequestBody; 15 | import okhttp3.Response; 16 | 17 | import java.io.IOException; 18 | 19 | /** 20 | * @author zxw 21 | * @Desriiption: Pinecone数据库的api 22 | */ 23 | @Slf4j 24 | public class PineconeApi { 25 | /** 26 | * 使用Pinecone作为向量数据库需要填写(另外需要在表admin_apikey中插入一条记录,type为4,name为Pinecone的apikey) 27 | * TODO 28 | */ 29 | private static final String PINECONE_API_URL = "https://xxxxxx.pinecone.io"; 30 | 31 | // 插入Pinecone向量库 32 | public static String insertEmbedding(PineconeInsertReq pineconeInsertReq, String apiKey){ 33 | 34 | Request request = new Request.Builder() 35 | .url(PINECONE_API_URL + "/vectors/upsert") 36 | .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), JSONUtil.parseObj(pineconeInsertReq).toString())) 37 | .header("accept", "application/json") 38 | .header("content-type", "application/json") 39 | .header("Api-Key", apiKey) 40 | .build(); 41 | Response response = null; 42 | try { 43 | response = OkHttpClientUtil.getClient().newCall(request).execute(); 44 | 45 | if(!response.isSuccessful()){ 46 | log.error("插入Pinecone向量库异常:{}", response.message()); 47 | throw new BaseException(response.message()); 48 | } 49 | 50 | String body = response.body().string(); 51 | 52 | return body; 53 | 54 | } catch (IOException e) { 55 | log.error("okHttpClient异常! {}", e.getMessage()); 56 | } 57 | finally { 58 | if(response != null){ 59 | response.close(); 60 | } 61 | } 62 | return ""; 63 | } 64 | 65 | // 从Pinecone向量库中查询相似 66 | public static PineconeQueryResp queryEmbedding(PineconeQueryReq pineconeQueryReq, String apiKey){ 67 | 68 | Request request = new Request.Builder() 69 | .url(PINECONE_API_URL + "/query") 70 | .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), JSONUtil.parseObj(pineconeQueryReq).toString())) 71 | .header("accept", "application/json") 72 | .header("content-type", "application/json") 73 | .header("Api-Key", apiKey) 74 | .build(); 75 | Response response = null; 76 | try { 77 | response = OkHttpClientUtil.getClient().newCall(request).execute(); 78 | 79 | if(!response.isSuccessful()){ 80 | log.error("查询Pinecone向量库异常:{}", response.message()); 81 | throw new BaseException(response.message()); 82 | } 83 | 84 | String body = response.body().string(); 85 | 86 | return JSONUtil.toBean(body, PineconeQueryResp.class); 87 | 88 | } 89 | catch (IOException e) { 90 | log.error("okHttpClient异常! {}", e.getMessage()); 91 | } 92 | finally { 93 | if(response != null) { 94 | response.close(); 95 | } 96 | } 97 | return null; 98 | } 99 | 100 | // 从Pinecone向量库中删除向量 101 | public static String deleteEmbedding(PineconeDeleteReq pineconeDeleteReq, String apiKey){ 102 | 103 | Request request = new Request.Builder() 104 | .url(PINECONE_API_URL + "/vectors/delete") 105 | .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), JSONUtil.parseObj(pineconeDeleteReq).toString())) 106 | .header("accept", "application/json") 107 | .header("content-type", "application/json") 108 | .header("Api-Key", apiKey) 109 | .build(); 110 | Response response = null; 111 | try { 112 | response = OkHttpClientUtil.getClient().newCall(request).execute(); 113 | 114 | if(!response.isSuccessful()){ 115 | log.error("删除Pinecone向量库异常:{}", response.message()); 116 | throw new BaseException(response.message()); 117 | } 118 | 119 | return "删除成功"; 120 | 121 | } 122 | catch (IOException e) { 123 | log.error("okHttpClient异常! {}", e.getMessage()); 124 | } 125 | finally { 126 | if(response != null){ 127 | response.close(); 128 | } 129 | } 130 | return "删除失败"; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/pinecone/req/PineconeDeleteReq.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.pinecone.req; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author zxw 11 | * @Desriiption: 向量库删除请求 12 | */ 13 | @Builder 14 | @Data 15 | public class PineconeDeleteReq { 16 | 17 | private List ids; 18 | 19 | private boolean deleteAll; 20 | 21 | private String namespace; 22 | 23 | private Map filter; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/pinecone/req/PineconeInsertReq.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.pinecone.req; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author zxw 10 | * @Desriiption: 11 | */ 12 | @Builder 13 | @Data 14 | public class PineconeInsertReq { 15 | 16 | /** 17 | * 需要插入的文本的向量库 18 | */ 19 | private List vectors; 20 | 21 | /** 22 | * 命名空间,用于区分每个文本 23 | */ 24 | private String namespace; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/pinecone/req/PineconeQueryReq.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.pinecone.req; 2 | 3 | import com.google.gson.JsonObject; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author zxw 12 | * @Desriiption: 向量库查询请求 13 | */ 14 | @Builder 15 | @Data 16 | public class PineconeQueryReq { 17 | 18 | /** 19 | * 命名空间 20 | */ 21 | private String namespace; 22 | 23 | /** 24 | * 需要最相似的前K条向量 25 | */ 26 | private Integer topK; 27 | 28 | /** 29 | * 可用于对metadata进行过滤 30 | */ 31 | private Map filter; 32 | 33 | /** 34 | * 指示响应中是否包含向量值 35 | 36 | */ 37 | private Boolean includeValues; 38 | 39 | /** 40 | * 指示响应中是否包含元数据以及id 41 | */ 42 | private Boolean includeMetadata = true; 43 | 44 | /** 45 | * 查询向量 46 | */ 47 | private List vector; 48 | 49 | /** 50 | * 向量稀疏数据。表示为索引列表和对应值列表,它们必须具有相同的长度 51 | */ 52 | private Map sparseVector; 53 | 54 | /** 55 | * 每条向量独一无二的id 56 | */ 57 | private String id; 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/pinecone/req/PineconeVectorsReq.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.pinecone.req; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author zxw 10 | * @Desriiption: 需要插入Pinecone向量库的向量对象 11 | */ 12 | 13 | @Data 14 | public class PineconeVectorsReq { 15 | 16 | /** 17 | * 每条向量的id 18 | */ 19 | private String id; 20 | 21 | /** 22 | * 分段后每一段的向量 23 | */ 24 | private List values; 25 | 26 | /** 27 | * 向量稀疏数据。表示为索引列表和对应值列表,它们必须具有相同的长度。 28 | */ 29 | private Map sparseValues; 30 | 31 | /** 32 | * 元数据,可以用来存储向量对应的文本 { key: "content", value: "对应文本" } 33 | */ 34 | private Map metadata; 35 | 36 | public PineconeVectorsReq(){ 37 | } 38 | 39 | public PineconeVectorsReq(String id, List values, Map metadata){ 40 | this.id = id; 41 | this.values = values; 42 | this.metadata = metadata; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/pinecone/resp/PineconeQueryDownResp.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.pinecone.resp; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author zxw 10 | * @Desriiption: 响应对象的下一级 11 | */ 12 | @Data 13 | public class PineconeQueryDownResp { 14 | 15 | /** 16 | * 向量id 17 | */ 18 | private String id; 19 | 20 | /** 21 | * 相似度分数 22 | */ 23 | private Float score; 24 | 25 | /** 26 | * 向量 27 | */ 28 | private List values; 29 | 30 | /** 31 | * 向量的元数据,存放对应文本 32 | */ 33 | private Map metadata; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/api/pinecone/resp/PineconeQueryResp.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.pinecone.resp; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author zxw 9 | * @Desriiption: 向量库查询响应 10 | */ 11 | @Data 12 | public class PineconeQueryResp { 13 | 14 | private List results; 15 | 16 | /** 17 | * 匹配的结果 18 | */ 19 | private List matches; 20 | 21 | /** 22 | * 命名空间 23 | */ 24 | private String namespace; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/config/FunAiConfig.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.config; 2 | 3 | import com.baomidou.mybatisplus.core.injector.ISqlInjector; 4 | import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector; 5 | import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.core.task.TaskExecutor; 10 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 11 | import org.springframework.transaction.annotation.EnableTransactionManagement; 12 | 13 | 14 | import java.util.concurrent.ThreadPoolExecutor; 15 | 16 | /** 17 | * @Author: huangpenglong 18 | * @Date: 2023/3/9 10:41 19 | */ 20 | 21 | @Configuration 22 | @EnableTransactionManagement 23 | @Slf4j 24 | public class FunAiConfig { 25 | 26 | /** 27 | * 分页插件 28 | */ 29 | @Bean 30 | public PaginationInterceptor paginationInterceptor() { 31 | return new PaginationInterceptor(); 32 | } 33 | 34 | /** 35 | * 异步线程池 36 | * @return 37 | */ 38 | @Bean(name = "queueThreadPool") 39 | public TaskExecutor queueThreadPool() { 40 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 41 | // 设置核心线程数 42 | executor.setCorePoolSize(15); 43 | // 设置最大线程数 44 | executor.setMaxPoolSize(1024); 45 | // 设置队列容量 46 | executor.setQueueCapacity(100); 47 | // 设置线程活跃时间(秒) 48 | executor.setKeepAliveSeconds(100); 49 | // 设置默认线程名称 50 | executor.setThreadNamePrefix("async-service-"); 51 | // 设置拒绝策略 52 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 53 | // 等待所有任务结束后再关闭线程池 54 | executor.setWaitForTasksToCompleteOnShutdown(true); 55 | log.info("创建一个线程池 corePoolSize is [" + executor.getCorePoolSize() + "]" + 56 | " maxPoolSize is [" + executor.getMaxPoolSize() + "] " + 57 | " queueCapacity is [" + 100 + "]" + 58 | " keepAliveSeconds is [" + executor.getKeepAliveSeconds() + "]" + 59 | " namePrefix is [" + executor.getThreadNamePrefix() + "]."); 60 | return executor; 61 | } 62 | 63 | /** 64 | * 逻辑删除 65 | * @return 66 | */ 67 | @Bean 68 | public ISqlInjector sqlInjector(){ 69 | return new LogicSqlInjector(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/config/MyWebMvcConfigurer.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.config; 2 | 3 | import com.gzhu.funai.interceptor.*; 4 | import com.gzhu.funai.redis.ChatRedisHelper; 5 | import com.gzhu.funai.service.UserApiKeyService; 6 | import com.gzhu.funai.service.UserService; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.data.redis.core.RedisTemplate; 9 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 10 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 11 | 12 | import javax.annotation.Resource; 13 | 14 | /** 15 | * @Author :wuxiaodong 16 | * @Date: 2023/3/24 14:40 17 | * @Description: springmvc配置,添加拦截器 18 | */ 19 | @Configuration 20 | public class MyWebMvcConfigurer implements WebMvcConfigurer { 21 | @Resource 22 | private RedisTemplate redisTemplate; 23 | @Resource 24 | private ChatRedisHelper chatRedisHelper; 25 | @Resource 26 | private UserApiKeyService userApiKeyService; 27 | @Resource 28 | private UserService userService; 29 | 30 | @Override 31 | public void addInterceptors(InterceptorRegistry registry) { 32 | 33 | // token携带认证。拦截所有请求 除了 登录、注册、发送验证码、重置密码的请求 34 | registry.addInterceptor(new UserLoginInterceptor()) 35 | .addPathPatterns("/**") 36 | .excludePathPatterns("/user/login", "/user/register","/user/sendCode", "/user/resetPwd"); 37 | 38 | // 接口防刷 39 | registry.addInterceptor(new AccessLimitInterceptor(this.redisTemplate)) 40 | .addPathPatterns("/user/sendCode"); 41 | 42 | // 聊天功能次数限制 43 | registry.addInterceptor(new UserChatLimitInterceptor(this.chatRedisHelper, this.userApiKeyService, this.userService)) 44 | .addPathPatterns( 45 | "/translation/translate", "/chat/game/startGameSession", 46 | "/chat/session", "/chat/oneShot", 47 | "/chat/streamSessionChat", "/chat/streamOneShotChat", 48 | "/file/chatWithFile", "/file/streamChatWithFile"); 49 | 50 | // 文件上传功能次数限制 51 | registry.addInterceptor(new UserFileUploadLimitInterceptor(this.chatRedisHelper, this.userApiKeyService, this.userService)) 52 | .addPathPatterns( 53 | "/file/chatPdfUpload" 54 | ); 55 | 56 | // 管理员权限接口禁用 57 | registry.addInterceptor(new AdminOperateInterceptor()) 58 | .addPathPatterns( 59 | "/**/admin/**" 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.RedisConnectionFactory; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; 8 | import org.springframework.data.redis.serializer.StringRedisSerializer; 9 | 10 | /** 11 | * @Author :wuxiaodong 12 | * @Date: 2023/4/8 14:41 13 | * @Description: Redis配置类 14 | */ 15 | @Configuration 16 | public class RedisConfig { 17 | @Bean 18 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 19 | RedisTemplate redisTemplate = new RedisTemplate<>(); 20 | redisTemplate.setConnectionFactory(redisConnectionFactory); 21 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 22 | redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); 23 | return redisTemplate; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.config; 2 | 3 | import com.google.common.base.Predicates; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import springfox.documentation.builders.ApiInfoBuilder; 7 | import springfox.documentation.builders.PathSelectors; 8 | import springfox.documentation.service.ApiInfo; 9 | import springfox.documentation.service.Contact; 10 | import springfox.documentation.spi.DocumentationType; 11 | import springfox.documentation.spring.web.plugins.Docket; 12 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 13 | 14 | /** 15 | * @Author: huangpenglong 16 | * @Date: 2023/3/9 10:41 17 | */ 18 | 19 | @Configuration 20 | @EnableSwagger2 21 | public class SwaggerConfig { 22 | 23 | /** 24 | * swagger插件 25 | */ 26 | @Bean 27 | public Docket webApiConfig(){ 28 | return new Docket(DocumentationType.SWAGGER_2) 29 | .groupName("FunAI") 30 | .apiInfo(webApiInfo()) 31 | .select() 32 | .paths(Predicates.not(PathSelectors.regex("/admin/.*"))) 33 | .paths(Predicates.not(PathSelectors.regex("/error.*"))) 34 | .build(); 35 | } 36 | 37 | 38 | private ApiInfo webApiInfo(){ 39 | return new ApiInfoBuilder() 40 | .title("FunAI网站-API文档") 41 | .description("本文档描述了FunAI接口的定义") 42 | .version("1.0") 43 | .contact(new Contact("hpl", "https://huangpengl.github.io/", "243031504@qq.com")) 44 | .build(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/controller/AdminController.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.controller; 2 | 3 | import com.gzhu.funai.service.AdminApiKeyService; 4 | import com.gzhu.funai.utils.ReturnResult; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import javax.annotation.Resource; 9 | 10 | /** 11 | * @Author: huangpenglong 12 | * @Date: 2023/4/10 23:59。 13 | */ 14 | @RestController 15 | @RequestMapping("/admin") 16 | @CrossOrigin 17 | public class AdminController { 18 | 19 | @Resource 20 | private AdminApiKeyService adminApiKeyService; 21 | 22 | /** 23 | * 刷新系统用的ApiKey缓存 24 | * @return 25 | */ 26 | @GetMapping("/flushApiKey") 27 | public ReturnResult flushApiKey(){ 28 | adminApiKeyService.load(); 29 | return ReturnResult.ok(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/controller/PromptController.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.controller; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.core.metadata.IPage; 5 | import com.gzhu.funai.dto.PromptQueryRequest; 6 | import com.gzhu.funai.entity.PromptEntity; 7 | import com.gzhu.funai.enums.PromptTarget; 8 | import com.gzhu.funai.enums.PromptType; 9 | import com.gzhu.funai.service.PromptService; 10 | import com.gzhu.funai.utils.ReturnResult; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.web.bind.annotation.*; 13 | 14 | import javax.annotation.Resource; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | /** 19 | * @Author: huangpenglong 20 | * @Date: 2023/4/11 18:46 21 | */ 22 | 23 | @RestController 24 | @RequestMapping("/prompt") 25 | @CrossOrigin 26 | public class PromptController { 27 | 28 | @Resource 29 | private PromptService promptService; 30 | 31 | @GetMapping("/admin/list/{page}/{limit}") 32 | public ReturnResult list(@PathVariable int page, @PathVariable int limit, PromptQueryRequest req){ 33 | 34 | IPage list = promptService.list(page, limit, req); 35 | return ReturnResult.ok().data("records", list.getRecords()).data("total", list.getTotal()); 36 | } 37 | 38 | /** 39 | * 获得所有专家领域的提示 40 | * @return 41 | */ 42 | @GetMapping("/listAllUserPrompt") 43 | public ReturnResult list() { 44 | List list = promptService.getByType(PromptType.CHATGPT).stream() 45 | .filter(item -> PromptTarget.USER.targetNo == item.getTarget()) 46 | .map(PromptEntity::getTopic) 47 | .collect(Collectors.toList()); 48 | 49 | return ReturnResult.ok().data("topic_list",list); 50 | } 51 | 52 | /** 53 | * 插入prompt 54 | * @param promptEntity 55 | * @return 56 | */ 57 | @PostMapping("/admin") 58 | public ReturnResult add(@RequestBody PromptEntity promptEntity){ 59 | promptService.save(promptEntity); 60 | promptService.load(); 61 | return ReturnResult.ok(); 62 | } 63 | 64 | /** 65 | * 编辑prompt 66 | * @param promptEntity 67 | * @return 68 | */ 69 | @PutMapping("/admin") 70 | public ReturnResult edit(@RequestBody PromptEntity promptEntity){ 71 | promptService.update(promptEntity, new QueryWrapper().eq("id", promptEntity.getId())); 72 | promptService.load(); 73 | return ReturnResult.ok(); 74 | } 75 | 76 | /** 77 | * 删除prompt 78 | * @param promptId 79 | * @return 80 | */ 81 | @DeleteMapping("/admin/{promptId}") 82 | public ReturnResult delete(@PathVariable Integer promptId){ 83 | promptService.removeById(promptId); 84 | promptService.load(); 85 | return ReturnResult.ok(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/controller/TranslationController.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.controller; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.gzhu.funai.api.openai.constant.OpenAIConst; 5 | import com.gzhu.funai.api.openai.enums.Role; 6 | import com.gzhu.funai.api.openai.req.ChatGPTReq; 7 | import com.gzhu.funai.api.openai.req.ContextMessage; 8 | import com.gzhu.funai.api.openai.resp.ChatGPTResp; 9 | import com.gzhu.funai.dto.TranslationRequest; 10 | import com.gzhu.funai.entity.UserApiKeyEntity; 11 | import com.gzhu.funai.enums.ApiType; 12 | import com.gzhu.funai.enums.Prompt; 13 | import com.gzhu.funai.service.AdminApiKeyService; 14 | import com.gzhu.funai.service.ChatService; 15 | import com.gzhu.funai.service.PromptService; 16 | import com.gzhu.funai.service.UserApiKeyService; 17 | import com.gzhu.funai.utils.ResultCode; 18 | import com.gzhu.funai.utils.ReturnResult; 19 | import lombok.extern.slf4j.Slf4j; 20 | import org.springframework.util.StringUtils; 21 | import org.springframework.web.bind.annotation.*; 22 | 23 | import javax.annotation.Resource; 24 | import javax.validation.Valid; 25 | 26 | /** 27 | * @Author : oujiajun 28 | * @Date: 2023/3/30 29 | * @Description: 翻译 30 | */ 31 | 32 | @RestController 33 | @CrossOrigin 34 | @Slf4j 35 | @RequestMapping("/translation") 36 | public class TranslationController { 37 | private static final String NAME_MESSAGE = "translateResult"; 38 | 39 | @Resource 40 | private ChatService chatService; 41 | @Resource 42 | private AdminApiKeyService adminApiKeyService; 43 | @Resource 44 | private PromptService promptService; 45 | @Resource 46 | private UserApiKeyService userApiKeyService; 47 | 48 | /** 49 | * 翻译 50 | * @param req 51 | * @return 52 | */ 53 | @PostMapping("/translate") 54 | public ReturnResult translate(@RequestBody @Valid TranslationRequest req) { 55 | if (StringUtils.isEmpty(req.getMessage()) || req.getUserId() == null) { 56 | return ReturnResult.error().codeAndMessage(ResultCode.EMPTY_PARAM); 57 | } 58 | 59 | String reqMsg = String.format(promptService.getByTopic(Prompt.TRANSLATE.topic), req.getLanguage(), req.getMessage()); 60 | 61 | // 若用户上传了apikey则使用用户的,否则采用本系统的 62 | UserApiKeyEntity userApiKeyEntity = userApiKeyService.getByUserIdAndType(req.getUserId(), ApiType.OPENAI); 63 | String apiKey = userApiKeyEntity != null && !StringUtils.isEmpty(userApiKeyEntity.getApikey()) 64 | ? userApiKeyEntity.getApikey() 65 | : adminApiKeyService.roundRobinGetByType(ApiType.OPENAI); 66 | if(apiKey == null){ 67 | return ReturnResult.error().codeAndMessage(ResultCode.ADMIN_APIKEY_NULL); 68 | } 69 | 70 | // 调用对话接口 71 | ChatGPTReq gptReq = ChatGPTReq.builder() 72 | .messages(ImmutableList.of(new ContextMessage(Role.USER.name, reqMsg))) 73 | .model(OpenAIConst.MODEL_NAME_CHATGPT_3_5) 74 | .build(); 75 | 76 | ChatGPTResp resp = chatService.oneShotChat(req.getUserId(), gptReq, apiKey); 77 | 78 | if (resp.getMessage() == null) { 79 | return ReturnResult.error(); 80 | } 81 | return ReturnResult.ok().data(NAME_MESSAGE, resp.getMessage()); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/AddSessionRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/3/17 18:09 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class AddSessionRequest { 17 | @JsonProperty("user_id") 18 | private String userId; 19 | 20 | @Length(max = 100, message = "会话名不能超过100字!") 21 | @JsonProperty("session_name") 22 | private String sessionName; 23 | 24 | @JsonProperty("type") 25 | private Integer type; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/ChangeUserLevelRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Author :wuxiaodong 7 | * @Date: 2023/4/26 16:45 8 | * @Description: 9 | */ 10 | @Data 11 | public class ChangeUserLevelRequest { 12 | private String userId; 13 | private Integer originalLevel; 14 | private Integer level; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/ChatWithFileRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.web.multipart.MultipartFile; 8 | 9 | /** 10 | * @author zxw 11 | * @Desriiption: chat with file 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class ChatWithFileRequest { 17 | 18 | private MultipartFile file; 19 | 20 | @JsonProperty("user_id") // 该注解用于标注 Java 对象的属性与 JSON 数据中的字段之间的映射关系,用于序列化与反序列化 21 | private String userId; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/OneShotChatRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/3/12 22:31 12 | */ 13 | 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class OneShotChatRequest { 18 | 19 | @Length(max = 2500, message = "消息不能超过2500字!") 20 | private String message; 21 | 22 | @JsonProperty("user_id") 23 | private String userId; 24 | 25 | @JsonProperty("session_type") 26 | private Integer sessionType = 0; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/PromptQueryRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @Author: huangpenglong 9 | * @Date: 2023/4/11 18:48 10 | */ 11 | 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class PromptQueryRequest { 16 | 17 | /** 18 | * Prompt类型,详情请见PromptType 19 | */ 20 | private Integer type; 21 | 22 | /** 23 | * 查询字段 24 | */ 25 | private String content; 26 | 27 | /** 28 | * Prompt面向的目标用户,详情请见PromptTarget 29 | */ 30 | private Integer target; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/RenameSessionRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/3/17 18:09 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class RenameSessionRequest { 17 | @JsonProperty("session_id") 18 | private Integer sessionId; 19 | 20 | @JsonProperty("session_name") 21 | @Length(max = 100, message = "会话名不能超过100字!") 22 | private String sessionName; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/SendCodeRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.Valid; 6 | import javax.validation.constraints.Pattern; 7 | 8 | /** 9 | * @Author :wuxiaodong 10 | * @Date: 2023/4/24 11:36 11 | * @Description: 12 | */ 13 | @Data 14 | public class SendCodeRequest { 15 | @Valid 16 | @Pattern(regexp = "^1[3-9]\\d{9}$", message = "Invalid phone number") 17 | private String phone; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/SessionChatRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/3/17 21:57 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class SessionChatRequest { 17 | 18 | @Length(max = 2500, message = "消息不能超过2500字!") 19 | private String message; 20 | 21 | @JsonProperty("user_id") 22 | private String userId; 23 | 24 | @JsonProperty("session_id") 25 | private Integer sessionId; 26 | 27 | @JsonProperty("session_type") 28 | private Integer sessionType = 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/StartGameStreamSessionRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @Author: oujiajun 11 | * @Date: 2023/4/10 15:57 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class StartGameStreamSessionRequest { 17 | 18 | @Length(max = 2500, message = "消息不能超过2500字!") 19 | private String message; 20 | 21 | @JsonProperty("user_id") 22 | private String userId; 23 | 24 | @JsonProperty("session_id") 25 | private Integer sessionId; 26 | 27 | @JsonProperty("sse_emitter_id") 28 | private Long sseEmitterId; 29 | 30 | @JsonProperty("story_type") 31 | private String storyType; 32 | 33 | @JsonProperty("session_type") 34 | private Integer sessionType; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/StreamOneShotChatRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/3/12 22:31 12 | */ 13 | 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class StreamOneShotChatRequest { 18 | 19 | @Length(max = 2500, message = "消息不能超过2500字!") 20 | private String message; 21 | 22 | @JsonProperty("user_id") 23 | private String userId; 24 | 25 | @JsonProperty("sse_emitter_id") 26 | private Long sseEmitterId; 27 | 28 | @JsonProperty("session_type") 29 | private Integer sessionType = 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/StreamSessionChatRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/4/7 15:57 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class StreamSessionChatRequest { 17 | 18 | @Length(max = 2500, message = "消息不能超过2500字!") 19 | private String message; 20 | 21 | @JsonProperty("user_id") 22 | private String userId; 23 | 24 | @JsonProperty("session_id") 25 | private Integer sessionId; 26 | 27 | @JsonProperty("sse_emitter_id") 28 | private Long sseEmitterId; 29 | 30 | @JsonProperty("session_type") 31 | private Integer sessionType = 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/TranslationRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @Author: oujiajun 11 | * @Date: 2023/3/30 10:00 12 | */ 13 | 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class TranslationRequest { 18 | 19 | @Length(max = 2500, message = "消息不能超过2500字!") 20 | private String message; 21 | 22 | private String language; 23 | /** 24 | * 该注解用于标注 Java 对象的属性与 JSON 数据中的字段之间的映射关系,用于序列化与反序列化 25 | */ 26 | @JsonProperty("user_id") 27 | private String userId; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/UserAdviceRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.hibernate.validator.constraints.Length; 9 | 10 | /** 11 | * @Author: oujiajun 12 | * @Date: 2023/4/29 16:02 13 | */ 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class UserAdviceRequest { 18 | /** 19 | * 用户ID 20 | */ 21 | @JsonProperty(value = "user_id") 22 | private String userId; 23 | 24 | /** 25 | * 用户ID 26 | */ 27 | @JsonProperty(value = "username") 28 | private String username; 29 | 30 | /** 31 | * 用户建议 32 | */ 33 | @Length(max = 500, message = "建议内容不能超过500个字符") 34 | @JsonProperty("user_advice") 35 | private String userAdvice; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/UserApiKeyRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.hibernate.validator.constraints.Length; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/4/20 20:02 12 | */ 13 | 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class UserApiKeyRequest { 18 | 19 | /** 20 | * 用户ID 21 | */ 22 | @JsonProperty(value = "user_id") 23 | private String userId; 24 | 25 | @Length(max = 200, message = "apiKey不能超过200字符!") 26 | @JsonProperty("api_key") 27 | private String apiKey; 28 | 29 | @JsonProperty("api_type_no") 30 | private Integer apiTypeNo; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/UserListRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * @Author :wuxiaodong 9 | * @Date: 2023/4/26 11:19 10 | * @Description: 11 | */ 12 | @Data 13 | public class UserListRequest { 14 | // 查询条件,用户名/手机号 15 | private String key; 16 | private Date startTime; 17 | private Date endTime; 18 | private Integer level; 19 | private Integer status; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/UserLockRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @Author :wuxiaodong 7 | * @Date: 2023/4/26 13:58 8 | * @Description: 9 | */ 10 | @Data 11 | public class UserLockRequest { 12 | private String userId; 13 | private Byte status; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/UserLoginRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import lombok.Data; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | 8 | /** 9 | * @Author :wuxiaodong 10 | * @Date: 2023/3/16 15:04 11 | * @Description:前端用户登录信息 12 | */ 13 | @Data 14 | public class UserLoginRequest { 15 | @NotEmpty(message = "用户名不能为空") 16 | @Length(min = 2, max = 15, message="用户名/手机号长度在2-15字符") 17 | private String loginAcct; 18 | 19 | @NotEmpty(message = "密码必须填写") 20 | @Length(min = 2,max = 18,message = "密码必须是2—18位字符") 21 | private String password; 22 | 23 | /** 24 | * 登录类型,详情见LoginType枚举类 25 | */ 26 | private Integer loginType; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/UserRegisterRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import lombok.Data; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | import javax.validation.constraints.Pattern; 8 | 9 | /** 10 | * @Author :wuxiaodong 11 | * @Date: 2023/3/16 15:08 12 | * @Description:用户注册信息 13 | */ 14 | @Data 15 | public class UserRegisterRequest { 16 | @NotEmpty(message = "用户名不能为空") 17 | @Length(min = 2, max = 10, message="用户名长度在2-10字符") 18 | private String userName; 19 | 20 | @NotEmpty(message = "密码必须填写") 21 | @Length(min = 2,max = 18,message = "密码必须是2—18位字符") 22 | private String password; 23 | 24 | @Pattern(regexp = "^1[3-9]\\d{9}$", message = "Invalid phone number") 25 | @NotEmpty(message = "手机号不能为空") 26 | private String phone; 27 | 28 | @NotEmpty(message = "验证码不能为空") 29 | private String code; 30 | 31 | private String email; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/dto/UserResetPasswordRequest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.dto; 2 | 3 | import lombok.Data; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.NotEmpty; 7 | import javax.validation.constraints.Pattern; 8 | 9 | /** 10 | * @Author :wuxiaodong 11 | * @Date: 2023/3/30 12:52 12 | * @Description: 13 | */ 14 | @Data 15 | public class UserResetPasswordRequest { 16 | 17 | @Pattern(regexp = "^1[3-9]\\d{9}$", message = "Invalid phone number") 18 | @NotEmpty(message = "手机号不能为空") 19 | private String phone; 20 | 21 | @NotEmpty(message = "验证码不能为空") 22 | private String code; 23 | 24 | @NotEmpty(message = "密码必须填写") 25 | @Length(min = 2,max = 18,message = "密码必须是2—18位字符") 26 | private String newPwd; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/entity/AdminApiKeyEntity.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.math.BigDecimal; 10 | import java.time.LocalDate; 11 | 12 | /** 13 | * @Author: huangpenglong 14 | * @Date: 2023/4/10 22:54 15 | */ 16 | 17 | @Builder 18 | @Data 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @TableName(value = "admin_apikey") 22 | public class AdminApiKeyEntity { 23 | /** 24 | * id 25 | */ 26 | @TableId(value = "id", type = IdType.AUTO) 27 | private Integer id; 28 | 29 | /** 30 | * api的类型编号, 详情见枚举类ApiType 31 | */ 32 | @TableField(value = "type") 33 | private Integer type; 34 | 35 | /** 36 | * 具体的apikey 37 | */ 38 | @TableField(value = "name") 39 | private String name; 40 | 41 | /** 42 | * 是否被删除, 0: 未删除, 1: 已删除 43 | */ 44 | @TableField(value = "is_deleted") 45 | @TableLogic 46 | private Integer isDeleted; 47 | 48 | @TableField(value = "priority") 49 | private Integer priority; 50 | 51 | @TableField(value = "total_amount") 52 | private BigDecimal totalAmount; 53 | 54 | @TableField(value = "total_usage") 55 | private BigDecimal totalUsage; 56 | 57 | @TableField(value = "expired_time") 58 | private LocalDate expiredTime; 59 | 60 | @TableField(value = "is_free") 61 | private Integer isFree; 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/entity/DataSqlEntity.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.entity; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author zxw 7 | * @Desriiption: 向量库实体 8 | */ 9 | public class DataSqlEntity { 10 | 11 | /** 12 | * 分段后的每一段的向量 13 | */ 14 | private List> ll; 15 | 16 | /** 17 | * 每一段的内容 18 | */ 19 | private List content; 20 | 21 | /** 22 | * 总共token数量 23 | */ 24 | private Integer total_token; 25 | 26 | public DataSqlEntity(){ 27 | } 28 | 29 | public List> getLl(){ 30 | return this.ll; 31 | } 32 | 33 | public void setLl(List> ll){ 34 | this.ll = ll; 35 | } 36 | 37 | public Integer getTotal_token() { 38 | return this.total_token; 39 | } 40 | 41 | public void setTotal_token(Integer total_token) { 42 | this.total_token = total_token; 43 | } 44 | 45 | public List getContent() { 46 | return content; 47 | } 48 | 49 | public void setContent(List content) { 50 | this.content = content; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/entity/PromptEntity.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.Date; 11 | 12 | /** 13 | * @Author: huangpenglong 14 | * @Date: 2023/4/11 16:32 15 | */ 16 | @Builder 17 | @Data 18 | @AllArgsConstructor 19 | @NoArgsConstructor 20 | @TableName(value = "prompt") 21 | public class PromptEntity { 22 | /** 23 | * id 24 | */ 25 | @TableId(value = "id", type = IdType.AUTO) 26 | private Integer id; 27 | 28 | /** 29 | * prompt的类型, 详情见枚举类PromptType 30 | */ 31 | @TableField(value = "type") 32 | private Integer type; 33 | 34 | /** 35 | * prompt的具体内容 36 | */ 37 | @TableField(value = "content") 38 | private String content; 39 | 40 | /** 41 | * 对prompt的描述 42 | */ 43 | @TableField(value = "description") 44 | private String description; 45 | 46 | /** 47 | * prompt的简单文字标识 48 | */ 49 | @TableField(value = "topic") 50 | private String topic; 51 | 52 | @JsonProperty("create_time") 53 | private Date createTime; 54 | 55 | @JsonProperty("update_time") 56 | private Date updateTime; 57 | 58 | /** 59 | * 是否被删除, 0: 未删除, 1: 已删除 60 | */ 61 | @TableField(value = "is_deleted") 62 | @TableLogic 63 | private Integer isDeleted; 64 | 65 | @TableField(value = "target") 66 | private Integer target; 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/entity/SessionChatRecordEntity.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import lombok.AllArgsConstructor; 8 | import lombok.NoArgsConstructor; 9 | import lombok.ToString; 10 | 11 | import java.io.Serializable; 12 | import java.util.Date; 13 | 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @TableName(value = "session_chat_record") 17 | @ToString 18 | public class SessionChatRecordEntity implements Serializable { 19 | 20 | @TableId(type = IdType.AUTO) 21 | @JsonProperty("session_chat_id") 22 | private Integer sessionChatId; 23 | 24 | @JsonProperty("session_id") 25 | private Integer sessionId; 26 | 27 | private String role; 28 | 29 | @JsonProperty("token_num") 30 | private Integer tokenNum; 31 | 32 | @JsonProperty("create_time") 33 | private Date createTime; 34 | 35 | @JsonProperty("update_time") 36 | private Date updateTime; 37 | 38 | private String content; 39 | 40 | private static final long serialVersionUID = 1L; 41 | 42 | public Integer getSessionChatId() { 43 | return sessionChatId; 44 | } 45 | 46 | public void setSessionChatId(Integer sessionChatId) { 47 | this.sessionChatId = sessionChatId; 48 | } 49 | 50 | public Integer getSessionId() { 51 | return sessionId; 52 | } 53 | 54 | public void setSessionId(Integer sessionId) { 55 | this.sessionId = sessionId; 56 | } 57 | 58 | public String getRole() { 59 | return role; 60 | } 61 | 62 | public void setRole(String role) { 63 | this.role = role == null ? null : role.trim(); 64 | } 65 | 66 | public Integer getTokenNum() { 67 | return tokenNum; 68 | } 69 | 70 | public void setTokenNum(Integer tokenNum) { 71 | this.tokenNum = tokenNum; 72 | } 73 | 74 | public Date getCreateTime() { 75 | return createTime; 76 | } 77 | 78 | public void setCreateTime(Date createTime) { 79 | this.createTime = createTime; 80 | } 81 | 82 | public Date getUpdateTime() { 83 | return updateTime; 84 | } 85 | 86 | public void setUpdateTime(Date updateTime) { 87 | this.updateTime = updateTime; 88 | } 89 | 90 | public String getContent() { 91 | return content; 92 | } 93 | 94 | public void setContent(String content) { 95 | this.content = content == null ? null : content.trim(); 96 | } 97 | 98 | public SessionChatRecordEntity(Integer sessionId, String role, String content, Integer tokenNum){ 99 | this.sessionId =sessionId; 100 | this.role = role; 101 | this.content = content; 102 | this.tokenNum = tokenNum; 103 | } 104 | 105 | public SessionChatRecordEntity(String role, String content){ 106 | this.role = role; 107 | this.content = content; 108 | } 109 | } -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/entity/UserAdvicesEntity.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.entity; 2 | 3 | 4 | import com.baomidou.mybatisplus.annotation.IdType; 5 | import com.baomidou.mybatisplus.annotation.TableField; 6 | import com.baomidou.mybatisplus.annotation.TableId; 7 | import com.baomidou.mybatisplus.annotation.TableName; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Builder; 10 | import lombok.Data; 11 | import lombok.NoArgsConstructor; 12 | 13 | /** 14 | *@Author :oujiajun 15 | *@Date: 2023/4/29 16:48 16 | *@Description: 17 | */ 18 | @Data 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @Builder 22 | @TableName(value = "user_advices") 23 | public class UserAdvicesEntity { 24 | /** 25 | * id 26 | */ 27 | @TableId(value = "id", type = IdType.AUTO) 28 | private Integer id; 29 | 30 | /** 31 | * 用户ID 32 | */ 33 | @TableField(value = "user_id") 34 | private String userId; 35 | 36 | /** 37 | * 用户名 38 | */ 39 | @TableField(value = "username") 40 | private String username; 41 | 42 | /** 43 | * 用户建议 44 | */ 45 | @TableField(value = "user_advice") 46 | private String advice; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/entity/UserApiKeyEntity.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.Date; 10 | 11 | /** 12 | * @Author: huangpenglong 13 | * @Date: 2023/4/20 19:36 14 | */ 15 | 16 | 17 | @Builder 18 | @Data 19 | @AllArgsConstructor 20 | @NoArgsConstructor 21 | @TableName(value = "user_apikey") 22 | public class UserApiKeyEntity { 23 | 24 | /** 25 | * id 26 | */ 27 | @TableId(value = "id", type = IdType.AUTO) 28 | private Integer id; 29 | 30 | /** 31 | * 用户ID 32 | */ 33 | @TableField(value = "user_id") 34 | private String userId; 35 | 36 | /** 37 | * api的类型编号, 详情见枚举类ApiType 38 | */ 39 | @TableField(value = "type") 40 | private Integer type; 41 | 42 | /** 43 | * 具体的apikey 44 | */ 45 | @TableField(value = "apikey") 46 | private String apikey; 47 | 48 | 49 | @TableField("create_time") 50 | private Date createTime; 51 | 52 | @TableField("update_time") 53 | private Date updateTime; 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/entity/UserEntity.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import java.util.Date; 8 | 9 | import com.gzhu.funai.dto.UserRegisterRequest; 10 | import com.gzhu.funai.utils.SequentialUuidHexGenerator; 11 | import lombok.AllArgsConstructor; 12 | import lombok.Builder; 13 | import lombok.Data; 14 | import lombok.NoArgsConstructor; 15 | 16 | /** 17 | *@Author :wuxiaodong 18 | *@Date: 2023/3/16 16:48 19 | *@Description: 20 | */ 21 | @Builder 22 | @Data 23 | @AllArgsConstructor 24 | @NoArgsConstructor 25 | @TableName(value = "user") 26 | public class UserEntity { 27 | /** 28 | * id 29 | */ 30 | @TableId(value = "id", type = IdType.INPUT) 31 | private String id; 32 | 33 | /** 34 | * 用户名 35 | */ 36 | @TableField(value = "username") 37 | private String username; 38 | 39 | /** 40 | * 密码 41 | */ 42 | @TableField(value = "password") 43 | private String password; 44 | 45 | /** 46 | * 昵称 47 | */ 48 | @TableField(value = "nickname") 49 | private String nickname; 50 | 51 | /** 52 | * 手机号码 53 | */ 54 | @TableField(value = "mobile") 55 | private String mobile; 56 | 57 | /** 58 | * 邮箱 59 | */ 60 | @TableField(value = "email") 61 | private String email; 62 | 63 | /** 64 | * 头像 65 | */ 66 | @TableField(value = "header") 67 | private String header; 68 | 69 | /** 70 | * 性别 71 | */ 72 | @TableField(value = "gender") 73 | private Byte gender; 74 | 75 | /** 76 | * 启用状态 77 | * 0 正常 78 | * 1 锁定 79 | */ 80 | @TableField(value = "status") 81 | private Byte status; 82 | 83 | /** 84 | * 注册时间 85 | */ 86 | @TableField(value = "create_time") 87 | private Date createTime; 88 | 89 | /** 90 | * 社交用户在社交软件的id 91 | */ 92 | @TableField(value = "social_uid") 93 | private String socialUid; 94 | 95 | /** 96 | * 更新时间 97 | */ 98 | @TableField(value = "update_time") 99 | private Date updateTime; 100 | 101 | /** 102 | * 用户等级。 0:管理员, 1:普通用户, 2:vip用户 103 | */ 104 | @TableField(value = "level") 105 | private Integer level; 106 | 107 | public static UserEntity of(UserRegisterRequest req){ 108 | UserEntity userEntity = new UserEntity(); 109 | userEntity.setId(SequentialUuidHexGenerator.generate()); 110 | userEntity.setUsername(req.getUserName()); 111 | userEntity.setEmail(req.getEmail()); 112 | userEntity.setCreateTime(new Date()); 113 | userEntity.setUpdateTime(new Date()); 114 | userEntity.setMobile(req.getPhone()); 115 | userEntity.setNickname(req.getUserName()); 116 | return userEntity; 117 | } 118 | } -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/entity/UserLoginRecord.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Builder; 9 | import lombok.Data; 10 | import lombok.NoArgsConstructor; 11 | 12 | import java.util.Date; 13 | 14 | /** 15 | * @Author: huangpenglong 16 | * @Date: 2023/4/26 16:58 17 | */ 18 | 19 | @Builder 20 | @Data 21 | @AllArgsConstructor 22 | @NoArgsConstructor 23 | @TableName(value = "user_login_record") 24 | public class UserLoginRecord { 25 | 26 | /** 27 | * id 28 | */ 29 | @TableId(value = "id", type = IdType.AUTO) 30 | private Integer id; 31 | 32 | /** 33 | * 用户ID 34 | */ 35 | @TableField(value = "user_id") 36 | private String userId; 37 | 38 | /** 39 | * 登录时间 40 | */ 41 | @TableField(value = "login_time") 42 | private Date loginTime; 43 | 44 | /** 45 | * 登录IP 46 | */ 47 | @TableField(value = "login_ip") 48 | private String loginIp; 49 | 50 | /** 51 | * 登录类型,详情见枚举类LoginType 52 | */ 53 | @TableField(value = "login_type") 54 | private Integer loginType; 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/entity/UserSessionEntity.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import lombok.AllArgsConstructor; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | import java.util.Date; 12 | 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @TableName(value = "user_session") 16 | public class UserSessionEntity implements Serializable { 17 | 18 | @TableId(type = IdType.AUTO) 19 | @JsonProperty("session_id") 20 | private Integer sessionId; 21 | 22 | @JsonProperty("user_id") 23 | private String userId; 24 | 25 | @JsonProperty("session_name") 26 | private String sessionName; 27 | 28 | @JsonProperty("create_time") 29 | private Date createTime; 30 | 31 | @JsonProperty("update_time") 32 | private Date updateTime; 33 | 34 | // 聊天的类型,区分普通聊天、pdf聊天、冒险游戏聊天 35 | @JsonProperty("type") 36 | private Integer type; 37 | 38 | private static final long serialVersionUID = 1L; 39 | 40 | public Integer getSessionId() { 41 | return sessionId; 42 | } 43 | 44 | public void setSessionId(Integer sessionId) { 45 | this.sessionId = sessionId; 46 | } 47 | 48 | public String getUserId() { 49 | return userId; 50 | } 51 | 52 | public void setUserId(String userId) { 53 | this.userId = userId; 54 | } 55 | 56 | public String getSessionName() { 57 | return sessionName; 58 | } 59 | 60 | public void setSessionName(String sessionName) { 61 | this.sessionName = sessionName == null ? null : sessionName.trim(); 62 | } 63 | 64 | public Date getCreateTime() { 65 | return createTime; 66 | } 67 | 68 | public void setCreateTime(Date createTime) { 69 | this.createTime = createTime; 70 | } 71 | 72 | public Date getUpdateTime() { 73 | return updateTime; 74 | } 75 | 76 | public void setUpdateTime(Date updateTime) { 77 | this.updateTime = updateTime; 78 | } 79 | 80 | public Integer getType() { 81 | return type; 82 | } 83 | 84 | public void setType(Integer type) { 85 | this.type = type; 86 | } 87 | 88 | public UserSessionEntity(String userId, String sessionName, Integer type){ 89 | this.userId = userId; 90 | this.sessionName = sessionName; 91 | this.type = type; 92 | } 93 | 94 | public UserSessionEntity(Integer sessionId, String sessionName){ 95 | this.sessionId = sessionId; 96 | this.sessionName = sessionName; 97 | } 98 | } -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/enums/ApiType.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.enums; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * @Author: huangpenglong 9 | * @Date: 2023/3/13 17:16 10 | */ 11 | public enum ApiType { 12 | /** 13 | * 0: openai 14 | * 1: microsoft 15 | * 2: 百度 16 | * 3: 梦网 17 | * 4: Pinecone 18 | */ 19 | OPENAI("openai", 0), 20 | MICROSOFT("microsoft", 1), 21 | BAIDU("baidu", 2), 22 | MENGWANG("mengwang", 3), 23 | PINECONE("pinecone", 4); 24 | 25 | public final String typeName; 26 | public final Integer typeNo; 27 | 28 | ApiType(String typeName, Integer typeNo){ 29 | this.typeName = typeName; 30 | this.typeNo = typeNo; 31 | } 32 | 33 | private static final Map MAP = Arrays.stream(values()) 34 | .collect(Collectors.toMap(item->item.typeNo, item->item)); 35 | 36 | public static boolean contains(Integer type){ 37 | return MAP.containsKey(type); 38 | } 39 | public static ApiType get(Integer type){ 40 | return MAP.get(type); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/enums/LoginType.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.enums; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * @Author: huangpenglong 9 | * @Date: 2023/4/26 17:05 10 | */ 11 | public enum LoginType { 12 | /** 13 | * 登录类型: 14 | * 0 普通登录(账号|手机号) 15 | * 1 微信 16 | * 2 游客 17 | */ 18 | NORMAL(0), 19 | WECHAT(1), 20 | VISITOR(2), 21 | ; 22 | public final Integer typeNo; 23 | 24 | private static final Map MAP = Arrays.stream(values()) 25 | .collect(Collectors.toMap(item->item.typeNo, item->item)); 26 | 27 | LoginType(Integer typeNo){ 28 | this.typeNo = typeNo; 29 | } 30 | 31 | public static LoginType get(Integer typeNo){ 32 | return MAP.get(typeNo); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/enums/Prompt.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.enums; 2 | 3 | /** 4 | * @Author: huangpenglong 5 | * @Date: 2023/4/12 10:34 6 | */ 7 | public enum Prompt { 8 | /** 9 | * 10 | */ 11 | GRAMMARLY("语法纠错"), 12 | TRANSLATE("牛逼翻译"), 13 | QA_PROMPT_TEMPLATE("PDF对话优化问题"), 14 | FINAL_PROMPT_TEMPLATE("PDF对话最终问题"), 15 | PDF_SUMMARY_TEMPLATE("总结PDF提示"), 16 | GAME_START("文字冒险游戏-开始"), 17 | ; 18 | 19 | public final String topic; 20 | 21 | Prompt(String topic){ 22 | this.topic = topic; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/enums/PromptTarget.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.enums; 2 | 3 | /** 4 | * @Author: huangpenglong 5 | * @Date: 2023/4/17 0:20 6 | */ 7 | public enum PromptTarget { 8 | 9 | /** 10 | * 0: 管理员 11 | * 1: 用户 12 | */ 13 | ADMIN("管理员", 0), 14 | USER("用户", 1) 15 | ; 16 | 17 | public final String targetName; 18 | public final int targetNo; 19 | 20 | PromptTarget(String targetName, int targetNo){ 21 | this.targetName = targetName; 22 | this.targetNo = targetNo; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/enums/PromptType.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.enums; 2 | 3 | /** 4 | * @Author: huangpenglong 5 | * @Date: 2023/4/11 16:35 6 | */ 7 | public enum PromptType { 8 | /** 9 | * 0: chatgpt 10 | * 1: midjourney 11 | */ 12 | CHATGPT("chatgpt", 0), 13 | MIDJOURNEY("midjourney", 1) 14 | ; 15 | 16 | public final String typeName; 17 | public final int typeNo; 18 | 19 | PromptType(String typeName, int typeNo){ 20 | this.typeName = typeName; 21 | this.typeNo = typeNo; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/enums/SessionType.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.enums; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * @Author: huangpenglong / zxw 9 | * @Date: 2023/4/8 17:12 10 | */ 11 | public enum SessionType { 12 | // 普通聊天 13 | NORMAL_CHAT(0, 3000), 14 | 15 | // pdf聊天 16 | PDF_CHAT(1, 3500), 17 | 18 | // 冒险游戏聊天 19 | GAME_CHAT(2, 3500), 20 | 21 | // 专家聊天 22 | EXPERT_CHAT(3, 3500); 23 | 24 | public final Integer type; 25 | /** 26 | * 聊天上下文窗口的token最大数量 27 | */ 28 | public final Integer maxContextToken; 29 | 30 | private static final Map MAP = Arrays.stream(values()) 31 | .collect(Collectors.toMap(item->item.type, item->item)); 32 | 33 | public static boolean contains(Integer type){ 34 | return MAP.containsKey(type); 35 | } 36 | public static SessionType get(Integer type){ 37 | return MAP.get(type); 38 | } 39 | SessionType(int type, int maxContextToken){ 40 | this.type = type; 41 | this.maxContextToken = maxContextToken; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/enums/UserLevel.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.enums; 2 | 3 | import java.util.Arrays; 4 | import java.util.Map; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * @Author: huangpenglong 9 | * @Date: 2023/4/17 9:32 10 | */ 11 | public enum UserLevel { 12 | /** 13 | * 管理员:每天限制无限制聊天次数、PDF上传次数 14 | * 15 | * 普通用户:每天50次聊天次数,3次PDF上传 16 | * 17 | * vip用户:每天无限制聊天次数、PDF上传次数 18 | * 19 | * 游客用户: 每天限制10次聊天次数, 1次PDF上传 20 | */ 21 | 22 | ADMIN("管理员", 0, Integer.MAX_VALUE, Integer.MAX_VALUE), 23 | NORMAL("用户", 1, 50, 3), 24 | VIP("vip用户", 2, Integer.MAX_VALUE, Integer.MAX_VALUE), 25 | VISITOR("游客", 3, 10, 1) 26 | ; 27 | 28 | public final String levelName; 29 | public final Integer levelNo; 30 | public final int dailyChatLimit; 31 | public final int dailyFileUploadLimit; 32 | 33 | private static final Map MAP = Arrays.stream(values()) 34 | .collect(Collectors.toMap(item->item.levelNo, item->item)); 35 | 36 | UserLevel(String levelName, int levelNo, int dailyChatLimit, int dailyFileUploadLimit) { 37 | this.levelName = levelName; 38 | this.levelNo = levelNo; 39 | this.dailyChatLimit = dailyChatLimit; 40 | this.dailyFileUploadLimit = dailyFileUploadLimit; 41 | } 42 | 43 | public static boolean contains(Integer levelNo){ 44 | return MAP.containsKey(levelNo); 45 | } 46 | public static UserLevel get(Integer levelNo){ 47 | return MAP.get(levelNo); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/exception/BaseException.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.exception; 2 | 3 | import com.gzhu.funai.utils.ReturnResult; 4 | import lombok.Data; 5 | 6 | /** 7 | * @Author: huangpenglong 8 | * @Date: 2023/3/28 0:39 9 | */ 10 | 11 | @Data 12 | public class BaseException extends RuntimeException { 13 | 14 | private final String msg; 15 | private final int code; 16 | 17 | public BaseException(String msg) { 18 | super(msg); 19 | this.code = ReturnResult.error().getCode(); 20 | this.msg = msg; 21 | } 22 | 23 | public BaseException(int code, String msg) { 24 | super(msg); 25 | this.code = code; 26 | this.msg = msg; 27 | } 28 | 29 | public BaseException() { 30 | super(ReturnResult.error().getMessage()); 31 | this.code = ReturnResult.error().getCode(); 32 | this.msg = ReturnResult.error().getMessage(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/exception/EmailException.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.exception; 2 | 3 | /** 4 | * @Author :wuxiaodong 5 | * @Date: 2023/3/16 15:56 6 | * @Description: 7 | */ 8 | public class EmailException extends RuntimeException{ 9 | public EmailException() { 10 | super("存在相同的邮箱"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/exception/PhoneException.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.exception; 2 | 3 | /** 4 | * @Description: 5 | **/ 6 | public class PhoneException extends RuntimeException { 7 | 8 | public PhoneException() { 9 | super("存在相同的手机号"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/exception/SessionNameRepeatException.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.exception; 2 | 3 | /** 4 | * @Author :wuxiaodong 5 | * @Date: 2023/3/16 15:56 6 | * @Description: 7 | */ 8 | public class SessionNameRepeatException extends RuntimeException{ 9 | public SessionNameRepeatException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/exception/UsernameException.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.exception; 2 | 3 | /** 4 | * @Description: 5 | **/ 6 | public class UsernameException extends RuntimeException { 7 | 8 | 9 | public UsernameException() { 10 | super("存在相同的用户名"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/filter/ChannelFilter.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.filter; 2 | 3 | import com.gzhu.funai.utils.RequestWrapper; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.servlet.*; 8 | import javax.servlet.annotation.WebFilter; 9 | import javax.servlet.http.HttpServletRequest; 10 | 11 | /** 12 | * @Author: huangpenglong 13 | * @Date: 2023/4/26 17:21 14 | */ 15 | 16 | @Component 17 | @Slf4j 18 | @WebFilter(filterName = "channelFilter", urlPatterns = {"/*"}) 19 | public class ChannelFilter implements Filter { 20 | 21 | @Override 22 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){ 23 | try { 24 | long start = System.currentTimeMillis(); 25 | ServletRequest requestWrapper = new RequestWrapper((HttpServletRequest) request); 26 | chain.doFilter(requestWrapper, response); 27 | String requestUri = ((HttpServletRequest) request).getRequestURI(); 28 | log.info("ip{}, 访问路径{}完毕,耗时:{}ms",request.getRemoteAddr(), requestUri, System.currentTimeMillis() - start); 29 | }catch (Exception e){ 30 | e.getMessage(); 31 | log.error(e.getMessage()); 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/filter/CorsFilter.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.filter; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.servlet.*; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | /** 12 | * @Author :wuxiaodong 13 | * @Date: 2023/3/24 17:04 14 | * @Description:解决跨域问题 15 | */ 16 | @Component 17 | public class CorsFilter implements Filter { 18 | 19 | @Override 20 | public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 21 | 22 | HttpServletResponse response = (HttpServletResponse) res; 23 | HttpServletRequest request = (HttpServletRequest) req; 24 | response.setHeader("x-frame-options", "SAMEORIGIN"); 25 | // 不使用*,自动适配跨域域名,避免携带Cookie时失效 26 | String origin = request.getHeader("Origin"); 27 | if (StringUtils.isNotBlank(origin)) { 28 | response.setHeader("Access-Control-Allow-Origin", origin); } 29 | // 自适应所有自定义头 30 | String headers = request.getHeader("Access-Control-Request-Headers"); 31 | if(StringUtils.isNotBlank(headers)) { 32 | response.setHeader("Access-Control-Allow-Headers", headers); 33 | response.setHeader("Access-Control-Expose-Headers", headers); 34 | } 35 | // 允许跨域的请求方法类型 36 | response.setHeader("Access-Control-Allow-Methods", "*"); 37 | // 预检命令(OPTIONS)缓存时间,单位:秒 38 | response.setHeader("Access-Control-Max-Age", "3600"); 39 | // 明确许可客户端发送Cookie,不允许删除字段即可 40 | response.setHeader("Access-Control-Allow-Credentials", "true"); 41 | chain.doFilter(request, response); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/global/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.global; 2 | 3 | import com.gzhu.funai.exception.*; 4 | import com.gzhu.funai.utils.ResultCode; 5 | import com.gzhu.funai.utils.ReturnResult; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ControllerAdvice; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.ResponseBody; 11 | 12 | /** 13 | * @Author: huangpenglong 14 | * @Date: 2023/3/9 16:42 15 | */ 16 | @Slf4j // logback记录日志 17 | @ControllerAdvice // 可对controller中被 @RequestMapping注解的方法加一些逻辑处理。最常用的就是异常处理 18 | public class GlobalExceptionHandler { 19 | 20 | // 前端参数校验异常 21 | @ExceptionHandler(value= MethodArgumentNotValidException.class) 22 | @ResponseBody 23 | public ReturnResult handleRegisterLackException(MethodArgumentNotValidException e){ 24 | log.error("数据校验出现问题{},异常类型:{}",e.getBindingResult().getAllErrors().get(0).getDefaultMessage(),e.getClass()); 25 | return ReturnResult.error().code(ResultCode.BAD_PARAM.code).message(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); 26 | } 27 | 28 | 29 | // 数据库注册信息已存在异常 30 | @ExceptionHandler(value= {EmailException.class, PhoneException.class, UsernameException.class}) 31 | @ResponseBody 32 | public ReturnResult handleRegisterRepeatException(Exception e){ 33 | log.error("注册信息判重出现问题{},异常类型:{}",e.getMessage(),e.getClass()); 34 | return ReturnResult.error().code(ResultCode.USER_REGISTER_PARAMS_REPEAT.code).message(ResultCode.USER_REGISTER_PARAMS_REPEAT.msg+":"+e.getMessage()); 35 | } 36 | 37 | @ExceptionHandler(BaseException.class) 38 | @ResponseBody 39 | public ReturnResult exception(BaseException e) { 40 | log.error(e.getMessage()); 41 | return ReturnResult.error().code(e.getCode()).message(e.getMsg()); 42 | } 43 | 44 | @ExceptionHandler(Exception.class) 45 | @ResponseBody 46 | public ReturnResult exception(Exception e) { 47 | log.error(e.getMessage()); 48 | return ReturnResult.error(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/global/constant/GlobalConstant.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.global.constant; 2 | 3 | /** 4 | * @Author: huangpenglong 5 | * @Date: 2023/4/26 9:35 6 | */ 7 | public class GlobalConstant { 8 | public static final long TEN_K = 1000L; 9 | 10 | private GlobalConstant(){} 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/global/constant/TimeInterval.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.global.constant; 2 | 3 | /** 4 | * @Author: huangpenglong 5 | * @Date: 2023/4/12 10:15 6 | */ 7 | public class TimeInterval { 8 | 9 | public static final long ZERO = 0L; 10 | public static final long ONE_MINUTE = 60 * 1000L; 11 | public static final long FIVE_MINUTE = 5 * 60 * 1000L; 12 | 13 | public static final long ONE_HOUR = 60 * 60 * 1000L; 14 | 15 | private TimeInterval() { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/handler/LoginHandler.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.handler; 2 | 3 | import com.gzhu.funai.session.LoginSession; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @Author: huangpenglong 9 | * @Date: 2023/4/30 20:51 10 | */ 11 | public interface LoginHandler { 12 | 13 | /** 14 | * 处理不同的登录情况 15 | * @param loginSession 16 | * @return 17 | */ 18 | Map login(LoginSession loginSession); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/handler/impl/NormalLoginHandler.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.handler.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.gzhu.funai.entity.UserEntity; 5 | import com.gzhu.funai.entity.UserLoginRecord; 6 | import com.gzhu.funai.enums.UserLevel; 7 | import com.gzhu.funai.handler.LoginHandler; 8 | import com.gzhu.funai.service.UserLoginRecordService; 9 | import com.gzhu.funai.service.UserService; 10 | import com.gzhu.funai.session.LoginSession; 11 | import com.gzhu.funai.utils.JwtUtil; 12 | import org.springframework.core.task.TaskExecutor; 13 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.util.StringUtils; 16 | 17 | import javax.annotation.Resource; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * @Author: huangpenglong 23 | * @Date: 2023/4/30 21:09 24 | */ 25 | 26 | @Service(value = "NormalLoginHandler") 27 | public class NormalLoginHandler implements LoginHandler { 28 | private static final String MAP_KEY_USERNAME = "username"; 29 | private static final String MAP_KEY_MOBILE = "mobile"; 30 | 31 | @Resource 32 | private UserService userService; 33 | @Resource 34 | private UserLoginRecordService userLoginRecordService; 35 | @Resource 36 | private TaskExecutor queueThreadPool; 37 | 38 | @Override 39 | public Map login(LoginSession loginSession) { 40 | Map map = new HashMap<>(5); 41 | 42 | // 去数据库查询 SELECT * FROM user WHERE level != ? and (username = ? OR mobile = ?) 43 | UserEntity userEntity = this.userService.getOne(new QueryWrapper() 44 | .ne("level", UserLevel.VISITOR.levelNo) 45 | .and(wrapper -> { 46 | wrapper.eq(MAP_KEY_MOBILE, loginSession.getLoginAcct()).or() 47 | .eq(MAP_KEY_USERNAME, loginSession.getLoginAcct()); 48 | return wrapper; 49 | })); 50 | 51 | // 无此账户,登录失败 52 | if (userEntity == null){ 53 | return null; 54 | } 55 | 56 | // 检验账户锁定情况 57 | if(userEntity.getStatus() == 1){ 58 | return map; 59 | } 60 | 61 | // 进行密码匹配失败 62 | if (!new BCryptPasswordEncoder().matches(loginSession.getPassword(), userEntity.getPassword())) { 63 | return null; 64 | } 65 | 66 | //登录成功,返回数据 67 | if (!StringUtils.isEmpty(userEntity.getNickname())) { 68 | map.put(MAP_KEY_USERNAME,userEntity.getNickname()); 69 | }else if (!StringUtils.isEmpty(userEntity.getUsername())){ 70 | map.put(MAP_KEY_USERNAME,userEntity.getUsername()); 71 | } else { 72 | map.put(MAP_KEY_USERNAME,userEntity.getMobile()); 73 | } 74 | map.put("userId", userEntity.getId()); 75 | map.put("userLevel", String.valueOf(userEntity.getLevel())); 76 | map.put("token", JwtUtil.createToken(userEntity.getId(), (String)(map.get(MAP_KEY_USERNAME)), userEntity.getLevel())); 77 | 78 | // 登录入库 79 | queueThreadPool.execute(() -> 80 | userLoginRecordService.save(UserLoginRecord.builder() 81 | .userId(userEntity.getId()) 82 | .loginType(loginSession.getLoginType().typeNo) 83 | .loginIp(loginSession.getIp()) 84 | .build()) 85 | ); 86 | return map; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/handler/impl/VisitorLoginHandler.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.handler.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.gzhu.funai.entity.UserEntity; 5 | import com.gzhu.funai.entity.UserLoginRecord; 6 | import com.gzhu.funai.enums.UserLevel; 7 | import com.gzhu.funai.handler.LoginHandler; 8 | import com.gzhu.funai.service.UserLoginRecordService; 9 | import com.gzhu.funai.service.UserService; 10 | import com.gzhu.funai.session.LoginSession; 11 | import com.gzhu.funai.utils.JwtUtil; 12 | import com.gzhu.funai.utils.SequentialUuidHexGenerator; 13 | import org.springframework.core.task.TaskExecutor; 14 | import org.springframework.stereotype.Service; 15 | 16 | import javax.annotation.Resource; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | /** 21 | * @Author: huangpenglong 22 | * @Date: 2023/4/30 21:13 23 | */ 24 | 25 | @Service(value = "VisitorLoginHandler") 26 | public class VisitorLoginHandler implements LoginHandler { 27 | private static final String MAP_KEY_USERNAME = "username"; 28 | 29 | @Resource 30 | private UserService userService; 31 | @Resource 32 | private UserLoginRecordService userLoginRecordService; 33 | @Resource 34 | private TaskExecutor queueThreadPool; 35 | 36 | @Override 37 | public Map login(LoginSession loginSession) { 38 | Map map = new HashMap<>(5); 39 | 40 | // 去数据库查询,以IP为username的游客账号是否存在 41 | UserEntity userEntity = this.userService.getOne(new QueryWrapper() 42 | .eq("level", UserLevel.VISITOR.levelNo) 43 | .eq(MAP_KEY_USERNAME, loginSession.getIp())); 44 | 45 | // 无此账户,则创建一个 46 | if (userEntity == null){ 47 | userEntity = UserEntity.builder() 48 | .id(SequentialUuidHexGenerator.generate()) 49 | .username(loginSession.getIp()) 50 | .status((byte) 0) 51 | .level(UserLevel.VISITOR.levelNo) 52 | .build(); 53 | 54 | userService.save(userEntity); 55 | } 56 | 57 | // 检验账户锁定情况 58 | if(userEntity.getStatus() == 1){ 59 | return map; 60 | } 61 | 62 | //登录成功,返回数据 63 | map.put(MAP_KEY_USERNAME,userEntity.getUsername()); 64 | map.put("userId", userEntity.getId()); 65 | map.put("userLevel", String.valueOf(userEntity.getLevel())); 66 | map.put("token", JwtUtil.createToken(userEntity.getId(), (String)(map.get(MAP_KEY_USERNAME)), userEntity.getLevel())); 67 | 68 | // 登录入库 69 | UserEntity finalUserEntity = userEntity; 70 | queueThreadPool.execute(() -> 71 | userLoginRecordService.save(UserLoginRecord.builder() 72 | .userId(finalUserEntity.getId()) 73 | .loginType(loginSession.getLoginType().typeNo) 74 | .loginIp(loginSession.getIp()) 75 | .build()) 76 | ); 77 | return map; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/interceptor/AccessLimitInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.interceptor; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import com.gzhu.funai.utils.ReturnResult; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.web.servlet.HandlerInterceptor; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.util.Objects; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | * @Author :wuxiaodong 18 | * @Date: 2023/4/8 13:55 19 | * @Description: 20 | */ 21 | @Slf4j 22 | public class AccessLimitInterceptor implements HandlerInterceptor { 23 | private final RedisTemplate redisTemplate; 24 | 25 | public AccessLimitInterceptor(RedisTemplate redisTemplate) { 26 | this.redisTemplate = redisTemplate; 27 | } 28 | 29 | /** 30 | * 多长时间内 31 | */ 32 | private Long minute = 5L; 33 | 34 | /** 35 | * 访问次数 36 | */ 37 | private Long times = 5L; 38 | 39 | /** 40 | * 禁用时长--单位/分钟 41 | */ 42 | 43 | private Long lockTime = 10L; 44 | 45 | /** 46 | * 锁住时的key前缀 47 | */ 48 | public static final String LOCK_PREFIX = "LOCK"; 49 | 50 | /** 51 | * 统计次数时的key前缀 52 | */ 53 | public static final String COUNT_PREFIX = "COUNT"; 54 | 55 | @Override 56 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 57 | 58 | String uri = request.getRequestURI(); 59 | // 这里忽略代理软件方式访问,默认直接访问,也就是获取得到的就是访问者真实ip地址 60 | String ip = request.getRemoteAddr(); 61 | String lockKey = LOCK_PREFIX + ip + uri; 62 | Object isLock = redisTemplate.opsForValue().get(lockKey); 63 | if(Objects.isNull(isLock)){ 64 | // 还未被禁用 65 | String countKey = COUNT_PREFIX + ip + uri; 66 | Object count = redisTemplate.opsForValue().get(countKey); 67 | if(Objects.isNull(count)){ 68 | // 首次访问 69 | log.info("首次访问,uri{},ip{}",uri,ip); 70 | redisTemplate.opsForValue().set(countKey,1,minute, TimeUnit.MINUTES); 71 | }else{ 72 | // 此用户前一点时间就访问过该接口 73 | if((Integer)count < times){ 74 | // 放行,访问次数 + 1 75 | redisTemplate.opsForValue().increment(countKey); 76 | }else{ 77 | log.info("{}禁用访问{}",ip, uri); 78 | // 禁用 79 | redisTemplate.opsForValue().set(lockKey, 1,lockTime, TimeUnit.MINUTES); 80 | // 删除统计 81 | redisTemplate.delete(countKey); 82 | out(ReturnResult.error().message("5分钟内超过接口访问次数限制"),response); 83 | } 84 | } 85 | }else{ 86 | // // 此用户访问此接口已被禁用 87 | out(ReturnResult.error().message("5分钟内超过接口访问次数限制"),response); 88 | return false; 89 | } 90 | return true; 91 | } 92 | 93 | /** 94 | * api接口鉴权失败返回数据 95 | */ 96 | private void out(ReturnResult result, HttpServletResponse response) throws IOException { 97 | response.setContentType("application/json;charset=UTF-8"); 98 | Gson gson = new Gson(); 99 | response.getWriter().write(gson.toJson(result)); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/interceptor/AdminOperateInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.interceptor; 2 | 3 | import com.google.gson.Gson; 4 | import com.gzhu.funai.enums.UserLevel; 5 | import com.gzhu.funai.utils.JwtUtil; 6 | import com.gzhu.funai.utils.ResultCode; 7 | import com.gzhu.funai.utils.ReturnResult; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.web.servlet.HandlerInterceptor; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | 15 | /** 16 | * @Author: huangpenglong 17 | * @Date: 2023/4/17 9:04 18 | */ 19 | 20 | @Slf4j 21 | public class AdminOperateInterceptor implements HandlerInterceptor { 22 | 23 | @Override 24 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 25 | String token = request.getHeader("token"); 26 | 27 | try { 28 | UserLevel uLevel = UserLevel.get(JwtUtil.getUserLevel(token)); 29 | if (!UserLevel.ADMIN.equals(uLevel)){ 30 | out(ReturnResult.error() 31 | .code(ResultCode.ADMIN_OPERATE_FORBIDDEN.code) 32 | .message(ResultCode.ADMIN_OPERATE_FORBIDDEN.msg), response); 33 | return false; 34 | } 35 | } catch (Exception e) { 36 | out(ReturnResult.error().code(ResultCode.USER_NOT_LOGIN.code).message(ResultCode.USER_NOT_LOGIN.msg), response); 37 | return false; 38 | } 39 | return true; 40 | } 41 | 42 | 43 | /** 44 | * 失败时返回数据 45 | */ 46 | private void out(ReturnResult result, HttpServletResponse response) throws IOException { 47 | response.setContentType("application/json;charset=UTF-8"); 48 | Gson gson = new Gson(); 49 | response.getWriter().write(gson.toJson(result)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/interceptor/UserChatLimitInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.interceptor; 2 | 3 | import com.google.gson.Gson; 4 | import com.gzhu.funai.entity.UserApiKeyEntity; 5 | import com.gzhu.funai.entity.UserEntity; 6 | import com.gzhu.funai.enums.ApiType; 7 | import com.gzhu.funai.enums.UserLevel; 8 | import com.gzhu.funai.redis.ChatRedisHelper; 9 | import com.gzhu.funai.service.UserApiKeyService; 10 | import com.gzhu.funai.service.UserService; 11 | import com.gzhu.funai.utils.JwtUtil; 12 | import com.gzhu.funai.utils.ResultCode; 13 | import com.gzhu.funai.utils.ReturnResult; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.util.StringUtils; 16 | import org.springframework.web.servlet.HandlerInterceptor; 17 | import org.springframework.web.servlet.ModelAndView; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.IOException; 22 | 23 | /** 24 | * @Author: huangpenglong 25 | * @Date: 2023/4/17 9:04 26 | */ 27 | 28 | @Slf4j 29 | public class UserChatLimitInterceptor implements HandlerInterceptor { 30 | 31 | private ChatRedisHelper chatRedisHelper; 32 | private UserApiKeyService userApiKeyService; 33 | private UserService userService; 34 | public UserChatLimitInterceptor(ChatRedisHelper chatRedisHelper, UserApiKeyService userApiKeyService, UserService userService){ 35 | this.chatRedisHelper = chatRedisHelper; 36 | this.userApiKeyService = userApiKeyService; 37 | this.userService = userService; 38 | } 39 | 40 | @Override 41 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 42 | String token = request.getHeader("token"); 43 | 44 | try { 45 | String userId = JwtUtil.getUserId(token); 46 | UserEntity userEntity = userService.getById(userId); 47 | UserLevel uLevel = UserLevel.get(userEntity.getLevel()); 48 | UserApiKeyEntity userApiKeyEntity = userApiKeyService.getByUserIdAndType(userId, ApiType.OPENAI); 49 | 50 | // 若用户没上传API-Key,则做限制 51 | if(userApiKeyEntity == null || StringUtils.isEmpty(userApiKeyEntity.getApikey())){ 52 | int dailyChatCount = this.chatRedisHelper.getDailyChatCount(userId); 53 | if (dailyChatCount >= uLevel.dailyChatLimit) { 54 | log.info("已限制用户id为{}的聊天功能,该用户聊天次数为{}次", userId, dailyChatCount); 55 | String extraMsg = ""; 56 | if(UserLevel.VISITOR.equals(uLevel)){ 57 | extraMsg = "请注册账号,获取更多使用额度~"; 58 | } 59 | else if(UserLevel.NORMAL.equals(uLevel)){ 60 | extraMsg = "请联系管理员升级账号,获取更多使用额度~"; 61 | } 62 | out(ReturnResult.error() 63 | .code(ResultCode.USER_CHAT_LIMITED.code) 64 | .message(ResultCode.USER_CHAT_LIMITED.msg + "已使用" + dailyChatCount + "次。" + extraMsg), response); 65 | return false; 66 | } 67 | } 68 | } catch (Exception e) { 69 | out(ReturnResult.error().code(ResultCode.USER_NOT_LOGIN.code).message(ResultCode.USER_NOT_LOGIN.msg), response); 70 | return false; 71 | } 72 | return true; 73 | } 74 | 75 | @Override 76 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 77 | String token = request.getHeader("token"); 78 | String userId = JwtUtil.getUserId(token); 79 | 80 | // 用户没有上传APIKey,那就记录使用次数 81 | UserApiKeyEntity userApiKeyEntity = userApiKeyService.getByUserIdAndType(userId, ApiType.OPENAI); 82 | if (userApiKeyEntity == null || StringUtils.isEmpty(userApiKeyEntity.getApikey())){ 83 | int count = chatRedisHelper.incrDailyChatCount(userId, 1); 84 | log.info("用户id为{}的当日聊天次数为{}", userId, count); 85 | } 86 | } 87 | 88 | /** 89 | * 失败时返回数据 90 | */ 91 | private void out(ReturnResult result, HttpServletResponse response) throws IOException { 92 | response.setContentType("application/json;charset=UTF-8"); 93 | Gson gson = new Gson(); 94 | response.getWriter().write(gson.toJson(result)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/interceptor/UserFileUploadLimitInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.interceptor; 2 | 3 | import com.google.gson.Gson; 4 | import com.gzhu.funai.entity.UserApiKeyEntity; 5 | import com.gzhu.funai.entity.UserEntity; 6 | import com.gzhu.funai.enums.ApiType; 7 | import com.gzhu.funai.enums.UserLevel; 8 | import com.gzhu.funai.redis.ChatRedisHelper; 9 | import com.gzhu.funai.service.UserApiKeyService; 10 | import com.gzhu.funai.service.UserService; 11 | import com.gzhu.funai.utils.JwtUtil; 12 | import com.gzhu.funai.utils.ResultCode; 13 | import com.gzhu.funai.utils.ReturnResult; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.util.StringUtils; 16 | import org.springframework.web.servlet.HandlerInterceptor; 17 | import org.springframework.web.servlet.ModelAndView; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | import javax.servlet.http.HttpServletResponse; 21 | import java.io.IOException; 22 | 23 | /** 24 | * @Author: huangpenglong 25 | * @Date: 2023/4/17 9:04 26 | */ 27 | 28 | @Slf4j 29 | public class UserFileUploadLimitInterceptor implements HandlerInterceptor { 30 | 31 | private ChatRedisHelper chatRedisHelper; 32 | private UserApiKeyService userApiKeyService; 33 | private UserService userService; 34 | public UserFileUploadLimitInterceptor(ChatRedisHelper chatRedisHelper, UserApiKeyService userApiKeyService, UserService userService){ 35 | this.chatRedisHelper = chatRedisHelper; 36 | this.userApiKeyService = userApiKeyService; 37 | this.userService = userService; 38 | } 39 | 40 | @Override 41 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 42 | String token = request.getHeader("token"); 43 | 44 | try { 45 | String userId = JwtUtil.getUserId(token); 46 | UserEntity userEntity = userService.getById(userId); 47 | UserLevel uLevel = UserLevel.get(userEntity.getLevel()); 48 | UserApiKeyEntity userApiKeyEntity = userApiKeyService.getByUserIdAndType(userId, ApiType.OPENAI); 49 | 50 | // 若用户没上传API-Key,则做限制 51 | if(userApiKeyEntity == null || StringUtils.isEmpty(userApiKeyEntity.getApikey())){ 52 | int dailyFileUploadCount = this.chatRedisHelper.getDailyFileUploadCount(userId); 53 | if (dailyFileUploadCount >= uLevel.dailyFileUploadLimit) { 54 | log.info("已限制用户id为{}的PDF阅读功能,该用户使用次数为{}次", userId, dailyFileUploadCount); 55 | String extraMsg = ""; 56 | if(UserLevel.VISITOR.equals(uLevel)){ 57 | extraMsg = "请注册账号,获取更多使用额度~"; 58 | } 59 | else if(UserLevel.NORMAL.equals(uLevel)){ 60 | extraMsg = "请联系管理员升级账号,获取更多使用额度~"; 61 | } 62 | out(ReturnResult.error() 63 | .code(ResultCode.USER_FILE_UPLOAD_LIMITED.code) 64 | .message(ResultCode.USER_FILE_UPLOAD_LIMITED.msg + "已使用" + dailyFileUploadCount + "次。" + extraMsg), 65 | response); 66 | return false; 67 | } 68 | } 69 | } catch (Exception e) { 70 | out(ReturnResult.error().code(ResultCode.USER_NOT_LOGIN.code).message(ResultCode.USER_NOT_LOGIN.msg), response); 71 | return false; 72 | } 73 | return true; 74 | } 75 | 76 | @Override 77 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 78 | String token = request.getHeader("token"); 79 | String userId = JwtUtil.getUserId(token); 80 | 81 | // 用户没有上传APIKey,那就做限制 82 | UserApiKeyEntity userApiKeyEntity = userApiKeyService.getByUserIdAndType(userId, ApiType.OPENAI); 83 | if (userApiKeyEntity == null || StringUtils.isEmpty(userApiKeyEntity.getApikey())){ 84 | int count = chatRedisHelper.incrDailyFileUploadCount(userId, 1); 85 | log.info("用户id为{}的当日文件上传次数为{}", userId, count); 86 | } 87 | } 88 | 89 | /** 90 | * 失败时返回数据 91 | */ 92 | private void out(ReturnResult result, HttpServletResponse response) throws IOException { 93 | response.setContentType("application/json;charset=UTF-8"); 94 | Gson gson = new Gson(); 95 | response.getWriter().write(gson.toJson(result)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/interceptor/UserLoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.interceptor; 2 | 3 | import com.gzhu.funai.utils.JwtUtil; 4 | import com.gzhu.funai.utils.ResultCode; 5 | import com.gzhu.funai.utils.ReturnResult; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.util.StringUtils; 8 | import org.springframework.web.servlet.HandlerInterceptor; 9 | import org.springframework.web.servlet.ModelAndView; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import com.google.gson.Gson; 15 | 16 | /** 17 | * @Author :wuxiaodong 18 | * @Date: 2023/3/24 14:38 19 | * @Description: 登录状态验证 20 | */ 21 | @Slf4j 22 | public class UserLoginInterceptor implements HandlerInterceptor { 23 | 24 | @Override 25 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 26 | String token = request.getHeader("token"); 27 | if (StringUtils.isEmpty(token)) { 28 | out(ReturnResult.error().code(ResultCode.USER_NOT_LOGIN.code).message(ResultCode.USER_NOT_LOGIN.msg),response); 29 | return false; 30 | } 31 | 32 | String userId; 33 | String userName; 34 | boolean tokenExpired; 35 | 36 | try { 37 | // 验证过程只要token是非法的,会自动抛异常 38 | userId = JwtUtil.getUserId(token); 39 | userName = JwtUtil.getUserName(token); 40 | tokenExpired = JwtUtil.isTokenExpired(token); 41 | if (!StringUtils.isEmpty(userName) && !StringUtils.isEmpty(userId) && !tokenExpired){ 42 | return true; 43 | } 44 | out(ReturnResult.error().code(ResultCode.USER_NOT_LOGIN.code).message(ResultCode.USER_NOT_LOGIN.msg),response); 45 | return false; 46 | } catch (Exception e) { 47 | out(ReturnResult.error().code(ResultCode.USER_NOT_LOGIN.code).message(ResultCode.USER_NOT_LOGIN.msg), response); 48 | return false; 49 | } 50 | } 51 | 52 | /** 53 | * api接口鉴权失败返回数据 54 | */ 55 | private void out(ReturnResult result,HttpServletResponse response) throws IOException { 56 | response.setContentType("application/json;charset=UTF-8"); 57 | Gson gson = new Gson(); 58 | response.getWriter().write(gson.toJson(result)); 59 | } 60 | 61 | @Override 62 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) 63 | throws Exception { 64 | // 在请求处理完成后执行该方法 65 | } 66 | 67 | @Override 68 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 69 | // 在视图渲染完成后执行该方法 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/AdminApiKeyMapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.gzhu.funai.entity.AdminApiKeyEntity; 5 | 6 | /** 7 | * @Author: huangpenglong 8 | * @Date: 2023/4/10 22:58 9 | */ 10 | public interface AdminApiKeyMapper extends BaseMapper { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/PromptMapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.gzhu.funai.entity.PromptEntity; 5 | 6 | /** 7 | * @Author: huangpenglong 8 | * @Date: 2023/4/11 16:37 9 | */ 10 | public interface PromptMapper extends BaseMapper{ 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/SessionChatRecordMapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.gzhu.funai.entity.SessionChatRecordEntity; 5 | 6 | /** 7 | * @Author: huangpenglong 8 | * @Date: 2023/3/17 22:09 9 | */ 10 | public interface SessionChatRecordMapper extends BaseMapper { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/UserAdvicesMapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.gzhu.funai.entity.UserAdvicesEntity; 5 | 6 | public interface UserAdvicesMapper extends BaseMapper { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/UserApiKeyMapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.gzhu.funai.entity.UserApiKeyEntity; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | /** 8 | * @Author: huangpenglong 9 | * @Date: 2023/4/20 19:46 10 | */ 11 | public interface UserApiKeyMapper extends BaseMapper{ 12 | 13 | void insertOrUpdate(@Param("userId") String userId, @Param("type") Integer type, @Param("apikey") String apikey); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/UserLoginRecordMapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.gzhu.funai.entity.UserLoginRecord; 5 | 6 | /** 7 | * @Author: huangpenglong 8 | * @Date: 2023/4/26 17:10 9 | */ 10 | public interface UserLoginRecordMapper extends BaseMapper { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.gzhu.funai.entity.UserEntity; 5 | 6 | /** 7 | *@Author :wuxiaodong 8 | *@Date: 2023/3/16 16:45 9 | *@Description: 10 | */ 11 | public interface UserMapper extends BaseMapper { 12 | } -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/UserSessionMapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.gzhu.funai.entity.UserSessionEntity; 5 | 6 | /** 7 | * @Author: huangpenglong 8 | * @Date: 2023/3/17 15:31 9 | */ 10 | public interface UserSessionMapper extends BaseMapper { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/xml/UserApiKeyMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | insert into user_apikey(`user_id`, `type`, `apikey`) 7 | values (#{userId}, #{type}, #{apikey}) 8 | ON DUPLICATE KEY UPDATE 9 | `apikey` = #{apikey} 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/mapper/xml/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | id, username, `password`, nickname, mobile, email, `header`, gender, `status`, create_time, 23 | social_uid 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/redis/AdminApiKeyRedisHelper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.redis; 2 | 3 | import org.springframework.data.redis.core.RedisTemplate; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.util.StringUtils; 6 | 7 | import javax.annotation.Resource; 8 | import java.time.Duration; 9 | import java.time.LocalDateTime; 10 | 11 | /** 12 | * @Author: huangpenglong 13 | * @Date: 2023/5/3 14:47 14 | */ 15 | 16 | @Component 17 | public class AdminApiKeyRedisHelper { 18 | 19 | @Resource 20 | private RedisTemplate redisTemplate; 21 | 22 | /** 23 | * 判断当前的openai免费key是否受限 24 | * @param apiKeyId 25 | * @return false表示不受限,true表示受限 26 | */ 27 | public boolean judgeOpenAiFreeKeyLimit(int apiKeyId){ 28 | String key = String.format(RedisKeys.ADMIN_OPENAI_FREE_KEY_LIMIT, apiKeyId); 29 | Object o = redisTemplate.opsForValue().get(key); 30 | 31 | // 若当前key不存在 或 次数小于3,那么说明当前key暂时不受限 32 | return !(StringUtils.isEmpty(o) || Integer.parseInt(String.valueOf(o)) < 3); 33 | } 34 | 35 | /** 36 | * 给当前的openai免费key记录受限信息 37 | * @param apiKeyId 38 | * @param count 39 | */ 40 | public int incrOpenAiFreeKeyLimit(int apiKeyId, int count){ 41 | String key = String.format(RedisKeys.ADMIN_OPENAI_FREE_KEY_LIMIT, apiKeyId); 42 | Object o = redisTemplate.opsForValue().get(key); 43 | 44 | // 若当前key不存在,则新建一个key。设置1分钟作为key的过期时间 45 | if(StringUtils.isEmpty(o)){ 46 | Duration duration = Duration.between(LocalDateTime.now(), LocalDateTime.now().plusMinutes(1)); 47 | redisTemplate.opsForValue().set(key, count, duration); 48 | return count; 49 | } 50 | // 若存在则新增 51 | return Integer.parseInt(String.valueOf(redisTemplate.opsForValue().increment(key, count))); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/redis/ChatRedisHelper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.redis; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.springframework.data.redis.core.RedisTemplate; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.util.StringUtils; 7 | 8 | import javax.annotation.Resource; 9 | import java.time.Duration; 10 | import java.time.LocalDateTime; 11 | import java.time.LocalTime; 12 | 13 | /** 14 | * @Author: huangpenglong 15 | * @Date: 2023/4/17 0:47 16 | */ 17 | 18 | @Component 19 | public class ChatRedisHelper { 20 | 21 | @Resource 22 | private RedisTemplate redisTemplate; 23 | 24 | /** 25 | * 新增用户当日的聊天次数count次 26 | * @param userId 27 | * @param count 28 | * @return 29 | */ 30 | public int incrDailyChatCount(String userId, int count){ 31 | String key = String.format(RedisKeys.USER_CHAT_DAILY_LIMIT, userId); 32 | Object o = redisTemplate.opsForValue().get(key); 33 | 34 | // 若当前key不存在,则新建一个key。计算当前时间到0点的时间差,作为key的过期时间 35 | if(StringUtils.isEmpty(o)){ 36 | Duration duration = Duration.between(LocalDateTime.now(), LocalDateTime.now().with(LocalTime.MAX)); 37 | redisTemplate.opsForValue().set(key, count, duration); 38 | return count; 39 | } 40 | // 若存在则新增 41 | return Integer.parseInt(String.valueOf(redisTemplate.opsForValue().increment(key, count))); 42 | } 43 | 44 | /** 45 | * 查看用户当日的聊天次数 46 | * @param userId 47 | * @return 48 | */ 49 | public int getDailyChatCount(String userId){ 50 | String key = String.format(RedisKeys.USER_CHAT_DAILY_LIMIT, userId); 51 | // 没有这个userId则返回0 52 | Object o = redisTemplate.opsForValue().get(key); 53 | if(StringUtils.isEmpty(o)){ 54 | return 0; 55 | } 56 | return Integer.parseInt(String.valueOf(o)); 57 | } 58 | 59 | /** 60 | * 新增用户上传次数count次 61 | * @param userId 62 | * @param count 63 | * @return 64 | */ 65 | public int incrDailyFileUploadCount(String userId, int count){ 66 | String key = String.format(RedisKeys.USER_FILE_UPLOAD_DAILY_LIMIT, userId); 67 | Object o = redisTemplate.opsForValue().get(key); 68 | 69 | // 若当前key不存在,则新建一个key。计算当前时间到0点的时间差,作为key的过期时间 70 | if(StringUtils.isEmpty(o)){ 71 | Duration duration = Duration.between(LocalDateTime.now(), LocalDateTime.now().with(LocalTime.MAX)); 72 | redisTemplate.opsForValue().set(key, count, duration); 73 | return count; 74 | } 75 | // 若存在则新增 76 | return Integer.parseInt(String.valueOf(redisTemplate.opsForValue().increment(key, count))); 77 | } 78 | 79 | /** 80 | * 查看用户当日的聊天次数 81 | * @param userId 82 | * @return 83 | */ 84 | public int getDailyFileUploadCount(String userId){ 85 | String key = String.format(RedisKeys.USER_FILE_UPLOAD_DAILY_LIMIT, userId); 86 | // 没有这个userId则返回0 87 | Object o = redisTemplate.opsForValue().get(key); 88 | if(StringUtils.isEmpty(o)){ 89 | return 0; 90 | } 91 | return Integer.parseInt(String.valueOf(o)); 92 | } 93 | 94 | /** 95 | * 清空限制 96 | * @param userId 97 | */ 98 | public void truncateLimit(String userId){ 99 | String dailyChatLimitKey = String.format(RedisKeys.USER_CHAT_DAILY_LIMIT, userId); 100 | String dailyFileUploadLimitKey = String.format(RedisKeys.USER_FILE_UPLOAD_DAILY_LIMIT, userId); 101 | 102 | redisTemplate.delete(ImmutableList.of(dailyChatLimitKey, dailyFileUploadLimitKey)); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/redis/RedisKeys.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.redis; 2 | 3 | /** 4 | * @Author: huangpenglong 5 | * @Date: 2023/3/17 15:21 6 | */ 7 | public class RedisKeys { 8 | 9 | /** 10 | * 用户验证码 11 | * register:code: [手机号] 12 | */ 13 | public static final String USER_REGISTER_CODE = "register:code:%s"; 14 | 15 | /** 16 | * 用户当前在聊天上的次数 17 | * chat:normal:limit: [用户ID] 18 | */ 19 | public static final String USER_CHAT_DAILY_LIMIT = "chat:normal:limit:%s"; 20 | 21 | /** 22 | * 用户当天文件上传的次数 23 | * chat:file:limit: [用户ID] 24 | */ 25 | public static final String USER_FILE_UPLOAD_DAILY_LIMIT = "chat:file:limit:%s"; 26 | 27 | /** 28 | * 管理员用的openai免费key的限制次数 (apikeyId为mysql表admin_apikey中的id字段) 29 | * admin:openai:freekey:limit:[apikeyId] 30 | */ 31 | public static final String ADMIN_OPENAI_FREE_KEY_LIMIT = "admin:openai:freekey:limit:%s"; 32 | private RedisKeys(){} 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/AdminApiKeyService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.gzhu.funai.entity.AdminApiKeyEntity; 5 | import com.gzhu.funai.enums.ApiType; 6 | 7 | import java.util.List; 8 | 9 | 10 | /** 11 | * @Author: huangpenglong 12 | * @Date: 2023/4/10 22:58 13 | */ 14 | public interface AdminApiKeyService extends IService { 15 | 16 | /** 17 | * 根据枚举类ApiType获取apikey列表 18 | * @param apiTypes 19 | * @return 20 | */ 21 | List getListByType(ApiType apiTypes); 22 | 23 | /** 24 | * 判断当前类型的apikey是否在库内 25 | * @param apiTypes 26 | * @param apiKey 27 | * @return 28 | */ 29 | boolean contains(ApiType apiTypes, String apiKey); 30 | 31 | /** 32 | * 根据apikey的类型,使用轮询算法获取一个apiKey 33 | * @param apiTypes 34 | * @return 35 | */ 36 | String roundRobinGetByType(ApiType apiTypes); 37 | 38 | 39 | /** 40 | * 刷新缓存 41 | */ 42 | void load(); 43 | 44 | /** 45 | * 根据apikey的类型,获取优先级最高的apikey 46 | * @param apiTypes 47 | * @return 48 | */ 49 | String getBestByType(ApiType apiTypes); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/ChatService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.gzhu.funai.api.openai.req.ChatGPTReq; 4 | import com.gzhu.funai.api.openai.resp.ChatGPTResp; 5 | import com.gzhu.funai.api.openai.resp.CreditGrantsResp; 6 | import com.gzhu.funai.enums.SessionType; 7 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 8 | 9 | /** 10 | * @Author: huangpenglong / oujiajun 11 | * @Date: 2023/3/13 17:28 12 | */ 13 | public interface ChatService { 14 | 15 | /** 16 | * 单轮聊天-普通输出 17 | * @param userId 18 | * @param chatGPTReq 19 | * @param apiKey 20 | * @return 21 | */ 22 | ChatGPTResp oneShotChat(String userId, ChatGPTReq chatGPTReq, String apiKey); 23 | 24 | /** 25 | * 单轮聊天-流式输出 26 | * @param userId 27 | * @param chatGPTReq 28 | * @param apiKey 29 | * @param sseEmitter 30 | */ 31 | void streamOneShotChat(String userId, ChatGPTReq chatGPTReq, String apiKey, SseEmitter sseEmitter); 32 | 33 | /** 34 | * 多轮聊天-普通输出 35 | * @param userId 36 | * @param sessionId 37 | * @param chatGPTReq 38 | * @param message 39 | * @param apiKey 40 | * @param sessionType 41 | * @return 42 | */ 43 | ChatGPTResp sessionChat(String userId, Integer sessionId, ChatGPTReq chatGPTReq, 44 | String message, String apiKey, SessionType sessionType); 45 | 46 | /** 47 | * 单轮聊天-流式输出 48 | * @param userId 49 | * @param sessionId 50 | * @param chatGPTReq 51 | * @param message 52 | * @param apiKey 53 | * @param sseEmitter 54 | * @param sessionType 55 | */ 56 | void streamSessionChat(String userId, Integer sessionId, ChatGPTReq chatGPTReq, 57 | String message, String apiKey, SseEmitter sseEmitter, SessionType sessionType); 58 | 59 | /** 60 | * 清除用户所有聊天会话的缓存 61 | * @param userId 62 | */ 63 | void clearUserCache(String userId); 64 | 65 | /** 66 | * 手动刷新缓存 67 | * @param sessionId 68 | */ 69 | void refreshWindowRecordCache(Integer sessionId); 70 | 71 | /** 72 | * 获取API-Key的额度 73 | * @param apiKey 74 | * @return 75 | */ 76 | CreditGrantsResp creditGrants(String apiKey); 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/FileChatService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.gzhu.funai.api.openai.req.ChatGPTReq; 4 | import com.gzhu.funai.api.openai.resp.ChatGPTResp; 5 | import io.milvus.grpc.MutationResult; 6 | import io.milvus.param.R; 7 | import org.springframework.web.multipart.MultipartFile; 8 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 9 | 10 | /** 11 | * @author zxw 12 | * @Desriiption: 文件对话业务 13 | */ 14 | public interface FileChatService { 15 | 16 | /** 17 | * 上传文件,并对文件进行处理后存入向量库,生成总结 18 | * @param file 19 | * @param userId 20 | * @param apiKey 21 | * @param chatGPTReq 22 | * @param usePinecone 是否使用pinecone向量库,如果是false则使用milvus向量库 23 | * @return 24 | */ 25 | String uploadFile(MultipartFile file, String userId, String apiKey, ChatGPTReq chatGPTReq, boolean usePinecone); 26 | 27 | /** 28 | * 与文件进行对话 29 | * @param userId 30 | * @param sessionId 31 | * @param message 32 | * @param apiKey 33 | * @param chatGPTReq 34 | * @param usePinecone 35 | * @return 36 | */ 37 | ChatGPTResp chatWithFile(String userId, Integer sessionId, String message, String apiKey, ChatGPTReq chatGPTReq, boolean usePinecone); 38 | 39 | /** 40 | * 删除向量库的索引,不可恢复 41 | * @param userId 42 | * @param sessionId 43 | * @param usePinecone 44 | * @return 45 | */ 46 | boolean dropCollection(String userId, Integer sessionId, boolean usePinecone); 47 | 48 | /** 49 | * 与文件进行对话,流式返回 50 | * @param userId 51 | * @param sessionId 52 | * @param message 53 | * @param apiKey 54 | * @param chatGPTReq 55 | * @param sseEmitter 56 | * @param usePinecone 57 | */ 58 | void streamChatWithFile(String userId, Integer sessionId, String message, String apiKey, ChatGPTReq chatGPTReq, SseEmitter sseEmitter, boolean usePinecone); 59 | 60 | /** 61 | * 手动刷新会话窗口缓存记录 62 | * @param sessionId 63 | */ 64 | void refreshWindowRecordCache(Integer sessionId); 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/FileService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import javax.servlet.http.HttpServletResponse; 4 | import java.util.List; 5 | 6 | /** 7 | * @Author: huangpenglong 8 | * @Date: 2023/4/7 21:06 9 | */ 10 | public interface FileService { 11 | 12 | /** 13 | * 将内容导出成CSV格式的文件,放入响应体中 14 | * 15 | * @param titleRow 16 | * @param contentList 17 | * @param response 18 | * @return 19 | */ 20 | boolean exportCsv(String[] titleRow, List contentList, HttpServletResponse response); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/PromptService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.gzhu.funai.dto.PromptQueryRequest; 6 | import com.gzhu.funai.entity.PromptEntity; 7 | import com.gzhu.funai.enums.PromptType; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @Author: huangpenglong 13 | * @Date: 2023/4/11 16:38 14 | */ 15 | public interface PromptService extends IService { 16 | /** 17 | * 查询prompt列表 18 | * @param page 19 | * @param limit 20 | * @param hospitalQueryVo 21 | * @return 22 | */ 23 | IPage list(int page, int limit, PromptQueryRequest hospitalQueryVo); 24 | 25 | /** 26 | * 根据 `主题` 获取提示 27 | * 实现:缓存 28 | * @param topic 29 | * @return 30 | */ 31 | String getByTopic(String topic); 32 | 33 | /** 34 | * 根据枚举类`PromptType`获取提示列表 35 | * @param promptType 36 | * @return 37 | */ 38 | List getByType(PromptType promptType); 39 | 40 | /** 41 | * 加载缓存 42 | */ 43 | void load(); 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/SessionChatRecordService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.gzhu.funai.entity.SessionChatRecordEntity; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @Author: huangpenglong 10 | * @Date: 2023/3/17 22:11 11 | */ 12 | public interface SessionChatRecordService extends IService { 13 | /** 14 | * 获取聊天记录 15 | * @param sessionId 16 | * @return 17 | */ 18 | List getSessionRecord(Integer sessionId); 19 | 20 | /** 21 | * 清空聊天记录 22 | * @param sessionId 23 | */ 24 | void truncateSessionChatRecord(Integer sessionId); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/UserAdvicesService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.gzhu.funai.entity.UserAdvicesEntity; 5 | 6 | 7 | public interface UserAdvicesService extends IService { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/UserApiKeyService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.gzhu.funai.entity.UserApiKeyEntity; 5 | import com.gzhu.funai.enums.ApiType; 6 | 7 | 8 | /** 9 | * @Author: huangpenglong 10 | * @Date: 2023/4/20 19:46 11 | */ 12 | public interface UserApiKeyService extends IService { 13 | 14 | /** 15 | * 获取用户的ApiKey 16 | * @param userId 17 | * @param type 18 | * @return 19 | */ 20 | UserApiKeyEntity getByUserIdAndType(String userId, ApiType type); 21 | 22 | /** 23 | * 根据唯一键 userId 和 type来决定插入还是更新数据 24 | * @param userApiKeyEntity 25 | */ 26 | void insertOrUpdate(UserApiKeyEntity userApiKeyEntity); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/UserLoginRecordService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.gzhu.funai.entity.UserLoginRecord; 5 | 6 | /** 7 | * @Author: huangpenglong 8 | * @Date: 2023/4/26 17:10 9 | */ 10 | public interface UserLoginRecordService extends IService { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.core.metadata.IPage; 4 | import com.gzhu.funai.dto.UserListRequest; 5 | import com.gzhu.funai.dto.UserRegisterRequest; 6 | import com.gzhu.funai.dto.UserResetPasswordRequest; 7 | import com.gzhu.funai.entity.UserEntity; 8 | import com.baomidou.mybatisplus.extension.service.IService; 9 | import com.gzhu.funai.enums.LoginType; 10 | import com.gzhu.funai.session.LoginSession; 11 | 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | *@Author :wuxiaodong 17 | *@Date: 2023/3/16 16:48 18 | *@Description: 19 | */ 20 | public interface UserService extends IService{ 21 | /** 22 | * 注册 23 | * @param userRegisterVo 24 | * @return 25 | */ 26 | UserEntity register(UserRegisterRequest userRegisterVo); 27 | 28 | /** 29 | * 登录 30 | * @param loginSession 31 | * @return 32 | */ 33 | Map login(LoginSession loginSession); 34 | 35 | /** 36 | * 重新设置密码 37 | * @param request 38 | * @return 39 | */ 40 | boolean resetPwd(UserResetPasswordRequest request); 41 | 42 | IPage getUserListByCondition(UserListRequest userListRequset, Long limit , Long page); 43 | 44 | boolean lock(String userId, Byte status); 45 | 46 | /** 47 | * 修改用户等级 48 | * 修改后用户当日的聊天 和 文件上传次数限制清空 49 | * @param userId 50 | * @param level 51 | * @return 52 | */ 53 | boolean changeLevel(String userId, int level); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/UserSessionService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.gzhu.funai.entity.UserSessionEntity; 5 | import com.gzhu.funai.enums.SessionType; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/3/17 15:33 12 | */ 13 | public interface UserSessionService extends IService{ 14 | 15 | /** 16 | * 新增会话 17 | * @param userId 18 | * @param sessionName 19 | * @param sessionType 20 | * @return 21 | */ 22 | UserSessionEntity save(String userId, String sessionName, SessionType sessionType); 23 | 24 | /** 25 | * 查询会话 26 | * @param userId 27 | * @param sessionType 28 | * @return 29 | */ 30 | List getSessionList(String userId, SessionType sessionType); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/helper/ExpertChatHelper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service.helper; 2 | 3 | import com.gzhu.funai.api.openai.ChatGPTApi; 4 | import com.gzhu.funai.api.openai.enums.Role; 5 | import com.gzhu.funai.entity.SessionChatRecordEntity; 6 | import com.gzhu.funai.entity.UserSessionEntity; 7 | import com.gzhu.funai.service.PromptService; 8 | import com.gzhu.funai.service.SessionChatRecordService; 9 | import com.gzhu.funai.service.UserSessionService; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.Resource; 13 | 14 | /** 15 | * @Author :wuxiaodong 16 | * @Date: 2023/4/24 19:18 17 | * @Description:会话名拆解、prompt构建、持久化聊天记录这些逻辑 18 | */ 19 | @Component 20 | public class ExpertChatHelper { 21 | @Resource 22 | SessionChatRecordService sessionChatRecordService; 23 | @Resource 24 | PromptService promptService; 25 | @Resource 26 | UserSessionService userSessionService; 27 | 28 | private ExpertChatHelper() {} 29 | public boolean handleSessionSystemRecord(UserSessionEntity userSessionEntity) { 30 | String[] split = userSessionEntity.getSessionName().split(":"); 31 | String prompt = promptService.getByTopic(split[split.length - 2]); 32 | SessionChatRecordEntity sessionChatRecordEntity = new SessionChatRecordEntity( 33 | userSessionEntity.getSessionId(), Role.SYSTEM.name, prompt, ChatGPTApi.getMessageTokenNum(prompt)); 34 | return sessionChatRecordService.save(sessionChatRecordEntity); 35 | } 36 | 37 | public String getExpertChatLanguage(Integer sessionId) { 38 | UserSessionEntity userSessionEntity = userSessionService.getById(sessionId); 39 | String[] split = userSessionEntity.getSessionName().split(":"); 40 | return split[split.length - 1]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/impl/FileServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service.impl; 2 | 3 | import com.gzhu.funai.service.FileService; 4 | import com.opencsv.CSVWriter; 5 | import com.opencsv.CSVWriterBuilder; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.util.CollectionUtils; 9 | 10 | import javax.servlet.ServletOutputStream; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.io.OutputStreamWriter; 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.List; 16 | 17 | /** 18 | * @Author: huangpenglong 19 | * @Date: 2023/4/7 21:10 20 | */ 21 | 22 | @Service 23 | @Slf4j 24 | public class FileServiceImpl implements FileService { 25 | @Override 26 | public boolean exportCsv(String[] titleRow, List contentList, HttpServletResponse response) { 27 | if(titleRow.length == 0 || CollectionUtils.isEmpty(contentList)){ 28 | return false; 29 | } 30 | 31 | try (ServletOutputStream out = response.getOutputStream()) { 32 | OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8); 33 | CSVWriter csvWriter = (CSVWriter) new CSVWriterBuilder(writer).build(); 34 | // 写入标题行 35 | csvWriter.writeNext(titleRow, false); 36 | // 写入数据行 37 | csvWriter.writeAll(contentList, false); 38 | 39 | csvWriter.flush(); 40 | csvWriter.close(); 41 | log.info("聊天记录导出成功!"); 42 | return true; 43 | } 44 | catch (IOException e) { 45 | log.error("解析excel异常!"); 46 | } 47 | 48 | return false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/impl/PromptServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service.impl; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 5 | import com.baomidou.mybatisplus.core.metadata.IPage; 6 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 7 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 8 | import com.google.common.collect.ImmutableMap; 9 | import com.gzhu.funai.dto.PromptQueryRequest; 10 | import com.gzhu.funai.entity.PromptEntity; 11 | import com.gzhu.funai.enums.PromptType; 12 | import com.gzhu.funai.global.constant.TimeInterval; 13 | import com.gzhu.funai.mapper.PromptMapper; 14 | import com.gzhu.funai.service.PromptService; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.scheduling.annotation.Scheduled; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.util.StringUtils; 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.stream.Collectors; 23 | 24 | /** 25 | * @Author: huangpenglong 26 | * @Date: 2023/4/11 16:38 27 | */ 28 | 29 | @Service 30 | @Slf4j 31 | public class PromptServiceImpl extends ServiceImpl implements PromptService{ 32 | 33 | private Map topicCache = ImmutableMap.of(); 34 | private Map> typeCache = ImmutableMap.of(); 35 | 36 | @Override 37 | public IPage list(int pageNum, int limit, PromptQueryRequest req) { 38 | 39 | Page page = new Page<>(pageNum, limit); 40 | 41 | QueryWrapper wrapper = new QueryWrapper<>(); 42 | if(!StringUtils.isEmpty(req.getContent())){ 43 | wrapper.and(w -> w.like("content", req.getContent()).or().like("topic", req.getContent())); 44 | } 45 | if(req.getType() != null){ 46 | wrapper.and(w -> w.eq("type", req.getType())); 47 | } 48 | if(req.getTarget() != null){ 49 | wrapper.and(w -> w.eq("target", req.getTarget())); 50 | } 51 | wrapper.orderByDesc("update_time"); 52 | 53 | return baseMapper.selectPage(page, wrapper); 54 | } 55 | 56 | @Override 57 | public String getByTopic(String topic) { 58 | PromptEntity promptEntity = this.topicCache.get(topic); 59 | return promptEntity == null ? null : promptEntity.getContent(); 60 | } 61 | 62 | @Override 63 | public List getByType(PromptType promptType) { 64 | return this.typeCache.get(promptType.typeNo); 65 | } 66 | 67 | @Scheduled(initialDelay = TimeInterval.ZERO, fixedRate = TimeInterval.ONE_HOUR) 68 | @Override 69 | public void load() { 70 | // 加载主键为topic的缓存 71 | this.topicCache = ImmutableMap.copyOf( 72 | baseMapper.selectList(null).stream() 73 | // 按照topic先分组 -> Map> 74 | .collect(Collectors.groupingBy(PromptEntity::getTopic)) 75 | // 对Map的value进行操作,取得第一个元素 76 | .entrySet().stream().collect( 77 | Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream().findFirst().orElse(null)))); 78 | 79 | // 加载主键为type的缓存 80 | this.typeCache = ImmutableMap.copyOf( 81 | baseMapper.selectList(null).stream() 82 | .collect(Collectors.groupingBy(PromptEntity::getType))); 83 | 84 | log.info("加载Prompt库缓存成功!, topicCache size:{}, typeCache size: {}", this.topicCache.size(), this.typeCache.size()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/impl/SessionChatRecordServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import com.gzhu.funai.entity.SessionChatRecordEntity; 6 | import com.gzhu.funai.mapper.SessionChatRecordMapper; 7 | import com.gzhu.funai.service.SessionChatRecordService; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @Author: huangpenglong 14 | * @Date: 2023/3/17 22:12 15 | */ 16 | @Service 17 | public class SessionChatRecordServiceImpl 18 | extends ServiceImpl 19 | implements SessionChatRecordService { 20 | 21 | @Override 22 | public List getSessionRecord(Integer sessionId) { 23 | return baseMapper.selectList(new QueryWrapper() 24 | .eq("session_id", sessionId) 25 | .orderByAsc("session_chat_id") 26 | ); 27 | } 28 | 29 | @Override 30 | public void truncateSessionChatRecord(Integer sessionId) { 31 | baseMapper.delete(new QueryWrapper().eq("session_id", sessionId)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/impl/UserAdvicesServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.gzhu.funai.entity.UserAdvicesEntity; 5 | import com.gzhu.funai.mapper.UserAdvicesMapper; 6 | import com.gzhu.funai.service.UserAdvicesService; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | @Slf4j 12 | public class UserAdvicesServiceImpl extends ServiceImpl 13 | implements UserAdvicesService { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/impl/UserApiKeyServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import com.gzhu.funai.entity.UserApiKeyEntity; 6 | import com.gzhu.funai.enums.ApiType; 7 | import com.gzhu.funai.mapper.UserApiKeyMapper; 8 | import com.gzhu.funai.service.UserApiKeyService; 9 | import org.springframework.stereotype.Service; 10 | 11 | import javax.annotation.Resource; 12 | 13 | /** 14 | * @Author: huangpenglong 15 | * @Date: 2023/4/20 19:47 16 | */ 17 | 18 | @Service 19 | public class UserApiKeyServiceImpl extends ServiceImpl implements UserApiKeyService { 20 | 21 | @Resource 22 | private UserApiKeyMapper userApiKeyMapper; 23 | 24 | @Override 25 | public UserApiKeyEntity getByUserIdAndType(String userId, ApiType type) { 26 | 27 | return baseMapper.selectOne(new QueryWrapper() 28 | .eq("user_id", userId) 29 | .eq("type", type.typeNo)); 30 | } 31 | 32 | @Override 33 | public void insertOrUpdate(UserApiKeyEntity userApiKeyEntity) { 34 | userApiKeyMapper.insertOrUpdate(userApiKeyEntity.getUserId(), userApiKeyEntity.getType(), userApiKeyEntity.getApikey()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/impl/UserLoginRecordServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.gzhu.funai.entity.UserLoginRecord; 5 | import com.gzhu.funai.mapper.UserLoginRecordMapper; 6 | import com.gzhu.funai.service.UserLoginRecordService; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * @Author: huangpenglong 12 | * @Date: 2023/4/26 17:10 13 | */ 14 | 15 | @Service 16 | @Slf4j 17 | public class UserLoginRecordServiceImpl 18 | extends ServiceImpl 19 | implements UserLoginRecordService { 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/service/impl/UserSessionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import com.gzhu.funai.entity.UserSessionEntity; 6 | import com.gzhu.funai.enums.SessionType; 7 | import com.gzhu.funai.mapper.UserSessionMapper; 8 | import com.gzhu.funai.service.UserSessionService; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @Author: huangpenglong 15 | * @Date: 2023/3/17 15:34 16 | */ 17 | 18 | @Service 19 | public class UserSessionServiceImpl extends ServiceImpl implements UserSessionService { 20 | 21 | @Override 22 | public UserSessionEntity save(String userId, String sessionName, SessionType sessionType) { 23 | UserSessionEntity userSession = new UserSessionEntity(userId, sessionName, sessionType.type); 24 | baseMapper.insert(userSession); 25 | return userSession; 26 | } 27 | 28 | @Override 29 | public List getSessionList(String userId, SessionType sessionType) { 30 | return baseMapper.selectList(new QueryWrapper() 31 | .eq("user_id", userId) 32 | .eq("type", sessionType.type)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/session/LoginSession.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.session; 2 | 3 | import com.gzhu.funai.enums.LoginType; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/4/30 20:52 12 | */ 13 | 14 | @Builder 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class LoginSession { 19 | 20 | private String loginAcct; 21 | 22 | private String password; 23 | 24 | private LoginType loginType; 25 | 26 | private String ip; 27 | 28 | private String socialUid; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/sse/OpenAIOneShotChatSSEListener.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.sse; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.gzhu.funai.api.openai.enums.OpenAiRespError; 5 | import com.gzhu.funai.api.openai.resp.ChatGPTResp; 6 | import com.gzhu.funai.redis.ChatRedisHelper; 7 | import com.gzhu.funai.utils.SpringUtil; 8 | import lombok.SneakyThrows; 9 | import lombok.extern.slf4j.Slf4j; 10 | import okhttp3.Response; 11 | import okhttp3.ResponseBody; 12 | import okhttp3.sse.EventSource; 13 | import okhttp3.sse.EventSourceListener; 14 | import org.springframework.util.StringUtils; 15 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 16 | 17 | import java.util.Objects; 18 | 19 | /** 20 | * @Author: huangpenglong 21 | * @Date: 2023/3/28 10:43 22 | */ 23 | 24 | @Slf4j 25 | public class OpenAIOneShotChatSSEListener extends EventSourceListener { 26 | 27 | private static final String DONE_SIGNAL = "[DONE]"; 28 | 29 | private String userId; 30 | 31 | private SseEmitter sseEmitter; 32 | public OpenAIOneShotChatSSEListener(SseEmitter sseEmitter, String userId) { 33 | this.sseEmitter = sseEmitter; 34 | this.userId = userId; 35 | } 36 | 37 | @SneakyThrows 38 | @Override 39 | public void onOpen(EventSource eventSource, Response response) { 40 | log.info("OpenAI建立sse连接..."); 41 | } 42 | 43 | @SneakyThrows 44 | @Override 45 | public void onEvent(EventSource eventSource, String id, String type, String data) { 46 | 47 | if (data.equals(DONE_SIGNAL)) { 48 | log.info("OpenAI返回数据结束"); 49 | sseEmitter.send(SseEmitter.event() 50 | .id(DONE_SIGNAL) 51 | .data(DONE_SIGNAL) 52 | .reconnectTime(3000)); 53 | return; 54 | } 55 | log.info(data); 56 | 57 | ChatGPTResp resp = JSONUtil.toBean(data, ChatGPTResp.class); 58 | if(StringUtils.isEmpty(resp)){ 59 | return; 60 | } 61 | String content = resp.getChoices().get(0).getDelta().getContent(); 62 | 63 | if(StringUtils.isEmpty(content)){ 64 | return; 65 | } 66 | content = content.replace(" ", "「`」"); 67 | content = content.replace("\n", "「·」"); 68 | content = content.replace("\t", "「~」"); 69 | sseEmitter.send(SseEmitter.event() 70 | .data(content) 71 | .reconnectTime(3000)); 72 | } 73 | 74 | @Override 75 | public void onClosed(EventSource eventSource) { 76 | log.info("OpenAI关闭sse连接..."); 77 | } 78 | 79 | @SneakyThrows 80 | @Override 81 | public void onFailure(EventSource eventSource, Throwable t, Response response) { 82 | if(Objects.isNull(response)){ 83 | log.error("OpenAI sse连接异常:{}", t); 84 | eventSource.cancel(); 85 | return; 86 | } 87 | 88 | ResponseBody body = response.body(); 89 | if(Objects.nonNull(body)){ 90 | OpenAiRespError openAiRespError = OpenAiRespError.get(response.code()); 91 | log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), openAiRespError.msg); 92 | sseEmitter.send(SseEmitter.event() 93 | .id("error") 94 | .data(openAiRespError.msg) 95 | .reconnectTime(3000)); 96 | } 97 | else { 98 | log.error("OpenAI sse连接异常data:{},异常:{}", response, t); 99 | } 100 | sseEmitter.send(SseEmitter.event() 101 | .id(DONE_SIGNAL) 102 | .data(DONE_SIGNAL) 103 | .reconnectTime(3000)); 104 | 105 | response.close(); 106 | eventSource.cancel(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/sse/OpenAISessionChatSSEListener.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.sse; 2 | 3 | import cn.hutool.json.JSONUtil; 4 | import com.google.common.collect.ImmutableList; 5 | import com.gzhu.funai.api.openai.ChatGPTApi; 6 | import com.gzhu.funai.api.openai.enums.OpenAiRespError; 7 | import com.gzhu.funai.api.openai.enums.Role; 8 | import com.gzhu.funai.api.openai.resp.ChatGPTResp; 9 | import com.gzhu.funai.entity.SessionChatRecordEntity; 10 | import com.gzhu.funai.enums.SessionType; 11 | import com.gzhu.funai.service.ChatService; 12 | import com.gzhu.funai.service.FileChatService; 13 | import com.gzhu.funai.service.SessionChatRecordService; 14 | import com.gzhu.funai.utils.MilvusClientUtil; 15 | import com.gzhu.funai.utils.SpringUtil; 16 | import lombok.SneakyThrows; 17 | import lombok.extern.slf4j.Slf4j; 18 | import okhttp3.Response; 19 | import okhttp3.ResponseBody; 20 | import okhttp3.sse.EventSource; 21 | import okhttp3.sse.EventSourceListener; 22 | import org.springframework.util.StringUtils; 23 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 24 | 25 | import java.io.IOException; 26 | import java.util.Objects; 27 | 28 | /** 29 | * @Author: huangpenglong 30 | * @Date: 2023/3/28 10:43 31 | */ 32 | 33 | @Slf4j 34 | public class OpenAISessionChatSSEListener extends EventSourceListener { 35 | 36 | private static final String DONE_SIGNAL = "[DONE]"; 37 | 38 | private SessionChatRecordService sessionChatRecordService; 39 | private ChatService chatService; 40 | private FileChatService fileChatService; 41 | 42 | private SseEmitter sseEmitter; 43 | private SessionChatRecordEntity askRecord; 44 | private StringBuilder respContent; 45 | private String collectionName; 46 | private SessionType sessionType; 47 | 48 | /** 49 | * 增加sessionType属性用于PDF对话释放连接 50 | */ 51 | public OpenAISessionChatSSEListener(SseEmitter sseEmitter, SessionChatRecordEntity askRecord, String collectionName, SessionType sessionType) { 52 | this.sseEmitter = sseEmitter; 53 | this.askRecord = askRecord; 54 | this.respContent = new StringBuilder(); 55 | this.collectionName = collectionName; 56 | this.sessionChatRecordService = SpringUtil.getBean("sessionChatRecordServiceImpl"); 57 | this.chatService = SpringUtil.getBean("chatServiceImpl"); 58 | this.fileChatService = SpringUtil.getBean("fileChatServiceImpl"); 59 | this.sessionType = sessionType; 60 | } 61 | 62 | @SneakyThrows 63 | @Override 64 | public void onOpen(EventSource eventSource, Response response) { 65 | log.info("OpenAI建立sse连接..."); 66 | } 67 | 68 | @SneakyThrows 69 | @Override 70 | public void onEvent(EventSource eventSource, String id, String type, String data) { 71 | if (data.equals(DONE_SIGNAL)) { 72 | log.info("OpenAI返回数据结束"); 73 | sseEmitter.send(SseEmitter.event() 74 | .id(DONE_SIGNAL) 75 | .data(DONE_SIGNAL) 76 | .reconnectTime(3000)); 77 | 78 | return; 79 | } 80 | 81 | ChatGPTResp resp = JSONUtil.toBean(data, ChatGPTResp.class); 82 | if(StringUtils.isEmpty(resp)){ 83 | return; 84 | } 85 | String content = resp.getChoices().get(0).getDelta().getContent(); 86 | 87 | if(StringUtils.isEmpty(content)){ 88 | return; 89 | } 90 | 91 | // 记录流输出结果,用于后续持久化 92 | this.respContent.append(content); 93 | 94 | // 对流输出进行格式化,传入Emitter通道与前端进行交互 95 | content = content.replace(" ", "「`」"); 96 | content = content.replace("\n", "「·」"); 97 | content = content.replace("\t", "「~」"); 98 | sseEmitter.send(SseEmitter.event() 99 | .data(content) 100 | .reconnectTime(3000)); 101 | } 102 | 103 | @Override 104 | public void onClosed(EventSource eventSource) { 105 | 106 | // 构造回复数据对象,持久化 107 | String respContentStr = this.respContent.toString(); 108 | SessionChatRecordEntity replyRecord = new SessionChatRecordEntity( 109 | this.askRecord.getSessionId(), Role.ASSISTANT.name, 110 | respContentStr, ChatGPTApi.getMessageTokenNum(respContentStr)); 111 | sessionChatRecordService.saveBatch(ImmutableList.of(this.askRecord, replyRecord)); 112 | 113 | // 刷新缓存 114 | chatService.refreshWindowRecordCache(this.askRecord.getSessionId()); 115 | 116 | if(sessionType.type.equals(SessionType.PDF_CHAT.type)) { 117 | fileChatService.refreshWindowRecordCache(this.askRecord.getSessionId()); 118 | } 119 | 120 | if(!StringUtils.isEmpty(collectionName)){ 121 | // 释放连接 122 | MilvusClientUtil.releaseCollection(collectionName); 123 | log.info("向量库连接释放成功"); 124 | } 125 | 126 | log.info("持久化应答数据成功!"); 127 | log.info("OpenAI关闭sse连接..."); 128 | } 129 | 130 | @SneakyThrows 131 | @Override 132 | public void onFailure(EventSource eventSource, Throwable t, Response response) { 133 | 134 | if(t != null){ 135 | if(t instanceof IOException){ 136 | log.error("网络错误!{}", t.getMessage()); 137 | } 138 | else{ 139 | log.error("服务错误!{}", t.getMessage()); 140 | } 141 | eventSource.cancel(); 142 | return ; 143 | } 144 | 145 | if(Objects.isNull(response)){ 146 | log.error("OpenAI sse连接异常:{}", t); 147 | eventSource.cancel(); 148 | return; 149 | } 150 | 151 | ResponseBody body = response.body(); 152 | if(Objects.nonNull(body)){ 153 | if(response.code() == 200){ 154 | eventSource.cancel(); 155 | return ; 156 | } 157 | OpenAiRespError openAiRespError = OpenAiRespError.get(response.code()); 158 | 159 | log.error("OpenAI sse连接异常data:{},异常:{}", body.string(), openAiRespError.msg); 160 | sseEmitter.send(SseEmitter.event() 161 | .data(openAiRespError.msg) 162 | .reconnectTime(3000)); 163 | } 164 | else { 165 | log.error("OpenAI sse连接异常data:{},异常:{}", response, t); 166 | } 167 | sseEmitter.send(SseEmitter.event() 168 | .id(DONE_SIGNAL) 169 | .data(DONE_SIGNAL) 170 | .reconnectTime(3000)); 171 | 172 | response.close(); 173 | eventSource.cancel(); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/AbstractUUIDGenerator.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import java.net.InetAddress; 4 | 5 | /** 6 | * @Author :wuxiaodong 7 | * @Date: 2023/4/2 23:01 8 | * @Description: 9 | */ 10 | abstract class AbstractUUIDGenerator { 11 | 12 | private static final int IP; 13 | static { 14 | int ipadd; 15 | try { 16 | ipadd = BytesHelper.toInt( InetAddress.getLocalHost().getAddress() ); 17 | } 18 | catch (Exception e) { 19 | ipadd = 0; 20 | } 21 | IP = ipadd; 22 | } 23 | 24 | private static short counter = (short) 0; 25 | private static final int JVM = (int) ( System.currentTimeMillis() >>> 8 ); 26 | 27 | AbstractUUIDGenerator() { 28 | } 29 | 30 | static int getJVM() { 31 | return JVM; 32 | } 33 | 34 | static short getCount() { 35 | synchronized(AbstractUUIDGenerator.class) { 36 | if ( counter < 0 ) { 37 | counter=0; 38 | } 39 | return counter++; 40 | } 41 | } 42 | 43 | static int getIP() { 44 | return IP; 45 | } 46 | 47 | static short getHiTime() { 48 | return (short) ( System.currentTimeMillis() >>> 32 ); 49 | } 50 | 51 | static int getLoTime() { 52 | return (int) System.currentTimeMillis(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/BytesHelper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | /** 4 | * @Author :wuxiaodong 5 | * @Date: 2023/4/2 23:05 6 | * @Description: 7 | */ 8 | public final class BytesHelper { 9 | 10 | private BytesHelper() { 11 | } 12 | 13 | public static int toInt(byte[] bytes) { 14 | int result = 0; 15 | for ( int i = 0; i < 4; i++ ) { 16 | result = ( result << 8 ) - Byte.MIN_VALUE + (int) bytes[i]; 17 | } 18 | return result; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/DateTimeFormatterUtil.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import java.time.format.DateTimeFormatter; 4 | 5 | /** 6 | * @Author: huangpenglong 7 | * @Date: 2023/4/26 13:36 8 | */ 9 | public class DateTimeFormatterUtil { 10 | private DateTimeFormatterUtil(){} 11 | 12 | public static final DateTimeFormatter DFT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import io.jsonwebtoken.*; 4 | import org.springframework.util.StringUtils; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | * @Author :wuxiaodong 10 | * @Date: 2022/9/13 16:53 11 | * @Description: 12 | */ 13 | public class JwtUtil { 14 | 15 | private JwtUtil(){ } 16 | 17 | // token过期时间 18 | private static final int TOKEN_EXPIRATION = 1000*60*60*3; 19 | // token签名密钥 20 | private static final String TOKEN_SIGN_KEY = "funai"; 21 | 22 | /** 23 | * 创建前端token 24 | */ 25 | public static String createToken(String userId, String userName, Integer userLevel) { 26 | return Jwts.builder() 27 | .setSubject("ChatGPT") 28 | .setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXPIRATION)) 29 | .claim("userId", userId) 30 | .claim("userName", userName) 31 | .claim("userLevel", userLevel) 32 | .signWith(SignatureAlgorithm.HS512, TOKEN_SIGN_KEY) 33 | .compressWith(CompressionCodecs.GZIP) 34 | .compact(); 35 | } 36 | 37 | /** 38 | * 获得token的userid信息 39 | */ 40 | public static String getUserId(String token) { 41 | if(StringUtils.isEmpty(token)) { 42 | return null; 43 | } 44 | Jws claimsJws = Jwts.parser().setSigningKey(TOKEN_SIGN_KEY).parseClaimsJws(token); 45 | Claims claims = claimsJws.getBody(); 46 | return (String)claims.get("userId"); 47 | } 48 | 49 | /** 50 | * 获得token的username信息 51 | */ 52 | public static String getUserName(String token) { 53 | if(StringUtils.isEmpty(token)) { 54 | return ""; 55 | } 56 | Jws claimsJws 57 | = Jwts.parser().setSigningKey(TOKEN_SIGN_KEY).parseClaimsJws(token); 58 | Claims claims = claimsJws.getBody(); 59 | return (String)claims.get("userName"); 60 | } 61 | 62 | /** 63 | * 获得token的用户级别信息 64 | */ 65 | public static Integer getUserLevel(String token) { 66 | if(StringUtils.isEmpty(token)) { 67 | return null; 68 | } 69 | Jws claimsJws 70 | = Jwts.parser().setSigningKey(TOKEN_SIGN_KEY).parseClaimsJws(token); 71 | Claims claims = claimsJws.getBody(); 72 | return Integer.valueOf(String.valueOf(claims.get("userLevel"))); 73 | } 74 | 75 | /** 76 | * 验证JWT token是否过期 77 | */ 78 | public static boolean isTokenExpired(String token) { 79 | try { 80 | // 创建一个JWT解析器 81 | JwtParser parser = Jwts.parser().setSigningKey(TOKEN_SIGN_KEY); 82 | // 解析JWT token 83 | Jws jws = parser.parseClaimsJws(token); 84 | // 从JWT token中获取到过期时间 85 | Long expiration = jws.getBody().getExpiration().getTime(); 86 | // 获取当前时间 87 | Long now = System.currentTimeMillis(); 88 | // 判断过期时间是否已经过期 89 | return expiration < now; 90 | } catch (Exception e) { 91 | // 解析失败或者token已经过期,都会抛出异常 92 | return true; 93 | } 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/OkHttpClientUtil.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import okhttp3.ConnectionPool; 4 | import okhttp3.OkHttpClient; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * @Author: huangpenglong 10 | * @Date: 2023/4/8 10:00 11 | */ 12 | public class OkHttpClientUtil { 13 | 14 | private OkHttpClientUtil(){} 15 | 16 | private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder() 17 | .connectTimeout(1, TimeUnit.MINUTES) 18 | .writeTimeout(1, TimeUnit.MINUTES) 19 | .readTimeout(1, TimeUnit.MINUTES) 20 | .connectionPool(new ConnectionPool(50, 1L, TimeUnit.MINUTES)).build(); 21 | 22 | public static OkHttpClient getClient(){ 23 | return OK_HTTP_CLIENT; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/PasswordEncoderUtil.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 4 | 5 | /** 6 | * @Author :wuxiaodong 7 | * @Date: 2023/3/30 13:26 8 | * @Description:用户密码加密工具类 9 | */ 10 | public class PasswordEncoderUtil { 11 | public static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); 12 | 13 | private PasswordEncoderUtil() { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/RecursiveCharacterTextSplitter.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * @author zxw 9 | * @Desriiption: 递归分词器 10 | */ 11 | public class RecursiveCharacterTextSplitter { 12 | private List separators; 13 | private int chunkSize = 500; 14 | private int chunkOverlap = 50; 15 | 16 | // 构造函数,接受分隔符列表、块大小和块重叠作为参数 17 | public RecursiveCharacterTextSplitter(List separators, int chunkSize, int chunkOverlap) { 18 | // 如果分隔符列表为null,则使用默认值 19 | if (separators == null) { 20 | this.separators = Arrays.asList("\n\n", "\n", " ", ""); 21 | } else { 22 | this.separators = separators; 23 | } 24 | this.chunkSize = chunkSize; 25 | this.chunkOverlap = chunkOverlap; 26 | } 27 | 28 | // 将文本分割成块的方法 29 | public List splitText(String text) { 30 | // 声明一个空的字符串列表,用于存储最终的文本块 31 | List finalChunks = new ArrayList<>(); 32 | String separator = separators.get(separators.size() - 1); 33 | 34 | // 循环遍历分隔符列表,找到可以在文本中找到的最合适的分隔符 35 | for (String s : separators) { 36 | if (text.contains(s) || s.isEmpty()) { 37 | separator = s; 38 | break; 39 | } 40 | } 41 | 42 | List splits = Arrays.asList(text.split(separator)); 43 | 44 | // 声明一个空的字符串列表,用于存储长度小于块大小的子字符串 45 | List goodSplits = new ArrayList<>(); 46 | // 循环遍历子字符串列表,将较短的子字符串添加到goodSplits列表中,将较长的子字符串递归地传递给splitText方法 47 | for (String s : splits) { 48 | if (s.length() < chunkSize) { 49 | goodSplits.add(s); 50 | } else { 51 | if (!goodSplits.isEmpty()) { 52 | // 将goodSplits列表中的子字符串合并为一个文本块,并将其添加到最终的文本块列表中 53 | List mergedText = mergeSplits(goodSplits, separator); 54 | finalChunks.addAll(mergedText); 55 | goodSplits.clear(); 56 | } 57 | // 递归地将较长的子字符串传递给splitText方法 58 | List otherInfo = splitText(s); 59 | finalChunks.addAll(otherInfo); 60 | } 61 | } 62 | 63 | if (!goodSplits.isEmpty()) { 64 | List mergedText = mergeSplits(goodSplits, separator); 65 | finalChunks.addAll(mergedText); 66 | } 67 | 68 | return finalChunks; 69 | } 70 | 71 | private List mergeSplits(List splits, String separator) { 72 | int separatorLen = separator.length(); 73 | 74 | List docs = new ArrayList<>(); 75 | List currentDoc = new ArrayList<>(); 76 | int total = 0; 77 | 78 | for (String d : splits) { 79 | int len = d.length(); 80 | if (total + len + (separatorLen > 0 && !currentDoc.isEmpty() ? separatorLen : 0) > chunkSize) { 81 | if (total > chunkSize) { 82 | System.out.println("Warning: Created a chunk of size " + total + ", which is longer than the specified " + chunkSize); 83 | } 84 | if (!currentDoc.isEmpty()) { 85 | String doc = joinDocs(currentDoc, separator); 86 | if (doc != null) { 87 | docs.add(doc); 88 | } 89 | // 通过移除currentDoc中的文档,将currentDoc的长度减小到指定的文档重叠长度chunkOverlap或更小, 结果存到下一个chunk的开始位置 90 | while (total > chunkOverlap || (total + len + (separatorLen > 0 && !currentDoc.isEmpty() ? separatorLen : 0) > chunkSize && total > 0)) { 91 | total -= currentDoc.get(0).length() + (separatorLen > 0 && currentDoc.size() > 1 ? separatorLen : 0); 92 | currentDoc.remove(0); 93 | } 94 | } 95 | } 96 | currentDoc.add(d); 97 | total += len + (separatorLen > 0 && currentDoc.size() > 1 ? separatorLen : 0); 98 | } 99 | 100 | String doc = joinDocs(currentDoc, separator); 101 | if (doc != null) { 102 | docs.add(doc); 103 | } 104 | 105 | return docs; 106 | } 107 | 108 | private String joinDocs(List docs, String separator) { 109 | if (docs.isEmpty()) { 110 | return null; 111 | } 112 | StringBuilder sb = new StringBuilder(); 113 | for (int i = 0; i < docs.size(); i++) { 114 | sb.append(docs.get(i)); 115 | if (i < docs.size() - 1) { 116 | sb.append(separator); 117 | } 118 | } 119 | return sb.toString(); 120 | } 121 | } -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/RequestWrapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import lombok.SneakyThrows; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.util.StreamUtils; 6 | 7 | import javax.servlet.ReadListener; 8 | import javax.servlet.ServletInputStream; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletRequestWrapper; 11 | import java.io.BufferedReader; 12 | import java.io.ByteArrayInputStream; 13 | import java.io.IOException; 14 | import java.io.InputStreamReader; 15 | import java.nio.charset.Charset; 16 | import java.nio.charset.StandardCharsets; 17 | 18 | /** 19 | * @Author: huangpenglong 20 | * @Date: 2023/4/26 18:11 21 | */ 22 | 23 | @Slf4j 24 | public class RequestWrapper extends HttpServletRequestWrapper { 25 | 26 | private byte[] requestBody=null; 27 | 28 | @SneakyThrows 29 | public RequestWrapper(HttpServletRequest request) { 30 | super(request); 31 | 32 | // 包含文件流,不打印body信息 33 | String body = ""; 34 | if(request.getContentType() != null && !request.getContentType().startsWith("multipart/form-data")){ 35 | body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); 36 | } 37 | 38 | String url = request.getRequestURI(); 39 | log.info("ip{}, 访问路径{} 方法入参: {}",request.getRemoteAddr(), url, body); 40 | 41 | requestBody=body.getBytes(Charset.defaultCharset()); 42 | 43 | } 44 | 45 | 46 | @Override 47 | public ServletInputStream getInputStream() throws IOException { 48 | final ByteArrayInputStream inputStream=new ByteArrayInputStream(requestBody); 49 | return new ServletInputStream() { 50 | @Override 51 | public boolean isFinished() { 52 | return false; 53 | } 54 | 55 | @Override 56 | public boolean isReady() { 57 | return false; 58 | } 59 | 60 | @Override 61 | public void setReadListener(ReadListener readListener) { 62 | 63 | } 64 | 65 | @Override 66 | public int read() throws IOException { 67 | return inputStream.read(); 68 | } 69 | }; 70 | } 71 | 72 | @Override 73 | public BufferedReader getReader() throws IOException { 74 | return new BufferedReader(new InputStreamReader(this.getInputStream())); 75 | } 76 | 77 | public byte[] getRequestBody(){ 78 | return this.requestBody; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/ResponseWrapper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import javax.servlet.ServletOutputStream; 4 | import javax.servlet.WriteListener; 5 | import javax.servlet.http.HttpServletResponse; 6 | import javax.servlet.http.HttpServletResponseWrapper; 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | 10 | /** 11 | * @Author: huangpenglong 12 | * @Date: 2023/4/26 18:28 13 | */ 14 | public class ResponseWrapper extends HttpServletResponseWrapper { 15 | 16 | ByteArrayOutputStream output; 17 | 18 | public ResponseWrapper(HttpServletResponse response) { 19 | super(response); 20 | output = new ByteArrayOutputStream(); 21 | } 22 | 23 | @Override 24 | public ServletOutputStream getOutputStream() throws IOException { 25 | return new ServletOutputStream() { 26 | @Override 27 | public boolean isReady() { 28 | return false; 29 | } 30 | 31 | @Override 32 | public void setWriteListener(WriteListener listener) { 33 | 34 | } 35 | 36 | @Override 37 | public void write(int b) throws IOException { 38 | output.write(b); 39 | } 40 | 41 | @Override 42 | public void write(byte[] b) throws IOException { 43 | output.write(b); 44 | } 45 | 46 | @Override 47 | public void write(byte[] b, int off, int len) throws IOException { 48 | output.write(b,off,len); 49 | } 50 | }; 51 | } 52 | 53 | public byte[] getDataStream() { 54 | return output.toByteArray(); 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/ResultCode.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | 4 | /** 5 | * @Author: huangpenglong 6 | * @Date: 2023/3/9 15:44 7 | */ 8 | 9 | public enum ResultCode { 10 | /** 11 | * 12 | */ 13 | SUCCESS(20000, "成功"), 14 | ERROR(20001, "服务器内部错误"), 15 | EMPTY_PARAM(20003, "非空参数需要传递"), 16 | BAD_PARAM(20004, "参数错误"), 17 | 18 | USER_REGISTER_PARAMS_REPEAT(10001,"用户注册信息重复"), 19 | USER_NOT_LOGIN(10002,"用户未登录"), 20 | USER_NOT_EXIST(10003,"用户手机号未注册"), 21 | USER_LOCKED(10004,"账号已被锁定,联系管理员"), 22 | 23 | USER_CHAT_LIMITED(30001, "用户当日聊天功能已达到上限!"), 24 | USER_FILE_UPLOAD_LIMITED(30002, "用户当日文件上传功能已达到上限!"), 25 | 26 | 27 | ADMIN_OPERATE_FORBIDDEN(40001, "禁止操作管理员权限的功能!"), 28 | ADMIN_APIKEY_NULL(40002, "系统API-Key使用繁忙!请稍后再试~"), 29 | 30 | UPLOAD_FILE_ERROR(50001, "文件处理失败,请检查文件页数!"), 31 | 32 | 33 | 34 | 35 | ; 36 | 37 | public final int code; 38 | public final String msg; 39 | 40 | ResultCode(int code, String msg){ 41 | this.code = code; 42 | this.msg = msg; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/ReturnResult.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Data; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: huangpenglong 12 | * @Date: 2023/3/9 15:44 13 | */ 14 | 15 | @Data 16 | public class ReturnResult { 17 | @ApiModelProperty("响应码") 18 | private Integer code; 19 | 20 | @ApiModelProperty("返回信息") 21 | private String message; 22 | 23 | @ApiModelProperty("返回数据") 24 | private Map data = new HashMap(); 25 | 26 | //无参构造方法私有(使得外部只能通过调用ok() 和 error()来获取对象) 27 | private ReturnResult() { } 28 | 29 | 30 | //成功 静态方法 31 | public static ReturnResult ok(){ 32 | ReturnResult r = new ReturnResult(); 33 | r.setCode(ResultCode.SUCCESS.code); 34 | r.setMessage(ResultCode.SUCCESS.msg); 35 | return r; 36 | } 37 | //失败 静态方法 38 | public static ReturnResult error(){ 39 | ReturnResult r = new ReturnResult(); 40 | r.setCode(ResultCode.ERROR.code); 41 | r.setMessage(ResultCode.ERROR.msg); 42 | return r; 43 | } 44 | 45 | public ReturnResult codeAndMessage(ResultCode status){ 46 | this.setCode(status.code); 47 | this.setMessage(status.msg); 48 | return this; 49 | } 50 | 51 | // 链式编程 52 | public ReturnResult code(Integer code){ 53 | this.setCode(code); 54 | return this; 55 | } 56 | 57 | public ReturnResult message(String message){ 58 | this.setMessage(message); 59 | return this; 60 | } 61 | 62 | public ReturnResult data(String key,Object value){ 63 | this.data.put(key,value); 64 | return this; 65 | } 66 | 67 | public ReturnResult data(Map map){ 68 | this.setData(map); 69 | return this; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/SequentialUuidHexGenerator.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | /** 4 | * @Author :wuxiaodong 5 | * @Date: 2023/4/2 23:01 6 | * @Description:生成递增有序的uuid 7 | */ 8 | public class SequentialUuidHexGenerator extends AbstractUUIDGenerator{ 9 | 10 | private static final String sep = "_"; 11 | 12 | public static String generate() { 13 | return 14 | format( getJVM() ) + sep 15 | + format( getHiTime() ) + sep 16 | + format( getLoTime() ) + sep 17 | + format( getIP() ) + sep 18 | + format( getCount() ); 19 | } 20 | 21 | protected static String format(int intValue) { 22 | String formatted = Integer.toHexString( intValue ); 23 | StringBuilder buf = new StringBuilder( "00000000" ); 24 | buf.replace( 8 - formatted.length(), 8, formatted ); 25 | return buf.toString(); 26 | } 27 | 28 | protected static String format(short shortValue) { 29 | String formatted = Integer.toHexString( shortValue ); 30 | StringBuilder buf = new StringBuilder( "0000" ); 31 | buf.replace( 4 - formatted.length(), 4, formatted ); 32 | return buf.toString(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/SnowflakeIdGenerator.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | /** 4 | * 雪花算法用于生成用户ID 5 | * @Author: huangpenglong 6 | * @Date: 2023/3/15 23:44 7 | */ 8 | public class SnowflakeIdGenerator { 9 | // 数据中心ID,可以根据实际情况修改 10 | private static long datacenterId = 1L; 11 | // 机器标识ID,可以根据实际情况修改 12 | private static long machineId = 1L; 13 | // 序列号 14 | private static long sequence = 0L; 15 | // 上一次生成ID的时间戳 16 | private static long lastTimestamp = -1L; 17 | // 数据中心ID位数 18 | private static long datacenterIdBits = 5L; 19 | // 机器标识ID位数 20 | private static long machineIdBits = 5L; 21 | // 序列号位数 22 | private static long sequenceBits = 12L; 23 | // 时间戳左移位数 24 | private static long timestampLeftShift = sequenceBits + machineIdBits + datacenterIdBits; 25 | // 数据中心ID左移位数 26 | private static long datacenterIdLeftShift = sequenceBits + machineIdBits; 27 | // 机器标识ID左移位数 28 | private static long machineIdLeftShift = sequenceBits; 29 | // 最大序列号 30 | private static long maxSequence = ~(-1L << sequenceBits); 31 | 32 | public static long nextId() { 33 | long timestamp = System.currentTimeMillis(); 34 | if (timestamp < lastTimestamp) { 35 | throw new RuntimeException("Clock moved backwards, refusing to generate id"); 36 | } 37 | if (lastTimestamp == timestamp) { 38 | sequence = (sequence + 1) & maxSequence; 39 | if (sequence == 0) { 40 | timestamp = tilNextMillis(lastTimestamp); 41 | } 42 | } else { 43 | sequence = 0L; 44 | } 45 | lastTimestamp = timestamp; 46 | return ((timestamp - 1609459200000L) << timestampLeftShift) | 47 | (datacenterId << datacenterIdLeftShift) | 48 | (machineId << machineIdLeftShift) | 49 | sequence; 50 | } 51 | 52 | private static long tilNextMillis(long lastTimestamp) { 53 | long timestamp = System.currentTimeMillis(); 54 | while (timestamp <= lastTimestamp) { 55 | timestamp = System.currentTimeMillis(); 56 | } 57 | return timestamp; 58 | } 59 | 60 | 61 | private SnowflakeIdGenerator(){} 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/SpringUtil.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.context.ApplicationContextAware; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * Spring ApplicationContext 工具类 11 | * @Author: huangpenglong 12 | * @Date: 2023/3/31 17:44 13 | */ 14 | @SuppressWarnings("unchecked") 15 | @Component 16 | public class SpringUtil implements ApplicationContextAware { 17 | 18 | private static ApplicationContext applicationContext; 19 | 20 | @Override 21 | public void setApplicationContext(ApplicationContext applicationContext){ 22 | SpringUtil.applicationContext = applicationContext; 23 | } 24 | 25 | public static T getBean(String beanName) { 26 | if(applicationContext.containsBean(beanName)){ 27 | return (T) applicationContext.getBean(beanName); 28 | }else{ 29 | return null; 30 | } 31 | } 32 | 33 | public static String[] getAllBeanName(){ 34 | return applicationContext.getBeanDefinitionNames(); 35 | } 36 | 37 | public static Map getBeansOfType(Class baseType){ 38 | return applicationContext.getBeansOfType(baseType); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/gzhu/funai/utils/VerificationCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * @Author: huangpenglong 7 | * @Date: 2023/3/24 21:07 8 | */ 9 | 10 | public class VerificationCodeGenerator { 11 | private static Random random = new Random(); 12 | 13 | public static String generateCode(int digit) { 14 | StringBuilder code = new StringBuilder(); 15 | for (int i = 0; i < digit; i++) { 16 | code.append(random.nextInt(10)); 17 | } 18 | return code.toString(); 19 | } 20 | private VerificationCodeGenerator(){ 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 服务端口 2 | server.port=8080 3 | 4 | # 配置SSL证书,没有可以不填 TODO 5 | #server.ssl.key-store=classpath:xxxx.pfx 6 | #server.ssl.key-store-password=xxx 7 | #server.ssl.keyStoreType=xxx 8 | 9 | # 服务名 10 | spring.application.name=FunAI 11 | 12 | # 激活的配置文件 13 | spring.profiles.active=dev 14 | 15 | # 数据源 TODO 16 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 17 | spring.datasource.url=jdbc:mysql://xxx:3306/funai?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8 18 | spring.datasource.username = xxx 19 | spring.datasource.password = xxx 20 | 21 | #日期格式化 22 | spring.jackson.date-format=yyyy-MM-dd HH:mm:ss 23 | spring.jackson.time-zone=GMT+8 24 | 25 | # mybatis配置 26 | mybatis-plus.mapper-locations=classpath:com/gzhu/funai/mapper/xml/*.xml 27 | 28 | #redis TODO 29 | spring.redis.host = xxx 30 | spring.redis.port = 6379 31 | spring.redis.password = xxx 32 | 33 | #梦网云短信服务 TODO 34 | sms.apikey = xxx 35 | sms.url = xxx 36 | 37 | #接口防刷 38 | interfaceAccess.minute = 5L 39 | interfaceAccess.times =5L 40 | interfaceAccess.lockTime =10L 41 | 42 | #上传的PDF大小限制 43 | spring.servlet.multipart.max-file-size = 10MB 44 | spring.servlet.multipart.max-request-size = 10MB 45 | spring.servlet.multipart.resolve-lazily=true 46 | server.tomcat.max-swallow-size = -1 -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/TestChatGPT.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai; 2 | 3 | import com.gzhu.funai.api.openai.enums.Role; 4 | import com.gzhu.funai.api.openai.ChatGPTApi; 5 | import com.gzhu.funai.api.openai.req.ChatGPTReq; 6 | import com.gzhu.funai.api.openai.req.ContextMessage; 7 | import com.gzhu.funai.api.openai.resp.ChatGPTResp; 8 | import com.gzhu.funai.api.openai.resp.EmbeddingResp; 9 | import org.junit.Test; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import java.util.Arrays; 12 | 13 | /** 14 | * @Author: huangpenglong 15 | * @Date: 2023/3/9 11:15 16 | */ 17 | 18 | @SpringBootTest 19 | public class TestChatGPT { 20 | 21 | @Test 22 | public void testOneShot(){ 23 | 24 | ChatGPTResp resp = ChatGPTApi.sessionReq( 25 | ChatGPTReq.builder().messages(Arrays.asList(new ContextMessage(Role.USER.name, "你好"))).build(), 26 | ""); 27 | 28 | System.out.println(resp.getChoices().get(0).getMessage().toString()); 29 | } 30 | 31 | @Test 32 | public void testEmbed(){ 33 | 34 | EmbeddingResp embeddingResp = ChatGPTApi.embeddings(Arrays.asList("this is a", "this is bb"), ""); 35 | 36 | System.out.println("test embed"); 37 | } 38 | 39 | @Test 40 | public void testExpertToken() { 41 | int tokenNum = ChatGPTApi.getTokenNum("用英文回答"); 42 | System.out.println(tokenNum); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/api/openai/TestChatGPTApi.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.api.openai; 2 | 3 | import com.gzhu.funai.api.openai.resp.BillingUsage; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | 10 | /** 11 | * @Author: huangpenglong 12 | * @Date: 2023/4/3 19:49 13 | */ 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest 17 | public class TestChatGPTApi { 18 | 19 | 20 | private final String encodeExample1 = "😀Hello 😀my name 😀is Kevin😀😀😀."; 21 | private final String encodeExample2 = "😀你好呀😀,😀我是ChatGPT😀"; 22 | private final String encodeExample3 = "No, 人民军人后勤部 is the appellant (上诉人) in this case, not the respondent (被上诉人)."; 23 | private final String encodeExample4 = "两数之和是一道经典的算法问题,可以使用多种方法来实现。下面是Java实现两数之和的代码:\n" + 24 | " \n" + 25 | " 方法1:暴力枚举\n" + 26 | " \n" + 27 | " ```java\n" + 28 | " public int[] twoSum(int[] nums, int target) {\n" + 29 | " for (int i = 0; i < nums.length; i++) {\n" + 30 | " for (int j = i + 1; j < nums.length; j++) {\n" + 31 | " if (nums[i] + nums[j] == target) {\n" + 32 | " return new int[] {i, j};\n" + 33 | " }\n" + 34 | " }\n" + 35 | " }\n" + 36 | " return null;\n" + 37 | " }\n" + 38 | " ```\n" + 39 | " \n" + 40 | " 在这个实现中,我们使用了两层循环来枚举所有可能的组合。时间复杂度为O(n^2),空间复杂度为O(1)。\n" + 41 | " \n" + 42 | " 方法2:哈希表\n" + 43 | " \n" + 44 | " ```java\n" + 45 | " public int[] twoSum(int[] nums, int target) {\n" + 46 | " Map map = new HashMap<>();\n" + 47 | " for (int i = 0; i < nums.length; i++) {\n" + 48 | " int complement = target - nums[i];\n" + 49 | " if (map.containsKey(complement)) {\n" + 50 | " return new int[] {map.get(complement), i};\n" + 51 | " }\n" + 52 | " map.put(nums[i], i);\n" + 53 | " }\n" + 54 | " return null;\n" + 55 | " }\n" + 56 | " ```\n" + 57 | " \n" + 58 | " 在这个实现中,我们使用了哈希表来存储每个元素与它的下标。在遍历数组时,我们检查哈希表中是否存在与当前元素组合成目标值的元素。时间复杂度为O(n),空间复杂度为O(n)。"; 59 | 60 | @Test 61 | public void testGetTokenNum() { 62 | 63 | long begin = System.currentTimeMillis(); 64 | for(int i=0;i<1000;i++) { 65 | System.out.println(ChatGPTApi.getTokenNum(encodeExample4)); 66 | } 67 | System.out.println(System.currentTimeMillis() - begin); 68 | } 69 | 70 | @Test 71 | public void tstGetToken(){ 72 | String msg = "现在开始一个文字版的关于[色情]的游戏。我作为游戏的主角,而你来设计游戏,详细描述场景中所有的物品和生物。第一,每1轮游戏当你叙述完毕后需要给玩家4个选项[只能是A,B,C,D]选择接来剧情走向, 不要假设帮主角选择任何选项, 并且每给出四个选项之后等待主角选择。第二,如果主角和场景中的任何生物互动,请把互动过程详细描述出来。第三,请不要出现重复的场景和对话,整体故事要曲折离奇、高潮迭起、生动有趣。那么,现在让我们开始游戏吧![再次警告:不需要帮主角假设选择选项,不需要帮助主角解释选项,每一轮只需要回答剧情和四个选项]输出格式:\n" + 73 | " 剧情描述:xxxxx \n" + 74 | " A:xxx \n" + 75 | " B:xxx \n" + 76 | " C:xxx \n" + 77 | " D:xxx \n" + 78 | " [等待主角进行选择]"; 79 | ChatGPTApi.getMessageTokenNum(msg); 80 | } 81 | 82 | @Test 83 | public void testGetCreditGrants(){ 84 | String apikey = ""; 85 | ChatGPTApi.creditGrants(apikey); 86 | } 87 | 88 | @Test 89 | public void testGetBillingUsage(){ 90 | String apikey = ""; 91 | BillingUsage billingUsage = ChatGPTApi.getBillingUsage(apikey); 92 | System.out.println(billingUsage); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/redis/TestChatRedisHelper.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.redis; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | import javax.annotation.Resource; 9 | 10 | /** 11 | * @Author: huangpenglong 12 | * @Date: 2023/4/17 0:54 13 | */ 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest 17 | public class TestChatRedisHelper { 18 | 19 | @Resource 20 | private ChatRedisHelper chatRedisHelper; 21 | 22 | @Test 23 | public void testChatCount(){ 24 | String userId = ""; 25 | chatRedisHelper.incrDailyChatCount(userId, 1); 26 | int dailyChatLimit = chatRedisHelper.getDailyChatCount(userId); 27 | System.out.println(dailyChatLimit); 28 | } 29 | 30 | @Test 31 | public void testPDFUploadCount(){ 32 | String userId = ""; 33 | chatRedisHelper.incrDailyFileUploadCount(userId, 1); 34 | int dailyChatLimit = chatRedisHelper.getDailyFileUploadCount(userId); 35 | System.out.println(dailyChatLimit); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/service/TestAdminApiKeyService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.gzhu.funai.entity.AdminApiKeyEntity; 5 | import com.gzhu.funai.enums.ApiType; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.core.task.TaskExecutor; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | 12 | import javax.annotation.Resource; 13 | import java.util.List; 14 | 15 | /** 16 | * @Author: huangpenglong 17 | * @Date: 2023/4/10 23:02 18 | */ 19 | @RunWith(SpringRunner.class) 20 | @SpringBootTest 21 | public class TestAdminApiKeyService { 22 | 23 | @Resource 24 | private AdminApiKeyService adminApiKeyService; 25 | 26 | @Resource 27 | private TaskExecutor queueThreadPool; 28 | 29 | @Test 30 | public void testGetBestByType(){ 31 | 32 | for(int i=0;i<10;i++) { 33 | String s = adminApiKeyService.getBestByType(ApiType.OPENAI); 34 | System.out.println(s); 35 | } 36 | } 37 | 38 | @Test 39 | public void testRoundRobinGetByType(){ 40 | 41 | for(int i=0;i<10;i++) { 42 | String s = adminApiKeyService.roundRobinGetByType(ApiType.OPENAI); 43 | System.out.println(s); 44 | } 45 | } 46 | 47 | @Test 48 | public void testList(){ 49 | List list = adminApiKeyService.list(null); 50 | for(AdminApiKeyEntity item: list){ 51 | System.out.println(item); 52 | } 53 | } 54 | 55 | @Test 56 | public void testAdd(){ 57 | AdminApiKeyEntity build = AdminApiKeyEntity.builder().name("123").build(); 58 | adminApiKeyService.save(build); 59 | } 60 | 61 | @Test 62 | public void testDelete(){ 63 | adminApiKeyService.remove(new QueryWrapper().eq("name", "123")); 64 | } 65 | 66 | @Test 67 | public void load(){ 68 | adminApiKeyService.load(); 69 | 70 | String bestByType = adminApiKeyService.getBestByType(ApiType.OPENAI); 71 | System.out.println(bestByType); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/service/TestChatService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | 4 | import com.google.common.collect.ImmutableList; 5 | import com.gzhu.funai.api.openai.constant.OpenAIConst; 6 | import com.gzhu.funai.api.openai.enums.Role; 7 | import com.gzhu.funai.api.openai.req.ChatGPTReq; 8 | import com.gzhu.funai.api.openai.req.ContextMessage; 9 | import com.gzhu.funai.api.openai.resp.ChatGPTResp; 10 | import com.gzhu.funai.api.openai.resp.CreditGrantsResp; 11 | 12 | import com.gzhu.funai.enums.ApiType; 13 | import com.gzhu.funai.enums.SessionType; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | import org.springframework.core.task.TaskExecutor; 19 | import org.springframework.test.context.junit4.SpringRunner; 20 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 21 | 22 | import javax.annotation.Resource; 23 | import java.util.concurrent.CountDownLatch; 24 | 25 | /** 26 | * @Author: huangpenglong 27 | * @Date: 2023/3/17 23:22 28 | */ 29 | @RunWith(SpringRunner.class) 30 | @SpringBootTest 31 | public class TestChatService { 32 | @Autowired 33 | private ChatService chatService; 34 | 35 | @Autowired 36 | private TaskExecutor queueThreadPool; 37 | 38 | @Resource 39 | private AdminApiKeyService adminApiKeyService; 40 | 41 | @Test 42 | public void chatOneShot(){ 43 | 44 | System.out.println("正在加载apiKey..."); 45 | adminApiKeyService.load(); 46 | System.out.println("加载apiKey成功"); 47 | ChatGPTReq chatGPTReq = ChatGPTReq.builder() 48 | .messages(ImmutableList.of(new ContextMessage(Role.USER.name, "你好"))) 49 | .model(OpenAIConst.MODEL_NAME_CHATGPT_3_5) 50 | .max_tokens(1000) 51 | .build(); 52 | ChatGPTResp resp = chatService.oneShotChat("", chatGPTReq, 53 | adminApiKeyService.roundRobinGetByType(ApiType.OPENAI)); 54 | System.out.println(resp.getMessage()); 55 | } 56 | 57 | @Test 58 | public void chatSession(){ 59 | adminApiKeyService.load(); 60 | ChatGPTReq chatGPTReq = ChatGPTReq.builder().build(); 61 | ChatGPTResp resp = chatService.sessionChat( 62 | "", 1, chatGPTReq,"我刚刚问了什么", 63 | adminApiKeyService.roundRobinGetByType(ApiType.OPENAI), SessionType.NORMAL_CHAT); 64 | System.out.println(resp.getMessage()); 65 | } 66 | 67 | /** 68 | * 改方法已废弃 69 | */ 70 | @Test 71 | public void testCreditGrants(){ 72 | adminApiKeyService.load(); 73 | CreditGrantsResp openAiCreditGrantsResp = chatService.creditGrants(adminApiKeyService.roundRobinGetByType(ApiType.OPENAI)); 74 | System.out.println(openAiCreditGrantsResp); 75 | } 76 | 77 | @Test 78 | public void testStreamSessionReq(){ 79 | adminApiKeyService.load(); 80 | String msg = "你好"; 81 | ChatGPTReq chatGPTReq = ChatGPTReq.builder() 82 | .messages(ImmutableList.of(new ContextMessage(Role.USER.name, msg))) 83 | .stream(true) 84 | .build(); 85 | 86 | SseEmitter sseEmitter = new SseEmitter(0L); 87 | chatService.streamSessionChat( 88 | "", 92, chatGPTReq,msg, 89 | adminApiKeyService.roundRobinGetByType(ApiType.OPENAI), sseEmitter, SessionType.NORMAL_CHAT); 90 | 91 | CountDownLatch countDownLatch = new CountDownLatch(1); 92 | try{ 93 | countDownLatch.await(); 94 | } 95 | catch (InterruptedException e) { 96 | e.printStackTrace(); 97 | } 98 | } 99 | 100 | @Test 101 | public void testStreamOneShotReq(){ 102 | adminApiKeyService.load(); 103 | String msg = "你好呀"; 104 | ChatGPTReq chatGPTReq = ChatGPTReq.builder() 105 | .messages(ImmutableList.of(new ContextMessage(Role.USER.name, msg))) 106 | .stream(true) 107 | .build(); 108 | 109 | SseEmitter sseEmitter = new SseEmitter(0L); 110 | chatService.streamOneShotChat( 111 | "", chatGPTReq, 112 | adminApiKeyService.roundRobinGetByType(ApiType.OPENAI), sseEmitter); 113 | 114 | CountDownLatch countDownLatch = new CountDownLatch(1); 115 | try { 116 | countDownLatch.await(); 117 | } catch (InterruptedException e) { 118 | e.printStackTrace(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/service/TestFileChatService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.gzhu.funai.api.openai.constant.OpenAIConst; 4 | import com.gzhu.funai.api.openai.req.ChatGPTReq; 5 | import com.gzhu.funai.api.openai.resp.ChatGPTResp; 6 | import com.gzhu.funai.enums.ApiType; 7 | import io.milvus.grpc.MutationResult; 8 | import io.milvus.param.R; 9 | import org.apache.commons.io.IOUtils; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.mock.web.MockMultipartFile; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.web.multipart.MultipartFile; 17 | 18 | import javax.annotation.Resource; 19 | import java.io.File; 20 | import java.io.FileInputStream; 21 | import java.io.FileNotFoundException; 22 | import java.io.IOException; 23 | 24 | /** 25 | * @author zxw 26 | * @Desriiption: 27 | */ 28 | @RunWith(SpringRunner.class) 29 | @SpringBootTest 30 | public class TestFileChatService { 31 | 32 | @Autowired 33 | private FileChatService fileChatService; 34 | @Resource 35 | private AdminApiKeyService adminApiKeyService; 36 | @Resource 37 | private PromptService promptService; 38 | 39 | @Test 40 | public void uploadFile(){ 41 | adminApiKeyService.load(); 42 | promptService.load(); 43 | //生成File文件 44 | File file = new File("D:\\!!笔记\\000学习\\6-专业前沿学习\\003-人工智能\\09-NLP相关论文\\14-chatgpt\\ChatGPT-SpringBoot\\src\\test\\java\\com\\gzhu\\funai\\service\\ICTAI2022_9_28___终稿.pdf"); 45 | 46 | //File文件转MultipartFile 47 | FileInputStream input = null; 48 | try { 49 | input = new FileInputStream(file); 50 | MultipartFile multipartFile = new MockMultipartFile("file", file.getName(), "text/plain", IOUtils.toByteArray(input)); 51 | 52 | ChatGPTReq chatGPTReq = ChatGPTReq.builder().model(OpenAIConst.MODEL_NAME_CHATGPT_3_5).build(); 53 | String resultR = fileChatService.uploadFile(multipartFile, "", adminApiKeyService.roundRobinGetByType(ApiType.OPENAI), chatGPTReq, false); 54 | 55 | System.out.println("文件处理成功"); 56 | 57 | ChatGPTResp resp = fileChatService.chatWithFile("", 85, "文章的创新点是什么?", adminApiKeyService.roundRobinGetByType(ApiType.OPENAI), chatGPTReq, false); 58 | 59 | if(resp != null){ 60 | System.out.println("结果0:" + resp.getMessage()); 61 | } 62 | 63 | ChatGPTResp resp1 = fileChatService.chatWithFile("", 85, "发表这篇文章的机构是谁?", adminApiKeyService.roundRobinGetByType(ApiType.OPENAI), chatGPTReq, false); 64 | if(resp1 != null){ 65 | System.out.println("结果1:" + resp1.getMessage()); 66 | } 67 | 68 | boolean result = fileChatService.dropCollection("", 85, false); 69 | if(result){ 70 | System.out.println("关闭连接成功"); 71 | }else{ 72 | System.out.println("关闭连接失败"); 73 | } 74 | } catch (FileNotFoundException e) { 75 | e.printStackTrace(); 76 | } catch (IOException e) { 77 | e.printStackTrace(); 78 | } 79 | 80 | 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/service/TestPromptService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.gzhu.funai.entity.PromptEntity; 4 | import com.gzhu.funai.enums.PromptType; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * @Author: huangpenglong 14 | * @Date: 2023/4/11 16:39 15 | */ 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest 19 | public class TestPromptService { 20 | @Resource 21 | private PromptService promptService; 22 | 23 | @Test 24 | public void testInsert(){ 25 | PromptEntity promptEntity = PromptEntity.builder() 26 | .content("你是一个翻译专家, 你需要帮我把以下句子翻译成英文,无需输出多余内容,把翻译内容给我。内容如下:\\%s") 27 | .type(PromptType.CHATGPT.typeNo) 28 | .description("多语言 -> 英语") 29 | .topic("翻译") 30 | .build(); 31 | 32 | promptService.save(promptEntity); 33 | } 34 | 35 | @Test 36 | public void testList(){ 37 | promptService.list(null).forEach(System.out::println); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/service/TestUserAdviceService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.gzhu.funai.entity.UserAdvicesEntity; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | import javax.annotation.Resource; 10 | 11 | @RunWith(SpringRunner.class) 12 | @SpringBootTest 13 | public class TestUserAdviceService { 14 | @Resource 15 | private UserAdvicesService userAdvicesService; 16 | 17 | @Test 18 | public void testUserAddAdvice(){ 19 | UserAdvicesEntity userAdvicesEntity = UserAdvicesEntity.builder() 20 | .userId("") 21 | .advice("这里的界面不太好") 22 | .build(); 23 | userAdvicesService.save(userAdvicesEntity); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/service/TestUserApiKeyService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.gzhu.funai.entity.UserApiKeyEntity; 5 | import com.gzhu.funai.enums.ApiType; 6 | import org.junit.Test; 7 | import org.junit.jupiter.api.TestTemplate; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | import springfox.documentation.service.ApiKey; 13 | 14 | /** 15 | * @Author: huangpenglong 16 | * @Date: 2023/4/20 19:48 17 | */ 18 | 19 | @RunWith(SpringRunner.class) 20 | @SpringBootTest 21 | public class TestUserApiKeyService { 22 | @Autowired 23 | private UserApiKeyService userApiKeyService; 24 | 25 | 26 | @Test 27 | public void testSave(){ 28 | 29 | UserApiKeyEntity userApiKeyEntity = UserApiKeyEntity.builder() 30 | .userId("") 31 | .apikey("AAABBB") 32 | .type(ApiType.OPENAI.typeNo) 33 | .build(); 34 | userApiKeyService.insertOrUpdate(userApiKeyEntity); 35 | } 36 | 37 | @Test 38 | public void testGetOne(){ 39 | UserApiKeyEntity one = userApiKeyService.getOne(new QueryWrapper() 40 | .eq("user_id", "") 41 | .eq("type", ApiType.OPENAI.typeNo) 42 | ); 43 | 44 | System.out.println(one); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/service/TestUserSessionService.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.service; 2 | 3 | import com.gzhu.funai.entity.UserSessionEntity; 4 | import com.gzhu.funai.enums.SessionType; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @Author: huangpenglong 15 | * @Date: 2023/3/17 15:36 16 | */ 17 | 18 | @RunWith(SpringRunner.class) 19 | @SpringBootTest 20 | public class TestUserSessionService { 21 | @Autowired 22 | private UserSessionService userSessionService; 23 | 24 | @Test 25 | public void testInsert(){ 26 | UserSessionEntity session = new UserSessionEntity("", "test_chattype", SessionType.NORMAL_CHAT.type); 27 | userSessionService.save(session); 28 | } 29 | @Test 30 | public void testRename(){ 31 | UserSessionEntity session = new UserSessionEntity(97, "测试修改会话名称"); 32 | userSessionService.updateById(session); 33 | } 34 | 35 | @Test 36 | public void testGetSessionList(){ 37 | List sessionList = userSessionService.getSessionList("", SessionType.NORMAL_CHAT); 38 | sessionList.stream().forEach(e -> System.out.println(e.getSessionId())); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/gzhu/funai/utils/SnowflakeIdGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.gzhu.funai.utils; 2 | 3 | import org.junit.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | import java.util.UUID; 8 | 9 | /** 10 | * @Author: huangpenglong 11 | * @Date: 2023/3/15 23:47 12 | */ 13 | 14 | @SpringBootTest 15 | public class SnowflakeIdGeneratorTest { 16 | 17 | @Test 18 | public void test1(){ 19 | int n = 10; 20 | for(int i=0;i