diff --git a/.gitignore b/.gitignore index 086475c..e129733 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ xuan-deduct-uniapp/unpackage/ xuan-deduct-uniapp/.hbuilderx/ .trae/ + +src/main/resources/application.properties diff --git a/src/main/java/xuan/config/AiProperties.java b/src/main/java/xuan/config/AiProperties.java new file mode 100644 index 0000000..48900b9 --- /dev/null +++ b/src/main/java/xuan/config/AiProperties.java @@ -0,0 +1,58 @@ +package xuan.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "ai") +public class AiProperties { + + private String provider = "openai-compatible"; + private String apiKey = ""; + private String baseUrl = "https://api.openai.com"; + private String model = "gpt-3.5-turbo"; + private double temperature = 0.7; + private double topP = 0.8; + private int maxTokens = 2048; + private int connectTimeout = 60; + private int readTimeout = 120; + + private TencentHunyuan tencentHunyuan = new TencentHunyuan(); + + public static class TencentHunyuan { + private String secretId = ""; + private String secretKey = ""; + private String region = "ap-guangzhou"; + private String model = "hunyuan-lite"; + + public String getSecretId() { return secretId; } + public void setSecretId(String secretId) { this.secretId = secretId; } + public String getSecretKey() { return secretKey; } + public void setSecretKey(String secretKey) { this.secretKey = secretKey; } + public String getRegion() { return region; } + public void setRegion(String region) { this.region = region; } + public String getModel() { return model; } + public void setModel(String model) { this.model = model; } + } + + public String getProvider() { return provider; } + public void setProvider(String provider) { this.provider = provider; } + public String getApiKey() { return apiKey; } + public void setApiKey(String apiKey) { this.apiKey = apiKey; } + public String getBaseUrl() { return baseUrl; } + public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } + 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 double getTopP() { return topP; } + public void setTopP(double topP) { this.topP = topP; } + public int getMaxTokens() { return maxTokens; } + public void setMaxTokens(int maxTokens) { this.maxTokens = maxTokens; } + public int getConnectTimeout() { return connectTimeout; } + public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; } + public int getReadTimeout() { return readTimeout; } + public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } + public TencentHunyuan getTencentHunyuan() { return tencentHunyuan; } + public void setTencentHunyuan(TencentHunyuan tencentHunyuan) { this.tencentHunyuan = tencentHunyuan; } +} diff --git a/src/main/java/xuan/config/WebConfig.java b/src/main/java/xuan/config/WebConfig.java index 6e3ebb7..be0cf24 100644 --- a/src/main/java/xuan/config/WebConfig.java +++ b/src/main/java/xuan/config/WebConfig.java @@ -1,11 +1,21 @@ package xuan.config; import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .maxAge(3600); + } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { diff --git a/src/main/java/xuan/controller/BaZiController.java b/src/main/java/xuan/controller/BaZiController.java index 452632b..d71f484 100644 --- a/src/main/java/xuan/controller/BaZiController.java +++ b/src/main/java/xuan/controller/BaZiController.java @@ -8,7 +8,7 @@ import xuan.core.bazi.settings.BaZiJiChuSetting; import xuan.core.bazi.settings.BaZiShenShaSetting; import xuan.core.bazi.settings.BaZiGanZhiLiuYiSetting; import xuan.entity.DeductRecord; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import xuan.service.DeductRecordService; @@ -19,7 +19,8 @@ import java.util.Map; @RequestMapping("/api/bazi") public class BaZiController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @Autowired private DeductRecordService recordService; diff --git a/src/main/java/xuan/controller/DaLiuRenController.java b/src/main/java/xuan/controller/DaLiuRenController.java index fe2c89e..0a76f8b 100644 --- a/src/main/java/xuan/controller/DaLiuRenController.java +++ b/src/main/java/xuan/controller/DaLiuRenController.java @@ -1,10 +1,11 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.daliuren.DaLiuRen; import xuan.core.daliuren.settings.DaLiuRenJiChuSetting; import xuan.core.daliuren.settings.DaLiuRenShenShaSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.*; @@ -13,7 +14,8 @@ import java.util.*; @RequestMapping("/api/daliuren") public class DaLiuRenController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody DaLiuRenRequest request) { diff --git a/src/main/java/xuan/controller/LiuYaoController.java b/src/main/java/xuan/controller/LiuYaoController.java index ee1e0c2..79a9279 100644 --- a/src/main/java/xuan/controller/LiuYaoController.java +++ b/src/main/java/xuan/controller/LiuYaoController.java @@ -1,10 +1,11 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.liuyao.LiuYao; import xuan.core.liuyao.settings.LiuYaoJiChuSetting; import xuan.core.liuyao.settings.LiuYaoShenShaSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.HashMap; @@ -14,7 +15,8 @@ import java.util.Map; @RequestMapping("/api/liuyao") public class LiuYaoController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody LiuYaoRequest request) { diff --git a/src/main/java/xuan/controller/MeiHuaController.java b/src/main/java/xuan/controller/MeiHuaController.java index 670e29a..c751b8c 100644 --- a/src/main/java/xuan/controller/MeiHuaController.java +++ b/src/main/java/xuan/controller/MeiHuaController.java @@ -1,9 +1,10 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.meihua.MeiHua; import xuan.core.meihua.settings.MeiHuaJiChuSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.HashMap; @@ -13,7 +14,8 @@ import java.util.Map; @RequestMapping("/api/meihua") public class MeiHuaController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody MeiHuaRequest request) { diff --git a/src/main/java/xuan/controller/NamingController.java b/src/main/java/xuan/controller/NamingController.java index 1c84393..cd52d08 100644 --- a/src/main/java/xuan/controller/NamingController.java +++ b/src/main/java/xuan/controller/NamingController.java @@ -1,7 +1,8 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.*; @@ -10,7 +11,8 @@ import java.util.*; @RequestMapping("/api/naming") public class NamingController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/generate") public Map generate(@RequestBody NamingRequest request) { diff --git a/src/main/java/xuan/controller/QiMenController.java b/src/main/java/xuan/controller/QiMenController.java index 917a062..76558c0 100644 --- a/src/main/java/xuan/controller/QiMenController.java +++ b/src/main/java/xuan/controller/QiMenController.java @@ -1,9 +1,10 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.qimen.zhuan.QiMenZhuanPan; import xuan.core.qimen.zhuan.settings.QiMenZhuanPanJiChuSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.HashMap; @@ -13,7 +14,8 @@ import java.util.Map; @RequestMapping("/api/qimen") public class QiMenController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody QiMenRequest request) { diff --git a/src/main/java/xuan/controller/TaLuoController.java b/src/main/java/xuan/controller/TaLuoController.java index 64beca0..cb998ba 100644 --- a/src/main/java/xuan/controller/TaLuoController.java +++ b/src/main/java/xuan/controller/TaLuoController.java @@ -2,7 +2,7 @@ package xuan.controller; import org.springframework.web.bind.annotation.*; import org.springframework.beans.factory.annotation.Autowired; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import xuan.service.DeductRecordService; import xuan.entity.DeductRecord; @@ -14,7 +14,8 @@ import java.util.*; @RequestMapping("/api/taluo") public class TaLuoController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @Autowired private DeductRecordService recordService; diff --git a/src/main/java/xuan/controller/ZhanXingController.java b/src/main/java/xuan/controller/ZhanXingController.java index 61627bd..0a1b024 100644 --- a/src/main/java/xuan/controller/ZhanXingController.java +++ b/src/main/java/xuan/controller/ZhanXingController.java @@ -1,7 +1,8 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.*; @@ -10,7 +11,8 @@ import java.util.*; @RequestMapping("/api/zhanxing") public class ZhanXingController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody ZhanXingRequest request) { diff --git a/src/main/java/xuan/controller/ZiWeiController.java b/src/main/java/xuan/controller/ZiWeiController.java index 0022126..d6b066a 100644 --- a/src/main/java/xuan/controller/ZiWeiController.java +++ b/src/main/java/xuan/controller/ZiWeiController.java @@ -1,9 +1,10 @@ package xuan.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import xuan.core.ziwei.ZiWei; import xuan.core.ziwei.settings.ZiWeiJiChuSetting; -import xuan.service.HunYuanAiService; +import xuan.service.AiService; import xuan.service.PromptTemplates; import java.util.*; @@ -12,7 +13,8 @@ import java.util.*; @RequestMapping("/api/ziwei") public class ZiWeiController { - private final HunYuanAiService aiService = new HunYuanAiService(); + @Autowired + private AiService aiService; @PostMapping("/paipan") public Map paiPan(@RequestBody ZiWeiRequest request) { diff --git a/src/main/java/xuan/service/AiService.java b/src/main/java/xuan/service/AiService.java new file mode 100644 index 0000000..95318be --- /dev/null +++ b/src/main/java/xuan/service/AiService.java @@ -0,0 +1,272 @@ +package xuan.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import okhttp3.*; +import org.springframework.stereotype.Service; +import xuan.config.AiProperties; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Service +public class AiService { + + private final AiProperties properties; + private final OkHttpClient client; + + public AiService(AiProperties properties) { + this.properties = properties; + this.client = new OkHttpClient.Builder() + .connectTimeout(properties.getConnectTimeout(), TimeUnit.SECONDS) + .readTimeout(properties.getReadTimeout(), TimeUnit.SECONDS) + .writeTimeout(properties.getConnectTimeout(), TimeUnit.SECONDS) + .build(); + } + + public String chat(String systemPrompt, String userMessage) { + String provider = properties.getProvider(); + if ("tencent-hunyuan".equals(provider)) { + return chatTencentHunyuan(systemPrompt, userMessage); + } + return chatOpenAICompatible(systemPrompt, userMessage); + } + + private String chatOpenAICompatible(String systemPrompt, String userMessage) { + String apiKey = properties.getApiKey(); + if (apiKey == null || apiKey.isEmpty()) { + return "AI服务未配置,请在application.properties中设置ai.api-key。"; + } + + try { + String baseUrl = properties.getBaseUrl().replaceAll("/+$", ""); + String url = baseUrl + "/v1/chat/completions"; + + JSONObject requestBody = new JSONObject(); + requestBody.put("model", properties.getModel()); + + JSONArray messages = new JSONArray(); + JSONObject systemMsg = new JSONObject(); + systemMsg.put("role", "system"); + systemMsg.put("content", systemPrompt); + messages.add(systemMsg); + + JSONObject userMsg = new JSONObject(); + userMsg.put("role", "user"); + userMsg.put("content", userMessage); + messages.add(userMsg); + + requestBody.put("messages", messages); + requestBody.put("temperature", properties.getTemperature()); + requestBody.put("top_p", properties.getTopP()); + requestBody.put("max_tokens", properties.getMaxTokens()); + + String payload = requestBody.toJSONString(); + + Request request = new Request.Builder() + .url(url) + .post(RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"))) + .addHeader("Authorization", "Bearer " + apiKey) + .addHeader("Content-Type", "application/json; charset=utf-8") + .build(); + + try (Response response = client.newCall(request).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + if (!response.isSuccessful()) { + return "AI服务请求失败(HTTP " + response.code() + "):" + responseBody; + } + return parseOpenAIResponse(responseBody); + } + } catch (Exception e) { + e.printStackTrace(); + return "AI分析暂时不可用,请稍后再试。错误信息:" + e.getMessage(); + } + } + + private String parseOpenAIResponse(String responseBody) { + try { + JSONObject json = JSON.parseObject(responseBody); + JSONArray choices = json.getJSONArray("choices"); + if (choices != null && !choices.isEmpty()) { + JSONObject choice = choices.getJSONObject(0); + JSONObject message = choice.getJSONObject("message"); + if (message != null) { + return message.getString("content"); + } + } + return "AI未返回有效内容"; + } catch (Exception e) { + return "解析AI响应失败:" + e.getMessage(); + } + } + + private String chatTencentHunyuan(String systemPrompt, String userMessage) { + AiProperties.TencentHunyuan hy = properties.getTencentHunyuan(); + String secretId = hy.getSecretId(); + String secretKey = hy.getSecretKey(); + if (secretId == null || secretId.isEmpty() || secretKey == null || secretKey.isEmpty()) { + return "腾讯混元AI服务未配置,请设置ai.tencent-hunyuan.secret-id和ai.tencent-hunyuan.secret-key。"; + } + + try { + String response = sendTencentHunyuanRequest(systemPrompt, userMessage, secretId, secretKey, hy); + return parseTencentHunyuanResponse(response); + } catch (Exception e) { + e.printStackTrace(); + return "AI分析暂时不可用,请稍后再试。错误信息:" + e.getMessage(); + } + } + + private String sendTencentHunyuanRequest(String systemPrompt, String userMessage, + String secretId, String secretKey, + AiProperties.TencentHunyuan hy) throws IOException { + String host = "hunyuan.tencentcloudapi.com"; + String service = "hunyuan"; + String action = "ChatCompletions"; + String version = "2023-09-01"; + String region = hy.getRegion(); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + Date now = new Date(); + String date = sdf.format(now); + String timestamp = String.valueOf(now.getTime() / 1000); + + JSONObject requestBody = new JSONObject(); + requestBody.put("Model", hy.getModel()); + + JSONArray messages = new JSONArray(); + JSONObject systemMsg = new JSONObject(); + systemMsg.put("Role", "system"); + systemMsg.put("Content", systemPrompt); + messages.add(systemMsg); + + JSONObject userMsg = new JSONObject(); + userMsg.put("Role", "user"); + userMsg.put("Content", userMessage); + messages.add(userMsg); + + requestBody.put("Messages", messages); + requestBody.put("Temperature", properties.getTemperature()); + requestBody.put("TopP", properties.getTopP()); + + String payload = requestBody.toJSONString(); + String authorization = generateTc3Authorization(secretId, secretKey, timestamp, date, payload, host, service); + + Request request = new Request.Builder() + .url("https://" + host) + .post(RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"))) + .addHeader("Authorization", authorization) + .addHeader("Content-Type", "application/json; charset=utf-8") + .addHeader("Host", host) + .addHeader("X-TC-Action", action) + .addHeader("X-TC-Version", version) + .addHeader("X-TC-Timestamp", timestamp) + .addHeader("X-TC-Region", region) + .build(); + + try (Response response = client.newCall(request).execute()) { + String responseBody = response.body() != null ? response.body().string() : ""; + if (!response.isSuccessful()) { + return "{\"Response\":{\"Error\":{\"Message\":\"HTTP " + response.code() + ": " + responseBody + "\"}}}"; + } + return responseBody; + } + } + + private String generateTc3Authorization(String secretId, String secretKey, String timestamp, + String date, String payload, String host, String service) { + try { + String canonicalUri = "/"; + String canonicalQueryString = ""; + String canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:" + host + "\n"; + String signedHeaders = "content-type;host"; + String hashedRequestPayload = sha256Hex(payload); + + String canonicalRequest = "POST\n" + + canonicalUri + "\n" + + canonicalQueryString + "\n" + + canonicalHeaders + "\n" + + signedHeaders + "\n" + + hashedRequestPayload; + + String algorithm = "TC3-HMAC-SHA256"; + String credentialScope = date + "/" + service + "/tc3_request"; + String hashedCanonicalRequest = sha256Hex(canonicalRequest); + String stringToSign = algorithm + "\n" + + timestamp + "\n" + + credentialScope + "\n" + + hashedCanonicalRequest; + + byte[] secretDate = hmacSha256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date); + byte[] secretService = hmacSha256(secretDate, service); + byte[] secretSigning = hmacSha256(secretService, "tc3_request"); + String signature = bytesToHex(hmacSha256(secretSigning, stringToSign)); + + return algorithm + " " + + "Credential=" + secretId + "/" + credentialScope + ", " + + "SignedHeaders=" + signedHeaders + ", " + + "Signature=" + signature; + } catch (Exception e) { + throw new RuntimeException("Failed to generate authorization", e); + } + } + + private String parseTencentHunyuanResponse(String response) { + try { + JSONObject jsonResponse = JSON.parseObject(response); + JSONObject responseObj = jsonResponse.getJSONObject("Response"); + + if (responseObj.containsKey("Error")) { + JSONObject error = responseObj.getJSONObject("Error"); + return "AI服务错误:" + error.getString("Message"); + } + + JSONArray choices = responseObj.getJSONArray("Choices"); + if (choices != null && !choices.isEmpty()) { + JSONObject choice = choices.getJSONObject(0); + JSONObject message = choice.getJSONObject("Message"); + return message.getString("Content"); + } + + return "AI未返回有效内容"; + } catch (Exception e) { + return "解析AI响应失败:" + e.getMessage(); + } + } + + private static String sha256Hex(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + return bytesToHex(hash); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static byte[] hmacSha256(byte[] key, String message) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + return mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } +} diff --git a/src/main/java/xuan/service/HunYuanAiService.java b/src/main/java/xuan/service/HunYuanAiService.java deleted file mode 100644 index 85be75d..0000000 --- a/src/main/java/xuan/service/HunYuanAiService.java +++ /dev/null @@ -1,186 +0,0 @@ -package xuan.service; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; -import okhttp3.*; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.concurrent.TimeUnit; - -public class HunYuanAiService { - - private static final String SECRET_ID = System.getenv("TENCENT_SECRET_ID"); - private static final String SECRET_KEY = System.getenv("TENCENT_SECRET_KEY"); - private static final String SERVICE = "hunyuan"; - private static final String HOST = "hunyuan.tencentcloudapi.com"; - private static final String REGION = "ap-guangzhou"; - private static final String ACTION = "ChatCompletions"; - private static final String VERSION = "2023-09-01"; - - private static final OkHttpClient client = new OkHttpClient.Builder() - .connectTimeout(60, TimeUnit.SECONDS) - .readTimeout(120, TimeUnit.SECONDS) - .writeTimeout(60, TimeUnit.SECONDS) - .build(); - - public String chat(String systemPrompt, String userMessage) { - if (SECRET_ID == null || SECRET_KEY == null) { - return "AI服务未配置,请设置TENCENT_SECRET_ID和TENCENT_SECRET_KEY环境变量。"; - } - try { - String response = sendRequest(systemPrompt, userMessage); - return parseResponse(response); - } catch (Exception e) { - e.printStackTrace(); - return "AI分析暂时不可用,请稍后再试。错误信息:" + e.getMessage(); - } - } - - private String sendRequest(String systemPrompt, String userMessage) throws IOException { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - Date now = new Date(); - String date = sdf.format(now); - String timestamp = String.valueOf(now.getTime() / 1000); - - JSONObject requestBody = new JSONObject(); - requestBody.put("Model", "hunyuan-lite"); - - JSONArray messages = new JSONArray(); - - JSONObject systemMsg = new JSONObject(); - systemMsg.put("Role", "system"); - systemMsg.put("Content", systemPrompt); - messages.add(systemMsg); - - JSONObject userMsg = new JSONObject(); - userMsg.put("Role", "user"); - userMsg.put("Content", userMessage); - messages.add(userMsg); - - requestBody.put("Messages", messages); - requestBody.put("Temperature", 0.7); - requestBody.put("TopP", 0.8); - - String payload = requestBody.toJSONString(); - - String authorization = generateAuthorization(timestamp, date, payload); - - Request request = new Request.Builder() - .url("https://" + HOST) - .post(RequestBody.create(payload, MediaType.parse("application/json; charset=utf-8"))) - .addHeader("Authorization", authorization) - .addHeader("Content-Type", "application/json; charset=utf-8") - .addHeader("Host", HOST) - .addHeader("X-TC-Action", ACTION) - .addHeader("X-TC-Version", VERSION) - .addHeader("X-TC-Timestamp", timestamp) - .addHeader("X-TC-Region", REGION) - .build(); - - try (Response response = client.newCall(request).execute()) { - String responseBody = response.body() != null ? response.body().string() : ""; - if (!response.isSuccessful()) { - return "{\"Response\":{\"Error\":{\"Message\":\"HTTP " + response.code() + ": " + responseBody + "\"}}}"; - } - return responseBody; - } - } - - private String generateAuthorization(String timestamp, String date, String payload) { - try { - String httpRequestMethod = "POST"; - String canonicalUri = "/"; - String canonicalQueryString = ""; - String canonicalHeaders = "content-type:application/json; charset=utf-8\nhost:" + HOST + "\n"; - String signedHeaders = "content-type;host"; - - String hashedRequestPayload = sha256Hex(payload); - - String canonicalRequest = httpRequestMethod + "\n" + - canonicalUri + "\n" + - canonicalQueryString + "\n" + - canonicalHeaders + "\n" + - signedHeaders + "\n" + - hashedRequestPayload; - - String algorithm = "TC3-HMAC-SHA256"; - String credentialScope = date + "/" + SERVICE + "/tc3_request"; - String hashedCanonicalRequest = sha256Hex(canonicalRequest); - String stringToSign = algorithm + "\n" + - timestamp + "\n" + - credentialScope + "\n" + - hashedCanonicalRequest; - - byte[] secretDate = hmacSha256(("TC3" + SECRET_KEY).getBytes(StandardCharsets.UTF_8), date); - byte[] secretService = hmacSha256(secretDate, SERVICE); - byte[] secretSigning = hmacSha256(secretService, "tc3_request"); - String signature = bytesToHex(hmacSha256(secretSigning, stringToSign)); - - return algorithm + " " + - "Credential=" + SECRET_ID + "/" + credentialScope + ", " + - "SignedHeaders=" + signedHeaders + ", " + - "Signature=" + signature; - } catch (Exception e) { - throw new RuntimeException("Failed to generate authorization", e); - } - } - - private String parseResponse(String response) { - try { - JSONObject jsonResponse = JSON.parseObject(response); - JSONObject responseObj = jsonResponse.getJSONObject("Response"); - - if (responseObj.containsKey("Error")) { - JSONObject error = responseObj.getJSONObject("Error"); - return "AI服务错误:" + error.getString("Message"); - } - - JSONArray choices = responseObj.getJSONArray("Choices"); - if (choices != null && !choices.isEmpty()) { - JSONObject choice = choices.getJSONObject(0); - JSONObject message = choice.getJSONObject("Message"); - return message.getString("Content"); - } - - return "AI未返回有效内容"; - } catch (Exception e) { - return "解析AI响应失败:" + e.getMessage(); - } - } - - private static String sha256Hex(String input) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); - return bytesToHex(hash); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static byte[] hmacSha256(byte[] key, String message) { - try { - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(key, "HmacSHA256")); - return mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private static String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b)); - } - return sb.toString(); - } -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties.template similarity index 50% rename from src/main/resources/application.properties rename to src/main/resources/application.properties.template index 70f8d81..9a8c026 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties.template @@ -4,8 +4,8 @@ spring.application.name=xuan-utils-pro # MySQL Database Configuration spring.datasource.url=jdbc:mysql://localhost:3306/xuan_deduct?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver -spring.datasource.username=xuan_user -spring.datasource.password=xuan_password +spring.datasource.username=YOUR_DB_USERNAME +spring.datasource.password=YOUR_DB_PASSWORD # JPA Configuration spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect @@ -17,10 +17,28 @@ spring.jpa.properties.hibernate.format_sql=true logging.level.xuan=DEBUG # WeChat Mini Program Configuration -wechat.appid=${WECHAT_APPID:} -wechat.secret=${WECHAT_SECRET:} +wechat.appid=${WECHAT_APPID:YOUR_WECHAT_APPID} +wechat.secret=${WECHAT_SECRET:YOUR_WECHAT_SECRET} # WeChat H5 QR Login Configuration wechat.web.appid=${WECHAT_WEB_APPID:${WECHAT_APPID:}} wechat.web.secret=${WECHAT_WEB_SECRET:${WECHAT_SECRET:}} wechat.web.callback-base-url=${WECHAT_WEB_CALLBACK_BASE_URL:https://api.bemover.xyz} + +# AI Configuration +# Provider: openai-compatible | tencent-hunyuan +ai.provider=${AI_PROVIDER:openai-compatible} +ai.api-key=${AI_API_KEY:YOUR_AI_API_KEY} +ai.base-url=${AI_BASE_URL:https://api.openai.com} +ai.model=${AI_MODEL:gpt-3.5-turbo} +ai.temperature=0.7 +ai.top-p=0.8 +ai.max-tokens=2048 +ai.connect-timeout=60 +ai.read-timeout=120 + +# Tencent Hunyuan (used when ai.provider=tencent-hunyuan) +ai.tencent-hunyuan.secret-id=${TENCENT_SECRET_ID:YOUR_TENCENT_SECRET_ID} +ai.tencent-hunyuan.secret-key=${TENCENT_SECRET_KEY:YOUR_TENCENT_SECRET_KEY} +ai.tencent-hunyuan.region=ap-guangzhou +ai.tencent-hunyuan.model=hunyuan-lite