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("导入数据为空或格式不正确,必须包含 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();
|
}
|
}
|