|
|
@@ -1,500 +1,737 @@
|
|
|
-package com.etotem.num.service;
|
|
|
-
|
|
|
-import com.google.gson.Gson;
|
|
|
-import com.google.gson.JsonObject;
|
|
|
-import org.springframework.beans.factory.annotation.Value;
|
|
|
-import org.springframework.http.HttpEntity;
|
|
|
-import org.springframework.http.HttpHeaders;
|
|
|
-import org.springframework.http.MediaType;
|
|
|
-import org.springframework.stereotype.Service;
|
|
|
-import org.springframework.web.client.RestTemplate;
|
|
|
-
|
|
|
-import java.util.Collections;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.LinkedHashMap;
|
|
|
-import java.util.Map;
|
|
|
-
|
|
|
-@Service
|
|
|
-public class DifyService {
|
|
|
-
|
|
|
- @Value("${num.dify.base-url}")
|
|
|
- private String baseUrl;
|
|
|
-
|
|
|
- @Value("${num.dify.api-key}")
|
|
|
- private String apiKey;
|
|
|
-
|
|
|
- private final RestTemplate restTemplate;
|
|
|
- private final Gson gson;
|
|
|
- private final ConfigService configService;
|
|
|
-
|
|
|
- public DifyService(RestTemplate restTemplate, Gson gson, ConfigService configService) {
|
|
|
- this.restTemplate = restTemplate;
|
|
|
- this.gson = gson;
|
|
|
- this.configService = configService;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Check whether Dify is configured with real credentials.
|
|
|
- * Returns true if api-key or base-url are still placeholders.
|
|
|
- */
|
|
|
- public boolean isMockMode() {
|
|
|
- return apiKey == null || apiKey.isEmpty() || apiKey.contains("your_")
|
|
|
- || baseUrl == null || baseUrl.isEmpty() || baseUrl.contains("your-");
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Invoke Dify chatflow, falling back to mock response when unavailable.
|
|
|
- */
|
|
|
- public String invokeChatflow(String query, String chartContext, String userId) {
|
|
|
- if (isMockMode()) {
|
|
|
- return generateMockResponse(chartContext, query);
|
|
|
- }
|
|
|
- try {
|
|
|
- JsonObject body = new JsonObject();
|
|
|
- body.addProperty("query", query);
|
|
|
- body.addProperty("user", userId);
|
|
|
- body.add("inputs", gson.toJsonTree(Collections.singletonMap("chart_context", chartContext)));
|
|
|
-
|
|
|
- HttpHeaders headers = new HttpHeaders();
|
|
|
- headers.set("Authorization", "Bearer " + apiKey);
|
|
|
- headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
- HttpEntity<String> entity = new HttpEntity<>(body.toString(), headers);
|
|
|
-
|
|
|
- String response = restTemplate.postForObject(baseUrl + "/chat-messages", entity, String.class);
|
|
|
- JsonObject json = gson.fromJson(response, JsonObject.class);
|
|
|
- return json.get("answer").getAsString();
|
|
|
- } catch (Exception e) {
|
|
|
- // Fallback to mock when real Dify call fails
|
|
|
- return generateMockResponse(chartContext, query);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Invoke Dify Workflow (for structured interpretation).
|
|
|
- * Uses POST /v1/workflows/run endpoint.
|
|
|
- * Returns the workflow outputs as JSON string.
|
|
|
- */
|
|
|
- public String invokeWorkflow(Map<String, Object> inputs, String userId) {
|
|
|
- if (isMockMode()) {
|
|
|
- return generateMockInterpretation(inputs);
|
|
|
- }
|
|
|
- try {
|
|
|
- String workflowAppId = configService.getValue("dify.workflow.app_id", "");
|
|
|
- JsonObject body = new JsonObject();
|
|
|
- body.addProperty("app_id", workflowAppId);
|
|
|
- body.add("inputs", gson.toJsonTree(inputs));
|
|
|
- body.addProperty("user", userId);
|
|
|
-
|
|
|
- HttpHeaders headers = new HttpHeaders();
|
|
|
- headers.set("Authorization", "Bearer " + apiKey);
|
|
|
- headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
- HttpEntity<String> entity = new HttpEntity<>(body.toString(), headers);
|
|
|
-
|
|
|
- String response = restTemplate.postForObject(baseUrl + "/workflows/run", entity, String.class);
|
|
|
- JsonObject json = gson.fromJson(response, JsonObject.class);
|
|
|
- JsonObject data = json.getAsJsonObject("data");
|
|
|
- if (data != null && data.has("outputs")) {
|
|
|
- return data.get("outputs").toString();
|
|
|
- }
|
|
|
- return json.toString();
|
|
|
- } catch (Exception e) {
|
|
|
- return generateMockInterpretation(inputs);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Generate a structured mock interpretation JSON from chart context.
|
|
|
- * Sections: mainCharacter, left (21-40), top (41-60), right (61+).
|
|
|
- */
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- String generateMockInterpretation(Map<String, Object> inputs) {
|
|
|
- try {
|
|
|
- String chartContext = (String) inputs.get("chart_context");
|
|
|
- JsonObject chart = gson.fromJson(chartContext, JsonObject.class);
|
|
|
- JsonObject pos = chart.getAsJsonObject("positions");
|
|
|
- if (pos == null) pos = chart;
|
|
|
-
|
|
|
- int O = getInt(pos, "O", 7);
|
|
|
- int P = getInt(pos, "P", 5);
|
|
|
- int Q = getInt(pos, "Q", 0);
|
|
|
- int R = getInt(pos, "R", 0);
|
|
|
- int V = getInt(pos, "V", 8);
|
|
|
- int W = getInt(pos, "W", 0);
|
|
|
- int X = getInt(pos, "X", 0);
|
|
|
- int S = getInt(pos, "S", 9);
|
|
|
- int T = getInt(pos, "T", 0);
|
|
|
- int U = getInt(pos, "U", 0);
|
|
|
-
|
|
|
- JsonObject result = new JsonObject();
|
|
|
-
|
|
|
- // 主性格解读
|
|
|
- JsonObject mainCharacter = new JsonObject();
|
|
|
- mainCharacter.addProperty("number", O);
|
|
|
- mainCharacter.addProperty("title", "主性格解读");
|
|
|
- mainCharacter.addProperty("content", generateMainInterpretation(O));
|
|
|
- result.add("mainCharacter", mainCharacter);
|
|
|
-
|
|
|
- // 左区(21-40岁)
|
|
|
- JsonObject left = new JsonObject();
|
|
|
- left.addProperty("number", P);
|
|
|
- left.addProperty("title", "左区(21-40岁)");
|
|
|
- left.addProperty("content", generateZoneInterpretation("左区(青年发展期)", P, Q, R));
|
|
|
- result.add("left", left);
|
|
|
-
|
|
|
- // 顶部(41-60岁)
|
|
|
- JsonObject top = new JsonObject();
|
|
|
- top.addProperty("number", V);
|
|
|
- top.addProperty("title", "顶部(41-60岁)");
|
|
|
- top.addProperty("content", generateZoneInterpretation("顶部(中年发展期)", V, W, X));
|
|
|
- result.add("top", top);
|
|
|
-
|
|
|
- // 右区(61+岁)
|
|
|
- JsonObject right = new JsonObject();
|
|
|
- right.addProperty("number", S);
|
|
|
- right.addProperty("title", "右区(61+岁)");
|
|
|
- right.addProperty("content", generateZoneInterpretation("右区(成熟发展期)", S, T, U));
|
|
|
- result.add("right", right);
|
|
|
-
|
|
|
- return result.toString();
|
|
|
- } catch (Exception e) {
|
|
|
- return "{\"mainCharacter\":{\"number\":7,\"title\":\"主性格解读\",\"content\":\"请查看完整命盘以获取详细解读。\"}}";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // ── Academic orientation (US-9.1) ───────────────────────────────────────────────
|
|
|
-
|
|
|
- public String invokeAcademicWorkflow(Map<String, Object> inputs, String userId) {
|
|
|
- if (isMockMode()) {
|
|
|
- return generateMockAcademicInterpretation(inputs);
|
|
|
- }
|
|
|
- try {
|
|
|
- String workflowAppId = configService.getValue("dify.workflow.app_id", "");
|
|
|
- JsonObject body = new JsonObject();
|
|
|
- body.addProperty("app_id", workflowAppId);
|
|
|
- body.add("inputs", gson.toJsonTree(inputs));
|
|
|
- body.addProperty("user", userId);
|
|
|
-
|
|
|
- HttpHeaders headers = new HttpHeaders();
|
|
|
- headers.set("Authorization", "Bearer " + apiKey);
|
|
|
- headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
- HttpEntity<String> entity = new HttpEntity<>(body.toString(), headers);
|
|
|
-
|
|
|
- String response = restTemplate.postForObject(baseUrl + "/workflows/run", entity, String.class);
|
|
|
- JsonObject json = gson.fromJson(response, JsonObject.class);
|
|
|
- JsonObject data = json.getAsJsonObject("data");
|
|
|
- if (data != null && data.has("outputs")) {
|
|
|
- return data.get("outputs").toString();
|
|
|
- }
|
|
|
- return json.toString();
|
|
|
- } catch (Exception e) {
|
|
|
- return generateMockAcademicInterpretation(inputs);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- String generateMockAcademicInterpretation(Map<String, Object> inputs) {
|
|
|
- try {
|
|
|
- String chartContext = (String) inputs.get("chart_context");
|
|
|
- JsonObject chart = gson.fromJson(chartContext, JsonObject.class);
|
|
|
- JsonObject positions = chart.getAsJsonObject("positions");
|
|
|
- if (positions == null) positions = chart;
|
|
|
-
|
|
|
- int O = getInt(positions, "O", 7);
|
|
|
- int P = getInt(positions, "P", 5);
|
|
|
- int V = getInt(positions, "V", 8);
|
|
|
- int S = getInt(positions, "S", 9);
|
|
|
- int I = getInt(positions, "I", 0);
|
|
|
- int J = getInt(positions, "J", 0);
|
|
|
- int K = getInt(positions, "K", 0);
|
|
|
- int L = getInt(positions, "L", 0);
|
|
|
-
|
|
|
- JsonObject result = new JsonObject();
|
|
|
-
|
|
|
- // 天赋倾向
|
|
|
- JsonObject talentTendency = new JsonObject();
|
|
|
- talentTendency.addProperty("title", "天赋倾向");
|
|
|
- talentTendency.addProperty("content", generateAcademicTalent(O, I, J, K, L));
|
|
|
- result.add("talentTendency", talentTendency);
|
|
|
-
|
|
|
- // 适合方向
|
|
|
- JsonObject suitableDirection = new JsonObject();
|
|
|
- suitableDirection.addProperty("title", "适合方向");
|
|
|
- suitableDirection.addProperty("content", "根据命盘数字组合分析,以下是不同方向的匹配度参考:");
|
|
|
- Map<String, Integer> percentages = generateDirectionMatchPercentages(O, P, V, S);
|
|
|
- JsonObject matchJson = new Gson().toJsonTree(percentages).getAsJsonObject();
|
|
|
- suitableDirection.add("matchPercentages", matchJson);
|
|
|
- result.add("suitableDirection", suitableDirection);
|
|
|
-
|
|
|
- // 学习特征
|
|
|
- JsonObject learningStyle = new JsonObject();
|
|
|
- learningStyle.addProperty("title", "学习特征");
|
|
|
- learningStyle.addProperty("content", generateLearningStyle(I, J, K, L, P, V, S));
|
|
|
- result.add("learningStyle", learningStyle);
|
|
|
-
|
|
|
- // 亲子沟通建议
|
|
|
- JsonObject parentingAdvice = new JsonObject();
|
|
|
- parentingAdvice.addProperty("title", "亲子沟通建议");
|
|
|
- parentingAdvice.addProperty("content", generateParentingAdvice(O));
|
|
|
- result.add("parentingAdvice", parentingAdvice);
|
|
|
-
|
|
|
- // 关键期提醒
|
|
|
- JsonObject keyPeriods = new JsonObject();
|
|
|
- keyPeriods.addProperty("title", "关键期提醒");
|
|
|
- keyPeriods.addProperty("content", generateKeyPeriods(V, getInt(positions, "W", 0), getInt(positions, "X", 0)));
|
|
|
- result.add("keyPeriods", keyPeriods);
|
|
|
-
|
|
|
- return result.toString();
|
|
|
- } catch (Exception e) {
|
|
|
- JsonObject fallback = new JsonObject();
|
|
|
- fallback.addProperty("title", "学业方向分析");
|
|
|
- fallback.addProperty("content", "命盘分析完成。请使用完整功能获取详细学业方向建议。");
|
|
|
- JsonObject wrapped = new JsonObject();
|
|
|
- wrapped.add("talentTendency", fallback);
|
|
|
- return wrapped.toString();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private String generateAcademicTalent(int O, int I, int J, int K, int L) {
|
|
|
- String mainDesc = NUMBER_DESC.getOrDefault(O > 9 ? 0 : O,
|
|
|
- O > 9 ? "卓越数能量,蕴含强大潜力" : "能量独特,需结合整体命盘解读");
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("孩子的主性格数字为 ").append(O).append("(").append(mainDesc).append(")。\n\n");
|
|
|
- sb.append("内在基础数字:");
|
|
|
- sb.append("日(").append(I).append(") ").append("月(").append(J).append(") ");
|
|
|
- sb.append("年前半(").append(K).append(") ").append("年后半(").append(L).append(")").append("\n\n");
|
|
|
- sb.append("这些数字共同构成了孩子的天赋底色:\n");
|
|
|
- sb.append("• 主性格数字反映核心特质与驱动力\n");
|
|
|
- sb.append("• 内在基础数字揭示潜意识需求与安全感来源\n");
|
|
|
- sb.append("建议结合具体年龄阶段观察和引导,避免标签化。");
|
|
|
- return sb.toString();
|
|
|
- }
|
|
|
-
|
|
|
- private Map<String, Integer> generateDirectionMatchPercentages(int O, int P, int V, int S) {
|
|
|
- Map<String, Integer> map = new LinkedHashMap<>();
|
|
|
- int humanities = 50 + (P==2?10: P==4?15: P==7?12: P==1?-5: P==5?5: 0)
|
|
|
- + (V==2?10: V==4?15: V==7?12: V==1?-5: V==5?5: 0)
|
|
|
- + (S==2?10: S==4?15: S==7?12: S==1?-5: S==5?5: 0);
|
|
|
- int science = 50 + (P==1?12: P==5?10: P==8?15: P==2?-5: P==4?8: 0)
|
|
|
- + (V==1?12: V==5?10: V==8?15: V==2?-5: V==4?8: 0)
|
|
|
- + (S==1?12: S==5?10: S==8?15: S==2?-5: S==4?8: 0);
|
|
|
- int arts = 50 + (P==3?15: P==6?10: P==9?5: 0)
|
|
|
- + (V==3?15: V==6?10: V==9?5: 0)
|
|
|
- + (S==3?15: S==6?10: S==9?5: 0);
|
|
|
- int sports = 50 + (P==1?10: P==5?15: P==8?10: 0)
|
|
|
- + (V==1?10: V==5?15: V==8?10: 0)
|
|
|
- + (S==1?10: S==5?15: S==8?10: 0);
|
|
|
- map.put("文科", Math.min(95, Math.max(5, humanities)));
|
|
|
- map.put("理科", Math.min(95, Math.max(5, science)));
|
|
|
- map.put("艺术", Math.min(95, Math.max(5, arts)));
|
|
|
- map.put("体育", Math.min(95, Math.max(5, sports)));
|
|
|
- return map;
|
|
|
- }
|
|
|
-
|
|
|
- private String generateLearningStyle(int I, int J, int K, int L, int P, int V, int S) {
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("从内在基础数字(日、月、年前半、年后半)分析学习特征:\n\n");
|
|
|
- String I_desc = NUMBER_DESC.getOrDefault(I > 9 ? 0 : I, "独特能量组合");
|
|
|
- sb.append("• 外在表现(日 ").append(I).append("):").append(I_desc).append("。\n");
|
|
|
- String J_desc = NUMBER_DESC.getOrDefault(J > 9 ? 0 : J, "独特能量组合");
|
|
|
- sb.append("• 内在需求(月 ").append(J).append("):").append(J_desc).append("。\n");
|
|
|
- String K_desc = NUMBER_DESC.getOrDefault(K > 9 ? 0 : K, "独特能量组合");
|
|
|
- sb.append("• 早年家庭影响(年前半 ").append(K).append("):").append(K_desc).append("。\n");
|
|
|
- String L_desc = NUMBER_DESC.getOrDefault(L > 9 ? 0 : L, "独特能量组合");
|
|
|
- sb.append("• 后天发展潜力(年后半 ").append(L).append("):").append(L_desc).append("。\n\n");
|
|
|
- sb.append("综合来看,建议注意:\n");
|
|
|
- sb.append("1. 日与月的搭配体现孩子表里一致程度;\n");
|
|
|
- sb.append("2. 年前半与年后半的关联揭示学习潜能的可塑性;\n");
|
|
|
- sb.append("3. 发展阶段数字(左区").append(P).append("、顶部").append(V).append("、右区").append(S).append(")反映不同年龄段的精力投入方向。\n");
|
|
|
- return sb.toString();
|
|
|
- }
|
|
|
-
|
|
|
- private String generateParentingAdvice(int O) {
|
|
|
- String mainDesc = NUMBER_DESC.getOrDefault(O > 9 ? 0 : O,
|
|
|
- O > 9 ? "卓越数能量,蕴含强大潜力" : "能量独特,需结合整体命盘解读");
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("基于主性格数字 ").append(O).append(",亲子沟通建议:\n\n");
|
|
|
- switch (O) {
|
|
|
- case 1:
|
|
|
- sb.append("• 尊重孩子的独立性,给予自主决策空间;\n");
|
|
|
- sb.append("• 避免过度干涉,多用鼓励代替命令;\n");
|
|
|
- break;
|
|
|
- case 2:
|
|
|
- sb.append("• 注重情感连接,营造安全和谐的家庭氛围;\n");
|
|
|
- sb.append("• 多倾听少批评,培养合作意识;\n");
|
|
|
- break;
|
|
|
- case 3:
|
|
|
- sb.append("• 鼓励创意表达,提供多样化活动体验;\n");
|
|
|
- sb.append("• 避免刻板说教,用游戏化方式互动;\n");
|
|
|
- break;
|
|
|
- case 4:
|
|
|
- sb.append("• 建立清晰规则,保持前后一致性;\n");
|
|
|
- sb.append("• 提供结构化学习环境,循序渐进;\n");
|
|
|
- break;
|
|
|
- case 5:
|
|
|
- sb.append("• 允许探索与试错,避免过度束缚;\n");
|
|
|
- sb.append("• 多接触外界,满足好奇心;\n");
|
|
|
- break;
|
|
|
- case 6:
|
|
|
- sb.append("• 给予充分关爱与责任感培养平衡;\n");
|
|
|
- sb.append("• 参与家庭事务,增强归属感;\n");
|
|
|
- break;
|
|
|
- case 7:
|
|
|
- sb.append("• 尊重思考空间,避免过多社交压力;\n");
|
|
|
- sb.append("• 提供深度阅读与探索资源;\n");
|
|
|
- break;
|
|
|
- case 8:
|
|
|
- sb.append("• 设定目标激励机制,培养领导力;\n");
|
|
|
- sb.append("• 给予展示能力的机会;\n");
|
|
|
- break;
|
|
|
- case 9:
|
|
|
- sb.append("• 培养同理心与社会责任感;\n");
|
|
|
- sb.append("• 鼓励关怀他人,避免理想主义挫败;\n");
|
|
|
- break;
|
|
|
- default:
|
|
|
- sb.append("• 因材施教,关注孩子独特天赋与短板;\n");
|
|
|
- sb.append("• 保持耐心,接受成长过程的起伏;\n");
|
|
|
- }
|
|
|
- sb.append("\n以上建议仅供参考,每个孩子都是独一无二的个体。");
|
|
|
- return sb.toString();
|
|
|
- }
|
|
|
-
|
|
|
- private String generateKeyPeriods(int V, int W, int X) {
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("关键年龄参考(基于外部三区数字):\n\n");
|
|
|
- sb.append("• 左区(青年发展期 21-40岁):主能量数字 ").append(V).append(" — 大学至立业阶段;\n");
|
|
|
- sb.append("• 顶部(中年发展期 41-60岁):主能量数字 ").append(V).append(" — 事业高峰期;\n");
|
|
|
- sb.append("• 右区(成熟发展期 61岁+):主能量数字 ").append(V).append(" — 人生总结期;\n\n");
|
|
|
- sb.append("对于K12阶段:\n");
|
|
|
- sb.append("• 18岁前重点关注[外在表现]日能量与[内在需求]月能量的平衡;\n");
|
|
|
- sb.append("• 发展期数字变化可视为各年龄段精力重心;\n");
|
|
|
- sb.append("建议结合具体命盘细节咨询能量师获取个性化方案。");
|
|
|
- return sb.toString();
|
|
|
- }
|
|
|
-
|
|
|
- private String generateMainInterpretation(int number) {
|
|
|
- String desc = NUMBER_DESC.getOrDefault(number,
|
|
|
- number > 9 ? "卓越数能量,蕴含强大潜力" : "能量独特,需结合整体命盘解读");
|
|
|
- return String.format(
|
|
|
- "您的核心数字为 %d,代表「%s」。\n\n" +
|
|
|
- "此数字奠定了您整体的性格基调与人生轨迹方向。" +
|
|
|
- "在决策、人际和事业发展中,%d 的能量将持续发挥作用。" +
|
|
|
- "建议深入了解此数字的优势与调整方向,以更好地发挥天赋潜能。",
|
|
|
- number, desc, number);
|
|
|
- }
|
|
|
-
|
|
|
- private String generateZoneInterpretation(String zoneName, int a, int b, int c) {
|
|
|
- String desc = NUMBER_DESC.getOrDefault(a,
|
|
|
- a > 9 ? "卓越数能量" : "独特能量组合");
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- sb.append(zoneName).append("的能量数字为 ").append(a);
|
|
|
- if (b != 0) sb.append(" → ").append(b);
|
|
|
- if (c != 0) sb.append(" → ").append(c);
|
|
|
- sb.append(",核心数字 ").append(a).append(" 代表「").append(desc).append("」。\n\n");
|
|
|
- sb.append("此阶段您在以下方面将有明显体现:\n");
|
|
|
- sb.append("• 个人成长与发展方向\n");
|
|
|
- sb.append("• 人际关系与社交模式\n");
|
|
|
- sb.append("• 事业机遇与挑战\n\n");
|
|
|
- sb.append("建议关注数字 ").append(a).append(" 的正向特质,同时觉察其过度或不足带来的影响。");
|
|
|
- return sb.toString();
|
|
|
- }
|
|
|
-
|
|
|
- // ── Mock response generation ──────────────────────────────────────────────
|
|
|
-
|
|
|
- /** Number descriptions for mock interpretations. */
|
|
|
- private static final Map<Integer, String> NUMBER_DESC = new HashMap<>();
|
|
|
- static {
|
|
|
- NUMBER_DESC.put(1, "独立、创造与领导力,开创性强,适合自主发展");
|
|
|
- NUMBER_DESC.put(2, "合作、平衡与细腻感知,擅长沟通与协调");
|
|
|
- NUMBER_DESC.put(3, "表达、创意与社交活力,富有艺术天赋");
|
|
|
- NUMBER_DESC.put(4, "稳定、务实与秩序感,执行力强,脚踏实地");
|
|
|
- NUMBER_DESC.put(5, "自由、变化与冒险精神,适应力强,多才多艺");
|
|
|
- NUMBER_DESC.put(6, "责任、关爱与和谐追求,家庭观念重");
|
|
|
- NUMBER_DESC.put(7, "分析、深度思考与灵性探索,求知欲强");
|
|
|
- NUMBER_DESC.put(8, "权力、财富与成就导向,商业头脑出众");
|
|
|
- NUMBER_DESC.put(9, "智慧、慈悲与完成之力,格局宏大");
|
|
|
- NUMBER_DESC.put(11, "直觉敏锐、灵感充沛,拥有启蒙他人的天赋(卓越数)");
|
|
|
- NUMBER_DESC.put(22, "将理想变为现实的建造者,执行力与远见并存(卓越数)");
|
|
|
- NUMBER_DESC.put(33, "大爱无私、奉献疗愈,是精神层面的引领者(卓越数)");
|
|
|
- }
|
|
|
-
|
|
|
- private static final Map<String, String> ZONE_DESC = new HashMap<>();
|
|
|
- static {
|
|
|
- ZONE_DESC.put("left", "青年发展期(21-40岁)");
|
|
|
- ZONE_DESC.put("top", "中年发展期(41-60岁)");
|
|
|
- ZONE_DESC.put("right", "成熟发展期(61岁以上)");
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Generate a structured mock initial interpretation from chart data.
|
|
|
- */
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
- String generateMockResponse(String chartContext, String query) {
|
|
|
- try {
|
|
|
- JsonObject chart = gson.fromJson(chartContext, JsonObject.class);
|
|
|
- JsonObject pos = chart.getAsJsonObject("positions");
|
|
|
- if (pos == null) pos = chart; // flat positions
|
|
|
-
|
|
|
- int O = getInt(pos, "O", 0);
|
|
|
- int I = getInt(pos, "I", 0);
|
|
|
- int J = getInt(pos, "J", 0);
|
|
|
- int K = getInt(pos, "K", 0);
|
|
|
- int L = getInt(pos, "L", 0);
|
|
|
- int P = getInt(pos, "P", 0);
|
|
|
- int Q = getInt(pos, "Q", 0);
|
|
|
- int R = getInt(pos, "R", 0);
|
|
|
- int V = getInt(pos, "V", 0);
|
|
|
- int W = getInt(pos, "W", 0);
|
|
|
- int X = getInt(pos, "X", 0);
|
|
|
- int S = getInt(pos, "S", 0);
|
|
|
- int T = getInt(pos, "T", 0);
|
|
|
- int U = getInt(pos, "U", 0);
|
|
|
-
|
|
|
- String mainDesc = NUMBER_DESC.getOrDefault(O,
|
|
|
- O > 9 ? "卓越数能量,蕴含强大潜力" : "能量独特,需结合整体命盘解读");
|
|
|
-
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("🔮 数字命盘初始解读\n\n");
|
|
|
- sb.append("根据您的出生信息,已为您生成完整命盘:\n\n");
|
|
|
-
|
|
|
- sb.append("【主性格数字 · ").append(O).append("】\n");
|
|
|
- sb.append("您的核心数字为 ").append(O).append(",代表").append(mainDesc).append("。\n\n");
|
|
|
-
|
|
|
- sb.append("【内在基础能量(底部左→右:日→月→年前半→年后半)】\n");
|
|
|
- sb.append("• 日能量(出生日/外在表现)— ").append(I).append("\n");
|
|
|
- sb.append("• 月能量(出生月/内在需求)— ").append(J).append("\n");
|
|
|
- sb.append("• 年前半(早年家庭影响)— ").append(K).append("\n");
|
|
|
- sb.append("• 年后半(后天发展潜力)— ").append(L).append("\n\n");
|
|
|
-
|
|
|
- sb.append("【发展阶段能量】\n");
|
|
|
- sb.append("• ").append(ZONE_DESC.get("left")).append(":").append(P).append(" → ").append(Q).append(" → ").append(R).append("\n");
|
|
|
- sb.append("• ").append(ZONE_DESC.get("top")).append(":").append(V).append(" → ").append(W).append(" → ").append(X).append("\n");
|
|
|
- sb.append("• ").append(ZONE_DESC.get("right")).append(":").append(S).append(" → ").append(T).append(" → ").append(U).append("\n\n");
|
|
|
-
|
|
|
- // Tailored response based on user question
|
|
|
- if (query != null && !query.trim().isEmpty() && !query.contains("综合解读")) {
|
|
|
- String cleanQuery = query.replace("请根据数字命盘为用户提供一个综合解读。用户关心的问题:", "").trim();
|
|
|
- if (!cleanQuery.isEmpty()) {
|
|
|
- sb.append("【关于您关心的问题 — ").append(cleanQuery).append("】\n");
|
|
|
- sb.append("从数字能量角度看,您当前的能量组合(主性格").append(O);
|
|
|
- sb.append(",发展期能量").append(P).append("/").append(V).append("/").append(S).append(")");
|
|
|
- sb.append("显示此方面存在发展潜力。建议结合具体情况进行更深入的咨询分析。\n\n");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- sb.append("💡 温馨提示:以上为系统自动生成的初始解读。您可以在下方输入具体问题,获取更深入的分析。");
|
|
|
-
|
|
|
- return sb.toString();
|
|
|
- } catch (Exception e) {
|
|
|
- // Ultimate fallback — plain text
|
|
|
- return "🔮 数字命盘已生成。您可以在下方输入您关心的问题获取详细解读。";
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private int getInt(JsonObject obj, String key, int fallback) {
|
|
|
- try {
|
|
|
- return obj.get(key).getAsInt();
|
|
|
- } catch (Exception e) {
|
|
|
- return fallback;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
+package com.etotem.num.service;
|
|
|
+
|
|
|
+import com.google.gson.Gson;
|
|
|
+import com.google.gson.JsonArray;
|
|
|
+import com.google.gson.JsonElement;
|
|
|
+import com.google.gson.JsonObject;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.http.HttpEntity;
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class DifyService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(DifyService.class);
|
|
|
+
|
|
|
+ @Value("${num.dify.base-url}")
|
|
|
+ private String baseUrl;
|
|
|
+
|
|
|
+ @Value("${num.dify.api-key}")
|
|
|
+ private String apiKey;
|
|
|
+
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
+ private final Gson gson;
|
|
|
+ private final ConfigService configService;
|
|
|
+
|
|
|
+ public DifyService(RestTemplate restTemplate, Gson gson, ConfigService configService) {
|
|
|
+ this.restTemplate = restTemplate;
|
|
|
+ this.gson = gson;
|
|
|
+ this.configService = configService;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Check whether Dify is configured with real credentials.
|
|
|
+ * Returns true if api-key or base-url are still placeholders.
|
|
|
+ */
|
|
|
+ public boolean isMockMode() {
|
|
|
+ return apiKey == null || apiKey.isEmpty() || apiKey.contains("your_")
|
|
|
+ || baseUrl == null || baseUrl.isEmpty() || baseUrl.contains("your-");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Invoke Dify chatflow, falling back to mock response when unavailable.
|
|
|
+ */
|
|
|
+ public String invokeChatflow(String query, String chartContext, String userId) {
|
|
|
+ if (isMockMode()) {
|
|
|
+ return generateMockResponse(chartContext, query);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ JsonObject body = new JsonObject();
|
|
|
+ body.addProperty("query", query);
|
|
|
+ body.addProperty("user", userId);
|
|
|
+ body.add("inputs", gson.toJsonTree(Collections.singletonMap("chart_context", chartContext)));
|
|
|
+
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.set("Authorization", "Bearer " + apiKey);
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ HttpEntity<String> entity = new HttpEntity<>(body.toString(), headers);
|
|
|
+
|
|
|
+ String response = restTemplate.postForObject(baseUrl + "/chat-messages", entity, String.class);
|
|
|
+ JsonObject json = gson.fromJson(response, JsonObject.class);
|
|
|
+ return json.get("answer").getAsString();
|
|
|
+ } catch (Exception e) {
|
|
|
+ // Fallback to mock when real Dify call fails
|
|
|
+ return generateMockResponse(chartContext, query);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Invoke Dify Workflow (for structured interpretation).
|
|
|
+ * Uses POST /v1/workflows/run endpoint.
|
|
|
+ * Returns the workflow outputs as JSON string.
|
|
|
+ */
|
|
|
+ public String invokeWorkflow(Map<String, Object> inputs, String userId) {
|
|
|
+ if (isMockMode()) {
|
|
|
+ return generateMockInterpretation(inputs);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ String workflowAppId = configService.getValue("dify.workflow.app_id", "");
|
|
|
+ JsonObject body = new JsonObject();
|
|
|
+ body.addProperty("app_id", workflowAppId);
|
|
|
+ body.add("inputs", gson.toJsonTree(inputs));
|
|
|
+ body.addProperty("user", userId);
|
|
|
+
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.set("Authorization", "Bearer " + apiKey);
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ HttpEntity<String> entity = new HttpEntity<>(body.toString(), headers);
|
|
|
+
|
|
|
+ String response = restTemplate.postForObject(baseUrl + "/workflows/run", entity, String.class);
|
|
|
+ JsonObject json = gson.fromJson(response, JsonObject.class);
|
|
|
+ JsonObject data = json.getAsJsonObject("data");
|
|
|
+ if (data != null && data.has("outputs")) {
|
|
|
+ return data.get("outputs").toString();
|
|
|
+ }
|
|
|
+ return json.toString();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return generateMockInterpretation(inputs);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate a structured mock interpretation JSON from chart context.
|
|
|
+ * Sections: mainCharacter, left (21-40), top (41-60), right (61+).
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ String generateMockInterpretation(Map<String, Object> inputs) {
|
|
|
+ try {
|
|
|
+ String chartContext = (String) inputs.get("chart_context");
|
|
|
+ JsonObject chart = gson.fromJson(chartContext, JsonObject.class);
|
|
|
+ JsonObject pos = chart.getAsJsonObject("positions");
|
|
|
+ if (pos == null) pos = chart;
|
|
|
+
|
|
|
+ int O = getInt(pos, "O", 7);
|
|
|
+ int P = getInt(pos, "P", 5);
|
|
|
+ int Q = getInt(pos, "Q", 0);
|
|
|
+ int R = getInt(pos, "R", 0);
|
|
|
+ int V = getInt(pos, "V", 8);
|
|
|
+ int W = getInt(pos, "W", 0);
|
|
|
+ int X = getInt(pos, "X", 0);
|
|
|
+ int S = getInt(pos, "S", 9);
|
|
|
+ int T = getInt(pos, "T", 0);
|
|
|
+ int U = getInt(pos, "U", 0);
|
|
|
+
|
|
|
+ JsonObject result = new JsonObject();
|
|
|
+
|
|
|
+ // 主性格解读
|
|
|
+ JsonObject mainCharacter = new JsonObject();
|
|
|
+ mainCharacter.addProperty("number", O);
|
|
|
+ mainCharacter.addProperty("title", "主性格解读");
|
|
|
+ mainCharacter.addProperty("content", generateMainInterpretation(O));
|
|
|
+ result.add("mainCharacter", mainCharacter);
|
|
|
+
|
|
|
+ // 左区(21-40岁)
|
|
|
+ JsonObject left = new JsonObject();
|
|
|
+ left.addProperty("number", P);
|
|
|
+ left.addProperty("title", "左区(21-40岁)");
|
|
|
+ left.addProperty("content", generateZoneInterpretation("左区(青年发展期)", P, Q, R));
|
|
|
+ result.add("left", left);
|
|
|
+
|
|
|
+ // 顶部(41-60岁)
|
|
|
+ JsonObject top = new JsonObject();
|
|
|
+ top.addProperty("number", V);
|
|
|
+ top.addProperty("title", "顶部(41-60岁)");
|
|
|
+ top.addProperty("content", generateZoneInterpretation("顶部(中年发展期)", V, W, X));
|
|
|
+ result.add("top", top);
|
|
|
+
|
|
|
+ // 右区(61+岁)
|
|
|
+ JsonObject right = new JsonObject();
|
|
|
+ right.addProperty("number", S);
|
|
|
+ right.addProperty("title", "右区(61+岁)");
|
|
|
+ right.addProperty("content", generateZoneInterpretation("右区(成熟发展期)", S, T, U));
|
|
|
+ result.add("right", right);
|
|
|
+
|
|
|
+ return result.toString();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return "{\"mainCharacter\":{\"number\":7,\"title\":\"主性格解读\",\"content\":\"请查看完整命盘以获取详细解读。\"}}";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── Academic orientation (US-9.1) ───────────────────────────────────────────────
|
|
|
+
|
|
|
+ public String invokeAcademicWorkflow(Map<String, Object> inputs, String userId) {
|
|
|
+ if (isMockMode()) {
|
|
|
+ return generateMockAcademicInterpretation(inputs);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ String workflowAppId = configService.getValue("dify.workflow.app_id", "");
|
|
|
+ JsonObject body = new JsonObject();
|
|
|
+ body.addProperty("app_id", workflowAppId);
|
|
|
+ body.add("inputs", gson.toJsonTree(inputs));
|
|
|
+ body.addProperty("user", userId);
|
|
|
+
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.set("Authorization", "Bearer " + apiKey);
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ HttpEntity<String> entity = new HttpEntity<>(body.toString(), headers);
|
|
|
+
|
|
|
+ String response = restTemplate.postForObject(baseUrl + "/workflows/run", entity, String.class);
|
|
|
+ JsonObject json = gson.fromJson(response, JsonObject.class);
|
|
|
+ JsonObject data = json.getAsJsonObject("data");
|
|
|
+ if (data != null && data.has("outputs")) {
|
|
|
+ return data.get("outputs").toString();
|
|
|
+ }
|
|
|
+ return json.toString();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return generateMockAcademicInterpretation(inputs);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ String generateMockAcademicInterpretation(Map<String, Object> inputs) {
|
|
|
+ try {
|
|
|
+ String chartContext = (String) inputs.get("chart_context");
|
|
|
+ JsonObject chart = gson.fromJson(chartContext, JsonObject.class);
|
|
|
+ JsonObject positions = chart.getAsJsonObject("positions");
|
|
|
+ if (positions == null) positions = chart;
|
|
|
+
|
|
|
+ int O = getInt(positions, "O", 7);
|
|
|
+ int P = getInt(positions, "P", 5);
|
|
|
+ int V = getInt(positions, "V", 8);
|
|
|
+ int S = getInt(positions, "S", 9);
|
|
|
+ int I = getInt(positions, "I", 0);
|
|
|
+ int J = getInt(positions, "J", 0);
|
|
|
+ int K = getInt(positions, "K", 0);
|
|
|
+ int L = getInt(positions, "L", 0);
|
|
|
+
|
|
|
+ JsonObject result = new JsonObject();
|
|
|
+
|
|
|
+ // 天赋倾向
|
|
|
+ JsonObject talentTendency = new JsonObject();
|
|
|
+ talentTendency.addProperty("title", "天赋倾向");
|
|
|
+ talentTendency.addProperty("content", generateAcademicTalent(O, I, J, K, L));
|
|
|
+ result.add("talentTendency", talentTendency);
|
|
|
+
|
|
|
+ // 适合方向
|
|
|
+ JsonObject suitableDirection = new JsonObject();
|
|
|
+ suitableDirection.addProperty("title", "适合方向");
|
|
|
+ suitableDirection.addProperty("content", "根据命盘数字组合分析,以下是不同方向的匹配度参考:");
|
|
|
+ Map<String, Integer> percentages = generateDirectionMatchPercentages(O, P, V, S);
|
|
|
+ JsonObject matchJson = new Gson().toJsonTree(percentages).getAsJsonObject();
|
|
|
+ suitableDirection.add("matchPercentages", matchJson);
|
|
|
+ result.add("suitableDirection", suitableDirection);
|
|
|
+
|
|
|
+ // 学习特征
|
|
|
+ JsonObject learningStyle = new JsonObject();
|
|
|
+ learningStyle.addProperty("title", "学习特征");
|
|
|
+ learningStyle.addProperty("content", generateLearningStyle(I, J, K, L, P, V, S));
|
|
|
+ result.add("learningStyle", learningStyle);
|
|
|
+
|
|
|
+ // 亲子沟通建议
|
|
|
+ JsonObject parentingAdvice = new JsonObject();
|
|
|
+ parentingAdvice.addProperty("title", "亲子沟通建议");
|
|
|
+ parentingAdvice.addProperty("content", generateParentingAdvice(O));
|
|
|
+ result.add("parentingAdvice", parentingAdvice);
|
|
|
+
|
|
|
+ // 关键期提醒
|
|
|
+ JsonObject keyPeriods = new JsonObject();
|
|
|
+ keyPeriods.addProperty("title", "关键期提醒");
|
|
|
+ keyPeriods.addProperty("content", generateKeyPeriods(V, getInt(positions, "W", 0), getInt(positions, "X", 0)));
|
|
|
+ result.add("keyPeriods", keyPeriods);
|
|
|
+
|
|
|
+ return result.toString();
|
|
|
+ } catch (Exception e) {
|
|
|
+ JsonObject fallback = new JsonObject();
|
|
|
+ fallback.addProperty("title", "学业方向分析");
|
|
|
+ fallback.addProperty("content", "命盘分析完成。请使用完整功能获取详细学业方向建议。");
|
|
|
+ JsonObject wrapped = new JsonObject();
|
|
|
+ wrapped.add("talentTendency", fallback);
|
|
|
+ return wrapped.toString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateAcademicTalent(int O, int I, int J, int K, int L) {
|
|
|
+ String mainDesc = NUMBER_DESC.getOrDefault(O > 9 ? 0 : O,
|
|
|
+ O > 9 ? "卓越数能量,蕴含强大潜力" : "能量独特,需结合整体命盘解读");
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ sb.append("孩子的主性格数字为 ").append(O).append("(").append(mainDesc).append(")。\n\n");
|
|
|
+ sb.append("内在基础数字:");
|
|
|
+ sb.append("日(").append(I).append(") ").append("月(").append(J).append(") ");
|
|
|
+ sb.append("年前半(").append(K).append(") ").append("年后半(").append(L).append(")").append("\n\n");
|
|
|
+ sb.append("这些数字共同构成了孩子的天赋底色:\n");
|
|
|
+ sb.append("• 主性格数字反映核心特质与驱动力\n");
|
|
|
+ sb.append("• 内在基础数字揭示潜意识需求与安全感来源\n");
|
|
|
+ sb.append("建议结合具体年龄阶段观察和引导,避免标签化。");
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Integer> generateDirectionMatchPercentages(int O, int P, int V, int S) {
|
|
|
+ Map<String, Integer> map = new LinkedHashMap<>();
|
|
|
+ int humanities = 50 + (P==2?10: P==4?15: P==7?12: P==1?-5: P==5?5: 0)
|
|
|
+ + (V==2?10: V==4?15: V==7?12: V==1?-5: V==5?5: 0)
|
|
|
+ + (S==2?10: S==4?15: S==7?12: S==1?-5: S==5?5: 0);
|
|
|
+ int science = 50 + (P==1?12: P==5?10: P==8?15: P==2?-5: P==4?8: 0)
|
|
|
+ + (V==1?12: V==5?10: V==8?15: V==2?-5: V==4?8: 0)
|
|
|
+ + (S==1?12: S==5?10: S==8?15: S==2?-5: S==4?8: 0);
|
|
|
+ int arts = 50 + (P==3?15: P==6?10: P==9?5: 0)
|
|
|
+ + (V==3?15: V==6?10: V==9?5: 0)
|
|
|
+ + (S==3?15: S==6?10: S==9?5: 0);
|
|
|
+ int sports = 50 + (P==1?10: P==5?15: P==8?10: 0)
|
|
|
+ + (V==1?10: V==5?15: V==8?10: 0)
|
|
|
+ + (S==1?10: S==5?15: S==8?10: 0);
|
|
|
+ map.put("文科", Math.min(95, Math.max(5, humanities)));
|
|
|
+ map.put("理科", Math.min(95, Math.max(5, science)));
|
|
|
+ map.put("艺术", Math.min(95, Math.max(5, arts)));
|
|
|
+ map.put("体育", Math.min(95, Math.max(5, sports)));
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateLearningStyle(int I, int J, int K, int L, int P, int V, int S) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ sb.append("从内在基础数字(日、月、年前半、年后半)分析学习特征:\n\n");
|
|
|
+ String I_desc = NUMBER_DESC.getOrDefault(I > 9 ? 0 : I, "独特能量组合");
|
|
|
+ sb.append("• 外在表现(日 ").append(I).append("):").append(I_desc).append("。\n");
|
|
|
+ String J_desc = NUMBER_DESC.getOrDefault(J > 9 ? 0 : J, "独特能量组合");
|
|
|
+ sb.append("• 内在需求(月 ").append(J).append("):").append(J_desc).append("。\n");
|
|
|
+ String K_desc = NUMBER_DESC.getOrDefault(K > 9 ? 0 : K, "独特能量组合");
|
|
|
+ sb.append("• 早年家庭影响(年前半 ").append(K).append("):").append(K_desc).append("。\n");
|
|
|
+ String L_desc = NUMBER_DESC.getOrDefault(L > 9 ? 0 : L, "独特能量组合");
|
|
|
+ sb.append("• 后天发展潜力(年后半 ").append(L).append("):").append(L_desc).append("。\n\n");
|
|
|
+ sb.append("综合来看,建议注意:\n");
|
|
|
+ sb.append("1. 日与月的搭配体现孩子表里一致程度;\n");
|
|
|
+ sb.append("2. 年前半与年后半的关联揭示学习潜能的可塑性;\n");
|
|
|
+ sb.append("3. 发展阶段数字(左区").append(P).append("、顶部").append(V).append("、右区").append(S).append(")反映不同年龄段的精力投入方向。\n");
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateParentingAdvice(int O) {
|
|
|
+ String mainDesc = NUMBER_DESC.getOrDefault(O > 9 ? 0 : O,
|
|
|
+ O > 9 ? "卓越数能量,蕴含强大潜力" : "能量独特,需结合整体命盘解读");
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ sb.append("基于主性格数字 ").append(O).append(",亲子沟通建议:\n\n");
|
|
|
+ switch (O) {
|
|
|
+ case 1:
|
|
|
+ sb.append("• 尊重孩子的独立性,给予自主决策空间;\n");
|
|
|
+ sb.append("• 避免过度干涉,多用鼓励代替命令;\n");
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ sb.append("• 注重情感连接,营造安全和谐的家庭氛围;\n");
|
|
|
+ sb.append("• 多倾听少批评,培养合作意识;\n");
|
|
|
+ break;
|
|
|
+ case 3:
|
|
|
+ sb.append("• 鼓励创意表达,提供多样化活动体验;\n");
|
|
|
+ sb.append("• 避免刻板说教,用游戏化方式互动;\n");
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ sb.append("• 建立清晰规则,保持前后一致性;\n");
|
|
|
+ sb.append("• 提供结构化学习环境,循序渐进;\n");
|
|
|
+ break;
|
|
|
+ case 5:
|
|
|
+ sb.append("• 允许探索与试错,避免过度束缚;\n");
|
|
|
+ sb.append("• 多接触外界,满足好奇心;\n");
|
|
|
+ break;
|
|
|
+ case 6:
|
|
|
+ sb.append("• 给予充分关爱与责任感培养平衡;\n");
|
|
|
+ sb.append("• 参与家庭事务,增强归属感;\n");
|
|
|
+ break;
|
|
|
+ case 7:
|
|
|
+ sb.append("• 尊重思考空间,避免过多社交压力;\n");
|
|
|
+ sb.append("• 提供深度阅读与探索资源;\n");
|
|
|
+ break;
|
|
|
+ case 8:
|
|
|
+ sb.append("• 设定目标激励机制,培养领导力;\n");
|
|
|
+ sb.append("• 给予展示能力的机会;\n");
|
|
|
+ break;
|
|
|
+ case 9:
|
|
|
+ sb.append("• 培养同理心与社会责任感;\n");
|
|
|
+ sb.append("• 鼓励关怀他人,避免理想主义挫败;\n");
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ sb.append("• 因材施教,关注孩子独特天赋与短板;\n");
|
|
|
+ sb.append("• 保持耐心,接受成长过程的起伏;\n");
|
|
|
+ }
|
|
|
+ sb.append("\n以上建议仅供参考,每个孩子都是独一无二的个体。");
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateKeyPeriods(int V, int W, int X) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ sb.append("关键年龄参考(基于外部三区数字):\n\n");
|
|
|
+ sb.append("• 左区(青年发展期 21-40岁):主能量数字 ").append(V).append(" — 大学至立业阶段;\n");
|
|
|
+ sb.append("• 顶部(中年发展期 41-60岁):主能量数字 ").append(V).append(" — 事业高峰期;\n");
|
|
|
+ sb.append("• 右区(成熟发展期 61岁+):主能量数字 ").append(V).append(" — 人生总结期;\n\n");
|
|
|
+ sb.append("对于K12阶段:\n");
|
|
|
+ sb.append("• 18岁前重点关注[外在表现]日能量与[内在需求]月能量的平衡;\n");
|
|
|
+ sb.append("• 发展期数字变化可视为各年龄段精力重心;\n");
|
|
|
+ sb.append("建议结合具体命盘细节咨询能量师获取个性化方案。");
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateMainInterpretation(int number) {
|
|
|
+ String desc = NUMBER_DESC.getOrDefault(number,
|
|
|
+ number > 9 ? "卓越数能量,蕴含强大潜力" : "能量独特,需结合整体命盘解读");
|
|
|
+ return String.format(
|
|
|
+ "您的核心数字为 %d,代表「%s」。\n\n" +
|
|
|
+ "此数字奠定了您整体的性格基调与人生轨迹方向。" +
|
|
|
+ "在决策、人际和事业发展中,%d 的能量将持续发挥作用。" +
|
|
|
+ "建议深入了解此数字的优势与调整方向,以更好地发挥天赋潜能。",
|
|
|
+ number, desc, number);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateZoneInterpretation(String zoneName, int a, int b, int c) {
|
|
|
+ String desc = NUMBER_DESC.getOrDefault(a,
|
|
|
+ a > 9 ? "卓越数能量" : "独特能量组合");
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ sb.append(zoneName).append("的能量数字为 ").append(a);
|
|
|
+ if (b != 0) sb.append(" → ").append(b);
|
|
|
+ if (c != 0) sb.append(" → ").append(c);
|
|
|
+ sb.append(",核心数字 ").append(a).append(" 代表「").append(desc).append("」。\n\n");
|
|
|
+ sb.append("此阶段您在以下方面将有明显体现:\n");
|
|
|
+ sb.append("• 个人成长与发展方向\n");
|
|
|
+ sb.append("• 人际关系与社交模式\n");
|
|
|
+ sb.append("• 事业机遇与挑战\n\n");
|
|
|
+ sb.append("建议关注数字 ").append(a).append(" 的正向特质,同时觉察其过度或不足带来的影响。");
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── Mock response generation ──────────────────────────────────────────────
|
|
|
+
|
|
|
+ /** Number descriptions for mock interpretations. */
|
|
|
+ private static final Map<Integer, String> NUMBER_DESC = new HashMap<>();
|
|
|
+ static {
|
|
|
+ NUMBER_DESC.put(1, "独立、创造与领导力,开创性强,适合自主发展");
|
|
|
+ NUMBER_DESC.put(2, "合作、平衡与细腻感知,擅长沟通与协调");
|
|
|
+ NUMBER_DESC.put(3, "表达、创意与社交活力,富有艺术天赋");
|
|
|
+ NUMBER_DESC.put(4, "稳定、务实与秩序感,执行力强,脚踏实地");
|
|
|
+ NUMBER_DESC.put(5, "自由、变化与冒险精神,适应力强,多才多艺");
|
|
|
+ NUMBER_DESC.put(6, "责任、关爱与和谐追求,家庭观念重");
|
|
|
+ NUMBER_DESC.put(7, "分析、深度思考与灵性探索,求知欲强");
|
|
|
+ NUMBER_DESC.put(8, "权力、财富与成就导向,商业头脑出众");
|
|
|
+ NUMBER_DESC.put(9, "智慧、慈悲与完成之力,格局宏大");
|
|
|
+ NUMBER_DESC.put(11, "直觉敏锐、灵感充沛,拥有启蒙他人的天赋(卓越数)");
|
|
|
+ NUMBER_DESC.put(22, "将理想变为现实的建造者,执行力与远见并存(卓越数)");
|
|
|
+ NUMBER_DESC.put(33, "大爱无私、奉献疗愈,是精神层面的引领者(卓越数)");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final Map<String, String> ZONE_DESC = new HashMap<>();
|
|
|
+ static {
|
|
|
+ ZONE_DESC.put("left", "青年发展期(21-40岁)");
|
|
|
+ ZONE_DESC.put("top", "中年发展期(41-60岁)");
|
|
|
+ ZONE_DESC.put("right", "成熟发展期(61岁以上)");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate a structured mock initial interpretation from chart data.
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ String generateMockResponse(String chartContext, String query) {
|
|
|
+ try {
|
|
|
+ JsonObject chart = gson.fromJson(chartContext, JsonObject.class);
|
|
|
+ JsonObject pos = chart.getAsJsonObject("positions");
|
|
|
+ if (pos == null) pos = chart; // flat positions
|
|
|
+
|
|
|
+ int O = getInt(pos, "O", 0);
|
|
|
+ int I = getInt(pos, "I", 0);
|
|
|
+ int J = getInt(pos, "J", 0);
|
|
|
+ int K = getInt(pos, "K", 0);
|
|
|
+ int L = getInt(pos, "L", 0);
|
|
|
+ int P = getInt(pos, "P", 0);
|
|
|
+ int Q = getInt(pos, "Q", 0);
|
|
|
+ int R = getInt(pos, "R", 0);
|
|
|
+ int V = getInt(pos, "V", 0);
|
|
|
+ int W = getInt(pos, "W", 0);
|
|
|
+ int X = getInt(pos, "X", 0);
|
|
|
+ int S = getInt(pos, "S", 0);
|
|
|
+ int T = getInt(pos, "T", 0);
|
|
|
+ int U = getInt(pos, "U", 0);
|
|
|
+
|
|
|
+ String mainDesc = NUMBER_DESC.getOrDefault(O,
|
|
|
+ O > 9 ? "卓越数能量,蕴含强大潜力" : "能量独特,需结合整体命盘解读");
|
|
|
+
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ sb.append("🔮 数字命盘初始解读\n\n");
|
|
|
+ sb.append("根据您的出生信息,已为您生成完整命盘:\n\n");
|
|
|
+
|
|
|
+ sb.append("【主性格数字 · ").append(O).append("】\n");
|
|
|
+ sb.append("您的核心数字为 ").append(O).append(",代表").append(mainDesc).append("。\n\n");
|
|
|
+
|
|
|
+ sb.append("【内在基础能量(底部左→右:日→月→年前半→年后半)】\n");
|
|
|
+ sb.append("• 日能量(出生日/外在表现)— ").append(I).append("\n");
|
|
|
+ sb.append("• 月能量(出生月/内在需求)— ").append(J).append("\n");
|
|
|
+ sb.append("• 年前半(早年家庭影响)— ").append(K).append("\n");
|
|
|
+ sb.append("• 年后半(后天发展潜力)— ").append(L).append("\n\n");
|
|
|
+
|
|
|
+ sb.append("【发展阶段能量】\n");
|
|
|
+ sb.append("• ").append(ZONE_DESC.get("left")).append(":").append(P).append(" → ").append(Q).append(" → ").append(R).append("\n");
|
|
|
+ sb.append("• ").append(ZONE_DESC.get("top")).append(":").append(V).append(" → ").append(W).append(" → ").append(X).append("\n");
|
|
|
+ sb.append("• ").append(ZONE_DESC.get("right")).append(":").append(S).append(" → ").append(T).append(" → ").append(U).append("\n\n");
|
|
|
+
|
|
|
+ // Tailored response based on user question
|
|
|
+ if (query != null && !query.trim().isEmpty() && !query.contains("综合解读")) {
|
|
|
+ String cleanQuery = query.replace("请根据数字命盘为用户提供一个综合解读。用户关心的问题:", "").trim();
|
|
|
+ if (!cleanQuery.isEmpty()) {
|
|
|
+ sb.append("【关于您关心的问题 — ").append(cleanQuery).append("】\n");
|
|
|
+ sb.append("从数字能量角度看,您当前的能量组合(主性格").append(O);
|
|
|
+ sb.append(",发展期能量").append(P).append("/").append(V).append("/").append(S).append(")");
|
|
|
+ sb.append("显示此方面存在发展潜力。建议结合具体情况进行更深入的咨询分析。\n\n");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ sb.append("💡 温馨提示:以上为系统自动生成的初始解读。您可以在下方输入具体问题,获取更深入的分析。");
|
|
|
+
|
|
|
+ return sb.toString();
|
|
|
+ } catch (Exception e) {
|
|
|
+ // Ultimate fallback — plain text
|
|
|
+ return "🔮 数字命盘已生成。您可以在下方输入您关心的问题获取详细解读。";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── Chart Interpretation (US-3.1) ─────────────────────────────────────────────
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调用 Dify Workflow 生成命盘完整解读(US-3.1)
|
|
|
+ * 返回 InterpretationResponse 格式:sections 数组 + summary + combinationNotes
|
|
|
+ */
|
|
|
+ public InterpretationResponse interpretChart(String chartContext, String userName, String birthday) {
|
|
|
+ if (isMockMode()) {
|
|
|
+ return generateMockInterpretationNew(chartContext, userName, birthday);
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ Map<String, Object> inputs = new HashMap<>();
|
|
|
+ inputs.put("chart_context", chartContext);
|
|
|
+ inputs.put("user_name", userName);
|
|
|
+ inputs.put("birthday", birthday);
|
|
|
+
|
|
|
+ String workflowResult = invokeWorkflow(inputs, "system");
|
|
|
+ return parseInterpretation(workflowResult);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("interpretChart failed, falling back to mock: {}", e.getMessage());
|
|
|
+ return generateMockInterpretationNew(chartContext, userName, birthday);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parse Workflow JSON output into InterpretationResponse.
|
|
|
+ * Returns null if parsing fails (triggers mock fallback).
|
|
|
+ */
|
|
|
+ InterpretationResponse parseInterpretation(String json) {
|
|
|
+ try {
|
|
|
+ JsonObject obj = gson.fromJson(json, JsonObject.class);
|
|
|
+ InterpretationResponse resp = new InterpretationResponse();
|
|
|
+
|
|
|
+ // Parse sections
|
|
|
+ JsonArray sectionsArr = obj.getAsJsonArray("sections");
|
|
|
+ List<InterpretationResponse.Section> sections = new ArrayList<>();
|
|
|
+ if (sectionsArr != null) {
|
|
|
+ for (JsonElement el : sectionsArr) {
|
|
|
+ JsonObject s = el.getAsJsonObject();
|
|
|
+ InterpretationResponse.Section section = new InterpretationResponse.Section();
|
|
|
+ section.title = getString(s, "title", "");
|
|
|
+ section.positions = getStringArray(s, "positions");
|
|
|
+ section.values = getIntArray(s, "values");
|
|
|
+ section.content = getString(s, "content", "");
|
|
|
+ section.keywords = getStringArray(s, "keywords");
|
|
|
+ sections.add(section);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ resp.sections = sections;
|
|
|
+
|
|
|
+ // Parse summary
|
|
|
+ resp.summary = getString(obj, "summary", "");
|
|
|
+
|
|
|
+ // Parse combinationNotes
|
|
|
+ JsonArray notesArr = obj.getAsJsonArray("combinationNotes");
|
|
|
+ List<String> notes = new ArrayList<>();
|
|
|
+ if (notesArr != null) {
|
|
|
+ for (JsonElement el : notesArr) {
|
|
|
+ notes.add(el.getAsString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ resp.combinationNotes = notes;
|
|
|
+
|
|
|
+ // Validate: at least one section with content
|
|
|
+ boolean hasContent = resp.sections.stream()
|
|
|
+ .anyMatch(s -> s.content != null && !s.content.isEmpty());
|
|
|
+ if (!hasContent) return null;
|
|
|
+
|
|
|
+ return resp;
|
|
|
+ } catch (Exception e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate mock InterpretationResponse in the new sections-based format (US-3.1).
|
|
|
+ */
|
|
|
+ InterpretationResponse generateMockInterpretationNew(String chartContext, String userName, String birthday) {
|
|
|
+ try {
|
|
|
+ JsonObject chart = gson.fromJson(chartContext, JsonObject.class);
|
|
|
+ JsonObject pos = chart.getAsJsonObject("positions");
|
|
|
+ if (pos == null) pos = chart;
|
|
|
+
|
|
|
+ int O = getInt(pos, "O", 7);
|
|
|
+ int P = getInt(pos, "P", 5);
|
|
|
+ int Q = getInt(pos, "Q", 0);
|
|
|
+ int R = getInt(pos, "R", 0);
|
|
|
+ int V = getInt(pos, "V", 8);
|
|
|
+ int W = getInt(pos, "W", 0);
|
|
|
+ int X = getInt(pos, "X", 0);
|
|
|
+ int S = getInt(pos, "S", 9);
|
|
|
+ int T = getInt(pos, "T", 0);
|
|
|
+ int U = getInt(pos, "U", 0);
|
|
|
+
|
|
|
+ InterpretationResponse resp = new InterpretationResponse();
|
|
|
+ resp.sections = new ArrayList<>();
|
|
|
+
|
|
|
+ // 主性格
|
|
|
+ InterpretationResponse.Section main = new InterpretationResponse.Section();
|
|
|
+ main.title = "主性格解读";
|
|
|
+ main.positions = Arrays.asList("O");
|
|
|
+ main.values = Arrays.asList(O);
|
|
|
+ main.content = "您的核心数字为 " + O + ",代表「"
|
|
|
+ + NUMBER_DESC.getOrDefault(O, O > 9 ? "卓越数能量" : "独特能量组合")
|
|
|
+ + "」。此数字奠定了您整体的性格基调与人生轨迹方向。";
|
|
|
+ main.keywords = Arrays.asList("主性格", "核心能量");
|
|
|
+ resp.sections.add(main);
|
|
|
+
|
|
|
+ // 左区
|
|
|
+ InterpretationResponse.Section left = new InterpretationResponse.Section();
|
|
|
+ left.title = "左区(21-40岁)";
|
|
|
+ left.positions = Arrays.asList("P", "Q", "R");
|
|
|
+ List<Integer> leftValues = new ArrayList<>();
|
|
|
+ leftValues.add(P); leftValues.add(Q); leftValues.add(R);
|
|
|
+ left.values = leftValues;
|
|
|
+ left.content = "左区能量数字为 " + P
|
|
|
+ + (Q != 0 ? " → " + Q : "") + (R != 0 ? " → " + R : "")
|
|
|
+ + ",核心数字 " + P + " 代表青年发展期的能量基调。";
|
|
|
+ left.keywords = Arrays.asList("青年期", "发展");
|
|
|
+ resp.sections.add(left);
|
|
|
+
|
|
|
+ // 顶部
|
|
|
+ InterpretationResponse.Section top = new InterpretationResponse.Section();
|
|
|
+ top.title = "顶部(41-60岁)";
|
|
|
+ top.positions = Arrays.asList("V", "W", "X");
|
|
|
+ List<Integer> topValues = new ArrayList<>();
|
|
|
+ topValues.add(V); topValues.add(W); topValues.add(X);
|
|
|
+ top.values = topValues;
|
|
|
+ top.content = "顶部能量数字为 " + V
|
|
|
+ + (W != 0 ? " → " + W : "") + (X != 0 ? " → " + X : "")
|
|
|
+ + ",核心数字 " + V + " 代表中年事业期的核心能量。";
|
|
|
+ top.keywords = Arrays.asList("中年", "事业");
|
|
|
+ resp.sections.add(top);
|
|
|
+
|
|
|
+ // 右区
|
|
|
+ InterpretationResponse.Section right = new InterpretationResponse.Section();
|
|
|
+ right.title = "右区(61+岁)";
|
|
|
+ right.positions = Arrays.asList("S", "T", "U");
|
|
|
+ List<Integer> rightValues = new ArrayList<>();
|
|
|
+ rightValues.add(S); rightValues.add(T); rightValues.add(U);
|
|
|
+ right.values = rightValues;
|
|
|
+ right.content = "右区能量数字为 " + S
|
|
|
+ + (T != 0 ? " → " + T : "") + (U != 0 ? " → " + U : "")
|
|
|
+ + ",核心数字 " + S + " 代表成熟沉淀期的能量导向。";
|
|
|
+ right.keywords = Arrays.asList("晚年", "沉淀");
|
|
|
+ resp.sections.add(right);
|
|
|
+
|
|
|
+ resp.summary = userName + "的命盘能量以" + O + "号主性格为核心,"
|
|
|
+ + "青年期(21-40)、中年期(41-60)、成熟期(61+)的能量分别为"
|
|
|
+ + P + "、" + V + "、" + S + "。整体来看,这是一组充满"
|
|
|
+ + (O > 5 ? "进取与扩张" : "稳定与内敛") + "能量的命盘配置。";
|
|
|
+ resp.combinationNotes = new ArrayList<>();
|
|
|
+ resp.combinationNotes.add("详细组合分析请使用完整解读功能查看");
|
|
|
+
|
|
|
+ return resp;
|
|
|
+ } catch (Exception e) {
|
|
|
+ InterpretationResponse fallback = new InterpretationResponse();
|
|
|
+ fallback.sections = new ArrayList<>();
|
|
|
+ InterpretationResponse.Section s = new InterpretationResponse.Section();
|
|
|
+ s.title = "主性格解读";
|
|
|
+ s.positions = Arrays.asList("O");
|
|
|
+ s.values = Arrays.asList(7);
|
|
|
+ s.content = "请查看完整命盘以获取详细解读。";
|
|
|
+ s.keywords = Arrays.asList("请重试");
|
|
|
+ fallback.sections.add(s);
|
|
|
+ fallback.summary = "解读生成失败,请稍后再试。";
|
|
|
+ fallback.combinationNotes = new ArrayList<>();
|
|
|
+ return fallback;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── DTO ──────────────────────────────────────────────────────────────────
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 命盘解读响应 DTO(US-3.1)
|
|
|
+ * 匹配 Section 9.6 输出 JSON 格式
|
|
|
+ */
|
|
|
+ public static class InterpretationResponse {
|
|
|
+ public List<Section> sections;
|
|
|
+ public String summary;
|
|
|
+ public List<String> combinationNotes;
|
|
|
+
|
|
|
+ public static class Section {
|
|
|
+ public String title;
|
|
|
+ public List<String> positions;
|
|
|
+ public List<Integer> values;
|
|
|
+ public String content;
|
|
|
+ public List<String> keywords;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── Helper methods ─────────────────────────────────────────────────────────
|
|
|
+
|
|
|
+ private String getString(JsonObject obj, String key, String fallback) {
|
|
|
+ try {
|
|
|
+ JsonElement el = obj.get(key);
|
|
|
+ return el != null && !el.isJsonNull() ? el.getAsString() : fallback;
|
|
|
+ } catch (Exception e) {
|
|
|
+ return fallback;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<String> getStringArray(JsonObject obj, String key) {
|
|
|
+ List<String> result = new ArrayList<>();
|
|
|
+ try {
|
|
|
+ JsonArray arr = obj.getAsJsonArray(key);
|
|
|
+ if (arr != null) {
|
|
|
+ for (JsonElement el : arr) {
|
|
|
+ result.add(el.getAsString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception ignored) {}
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Integer> getIntArray(JsonObject obj, String key) {
|
|
|
+ List<Integer> result = new ArrayList<>();
|
|
|
+ try {
|
|
|
+ JsonArray arr = obj.getAsJsonArray(key);
|
|
|
+ if (arr != null) {
|
|
|
+ for (JsonElement el : arr) {
|
|
|
+ result.add(el.getAsInt());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception ignored) {}
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int getInt(JsonObject obj, String key, int fallback) {
|
|
|
+ try {
|
|
|
+ return obj.get(key).getAsInt();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return fallback;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|