feat(ai): 集成DeepSeek AI服务实现技能生成与分析功能

- 添加DeepSeek API配置类和application.yml配置
- 实现HTTP工具类用于发送AI API请求
- 创建技能生成和分析的请求响应实体类
- 开发技能生成控制器提供generate和analyze接口
- 实现SkillGenService服务完成AI交互逻辑
- 设计SkillRequest封装聊天完成API调用参数
- 添加跨域支持和Swagger API文档注解
This commit is contained in:
wangzhiwei 2026-01-28 20:30:50 +08:00
parent 5bb2bfe0b9
commit 18787b68a0
10 changed files with 462 additions and 0 deletions

View File

@ -0,0 +1,58 @@
package com.kexue.skills.common.util;
import com.alibaba.fastjson.JSON;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
/**
* HTTP工具类
*
* @author 维哥
* @since 2026-01-28
*/
public class HttpUtil {
/**
* 发送POST请求到指定URL通用HTTP客户端方法使用JDK自带HttpClient
*
* @param url 请求URL
* @param requestBody 请求体对象
* @param apiKey API密钥
* @param authHeader 认证头
* @return 响应结果
* @throws Exception 异常信息
*/
public static String sendPostRequest(String url, Object requestBody, String apiKey, String authHeader) throws Exception {
// 创建HttpClient实例
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
// 构建请求体
String jsonInputString = JSON.toJSONString(requestBody);
// 构建HttpRequest
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonInputString, StandardCharsets.UTF_8));
// 设置认证头
if (authHeader != null) {
requestBuilder.header("Authorization", authHeader);
} else if (apiKey != null) {
requestBuilder.header("Authorization", "Bearer " + apiKey);
}
// 发送请求并获取响应
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
return response.body();
}
}

View File

@ -0,0 +1,73 @@
package com.kexue.skills.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* DeepSeek API配置类
*
* @author 维哥
* @since 2026-01-28
*/
@Component
@ConfigurationProperties(prefix = "spring.ai.deepseek")
public class DeepSeekConfig {
private String baseUrl;
private String apiKey;
private ChatOptions chat;
public static class ChatOptions {
private String model;
private Double temperature;
private Integer maxTokens;
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
public Integer getMaxTokens() {
return maxTokens;
}
public void setMaxTokens(Integer maxTokens) {
this.maxTokens = maxTokens;
}
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public ChatOptions getChat() {
return chat;
}
public void setChat(ChatOptions chat) {
this.chat = chat;
}
}

View File

@ -0,0 +1,48 @@
package com.kexue.skills.controller;
import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.response.SkillResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 技能生成控制器
*
* @author 维哥
* @since 2026-01-28
*/
@RestController
@RequestMapping("api/skillGen")
@Tag(name = "技能生成 Api")
@CrossOrigin(origins = "*")
public class SkillGenController {
@Resource
private com.kexue.skills.service.SkillGenService skillGenService;
/**
* 生成技能
*
* @param request 生成请求
* @return 生成结果
*/
@PostMapping("/generate")
@Operation(summary = "生成技能", description = "生成技能")
public CommonResult<SkillResponse> generate(@RequestBody com.kexue.skills.entity.request.SkillGenRequest request) {
return CommonResult.success(skillGenService.generateSkill(request));
}
/**
* 分析技能
*
* @param request 分析请求
* @return 分析结果
*/
@PostMapping("/analyze")
@Operation(summary = "分析技能", description = "分析技能")
public CommonResult<String> analyze(@RequestBody com.kexue.skills.entity.request.SkillAnalyzeRequest request) {
return CommonResult.success(skillGenService.analyzeSkill(request));
}
}

View File

@ -0,0 +1,24 @@
package com.kexue.skills.entity.request;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 技能分析请求参数
*
* @author 维哥
* @since 2026-01-28
*/
@Data
@ApiModel(value = "技能分析请求参数")
public class SkillAnalyzeRequest implements Serializable {
@Schema(description = "技能ID")
private Long skillId;
@Schema(description = "分析类型")
private String analyzeType;
}

View File

@ -0,0 +1,22 @@
package com.kexue.skills.entity.request;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 技能生成请求参数
*
* @author 维哥
* @since 2026-01-28
*/
@Data
@ApiModel(value = "技能生成请求参数")
public class SkillGenRequest implements Serializable {
@Schema(description = "用户提示词")
private String prompt;
}

View File

@ -0,0 +1,91 @@
package com.kexue.skills.entity.request;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SkillRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String model;
private List<Message> messages;
private double temperature;
private int max_tokens;
private ResponseFormat response_format;
public SkillRequest(boolean useDefaultSettings) {
if (useDefaultSettings) {
this.model = "deepseek-chat";
this.messages = new ArrayList<>();
Message systemMessage = new Message();
systemMessage.setRole("system");
systemMessage.setContent("你是一个专业的AI技能设计助手。请严格按照指定的JSON格式输出仅包含要求的字段以中文形式返回。");
this.messages.add(systemMessage);
Message userMessage = new Message();
userMessage.setRole("user");
userMessage.setContent("主题我想要做一个将数据库设计表转换成sql schema语言并迁移到数据库服务器中的skill。请根据agent skills撰写规范帮我生成这个skill的名称、描述并从以下标签列表中选择一个或者多个标签\"软件开发系统集成网络工程云计算大数据人工智能物联网区块链信息安全运维服务测试认证IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式仅输出以上所提到的名称、描述、标签节点名称分别为name、description、tags节点内容以中文形式返回。");
this.messages.add(userMessage);
this.temperature = 0.3;
this.max_tokens = 500;
this.response_format = new ResponseFormat();
this.response_format.setType("json_object");
}
}
public SkillRequest(boolean useDefaultSettings, String model, Double temperature, Integer maxTokens,String prompt) {
if (useDefaultSettings) {
this.model = model;
this.messages = new ArrayList<>();
Message systemMessage = new Message();
systemMessage.setRole("system");
systemMessage.setContent("你是一个专业的AI技能设计助手。请严格按照指定的JSON格式输出仅包含要求的字段以中文形式返回。");
this.messages.add(systemMessage);
Message userMessage = new Message();
userMessage.setRole("user");
userMessage.setContent("主题:"+ prompt +"。请根据agent skills撰写规范帮我生成这个skill的名称、描述并从以下标签列表中选择一个或者多个标签\"软件开发系统集成网络工程云计算大数据人工智能物联网区块链信息安全运维服务测试认证IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式仅输出以上所提到的名称、描述、标签节点名称分别为name、description、tags节点内容以中文形式返回。");
this.messages.add(userMessage);
this.temperature = temperature;
this.max_tokens = maxTokens;
this.response_format = new ResponseFormat();
this.response_format.setType("json_object");
}
}
public static SkillRequest createDefault() {
return new SkillRequest(true);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String role;
private String content;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ResponseFormat implements Serializable {
private static final long serialVersionUID = 1L;
private String type;
}
}

View File

@ -0,0 +1,15 @@
package com.kexue.skills.entity.response;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class SkillResponse implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String description;
private List<String> tags;
}

