supeng преди 1 седмица
родител
ревизия
f7795d0be1

+ 152 - 0
core/src/main/java/com/tzld/supply/api/google/GeminiApiService.java

@@ -0,0 +1,152 @@
+
+package com.tzld.supply.api.google;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+public class GeminiApiService {
+
+    private OkHttpClient client;
+    @Value("${airouter.gemini.apikey:}")
+    private String aiRouterApiKey;
+    /**
+     * AIRouter url
+     */
+    private static final String URL = "https://airouter.piaoquantv.com/v1beta/models/%s:generateContent?key=%s";
+
+    private static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8");
+
+    @PostConstruct
+    public void init() {
+        // 初始化 OkHttpClient (设置超时时间为 60 秒)
+        OkHttpClient.Builder builder = new OkHttpClient.Builder()
+                .connectTimeout(60, TimeUnit.SECONDS)
+                .readTimeout(600, TimeUnit.SECONDS)
+                .writeTimeout(600, TimeUnit.SECONDS);
+//        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
+//        builder.proxy(proxy);
+        client = builder.build();
+    }
+
+    /**
+     * 调用 Gemini
+     *
+     * @param systemPrompt 你的 Prompt(定义角色、规则、输出格式)
+     * @param input  输入内容
+     * @return 解析后的 JSONObject 结果,解析失败返回 null
+     */
+    public JSONObject callGemini(ModelEnum model, String systemPrompt, String input) throws IOException {
+        if (Objects.isNull(model)) {
+            model = ModelEnum.GEMINI_2_FLASH;
+        }
+        JSONObject requestBodyJson = new JSONObject();
+
+        // ==========================================
+        // 使用 System Instruction 隔离规则与输入
+        // 这会让大模型更严格地遵守你设定的“红灯标准”和“评分流程”
+        // ==========================================
+        JSONObject systemInstruction = new JSONObject();
+        JSONArray sysParts = new JSONArray();
+        JSONObject sysText = new JSONObject();
+        sysText.put("text", systemPrompt);
+        sysParts.add(sysText);
+        systemInstruction.put("parts", sysParts);
+        requestBodyJson.put("system_instruction", systemInstruction);
+
+        // ==========================================
+        // User Content 仅放入待处理的目标内容
+        // ==========================================
+        JSONArray contentsArray = new JSONArray();
+        JSONObject userContent = new JSONObject();
+        userContent.put("role", "user");
+        JSONArray userParts = new JSONArray();
+        JSONObject userText = new JSONObject();
+        userText.put("text", input != null ? input : "");
+        userParts.add(userText);
+        userContent.put("parts", userParts);
+        contentsArray.add(userContent);
+        requestBodyJson.put("contents", contentsArray);
+
+        // ==========================================
+        // 强制开启 JSON 模式 (JSON Mode)
+        // Gemini 官方特性,强制返回标准的 JSON 格式
+        // ==========================================
+        JSONObject generationConfig = new JSONObject();
+        generationConfig.put("responseMimeType", "application/json");
+        requestBodyJson.put("generationConfig", generationConfig);
+
+        log.info(requestBodyJson.toJSONString());
+        // 构建并发送 HTTP 请求
+        String url = String.format(URL, model.getModel(), aiRouterApiKey);
+        RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, requestBodyJson.toJSONString());
+        Request request = new Request.Builder()
+                .url(url)
+                .post(body)
+                .build();
+
+        try (Response response = client.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                String errorBody = response.body() != null ? response.body().string() : "无";
+                throw new IOException("请求失败!HTTP: " + response.code() + " | 详情: " + errorBody);
+            }
+
+            String responseBodyString = response.body().string();
+            log.info(responseBodyString);
+            // 提取大模型的回复文本
+            JSONObject responseJson = JSONObject.parseObject(responseBodyString);
+            JSONArray candidates = responseJson.getJSONArray("candidates");
+
+            if (candidates != null && !candidates.isEmpty()) {
+                JSONObject firstCandidate = candidates.getJSONObject(0);
+                JSONObject content = firstCandidate.getJSONObject("content");
+                JSONArray parts = content.getJSONArray("parts");
+
+                if (parts != null && !parts.isEmpty()) {
+                    String rawLlmOutput = parts.getJSONObject(0).getString("text");
+                    // ==========================================
+                    // 防御性清洗并转化为 JSONObject
+                    // 应对极少数情况下大模型仍然返回 Markdown 格式的情况
+                    // ==========================================
+                    String cleanJsonStr = cleanMarkdown(rawLlmOutput);
+                    try {
+                        return JSONObject.parseObject(cleanJsonStr);
+                    } catch (Exception e) {
+                        System.err.println("大模型返回的内容不是有效的 JSON: " + rawLlmOutput);
+                        return null;
+                    }
+                }
+            }
+            throw new IOException("未在响应中找到有效的内容 (candidates)");
+        }
+    }
+
+    /**
+     * 兜底工具:移除大模型喜欢附带的 ```json 和 ``` 标记
+     */
+    private String cleanMarkdown(String raw) {
+        if (raw == null) {
+            return null;
+        }
+        String cleaned = raw.trim();
+        if (cleaned.startsWith("```json")) {
+            cleaned = cleaned.substring(7);
+        } else if (cleaned.startsWith("```")) {
+            cleaned = cleaned.substring(3);
+        }
+        if (cleaned.endsWith("```")) {
+            cleaned = cleaned.substring(0, cleaned.length() - 3);
+        }
+        return cleaned.trim();
+    }
+}

