From 5e492e5d5a2b743e2e99443220d343f72a633f6d Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期二, 03 三月 2026 16:57:52 +0800
Subject: [PATCH] #

---
 src/main/java/com/zy/ai/controller/LlmRouteConfigController.java |  330 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 330 insertions(+), 0 deletions(-)

diff --git a/src/main/java/com/zy/ai/controller/LlmRouteConfigController.java b/src/main/java/com/zy/ai/controller/LlmRouteConfigController.java
new file mode 100644
index 0000000..43c7453
--- /dev/null
+++ b/src/main/java/com/zy/ai/controller/LlmRouteConfigController.java
@@ -0,0 +1,330 @@
+package com.zy.ai.controller;
+
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.core.annotations.ManagerAuth;
+import com.core.common.R;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.zy.ai.entity.LlmRouteConfig;
+import com.zy.ai.service.LlmRouteConfigService;
+import com.zy.ai.service.LlmRoutingService;
+import com.zy.common.web.BaseController;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping("/ai/llm/config")
+@RequiredArgsConstructor
+public class LlmRouteConfigController extends BaseController {
+
+    private final LlmRouteConfigService llmRouteConfigService;
+    private final LlmRoutingService llmRoutingService;
+    private final ObjectMapper objectMapper;
+
+    @GetMapping("/list/auth")
+    @ManagerAuth
+    public R list() {
+        EntityWrapper<LlmRouteConfig> wrapper = new EntityWrapper<>();
+        wrapper.orderBy("priority", true).orderBy("id", true);
+        List<LlmRouteConfig> list = llmRouteConfigService.selectList(wrapper);
+        return R.ok(list);
+    }
+
+    @PostMapping("/save/auth")
+    @ManagerAuth
+    public R save(@RequestBody LlmRouteConfig config) {
+        if (config == null) {
+            return R.error("鍙傛暟涓嶈兘涓虹┖");
+        }
+
+        if (isBlank(config.getBaseUrl()) || isBlank(config.getApiKey()) || isBlank(config.getModel())) {
+            return R.error("蹇呴』濉啓 baseUrl/apiKey/model");
+        }
+
+        if (config.getId() == null) {
+            llmRoutingService.fillAndNormalize(config, true);
+            llmRouteConfigService.insert(config);
+        } else {
+            LlmRouteConfig db = llmRouteConfigService.selectById(config.getId());
+            if (db == null) {
+                return R.error("閰嶇疆涓嶅瓨鍦�");
+            }
+            // 淇濈暀缁熻瀛楁锛岄伩鍏嶅墠绔瑕嗙洊
+            Integer failCount = db.getFailCount();
+            Integer successCount = db.getSuccessCount();
+            Integer consecutiveFailCount = db.getConsecutiveFailCount();
+            Date lastFailTime = db.getLastFailTime();
+            Date lastUsedTime = db.getLastUsedTime();
+            String lastError = db.getLastError();
+
+            llmRoutingService.fillAndNormalize(config, false);
+            config.setFailCount(failCount);
+            config.setSuccessCount(successCount);
+            config.setConsecutiveFailCount(consecutiveFailCount);
+            config.setLastFailTime(lastFailTime);
+            config.setLastUsedTime(lastUsedTime);
+            config.setLastError(lastError);
+            config.setCreateTime(db.getCreateTime());
+            llmRouteConfigService.updateById(config);
+        }
+
+        llmRoutingService.evictCache();
+        return R.ok(config);
+    }
+
+    @PostMapping("/delete/auth")
+    @ManagerAuth
+    public R delete(@RequestParam("id") Long id) {
+        if (id == null) {
+            return R.error("id涓嶈兘涓虹┖");
+        }
+        llmRouteConfigService.deleteById(id);
+        llmRoutingService.evictCache();
+        return R.ok();
+    }
+
+    @PostMapping("/clearCooldown/auth")
+    @ManagerAuth
+    public R clearCooldown(@RequestParam("id") Long id) {
+        if (id == null) {
+            return R.error("id涓嶈兘涓虹┖");
+        }
+        LlmRouteConfig cfg = llmRouteConfigService.selectById(id);
+        if (cfg == null) {
+            return R.error("閰嶇疆涓嶅瓨鍦�");
+        }
+        cfg.setCooldownUntil(null);
+        cfg.setConsecutiveFailCount(0);
+        cfg.setUpdateTime(new Date());
+        llmRouteConfigService.updateById(cfg);
+        llmRoutingService.evictCache();
+        return R.ok();
+    }
+
+    @PostMapping("/test/auth")
+    @ManagerAuth
+    public R test(@RequestBody LlmRouteConfig config) {
+        if (config == null) {
+            return R.error("鍙傛暟涓嶈兘涓虹┖");
+        }
+        if (isBlank(config.getBaseUrl()) || isBlank(config.getApiKey()) || isBlank(config.getModel())) {
+            return R.error("娴嬭瘯澶辫触锛氬繀椤诲~鍐� baseUrl/apiKey/model");
+        }
+        Map<String, Object> data = llmRoutingService.testRoute(config);
+        if (Boolean.TRUE.equals(data.get("ok")) && config.getId() != null) {
+            LlmRouteConfig db = llmRouteConfigService.selectById(config.getId());
+            if (db != null) {
+                db.setCooldownUntil(null);
+                db.setConsecutiveFailCount(0);
+                db.setUpdateTime(new Date());
+                llmRouteConfigService.updateById(db);
+                llmRoutingService.evictCache();
+            }
+        }
+        return R.ok(data);
+    }
+
+    @GetMapping("/export/auth")
+    @ManagerAuth
+    public R exportConfig() {
+        EntityWrapper<LlmRouteConfig> wrapper = new EntityWrapper<>();
+        wrapper.orderBy("priority", true).orderBy("id", true);
+        List<LlmRouteConfig> list = llmRouteConfigService.selectList(wrapper);
+        List<Map<String, Object>> routes = new ArrayList<>();
+        if (list != null) {
+            for (LlmRouteConfig cfg : list) {
+                routes.add(exportRow(cfg));
+            }
+        }
+        HashMap<String, Object> result = new HashMap<>();
+        result.put("version", "1.0");
+        result.put("exportTime", new Date());
+        result.put("count", routes.size());
+        result.put("routes", routes);
+        return R.ok(result);
+    }
+
+    @PostMapping("/import/auth")
+    @ManagerAuth
+    @Transactional(rollbackFor = Exception.class)
+    public R importConfig(@RequestBody Object body) {
+        boolean replace = false;
+        List<?> rawRoutes = null;
+
+        if (body instanceof Map) {
+            Map<?, ?> map = (Map<?, ?>) body;
+            replace = parseBoolean(map.get("replace"));
+            Object routesObj = map.get("routes");
+            if (routesObj instanceof List) {
+                rawRoutes = (List<?>) routesObj;
+            }
+        } else if (body instanceof List) {
+            rawRoutes = (List<?>) body;
+        }
+
+        if (rawRoutes == null || rawRoutes.isEmpty()) {
+            return R.error("瀵煎叆鏁版嵁涓虹┖鎴栨牸寮忎笉姝g‘锛屽繀椤诲寘鍚� routes 鏁扮粍");
+        }
+
+        int inserted = 0;
+        int updated = 0;
+        int skipped = 0;
+        List<String> errors = new ArrayList<>();
+        List<LlmRouteConfig> validRoutes = new ArrayList<>();
+
+        for (int i = 0; i < rawRoutes.size(); i++) {
+            Object row = rawRoutes.get(i);
+            LlmRouteConfig cfg;
+            try {
+                cfg = objectMapper.convertValue(row, LlmRouteConfig.class);
+            } catch (Exception e) {
+                skipped++;
+                errors.add("绗�" + (i + 1) + "鏉¤В鏋愬け璐�: " + safeMsg(e.getMessage()));
+                continue;
+            }
+            if (cfg == null) {
+                skipped++;
+                errors.add("绗�" + (i + 1) + "鏉′负绌�");
+                continue;
+            }
+
+            cfg.setName(trim(cfg.getName()));
+            cfg.setBaseUrl(trim(cfg.getBaseUrl()));
+            cfg.setApiKey(trim(cfg.getApiKey()));
+            cfg.setModel(trim(cfg.getModel()));
+            cfg.setMemo(trim(cfg.getMemo()));
+            if (isBlank(cfg.getBaseUrl()) || isBlank(cfg.getApiKey()) || isBlank(cfg.getModel())) {
+                skipped++;
+                errors.add("绗�" + (i + 1) + "鏉$己灏戝繀濉瓧娈� baseUrl/apiKey/model");
+                continue;
+            }
+            validRoutes.add(cfg);
+        }
+
+        if (validRoutes.isEmpty()) {
+            String firstError = errors.isEmpty() ? "" : ("锛岄鏉″師鍥狅細" + errors.get(0));
+            return R.error("瀵煎叆澶辫触锛氭病鏈夊彲鐢ㄩ厤缃�" + firstError);
+        }
+
+        if (replace) {
+            llmRouteConfigService.delete(new EntityWrapper<LlmRouteConfig>());
+        }
+
+        HashMap<Long, LlmRouteConfig> dbById = new HashMap<>();
+        if (!replace) {
+            List<LlmRouteConfig> current = llmRouteConfigService.selectList(new EntityWrapper<>());
+            if (current != null) {
+                for (LlmRouteConfig item : current) {
+                    if (item != null && item.getId() != null) {
+                        dbById.put(item.getId(), item);
+                    }
+                }
+            }
+        }
+
+        for (LlmRouteConfig cfg : validRoutes) {
+
+            if (!replace && cfg.getId() != null && dbById.containsKey(cfg.getId())) {
+                LlmRouteConfig db = dbById.get(cfg.getId());
+                Long keepId = db.getId();
+                Date createTime = db.getCreateTime();
+                Integer failCount = db.getFailCount();
+                Integer successCount = db.getSuccessCount();
+                Integer consecutiveFailCount = db.getConsecutiveFailCount();
+                Date lastFailTime = db.getLastFailTime();
+                Date lastUsedTime = db.getLastUsedTime();
+                String lastError = db.getLastError();
+                Date cooldownUntil = db.getCooldownUntil();
+
+                llmRoutingService.fillAndNormalize(cfg, false);
+                cfg.setId(keepId);
+                cfg.setCreateTime(createTime);
+                cfg.setFailCount(failCount);
+                cfg.setSuccessCount(successCount);
+                cfg.setConsecutiveFailCount(consecutiveFailCount);
+                cfg.setLastFailTime(lastFailTime);
+                cfg.setLastUsedTime(lastUsedTime);
+                cfg.setLastError(lastError);
+                cfg.setCooldownUntil(cooldownUntil);
+                llmRouteConfigService.updateById(cfg);
+                updated++;
+                continue;
+            }
+
+            cfg.setId(null);
+            cfg.setCooldownUntil(null);
+            cfg.setFailCount(0);
+            cfg.setSuccessCount(0);
+            cfg.setConsecutiveFailCount(0);
+            cfg.setLastFailTime(null);
+            cfg.setLastUsedTime(null);
+            cfg.setLastError(null);
+            llmRoutingService.fillAndNormalize(cfg, true);
+            llmRouteConfigService.insert(cfg);
+            inserted++;
+        }
+
+        llmRoutingService.evictCache();
+
+        HashMap<String, Object> result = new HashMap<>();
+        result.put("replace", replace);
+        result.put("total", rawRoutes.size());
+        result.put("inserted", inserted);
+        result.put("updated", updated);
+        result.put("skipped", skipped);
+        result.put("errorCount", errors.size());
+        if (!errors.isEmpty()) {
+            int max = Math.min(errors.size(), 20);
+            result.put("errors", errors.subList(0, max));
+        }
+        log.info("LLM璺敱瀵煎叆瀹屾垚, replace={}, total={}, inserted={}, updated={}, skipped={}",
+                replace, rawRoutes.size(), inserted, updated, skipped);
+        return R.ok(result);
+    }
+
+    private Map<String, Object> exportRow(LlmRouteConfig cfg) {
+        LinkedHashMap<String, Object> row = new LinkedHashMap<>();
+        row.put("id", cfg.getId());
+        row.put("name", cfg.getName());
+        row.put("baseUrl", cfg.getBaseUrl());
+        row.put("apiKey", cfg.getApiKey());
+        row.put("model", cfg.getModel());
+        row.put("thinking", cfg.getThinking());
+        row.put("priority", cfg.getPriority());
+        row.put("status", cfg.getStatus());
+        row.put("switchOnQuota", cfg.getSwitchOnQuota());
+        row.put("switchOnError", cfg.getSwitchOnError());
+        row.put("cooldownSeconds", cfg.getCooldownSeconds());
+        row.put("memo", cfg.getMemo());
+        return row;
+    }
+
+    private boolean parseBoolean(Object x) {
+        if (x instanceof Boolean) return (Boolean) x;
+        if (x == null) return false;
+        String s = String.valueOf(x).trim();
+        return "1".equals(s) || "true".equalsIgnoreCase(s) || "yes".equalsIgnoreCase(s);
+    }
+
+    private String trim(String s) {
+        return s == null ? null : s.trim();
+    }
+
+    private String safeMsg(String s) {
+        if (s == null) return "";
+        return s.length() > 200 ? s.substring(0, 200) : s;
+    }
+
+    private boolean isBlank(String s) {
+        return s == null || s.trim().isEmpty();
+    }
+}

--
Gitblit v1.9.1