View File

@ -0,0 +1,30 @@
package com.kexue.skills.service;
import com.kexue.skills.entity.request.SkillGenRequest;
import com.kexue.skills.entity.request.SkillAnalyzeRequest;
import com.kexue.skills.entity.response.SkillResponse;
/**
* 技能生成服务接口
*
* @author 维哥
* @since 2026-01-28
*/
public interface SkillGenService {
/**
* 生成技能
*
* @param request 生成请求
* @return 生成结果
*/
SkillResponse generateSkill(SkillGenRequest request);
/**
* 分析技能
*
* @param request 分析请求
* @return 分析结果
*/
String analyzeSkill(SkillAnalyzeRequest request);
}

View File

@ -0,0 +1,91 @@
package com.kexue.skills.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.kexue.skills.common.util.HttpUtil;
import com.kexue.skills.config.DeepSeekConfig;
import com.kexue.skills.entity.request.SkillAnalyzeRequest;
import com.kexue.skills.entity.request.SkillGenRequest;
import com.kexue.skills.entity.request.SkillRequest;
import com.kexue.skills.entity.response.SkillResponse;
import com.kexue.skills.service.SkillGenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 技能生成服务实现
*
* @author 维哥
* @since 2026-01-28
*/
@Slf4j
@Service
public class SkillGenServiceImpl implements SkillGenService {
@Autowired
private DeepSeekConfig deepSeekConfig;
/**
* 生成技能
*
* @param request 生成请求
* @return 生成结果
*/
@Override
public SkillResponse generateSkill(SkillGenRequest request) {
log.info("生成技能请求: {}", request);
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),
deepSeekConfig.getChat().getTemperature(), deepSeekConfig.getChat().getMaxTokens(),request.getPrompt());
try {
// 发送HTTP请求到deepseek API
String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
log.info("Deepseek API响应: {}", deepseekResponse);
// 解析deepseek返回结果
JSONObject responseJson = JSON.parseObject(deepseekResponse);
List<JSONObject> choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class);
if (choices != null && !choices.isEmpty()) {
// 获取最新的choice这里是第一个因为只有一个
JSONObject latestChoice = choices.get(0);
JSONObject message = latestChoice.getJSONObject("message");
String content = message.getString("content");
// 解析content中的JSON
JSONObject skillJson = JSON.parseObject(content);
SkillResponse skillResponse = new SkillResponse();
skillResponse.setName(skillJson.getString("name"));
skillResponse.setDescription(skillJson.getString("description"));
skillResponse.setTags(skillJson.getJSONArray("tags").toJavaList(String.class));
log.info("解析技能响应: {}", skillResponse);
return skillResponse;
}
} catch (Exception e) {
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
}
return null;
}
/**
* 分析技能
*
* @param request 分析请求
* @return 分析结果
*/
@Override
public String analyzeSkill(SkillAnalyzeRequest request) {
log.info("分析技能请求: {}", request);
// 这里可以实现技能分析逻辑
// 不需要数据库操作直接返回结果
return "技能分析成功: " + request.getSkillId();
}
}

View File

@ -27,6 +27,15 @@ spring:
max-request-size: 20MB max-request-size: 20MB
max-file-size: 20MB max-file-size: 20MB
ai:
deepseek:
base-url: https://api.deepseek.com
api-key: ${DEEPSEEK_API_KEY:sk-7334f88754a84a86abbd98fc907d3354}
chat:
model: deepseek-chat
temperature: 0.3
max-tokens: 500
# 包含公共配置文件 # 包含公共配置文件
spring.config.import: spring.config.import:
- classpath:application-common.yml - classpath:application-common.yml
@ -72,3 +81,4 @@ sms:
# 短信模板变量名,对应验证码的变量 # 短信模板变量名,对应验证码的变量
template-param-name: code template-param-name: code
template-cache: true template-cache: true