Merge remote main: accept deletion of application.properties (replaced by template)
This commit is contained in:
commit
d75f9266bb
15 files changed
+398
-208
No files matched your search
@@ -21,3 +21,5 @@ xuan-deduct-uniapp/unpackage/
|
||||
xuan-deduct-uniapp/.hbuilderx/
|
||||
|
||||
.trae/
|
||||
|
||||
src/main/resources/application.properties
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String, Object> paiPan(@RequestBody DaLiuRenRequest request) {
|
||||
|
||||
@@ -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<String, Object> paiPan(@RequestBody LiuYaoRequest request) {
|
||||
|
||||
@@ -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<String, Object> paiPan(@RequestBody MeiHuaRequest request) {
|
||||
|
||||
@@ -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<String, Object> generate(@RequestBody NamingRequest request) {
|
||||
|
||||
@@ -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<String, Object> paiPan(@RequestBody QiMenRequest request) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<String, Object> paiPan(@RequestBody ZhanXingRequest request) {
|
||||
|
||||
@@ -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<String, Object> paiPan(@RequestBody ZiWeiRequest request) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
+22
-4
@@ -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
|
||||
Reference in new issue
Block a user