+ 16 - 0
core/src/main/java/com/tzld/supply/api/google/ModelEnum.java

@@ -0,0 +1,16 @@
+package com.tzld.supply.api.google;
+
+import lombok.Getter;
+
+@Getter
+public enum ModelEnum {
+    GEMINI_2_FLASH("gemini-2.0-flash"),
+    GEMINI_25_FLASH("gemini-2.5-flash"),
+    GEMINI_3_FLASH_PREVIEW("gemini-3-flash-preview"),
+    ;
+
+    private final String model;
+    ModelEnum(String model) {
+        this.model = model;
+    }
+}

+ 1 - 1
core/src/main/java/com/tzld/supply/common/enums/ArticleAnalyseStrategyEnum.java

@@ -7,7 +7,7 @@ import java.util.List;
 
 @Getter
 public enum ArticleAnalyseStrategyEnum {
-    SUITABLE_OLDER(1,"适老分析");
+    ARTICLE_ANALYSE_SUITABLE_OLDER(1,"适老分析");
 
     private final Integer value;
     private final String desc;

+ 1 - 1
core/src/main/java/com/tzld/supply/common/enums/TitleAnalyseStrategyEnum.java

@@ -7,7 +7,7 @@ import java.util.List;
 
 @Getter
 public enum TitleAnalyseStrategyEnum {
-    SUITABLE_OLDER(1, "适老分析");
+    TITLE_ANALYSE_SUITABLE_OLDER(1, "适老分析");
 
     private final Integer value;
     private final String desc;

+ 16 - 4
core/src/main/java/com/tzld/supply/job/ContentScreenJob.java

@@ -13,6 +13,7 @@ import com.tzld.supply.common.enums.TitleAnalyseStrategyEnum;
 import com.tzld.supply.dao.mapper.supply.spider.SpiderContentMapper;
 import com.tzld.supply.dao.mapper.supply.spider.ext.SpiderMapperExt;
 import com.tzld.supply.model.dto.ArticleAnalyseConfigDTO;
+import com.tzld.supply.model.dto.StrategyContentDTO;
 import com.tzld.supply.model.dto.TitleAnalyseConfigDTO;
 import com.tzld.supply.model.entity.DeepSeekResult;
 import com.tzld.supply.model.entity.PrecisionScreenEntity;
@@ -66,9 +67,9 @@ public class ContentScreenJob {
     private ArticleAnalyseConfigDTO articleAnalyseConfigDTO;
 
     @Autowired
-    private Map<TitleAnalyseStrategyEnum, TitleAnalyseStrategy> titleAnalyseStrategyMap;
+    private Map<String, TitleAnalyseStrategy> titleAnalyseStrategyMap;
     @Autowired
-    private Map<ArticleAnalyseStrategyEnum, ArticleAnalyseStrategy> articleAnalyseStrategyMap;
+    private Map<String, ArticleAnalyseStrategy> articleAnalyseStrategyMap;
 
     /**
      * 粗筛
@@ -385,6 +386,10 @@ public class ContentScreenJob {
                 content.setUpdateTime(System.currentTimeMillis());
                 spiderContentMapper.updateByPrimaryKeySelective(content);
             }
+            //需要有标题
+            if (Objects.isNull(content.getTitle()) || content.getTitle().trim().isEmpty()) {
+                continue;
+            }
             //执行策略
             JSONObject jsonObject = new JSONObject();
             for (TitleAnalyseConfigDTO.StrategyConfig strategyConfig : strategies) {
@@ -396,7 +401,9 @@ public class ContentScreenJob {
                 if (Objects.isNull(titleAnalyseStrategy)) {
                     continue;
                 }
-                String result = titleAnalyseStrategy.execute();
+                StrategyContentDTO contentDTO = new StrategyContentDTO();
+                contentDTO.setInput(content.getTitle());
+                String result = titleAnalyseStrategy.execute(contentDTO);
                 if (StringUtils.isNotBlank(result)) {
                     jsonObject.put(strategyName, result);
                 }
@@ -457,6 +464,9 @@ public class ContentScreenJob {
                 content.setUpdateTime(System.currentTimeMillis());
                 spiderContentMapper.updateByPrimaryKeySelective(content);
             }
+            if (Objects.isNull(content.getContent()) || content.getContent().trim().isEmpty()) {
+                continue;
+            }
             //执行策略
             JSONObject jsonObject = new JSONObject();
             for (ArticleAnalyseConfigDTO.StrategyConfig strategyConfig : strategies) {
@@ -468,7 +478,9 @@ public class ContentScreenJob {
                 if (Objects.isNull(articleAnalyseStrategy)) {
                     continue;
                 }
-                String result = articleAnalyseStrategy.execute();
+                StrategyContentDTO strategyContentDTO = new StrategyContentDTO();
+                strategyContentDTO.setInput(content.getContent());
+                String result = articleAnalyseStrategy.execute(strategyContentDTO);
                 if (StringUtils.isNotBlank(result)) {
                     jsonObject.put(strategyName, result);
                 }

+ 8 - 0
core/src/main/java/com/tzld/supply/model/dto/StrategyContentDTO.java

@@ -0,0 +1,8 @@
+package com.tzld.supply.model.dto;
+
+import lombok.Data;
+
+@Data
+public class StrategyContentDTO {
+    private String input;
+}

+ 3 - 2
core/src/main/java/com/tzld/supply/service/strategy/ArticleAnalyseStrategy.java

@@ -1,10 +1,11 @@
 package com.tzld.supply.service.strategy;
 
 import com.tzld.supply.common.enums.ArticleAnalyseStrategyEnum;
+import com.tzld.supply.model.dto.StrategyContentDTO;
 
 public interface ArticleAnalyseStrategy {
 
-    ArticleAnalyseStrategyEnum getStrategy();
+    String getStrategy();
 
-    String execute();
+    String execute(StrategyContentDTO contentDTO);
 }

+ 27 - 4
core/src/main/java/com/tzld/supply/service/strategy/SuitableOlderArticleAnalyseStrategy.java

@@ -1,20 +1,43 @@
 package com.tzld.supply.service.strategy;
 
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.supply.api.google.GeminiApiService;
+import com.tzld.supply.api.google.ModelEnum;
 import com.tzld.supply.common.enums.ArticleAnalyseStrategyEnum;
+import com.tzld.supply.model.dto.StrategyContentDTO;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
+import java.util.Objects;
+
 @Slf4j
-@Service
+@Service(value = "ARTICLE_ANALYSE_SUITABLE_OLDER")
 public class SuitableOlderArticleAnalyseStrategy implements ArticleAnalyseStrategy {
 
+    @Autowired
+    private GeminiApiService geminiApiService;
+
+    @Value("article.analyse.systemprompt:")
+    private String systemPrompt;
+
     @Override
-    public ArticleAnalyseStrategyEnum getStrategy() {
-        return ArticleAnalyseStrategyEnum.SUITABLE_OLDER;
+    public String getStrategy() {
+        return ArticleAnalyseStrategyEnum.ARTICLE_ANALYSE_SUITABLE_OLDER.name();
     }
 
     @Override
-    public String execute() {
+    public String execute(StrategyContentDTO contentDTO) {
+        if (Objects.isNull(contentDTO)) {
+            return "";
+        }
+        try {
+            JSONObject jsonObject = geminiApiService.callGemini(ModelEnum.GEMINI_3_FLASH_PREVIEW, systemPrompt, contentDTO.getInput());
+            return Objects.isNull(jsonObject) ? "" : jsonObject.toJSONString();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
         return "";
     }
 }

+ 27 - 4
core/src/main/java/com/tzld/supply/service/strategy/SuitableOlderTitleAnalyseStrategy.java

@@ -1,20 +1,43 @@
 package com.tzld.supply.service.strategy;
 
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.supply.api.google.GeminiApiService;
+import com.tzld.supply.api.google.ModelEnum;
 import com.tzld.supply.common.enums.TitleAnalyseStrategyEnum;
+import com.tzld.supply.model.dto.StrategyContentDTO;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
+import java.util.Objects;
+
 @Slf4j
-@Service
+@Service(value = "TITLE_ANALYSE_SUITABLE_OLDER")
 public class SuitableOlderTitleAnalyseStrategy implements TitleAnalyseStrategy {
 
+    @Autowired
+    private GeminiApiService geminiApiService;
+
+    @Value("title.analyse.systemprompt:")
+    private String systemPrompt;
+
     @Override
-    public TitleAnalyseStrategyEnum getStrategy() {
-        return TitleAnalyseStrategyEnum.SUITABLE_OLDER;
+    public String getStrategy() {
+        return TitleAnalyseStrategyEnum.TITLE_ANALYSE_SUITABLE_OLDER.name();
     }
 
     @Override
-    public String execute() {
+    public String execute(StrategyContentDTO contentDTO) {
+        if (Objects.isNull(contentDTO)) {
+            return "";
+        }
+        try {
+            JSONObject jsonObject = geminiApiService.callGemini(ModelEnum.GEMINI_3_FLASH_PREVIEW, systemPrompt, contentDTO.getInput());
+            return Objects.isNull(jsonObject) ? "" : jsonObject.toJSONString();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
         return "";
     }
 }

+ 3 - 2
core/src/main/java/com/tzld/supply/service/strategy/TitleAnalyseStrategy.java

@@ -1,10 +1,11 @@
 package com.tzld.supply.service.strategy;
 
 import com.tzld.supply.common.enums.TitleAnalyseStrategyEnum;
+import com.tzld.supply.model.dto.StrategyContentDTO;
 
 public interface TitleAnalyseStrategy {
 
-    TitleAnalyseStrategyEnum getStrategy();
+    String getStrategy();
 
-    String execute();
+    String execute(StrategyContentDTO contentDTO);
 }

+ 1 - 1
server/src/main/resources/application.yml

@@ -1,6 +1,6 @@
 spring:
   profiles:
-    active: dev
+    active: test
   application:
     name: supply-server
 

+ 31 - 0
server/src/test/java/GeminiTest.java

@@ -0,0 +1,31 @@
+import com.alibaba.fastjson.JSONObject;
+import com.tzld.supply.Application;
+import com.tzld.supply.api.google.GeminiApiService;
+import com.tzld.supply.api.google.ModelEnum;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.io.IOException;
+
+@SpringBootTest(classes = Application.class)
+@Slf4j
+public class GeminiTest {
+    @Autowired
+    private GeminiApiService geminiApiService;
+
+    @Test
+    public void testGemini() {
+        String systemPrompt = "分析以下标题的主题、关键词、是否适合老年人等信息";
+        String content = "郑建成:中国现代文学评论中的金岳霖";
+
+        try {
+            JSONObject jsonObject = geminiApiService.callGemini(ModelEnum.GEMINI_3_FLASH_PREVIEW, systemPrompt,content);
+            log.info(jsonObject.toString());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+}