diff --git a/.gitignore b/.gitignore index 549e00a..a85c9d3 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,7 @@ build/ ### VS Code ### .vscode/ + +info.text + +conf \ No newline at end of file diff --git a/README.md b/README.md index 285c2ee..43aea4c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,16 @@ # AiCoder -Ai模型 +Ai大模型 -- 通义千问-MAX -- ollama +**在线大模型** -使用建议将AiExecutor中的ApiKey改为你在阿里云上申请的token \ No newline at end of file +_apiKey需要在对应的平台进行申请_ + +- 阿里云 + - 通义千问大模型(qwen-max、qwen-long) +- 腾讯云 + - 混元大模型(hunyuan-lite) + +**本地大模型** + +- ollama大模型(llama3) diff --git a/pom.xml b/pom.xml index a551955..d113065 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.shockkid AiCoder - 0.0.1-SNAPSHOT + 0.0.1 jar AiCoder @@ -45,6 +45,62 @@ commonmark 0.19.0 + + + com.github.xiaoymin + knife4j-openapi2-spring-boot-starter + 4.4.0 + + + + + org.aspectj + aspectjweaver + 1.9.9.1 + + + + + + + cn.smallbun.screw + screw-core + 1.0.5 + + + com.pig4cloud.screw + screw-extension + 0.0.2 + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.1 + + + mysql + mysql-connector-java + runtime + + + + + + org.springframework.retry + spring-retry + 1.3.2 + + + + org.openjdk.jol + jol-core + 0.16 + org.springframework.boot diff --git a/src/main/java/com/shockkid/aicoder/AiCoderApplication.java b/src/main/java/com/shockkid/aicoder/AiCoderApplication.java index b088290..eff1066 100644 --- a/src/main/java/com/shockkid/aicoder/AiCoderApplication.java +++ b/src/main/java/com/shockkid/aicoder/AiCoderApplication.java @@ -3,6 +3,9 @@ package com.shockkid.aicoder; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +// @ComponentScan(value = "com.shockkid.aicoder", +// excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, +// value = {Controller.class, RestController.class})}) @SpringBootApplication public class AiCoderApplication { diff --git a/src/main/java/com/shockkid/aicoder/annotation/MethodExporter.java b/src/main/java/com/shockkid/aicoder/annotation/MethodExporter.java new file mode 100644 index 0000000..a856c77 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/annotation/MethodExporter.java @@ -0,0 +1,19 @@ +package com.shockkid.aicoder.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @Author lisang + * @Date 2022/4/10 23:53 + * @Description 方法记录 + * @Version 1.0 + */ +//注解用在方法上 +@Target(ElementType.METHOD) +//注解保留多久,RUNTIME运行时 +@Retention(RetentionPolicy.RUNTIME) +public @interface MethodExporter { +} diff --git a/src/main/java/com/shockkid/aicoder/aspect/MethodExporterAspect.java b/src/main/java/com/shockkid/aicoder/aspect/MethodExporterAspect.java new file mode 100644 index 0000000..24f3aec --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/aspect/MethodExporterAspect.java @@ -0,0 +1,40 @@ +package com.shockkid.aicoder.aspect; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +import java.time.Instant; + +/** + * @Author lisang + * @Date 2022/4/10 23:55 + * @Description + * @Version 1.0 + */ +@Aspect +@Component +@Slf4j +public class MethodExporterAspect { + + @Around("@annotation(com.shockkid.aicoder.annotation.MethodExporter)") + public Object methodExporter(ProceedingJoinPoint joinPoint) throws Throwable { + long st = Instant.now().toEpochMilli(); + Object proceed = joinPoint.proceed(); + long et = Instant.now().toEpochMilli(); + ObjectMapper mapper = new ObjectMapper(); + String jsonParam = mapper.writeValueAsString(joinPoint.getArgs()); + String jsonResult = null; + jsonResult = proceed != null ? mapper.writeValueAsString(proceed) : "null"; + log.info("正在上报服务调用过程:\ntarget{}.{}\nexecution:{}ms,\nparameter:{}\nresult:{}" + , joinPoint.getTarget().getClass().getSimpleName() + , joinPoint.getSignature().getName() + , (et - st) + , jsonParam + , jsonResult); + return proceed; + } +} diff --git a/src/main/java/com/shockkid/aicoder/config/HunYuanProperties.java b/src/main/java/com/shockkid/aicoder/config/HunYuanProperties.java new file mode 100644 index 0000000..4d420db --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/config/HunYuanProperties.java @@ -0,0 +1,44 @@ +package com.shockkid.aicoder.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @Author lisang + * @Date 2024/11/28 18:18 + * @Description: + * @Version 1.0 + **/ +@Configuration +@ConfigurationProperties(prefix = "shockkid.ai.hunyuan") +public class HunYuanProperties { + private String url; + + private String apiKey; + + private String model; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } +} diff --git a/src/main/java/com/shockkid/aicoder/config/Knife4jConfiguration.java b/src/main/java/com/shockkid/aicoder/config/Knife4jConfiguration.java new file mode 100644 index 0000000..7e80ce4 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/config/Knife4jConfiguration.java @@ -0,0 +1,31 @@ +package com.shockkid.aicoder.config; + +/** + * @auther lisang + * @date 2024/8/6 13:19 + **/ +// @Configuration +// @EnableSwagger2WebMvc +public class Knife4jConfiguration { + + // @Bean(value = "dockerBean") + // public Docket dockerBean() { + // //指定使用Swagger2规范 + // Docket docket=new Docket(DocumentationType.SWAGGER_2) + // .apiInfo(new ApiInfoBuilder() + // //描述字段支持Markdown语法 + // .description("# Knife4j RESTful APIs") + // .termsOfServiceUrl("https://mlisan.com/") + // .contact("lisang_chn@163.com") + // .version("1.0") + // .build()) + // //分组名称 + // .groupName("AI服务") + // .select() + // //这里指定Controller扫描包路径 + // .apis(RequestHandlerSelectors.basePackage("com.shockkid.aicoder.controller")) + // .paths(PathSelectors.any()) + // .build(); + // return docket; + // } +} \ No newline at end of file diff --git a/src/main/java/com/shockkid/aicoder/config/ShockKidAiProperties.java b/src/main/java/com/shockkid/aicoder/config/ShockKidAiProperties.java new file mode 100644 index 0000000..4f2a596 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/config/ShockKidAiProperties.java @@ -0,0 +1,43 @@ +package com.shockkid.aicoder.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @Author lisang + * @Date 2024/11/28 22:03 + * @Description: AI 配置类 + * @Version 1.0 + **/ +@Configuration +@ConfigurationProperties(prefix = "shockkid.ai") +@Data +public class ShockKidAiProperties { + private AliyunTongYiPropertis tongyi; + private TencentHunYuanPropertis hunyuan; + private OllmaPropertis ollma; + + @Data + public static class AliyunTongYiPropertis { + private boolean enable; + private String url; + private String apiKey; + private String model; + } + + @Data + public static class TencentHunYuanPropertis { + private boolean enable; + private String url; + private String apiKey; + private String model; + } + + @Data + public static class OllmaPropertis { + private boolean enable; + private String url; + private String model; + } +} diff --git a/src/main/java/com/shockkid/aicoder/constant/ModelConstant.java b/src/main/java/com/shockkid/aicoder/constant/ModelConstant.java index aa9cb8c..a463628 100644 --- a/src/main/java/com/shockkid/aicoder/constant/ModelConstant.java +++ b/src/main/java/com/shockkid/aicoder/constant/ModelConstant.java @@ -15,4 +15,5 @@ public class ModelConstant { public static final String ASSISTANT_ROLE = "assistant"; public static final String MESSAGE = "message"; public static final String CONTENT = "content"; + public static final String HUNYUAN_LITE = "hunyuan-lite"; } diff --git a/src/main/java/com/shockkid/aicoder/controller/AiController.java b/src/main/java/com/shockkid/aicoder/controller/AiController.java index 85562d8..996eb6c 100644 --- a/src/main/java/com/shockkid/aicoder/controller/AiController.java +++ b/src/main/java/com/shockkid/aicoder/controller/AiController.java @@ -1,7 +1,13 @@ package com.shockkid.aicoder.controller; import com.shockkid.aicoder.executor.AiExecutor; +import com.shockkid.aicoder.factory.AiExecutorFactory; import com.shockkid.aicoder.storage.MessageHistoryStorage; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @@ -9,16 +15,25 @@ import org.springframework.web.bind.annotation.RestController; * @auther lisang * @date 2024/5/22 23:43 **/ +@Api(tags = "AI模块") @RestController public class AiController { + @Autowired@Qualifier("aiHunYuanExecutor") + private AiExecutorFactory aiExecutor; + + @ApiImplicitParam(name = "text", value = "请求文本", required = true) + @ApiOperation(value = "AI反馈结果") @PostMapping("/aiChat") public String aiChat(String text) { - AiExecutor aiExecutor = new AiExecutor(); - String result = aiExecutor.getChatMessageByTongYi(text); + /*AiExecutor aiExecutor = new AiExecutor(); + String result = aiExecutor.getChatMessageByTongYi(text);*/ + String result = aiExecutor.getChatMessage(text); return result; } + @ApiImplicitParam(name = "text", value = "请求文本含历史记录", required = true) + @ApiOperation(value = "AI反馈结果") @PostMapping("/aiChatHistory") public String aiChatHistory(String text) { AiExecutor aiExecutor = new AiExecutor(); diff --git a/src/main/java/com/shockkid/aicoder/example/pay/AsyncPaymentService.java b/src/main/java/com/shockkid/aicoder/example/pay/AsyncPaymentService.java new file mode 100644 index 0000000..697d6fb --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/example/pay/AsyncPaymentService.java @@ -0,0 +1,55 @@ +package com.shockkid.aicoder.example.pay; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @auther lisang + * @date 2024/6/25 10:49 + **/ +public class AsyncPaymentService { + // 创建线程池 + private final ExecutorService single = Executors.newSingleThreadExecutor(); + + // 阻塞队列 -> 可以替换成分布式队列 + ArrayBlockingQueue taskQueue = new ArrayBlockingQueue(10); + + // 标识 + private volatile boolean isRunning = true; + + { + init(); + } + + // 异步处理 + public void init() { + single.execute(()->{ + while (isRunning) { + try { + PaymentRequest paymentRequest = taskQueue.take();// 阻塞方式拿到支付任务 + doPay(paymentRequest); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + } + + public String submitPay(PaymentRequest paymentRequest) { + System.out.println("开始校验请求参数:" + paymentRequest); + taskQueue.add(paymentRequest); // 添加到队列 + return "processing"; // 返回支付处理成功 -> 可能变成失败 + } + + public String doPay(PaymentRequest paymentRequest) { + try { + System.out.println("开始处理支付请求:" + paymentRequest); + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("支付处理结束:" + paymentRequest); + return "success"; + } +} diff --git a/src/main/java/com/shockkid/aicoder/example/pay/PaymentRequest.java b/src/main/java/com/shockkid/aicoder/example/pay/PaymentRequest.java new file mode 100644 index 0000000..2bab555 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/example/pay/PaymentRequest.java @@ -0,0 +1,44 @@ +package com.shockkid.aicoder.example.pay; + +/** + * @auther lisang + * @date 2024/6/25 10:41 + **/ +public class PaymentRequest { + public int payMethod; + public int totalFee; + public String returnUrl; + + public int getPayMethod() { + return payMethod; + } + + public void setPayMethod(int payMethod) { + this.payMethod = payMethod; + } + + public int getTotalFee() { + return totalFee; + } + + public void setTotalFee(int totalFee) { + this.totalFee = totalFee; + } + + public String getReturnUrl() { + return returnUrl; + } + + public void setReturnUrl(String returnUrl) { + this.returnUrl = returnUrl; + } + + @Override + public String toString() { + return "PaymentRequest{" + + "payMethod=" + payMethod + + ", totalFee=" + totalFee + + ", returnUrl='" + returnUrl + '\'' + + '}'; + } +} diff --git a/src/main/java/com/shockkid/aicoder/example/pay/PaymentService.java b/src/main/java/com/shockkid/aicoder/example/pay/PaymentService.java new file mode 100644 index 0000000..396ffc4 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/example/pay/PaymentService.java @@ -0,0 +1,23 @@ +package com.shockkid.aicoder.example.pay; + +/** + * @auther lisang + * @date 2024/6/25 10:41 + **/ +public class PaymentService { + public String submitPay(PaymentRequest paymentRequest) { + System.out.println("开始校验请求参数:" + paymentRequest); + return doPay(paymentRequest); + } + + public String doPay(PaymentRequest paymentRequest) { + try { + System.out.println("开始处理支付请求:" + paymentRequest); + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("支付处理结束:" + paymentRequest); + return "success"; + } +} diff --git a/src/main/java/com/shockkid/aicoder/example/pay/PaymentTestMain.java b/src/main/java/com/shockkid/aicoder/example/pay/PaymentTestMain.java new file mode 100644 index 0000000..d6d5909 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/example/pay/PaymentTestMain.java @@ -0,0 +1,21 @@ +package com.shockkid.aicoder.example.pay; + +import java.io.IOException; + +/** + * @auther lisang + * @date 2024/6/25 10:41 + **/ +public class PaymentTestMain { + public static void main(String[] args) throws IOException { + long start = System.currentTimeMillis(); + PaymentRequest request = new PaymentRequest(); + request.setPayMethod(1); + request.setTotalFee(100); + request.setReturnUrl("http://www.baidu.com"); + String result = new AsyncPaymentService().submitPay(request); + long end = System.currentTimeMillis() - start; + System.out.println("支付处理结果:"+result+"->总处理时间:"+end); + //System.in.read(); + } +} diff --git a/src/main/java/com/shockkid/aicoder/executor/AiExecutor.java b/src/main/java/com/shockkid/aicoder/executor/AiExecutor.java index 99fa6fa..eac81ca 100644 --- a/src/main/java/com/shockkid/aicoder/executor/AiExecutor.java +++ b/src/main/java/com/shockkid/aicoder/executor/AiExecutor.java @@ -23,7 +23,7 @@ import static com.shockkid.aicoder.constant.ModelConstant.*; public class AiExecutor { private String generateUrl = "http://127.0.0.1:11434/api/generate"; private String chatUrl = "http://127.0.0.1:11434/api/chat"; - private String ApiKey = "Bearer 你的TOKEN"; + private String ApiKey = "Bearer 通义千问TOKEN"; /** * 通义大模型模型链接 */ diff --git a/src/main/java/com/shockkid/aicoder/executor/AiHunYuanExecutor.java b/src/main/java/com/shockkid/aicoder/executor/AiHunYuanExecutor.java new file mode 100644 index 0000000..453a193 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/executor/AiHunYuanExecutor.java @@ -0,0 +1,92 @@ +package com.shockkid.aicoder.executor; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.shockkid.aicoder.config.HunYuanProperties; +import com.shockkid.aicoder.config.OkHttpClientSingleton; +import com.shockkid.aicoder.factory.AiExecutorFactory; +import com.shockkid.aicoder.model.InputModel; +import com.shockkid.aicoder.model.OpenAiModel; +import com.shockkid.aicoder.model.RoleContentModel; +import okhttp3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.shockkid.aicoder.constant.ModelConstant.HUNYUAN_LITE; +import static com.shockkid.aicoder.constant.ModelConstant.USER_ROLE; + +/** + * @Author lisang + * @Date 2024/11/28 18:25 + * @Description: 腾讯混元大模型 + * @Version 1.0 + **/ +@Service +public class AiHunYuanExecutor implements AiExecutorFactory { + private static final Logger log = LoggerFactory.getLogger(AiHunYuanExecutor.class); + + @Autowired + private HunYuanProperties hunYuanConfig; + + /** + * 基于腾讯混元大模型API获取返回结果 + * + * @param prompt + * @return + */ + public String getChatMessage(String prompt) { + OkHttpClient client = OkHttpClientSingleton.getInstance(); + // 构建入参 + OpenAiModel model = new OpenAiModel(); + model.setModel(HUNYUAN_LITE); + InputModel inputModel = new InputModel(); + List messages = new ArrayList<>(); + RoleContentModel roleContentModel = new RoleContentModel(); + roleContentModel.setRole(USER_ROLE); + roleContentModel.setContent(prompt); + messages.add(roleContentModel); + model.setMessages(messages); + // 发起请求 + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(model)); + log.info("" + JSON.toJSONString(model)); + Request request = new Request.Builder() + .header("Authorization", "Bearer " + hunYuanConfig.getApiKey()) + .url(hunYuanConfig.getUrl()) + .post(requestBody) + .build(); + try (Response response = client.newCall(request).execute()) { + // 处理响应 + if (response.isSuccessful()) { + // 请求成功 + ResponseBody responseBody = response.body(); + String string = responseBody.string(); + JSONObject jsonObject = JSON.parseObject(string); + // 处理响应体 + List choices = jsonObject.getObject("choices", List.class); + if (choices.size() > 0) { + JSONObject jsonObject2 = JSON.parseObject(jsonObject.getObject("choices", List.class).get(0).toString()); + return jsonObject2.getJSONObject("message").getString("content"); + } + return "模型未给出相关回答"; + } else { + // 请求失败 + return "请求失败,响应码: " + response.code(); + } + } catch (IOException e) { + // 发生异常 + e.printStackTrace(); + } + return "请求失败"; + } + + @Override + public String getChatHistory(String prompt) { + return null; + } +} diff --git a/src/main/java/com/shockkid/aicoder/executor/AiOllmaExecutor.java b/src/main/java/com/shockkid/aicoder/executor/AiOllmaExecutor.java new file mode 100644 index 0000000..5557efe --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/executor/AiOllmaExecutor.java @@ -0,0 +1,108 @@ +package com.shockkid.aicoder.executor; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.shockkid.aicoder.config.OkHttpClientSingleton; +import com.shockkid.aicoder.config.ShockKidAiProperties; +import com.shockkid.aicoder.factory.AiExecutorFactory; +import com.shockkid.aicoder.model.ChatModel; +import com.shockkid.aicoder.model.ChatRoleModel; +import com.shockkid.aicoder.storage.MessageHistoryStorage; +import okhttp3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +import static com.shockkid.aicoder.constant.ModelConstant.*; + +/** + * @Author lisang + * @Date 2024/11/28 18:25 + * @Description: ollma大模型 + * @Version 1.0 + **/ +@Service +public class AiOllmaExecutor implements AiExecutorFactory { + private static final Logger log = LoggerFactory.getLogger(AiOllmaExecutor.class); + + @Autowired + private ShockKidAiProperties aiConf; + + /** + * 基于ollma大模型API获取返回结果 + * + * @param prompt + * @return + */ + public String getChatMessage(String prompt) { + if (!aiConf.getOllma().isEnable()){ + return "llama接口尚未接入"; + } + OkHttpClient client = OkHttpClientSingleton.getInstance(); + ChatModel chatModel = new ChatModel(); + chatModel.setModel(aiConf.getOllma().getModel()); + chatModel.setPrompt(prompt); + chatModel.setStream(false); + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(chatModel)); + Request request = new Request.Builder() + .url(aiConf.getOllma().getUrl()) + .post(requestBody) + .build(); + try (Response response = client.newCall(request).execute()) { + // 处理响应 + if (response.isSuccessful()) { + // 请求成功 + ResponseBody responseBody = response.body(); + String string = responseBody.string(); + JSONObject jsonObject = JSON.parseObject(string); + // 处理响应体 + return jsonObject.getString(RESPONSE); + } else { + // 请求失败 + return "请求失败,响应码: " + response.code(); + } + } catch (IOException e) { + // 发生异常 + e.printStackTrace(); + } + return "请求失败"; + } + + @Override + public String getChatHistory(String prompt) { + if (!aiConf.getOllma().isEnable()){ + return "llama尚未开启"; + } + OkHttpClient client = OkHttpClientSingleton.getInstance(); + ChatRoleModel chatRoleModel = new ChatRoleModel(); + chatRoleModel.setModel(aiConf.getOllma().getModel()); + chatRoleModel.setStream(false); + chatRoleModel.setMessages(MessageHistoryStorage.buildRoleContentModel(prompt)); + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(chatRoleModel)); + Request request = new Request.Builder() + .url(aiConf.getOllma().getUrl()) + .post(requestBody) + .build(); + try (Response response = client.newCall(request).execute()) { + // 处理响应 + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + String string = responseBody.string(); + JSONObject jsonObject = JSON.parseObject(string); + JSONObject messageJson = jsonObject.getJSONObject(MESSAGE); + return messageJson.getString(CONTENT); + } else { + // 请求失败 + System.out.println("请求失败,响应码: " + response.code()); + return "请求失败,响应码: " + response.code(); + } + } catch (IOException e) { + // 发生异常 + e.printStackTrace(); + } + return "请求失败"; + } +} diff --git a/src/main/java/com/shockkid/aicoder/executor/AiTongYiExecutor.java b/src/main/java/com/shockkid/aicoder/executor/AiTongYiExecutor.java new file mode 100644 index 0000000..0cb1690 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/executor/AiTongYiExecutor.java @@ -0,0 +1,124 @@ +package com.shockkid.aicoder.executor; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.shockkid.aicoder.config.OkHttpClientSingleton; +import com.shockkid.aicoder.config.ShockKidAiProperties; +import com.shockkid.aicoder.factory.AiExecutorFactory; +import com.shockkid.aicoder.model.InputModel; +import com.shockkid.aicoder.model.RoleContentModel; +import com.shockkid.aicoder.model.TongYiModel; +import com.shockkid.aicoder.storage.MessageHistoryStorage; +import okhttp3.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.shockkid.aicoder.constant.ModelConstant.*; + +/** + * @Author lisang + * @Date 2024/11/28 18:25 + * @Description: 阿里通义千问大模型 + * @Version 1.0 + **/ +@Service +public class AiTongYiExecutor implements AiExecutorFactory { + private static final Logger log = LoggerFactory.getLogger(AiTongYiExecutor.class); + + @Autowired + private ShockKidAiProperties aiConf; + + /** + * 基于阿里通义千问大模型API获取返回结果 + * + * @param prompt + * @return + */ + public String getChatMessage(String prompt) { + if (!aiConf.getTongyi().isEnable()){ + return "通义千问接口尚未接入"; + } + OkHttpClient client = OkHttpClientSingleton.getInstance(); + // 构建入参 + TongYiModel model = new TongYiModel(); + model.setModel(aiConf.getTongyi().getModel()); + InputModel inputModel = new InputModel(); + List messages = new ArrayList<>(); + RoleContentModel roleContentModel = new RoleContentModel(); + roleContentModel.setRole(USER_ROLE); + roleContentModel.setContent(prompt); + messages.add(roleContentModel); + inputModel.setMessages(messages); + model.setInput(inputModel); + // 发起请求 + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(model)); + Request request = new Request.Builder() + .header("Authorization","Bearer "+ aiConf.getTongyi().getApiKey()) + .url(aiConf.getTongyi().getUrl()) + .post(requestBody) + .build(); + try (Response response = client.newCall(request).execute()) { + // 处理响应 + if (response.isSuccessful()) { + // 请求成功 + ResponseBody responseBody = response.body(); + String string = responseBody.string(); + JSONObject jsonObject = JSON.parseObject(string); + // 处理响应体 + JSONObject outputJson = jsonObject.getJSONObject(OUTPUT); + return outputJson.getString(TEXT); + } else { + // 请求失败 + return "请求失败,响应码: " + response.code(); + } + } catch (IOException e) { + // 发生异常 + e.printStackTrace(); + } + return "请求失败"; + } + + @Override + public String getChatHistory(String prompt) { + OkHttpClient client = OkHttpClientSingleton.getInstance(); + // 构建入参 + TongYiModel model = new TongYiModel(); + model.setModel(aiConf.getTongyi().getModel()); + InputModel inputModel = new InputModel(); + // 构建上下文入参 + inputModel.setMessages(MessageHistoryStorage.buildRoleContentModel(prompt)); + model.setInput(inputModel); + // 发起请求 + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(model)); + Request request = new Request.Builder() + .header("Authorization","Bearer "+ aiConf.getTongyi().getApiKey()) + .url(aiConf.getTongyi().getUrl()) + .post(requestBody) + .build(); + try (Response response = client.newCall(request).execute()) { + // 处理响应 + if (response.isSuccessful()) { + // 请求成功 + ResponseBody responseBody = response.body(); + String string = responseBody.string(); + JSONObject jsonObject = JSON.parseObject(string); + // 处理响应体 + JSONObject outputJson = jsonObject.getJSONObject(OUTPUT); + return outputJson.getString(TEXT); + } else { + // 请求失败 + return "请求失败,响应码: " + response.code(); + } + } catch (IOException e) { + // 发生异常 + e.printStackTrace(); + } + return "请求失败"; + } +} diff --git a/src/main/java/com/shockkid/aicoder/factory/AiExecutorFactory.java b/src/main/java/com/shockkid/aicoder/factory/AiExecutorFactory.java new file mode 100644 index 0000000..fc7b16e --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/factory/AiExecutorFactory.java @@ -0,0 +1,13 @@ +package com.shockkid.aicoder.factory; + +/** + * @Author lisang + * @Date 2024/11/28 20:05 + * @Description: + * @Version 1.0 + **/ +public interface AiExecutorFactory { + public String getChatMessage(String prompt); + + public String getChatHistory(String prompt); +} diff --git a/src/main/java/com/shockkid/aicoder/model/OpenAiModel.java b/src/main/java/com/shockkid/aicoder/model/OpenAiModel.java new file mode 100644 index 0000000..672b077 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/model/OpenAiModel.java @@ -0,0 +1,17 @@ +package com.shockkid.aicoder.model; + +import lombok.Data; + +import java.util.List; + +/** + * @Author lisang + * @Date 2024/11/28 18:41 + * @Description: OpenAi通用模型参数 + * @Version 1.0 + **/ +@Data +public class OpenAiModel { + private String model; + private List messages; +} diff --git a/src/main/java/com/shockkid/aicoder/util/BatchUtils.java b/src/main/java/com/shockkid/aicoder/util/BatchUtils.java new file mode 100644 index 0000000..c67e6e0 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/util/BatchUtils.java @@ -0,0 +1,26 @@ +package com.shockkid.aicoder.util; + +import java.util.Random; + +/** + * 批量生成工具 + * + * @auther lisang + * @date 2024/11/7 0:44 + **/ +public class BatchUtils { + public String autoBuildAcount() { + long t = System.currentTimeMillis(); + Random random = new Random(); + int i = random.nextInt(900000) + 100000; + int checksum = (int) ((t + i) % 10); + return String.format("%d%d%d", t, i, checksum); + } + + public static void main(String[] args) { + BatchUtils batchUtils = new BatchUtils(); + for (int i = 0; i < 10; i++) { + System.out.println(batchUtils.autoBuildAcount()); + } + } +} diff --git a/src/main/java/com/shockkid/aicoder/util/DataExportUtil.java b/src/main/java/com/shockkid/aicoder/util/DataExportUtil.java new file mode 100644 index 0000000..f011754 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/util/DataExportUtil.java @@ -0,0 +1,95 @@ +package com.shockkid.aicoder.util; + +import cn.smallbun.screw.core.Configuration; +import cn.smallbun.screw.core.engine.EngineConfig; +import cn.smallbun.screw.core.engine.EngineFileType; +import cn.smallbun.screw.core.engine.EngineTemplateType; +import cn.smallbun.screw.core.execute.DocumentationExecute; +import cn.smallbun.screw.core.process.ProcessConfig; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import javax.sql.DataSource; +import java.time.Instant; +import java.util.ArrayList; + +/** + * @Author lisang + * @Date 2022/4/11 9:04 + * @Description 数据库结构文档导出工具 + * @Version 1.0 + */ +public class DataExportUtil { + String FILE_OUT_PUT_DIR = "E:\\test"; + + /** + * 文档生成 + */ + public void documentGeneration() { + //数据源 + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver"); + hikariConfig.setJdbcUrl("jdbc:mysql://49.232.23.130:13306/onlinegit"); + hikariConfig.setUsername("root"); + hikariConfig.setPassword("sz@ongit123"); + //设置可以获取tables remarks信息 + hikariConfig.addDataSourceProperty("useInformationSchema", "true"); + hikariConfig.setMinimumIdle(2); + hikariConfig.setMaximumPoolSize(5); + DataSource dataSource = new HikariDataSource(hikariConfig); + //生成配置 + EngineConfig engineConfig = EngineConfig.builder() + //生成文件路径 + .fileOutputDir(FILE_OUT_PUT_DIR) + //打开目录 + .openOutputDir(true) + //文件类型 + .fileType(EngineFileType.HTML) + //生成模板实现 + .produceType(EngineTemplateType.freemarker) + //自定义文件名称 + .fileName("onlinegit").build(); + + //忽略表 + ArrayList ignoreTableName = new ArrayList<>(); + ignoreTableName.add("test_user"); + ignoreTableName.add("test_group"); + //忽略表前缀 + ArrayList ignorePrefix = new ArrayList<>(); + ignorePrefix.add("test_"); + //忽略表后缀 + ArrayList ignoreSuffix = new ArrayList<>(); + ignoreSuffix.add("_test"); + ProcessConfig processConfig = ProcessConfig.builder() + //指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置 + //根据名称指定表生成 + .designatedTableName(new ArrayList<>()) + //根据表前缀生成 + .designatedTablePrefix(new ArrayList<>()) + //根据表后缀生成 + .designatedTableSuffix(new ArrayList<>()) + //忽略表名 + .ignoreTableName(ignoreTableName) + //忽略表前缀 + .ignoreTablePrefix(ignorePrefix) + //忽略表后缀 + .ignoreTableSuffix(ignoreSuffix).build(); + //配置 + Configuration config = Configuration.builder() + .title("在线课堂设计文档") + //版本 + .version("1.0.0") + //描述 + .description("在线课堂数据库设计文档\n时间:" + Instant.now()) + //数据源 + .dataSource(dataSource) + //生成配置 + .engineConfig(engineConfig) + //生成配置 + .produceConfig(processConfig) + .build(); + //执行生成 + new DocumentationExecute(config).execute(); + } + +} diff --git a/src/main/java/com/shockkid/aicoder/util/MakePojoUtil.java b/src/main/java/com/shockkid/aicoder/util/MakePojoUtil.java new file mode 100644 index 0000000..5b2f05e --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/util/MakePojoUtil.java @@ -0,0 +1,66 @@ +package com.shockkid.aicoder.util; + +import cn.smallbun.screw.core.process.ProcessConfig; +import cn.smallbun.screw.extension.pojo.PojoConfiguration; +import cn.smallbun.screw.extension.pojo.execute.PojoExecute; +import cn.smallbun.screw.extension.pojo.strategy.HumpNameStrategy; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import javax.sql.DataSource; +import java.util.ArrayList; + +/** + * @Author lisang + * @Date 2022/4/11 12:07 + * @Description 生成实体类 + * @Version 1.0 + */ +public class MakePojoUtil { + final String POJOFILE_PATH = "E:\\wk\\idea\\AiCoder\\src\\main\\java\\com\\shockkid\\aicoder\\pojo"; + final String PACKAGE_NAME = "com.shockkid.aicoder.pojo"; + + /** + * pojo生成 + */ + public void pojoGeneration() { + //数据源 + HikariConfig hikariConfig = new HikariConfig(); + hikariConfig.setDriverClassName("com.mysql.cj.jdbc.Driver"); + hikariConfig.setJdbcUrl("jdbc:mysql://192.168.56.204:3306/onlinegit"); + hikariConfig.setUsername("root"); + hikariConfig.setPassword("123456"); + //设置可以获取tables remarks信息 + hikariConfig.addDataSourceProperty("useInformationSchema", "true"); + hikariConfig.setMinimumIdle(2); + hikariConfig.setMaximumPoolSize(5); + DataSource dataSource = new HikariDataSource(hikariConfig); + + ProcessConfig processConfig = ProcessConfig.builder() + //指定生成逻辑、当存在指定表、指定表前缀、指定表后缀时,将生成指定表,其余表不生成、并跳过忽略表配置 + //根据名称指定表生成 + .designatedTableName(new ArrayList<>()) + //根据表前缀生成 + .designatedTablePrefix(new ArrayList<>()) + //根据表后缀生成 + .designatedTableSuffix(new ArrayList<>()).build(); + + //设置生成pojo相关配置 + PojoConfiguration config = new PojoConfiguration(); + //设置文件存放路径 + config.setPath(POJOFILE_PATH); + //设置包名 + config.setPackageName(PACKAGE_NAME); + //设置是否使用lombok + config.setUseLombok(false); + //设置数据源 + config.setDataSource(dataSource); + //设置命名策略 + config.setNameStrategy(new HumpNameStrategy()); + //设置表过滤逻辑 + config.setProcessConfig(processConfig); + //执行生成 + new PojoExecute(config).execute(); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 11152c6..46ed550 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,15 @@ -server.port=8520 \ No newline at end of file +server.port=8520 + +# \u914D\u7F6E\u57FA\u7840\u7684\u6570\u636E\u6E90 +#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/aicoder +#spring.datasource.username=root +#spring.datasource.password=123456 +#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +# \u6253\u5F00\u9A7C\u5CF0\u547D\u540D +mybatis-plus.configuration.map-underscore-to-camel-case=true + +# \u63A7\u5236\u53F0\u6253\u5370SQL +#mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl +# \u65E5\u5FD7\u65B9\u5F0F\u6253\u5370SQL +logging.level.com.lisang.boot.swagger.service=debug \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..56ea0c6 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,44 @@ +spring: + application: + # 应用名称 + name: AiCoder + # 配置基础的数据源 + datasource: + url: jdbc:mysql://127.0.0.1:3306/aicoder + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + +knife4j: + enable: true + openapi: + title: AI模块 + description: "AI模块相关接口" + concat: ShockKid + version: v1.0 + terms-of-service-url: https://mlisan.com + group: + default: + group-name: 默认分组 + api-rule: package + api-rule-resources: + - com.shockkid.aicoder.controller + +shockkid: + ai: + hunyuan: + enable: true + url: "https://api.hunyuan.cloud.tencent.com/v1/chat/completions" + apiKey: "xxxx" + model: "hunyuan-lite" + tongyi: + enable: false + url: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation" + apiKey: "xxxx" + model: "qwen-max" + ollama: + enable: false + url: "http://127.0.0.1:11434/api/generate" + model: "llama3" + info: + version: 0.0.1 \ No newline at end of file diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..3cff2ef --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,6 @@ +Application Name: ${spring.application.name} +Application Version: ${shockkid.info.version} +Spring Boot Version: ${spring-boot.version} ++-+-+-+-+-+-+-+ +|A|i|C|o|d|e|r| ++-+-+-+-+-+-+-+ diff --git a/src/test/java/com/shockkid/aicoder/AiCoderApplicationTests.java b/src/test/java/com/shockkid/aicoder/AiCoderApplicationTests.java index 2552b79..ae157b5 100644 --- a/src/test/java/com/shockkid/aicoder/AiCoderApplicationTests.java +++ b/src/test/java/com/shockkid/aicoder/AiCoderApplicationTests.java @@ -1,5 +1,6 @@ package com.shockkid.aicoder; +import com.shockkid.aicoder.util.MakePojoUtil; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @@ -8,6 +9,8 @@ class AiCoderApplicationTests { @Test void contextLoads() { + MakePojoUtil makePojoUtil = new MakePojoUtil(); + makePojoUtil.pojoGeneration(); } }