|
|
@@ -0,0 +1,370 @@
|
|
|
+package com.fs.aicall.controller;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.fs.aicall.domain.CompanyBindAiModel;
|
|
|
+import com.fs.aicall.domain.CcCallTask;
|
|
|
+import com.fs.aicall.domain.CcLlmAgentAccount;
|
|
|
+import com.fs.aicall.service.ICcCallTaskService;
|
|
|
+import com.fs.aicall.service.ICcLlmAgentAccountService;
|
|
|
+import com.fs.aicall.service.ICcParamsService;
|
|
|
+import com.fs.aicall.service.ICompanyBindAiModelService;
|
|
|
+import com.fs.aicall.utils.CommonUtils;
|
|
|
+import com.fs.aicall.utils.StringUtils;
|
|
|
+import com.fs.common.annotation.Log;
|
|
|
+import com.fs.common.core.controller.BaseController;
|
|
|
+import com.fs.common.core.domain.AjaxResult;
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
+import com.fs.common.core.page.TableDataInfo;
|
|
|
+import com.fs.common.core.text.Convert;
|
|
|
+import com.fs.common.enums.BusinessType;
|
|
|
+import com.fs.common.utils.poi.ExcelUtil;
|
|
|
+import com.fs.company.cache.ICompanyCacheService;
|
|
|
+import com.fs.common.core.domain.model.LoginUser;
|
|
|
+import com.fs.common.utils.ServletUtils;
|
|
|
+import com.fs.framework.datasource.TenantDataSourceManager;
|
|
|
+import com.fs.framework.web.service.TokenService;
|
|
|
+import com.fs.system.domain.SysConfig;
|
|
|
+import com.fs.system.mapper.SysConfigMapper;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.security.access.prepost.PreAuthorize;
|
|
|
+import org.springframework.web.bind.annotation.*;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 机器人参数配置Controller(总后台)
|
|
|
+ * <p>
|
|
|
+ * 模型数据存于共享 EASYCALL 库,需通过当前租户的公司绑定关系过滤;
|
|
|
+ * 默认展示本租户下全部公司已绑定模型,可按 companyId 进一步筛选。
|
|
|
+ * </p>
|
|
|
+ */
|
|
|
+@RestController
|
|
|
+@RequestMapping("/aicall/account")
|
|
|
+public class CcLlmAgentAccountController extends BaseController {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ICcLlmAgentAccountService ccLlmAgentAccountService;
|
|
|
+ @Autowired
|
|
|
+ private ICcParamsService ccParamsService;
|
|
|
+ @Autowired
|
|
|
+ private ICcCallTaskService ccCallTaskService;
|
|
|
+ @Autowired
|
|
|
+ private ICompanyBindAiModelService companyBindAiModelService;
|
|
|
+ @Autowired
|
|
|
+ private SysConfigMapper sysConfigMapper;
|
|
|
+ @Autowired
|
|
|
+ private ICompanyCacheService companyCacheService;
|
|
|
+ @Autowired
|
|
|
+ private TenantDataSourceManager tenantDataSourceManager;
|
|
|
+ @Autowired
|
|
|
+ private TokenService tokenService;
|
|
|
+
|
|
|
+ private static final List<String> HIDE_KEYS = Arrays.asList(
|
|
|
+ "apiKey", "oauthPrivateKey", "oauthPublicKeyId", "patToken");
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询机器人参数配置列表(当前租户下全部公司已绑定模型;可选 companyId 筛选)
|
|
|
+ */
|
|
|
+ @PreAuthorize("@ss.hasPermi('aicall:account:list')")
|
|
|
+ @PostMapping("/list")
|
|
|
+ public TableDataInfo list(@RequestBody CcLlmAgentAccount ccLlmAgentAccount,
|
|
|
+ @RequestParam(value = "companyId", required = false) Long companyId) {
|
|
|
+ List<Long> modelIds = resolveTenantModelIds(companyId);
|
|
|
+ if (modelIds.isEmpty()) {
|
|
|
+ return getDataTable(new ArrayList<>());
|
|
|
+ }
|
|
|
+ ccLlmAgentAccount.setModelIds(modelIds);
|
|
|
+
|
|
|
+ startPage();
|
|
|
+ List<CcLlmAgentAccount> list = ccLlmAgentAccountService.selectCcLlmAgentAccountList(ccLlmAgentAccount);
|
|
|
+ TableDataInfo tableDataInfo = getDataTable(list);
|
|
|
+ List<CcLlmAgentAccount> records = (List<CcLlmAgentAccount>) tableDataInfo.getRows();
|
|
|
+ fillCompanyBindInfo(records);
|
|
|
+ maskAccountJson(records);
|
|
|
+ return tableDataInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取机器人参数配置详情
|
|
|
+ */
|
|
|
+ @PreAuthorize("@ss.hasPermi('aicall:account:query')")
|
|
|
+ @GetMapping("/{id}")
|
|
|
+ public AjaxResult getInfo(@PathVariable("id") Integer id) {
|
|
|
+ CcLlmAgentAccount account = ccLlmAgentAccountService.selectCcLlmAgentAccountById(id);
|
|
|
+ if (StringUtils.isBlank(account.getInterruptIgnoreKeywords())) {
|
|
|
+ account.setInterruptIgnoreKeywords(
|
|
|
+ ccParamsService.getParamValueByCode("default_interrupt_ignore_keywords", ""));
|
|
|
+ }
|
|
|
+ maskAccountJson(account);
|
|
|
+ fillCompanyBindInfo(Collections.singletonList(account));
|
|
|
+ return AjaxResult.success(account);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出机器人参数配置列表
|
|
|
+ */
|
|
|
+ @PreAuthorize("@ss.hasPermi('aicall:account:export')")
|
|
|
+ @Log(title = "机器人参数配置", businessType = BusinessType.EXPORT)
|
|
|
+ @PostMapping("/export")
|
|
|
+ public AjaxResult export(CcLlmAgentAccount ccLlmAgentAccount,
|
|
|
+ @RequestParam(value = "companyId", required = false) Long companyId) {
|
|
|
+ List<Long> modelIds = resolveTenantModelIds(companyId);
|
|
|
+ if (modelIds.isEmpty()) {
|
|
|
+ ExcelUtil<CcLlmAgentAccount> emptyUtil = new ExcelUtil<>(CcLlmAgentAccount.class);
|
|
|
+ return emptyUtil.exportExcel(new ArrayList<>(), "机器人参数配置数据");
|
|
|
+ }
|
|
|
+ ccLlmAgentAccount.setModelIds(modelIds);
|
|
|
+ List<CcLlmAgentAccount> list = ccLlmAgentAccountService.selectCcLlmAgentAccountList(ccLlmAgentAccount);
|
|
|
+ ExcelUtil<CcLlmAgentAccount> util = new ExcelUtil<>(CcLlmAgentAccount.class);
|
|
|
+ return util.exportExcel(list, "机器人参数配置数据");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 新增保存机器人参数配置
|
|
|
+ */
|
|
|
+ @PreAuthorize("@ss.hasPermi('aicall:account:add')")
|
|
|
+ @Log(title = "配置模型新增", businessType = BusinessType.INSERT)
|
|
|
+ @PostMapping
|
|
|
+ public AjaxResult addSave(@RequestBody CcLlmAgentAccount ccLlmAgentAccount,
|
|
|
+ @RequestParam(value = "companyId", required = false) Long companyId) {
|
|
|
+ prepareAccountEntity(ccLlmAgentAccount);
|
|
|
+ fillDeepSeekApiKey(ccLlmAgentAccount);
|
|
|
+ if (ccLlmAgentAccount.getKbCatId() == null) {
|
|
|
+ ccLlmAgentAccount.setKbCatId(-1);
|
|
|
+ }
|
|
|
+
|
|
|
+ int result = ccLlmAgentAccountService.insertCcLlmAgentAccount(ccLlmAgentAccount);
|
|
|
+ if (result > 0 && companyId != null) {
|
|
|
+ ensureTenantDataSource();
|
|
|
+ companyBindAiModelService.bindCompanyToModel(ccLlmAgentAccount.getId().longValue(), companyId);
|
|
|
+ }
|
|
|
+ return toAjax(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 修改保存机器人参数配置
|
|
|
+ */
|
|
|
+ @PreAuthorize("@ss.hasPermi('aicall:account:edit')")
|
|
|
+ @Log(title = "配置模型修改", businessType = BusinessType.UPDATE)
|
|
|
+ @PutMapping
|
|
|
+ public AjaxResult editSave(@RequestBody CcLlmAgentAccount ccLlmAgentAccount) {
|
|
|
+ if (ccLlmAgentAccount.getId() != null && ccLlmAgentAccount.getId() > 0) {
|
|
|
+ String errMsg = checkEdit(ccLlmAgentAccount.getId());
|
|
|
+ if (StringUtils.isNotEmpty(errMsg)) {
|
|
|
+ return AjaxResult.error(errMsg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ prepareAccountEntity(ccLlmAgentAccount);
|
|
|
+
|
|
|
+ Integer originId = ccLlmAgentAccount.getId();
|
|
|
+ if (originId != null && originId < 0) {
|
|
|
+ originId = originId * -1;
|
|
|
+ }
|
|
|
+ if (originId != null) {
|
|
|
+ CcLlmAgentAccount oldAccount = ccLlmAgentAccountService.selectCcLlmAgentAccountById(originId);
|
|
|
+ JSONObject oldAccountJson = JSONObject.parseObject(oldAccount.getAccountJson());
|
|
|
+ JSONObject newAccountJson = JSONObject.parseObject(ccLlmAgentAccount.getAccountJson());
|
|
|
+ for (String key : newAccountJson.keySet()) {
|
|
|
+ if (HIDE_KEYS.contains(key) && newAccountJson.getString(key).contains("****")) {
|
|
|
+ newAccountJson.put(key, oldAccountJson.getString(key));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ccLlmAgentAccount.setAccountJson(JSONObject.toJSONString(newAccountJson));
|
|
|
+ }
|
|
|
+ if (ccLlmAgentAccount.getKbCatId() == null) {
|
|
|
+ ccLlmAgentAccount.setKbCatId(-1);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ccLlmAgentAccount.getId() != null && ccLlmAgentAccount.getId() > 0) {
|
|
|
+ return toAjax(ccLlmAgentAccountService.updateCcLlmAgentAccount(ccLlmAgentAccount));
|
|
|
+ }
|
|
|
+ ccLlmAgentAccount.setId(null);
|
|
|
+ return toAjax(ccLlmAgentAccountService.insertCcLlmAgentAccount(ccLlmAgentAccount));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除机器人参数配置(同时清理全部公司绑定关系)
|
|
|
+ */
|
|
|
+ @PreAuthorize("@ss.hasPermi('aicall:account:remove')")
|
|
|
+ @Log(title = "机器人参数配置", businessType = BusinessType.DELETE)
|
|
|
+ @DeleteMapping("/{ids}")
|
|
|
+ public AjaxResult remove(@PathVariable String ids) {
|
|
|
+ ensureTenantDataSource();
|
|
|
+ for (String id : Convert.toStrArray(ids)) {
|
|
|
+ companyBindAiModelService.deleteCompanyBindAiModelByModelId(Long.parseLong(id));
|
|
|
+ }
|
|
|
+ return toAjax(ccLlmAgentAccountService.deleteCcLlmAgentAccountByIds(ids));
|
|
|
+ }
|
|
|
+
|
|
|
+ @PreAuthorize("@ss.hasPermi('aicall:account:list')")
|
|
|
+ @GetMapping("/all")
|
|
|
+ public AjaxResult all(@RequestParam(value = "companyId", required = false) Long companyId) {
|
|
|
+ CcLlmAgentAccount query = new CcLlmAgentAccount();
|
|
|
+ List<Long> modelIds = resolveTenantModelIds(companyId);
|
|
|
+ if (modelIds.isEmpty()) {
|
|
|
+ return AjaxResult.success(new ArrayList<>());
|
|
|
+ }
|
|
|
+ query.setModelIds(modelIds);
|
|
|
+ List<CcLlmAgentAccount> list = ccLlmAgentAccountService.selectCcLlmAgentAccountList(query);
|
|
|
+ fillCompanyBindInfo(list);
|
|
|
+ maskAccountJson(list);
|
|
|
+ return AjaxResult.success(list);
|
|
|
+ }
|
|
|
+
|
|
|
+ @GetMapping("/getCidConfig")
|
|
|
+ public R getCidConfig() {
|
|
|
+ SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("cId.config");
|
|
|
+ return R.ok().put("data", sysConfig.getConfigValue());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 绑定模型与公司
|
|
|
+ */
|
|
|
+ @PreAuthorize("@ss.hasPermi('aicall:account:edit')")
|
|
|
+ @Log(title = "配置模型绑定公司", businessType = BusinessType.UPDATE)
|
|
|
+ @PostMapping("/bindCompany")
|
|
|
+ public AjaxResult bindCompany(@RequestParam Long modelId, @RequestParam Long companyId) {
|
|
|
+ ensureTenantDataSource();
|
|
|
+ return toAjax(companyBindAiModelService.bindCompanyToModel(modelId, companyId));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解除模型与公司的绑定
|
|
|
+ */
|
|
|
+ @PreAuthorize("@ss.hasPermi('aicall:account:edit')")
|
|
|
+ @Log(title = "配置模型解绑公司", businessType = BusinessType.UPDATE)
|
|
|
+ @PostMapping("/unbindCompany")
|
|
|
+ public AjaxResult unbindCompany(@RequestParam Long modelId, @RequestParam Long companyId) {
|
|
|
+ ensureTenantDataSource();
|
|
|
+ companyBindAiModelService.deleteBindAiModelByCompanyIdAndModelIds(companyId, String.valueOf(modelId));
|
|
|
+ return AjaxResult.success();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void prepareAccountEntity(CcLlmAgentAccount ccLlmAgentAccount) {
|
|
|
+ if ("Coze".equalsIgnoreCase(ccLlmAgentAccount.getProviderClassName())
|
|
|
+ || "LocalNlpChat".equalsIgnoreCase(ccLlmAgentAccount.getProviderClassName())) {
|
|
|
+ ccLlmAgentAccount.setAccountEntity("CozeAccount");
|
|
|
+ } else if ("JiutianWorkflow".equalsIgnoreCase(ccLlmAgentAccount.getProviderClassName())
|
|
|
+ || "JiutianAgent".equalsIgnoreCase(ccLlmAgentAccount.getProviderClassName())) {
|
|
|
+ ccLlmAgentAccount.setAccountEntity("JiutianAccount");
|
|
|
+ } else {
|
|
|
+ ccLlmAgentAccount.setAccountEntity("LlmAccount");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void fillDeepSeekApiKey(CcLlmAgentAccount ccLlmAgentAccount) {
|
|
|
+ if (!"DeepSeekChat".equalsIgnoreCase(ccLlmAgentAccount.getProviderClassName())) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ JSONObject jsonObject = JSONObject.parseObject(ccLlmAgentAccount.getAccountJson());
|
|
|
+ if (jsonObject == null || !jsonObject.containsKey("apiKey")
|
|
|
+ || !jsonObject.getString("apiKey").contains("**")) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("cId.config");
|
|
|
+ if (sysConfig != null && StringUtils.isNotBlank(sysConfig.getConfigValue())) {
|
|
|
+ JSONObject configValue = JSONObject.parseObject(sysConfig.getConfigValue());
|
|
|
+ jsonObject.put("apiKey", configValue.getString("apiKey"));
|
|
|
+ ccLlmAgentAccount.setAccountJson(JSONObject.toJSONString(jsonObject));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void maskAccountJson(List<CcLlmAgentAccount> records) {
|
|
|
+ for (CcLlmAgentAccount data : records) {
|
|
|
+ maskAccountJson(data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void maskAccountJson(CcLlmAgentAccount data) {
|
|
|
+ JSONObject accountJson = JSONObject.parseObject(data.getAccountJson());
|
|
|
+ for (String key : accountJson.keySet()) {
|
|
|
+ if (HIDE_KEYS.contains(key)) {
|
|
|
+ accountJson.put(key, CommonUtils.maskStringUtil(accountJson.getString(key)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ data.setAccountJson(JSONObject.toJSONString(accountJson));
|
|
|
+ }
|
|
|
+
|
|
|
+ private String checkEdit(Integer id) {
|
|
|
+ List<CcCallTask> ccCallTaskList = ccCallTaskService.selectCcCallTaskList(new CcCallTask().setLlmAccountId(id));
|
|
|
+ List<String> ids = new ArrayList<>();
|
|
|
+ for (CcCallTask ccCallTask : ccCallTaskList) {
|
|
|
+ if (ccCallTask.getTaskType() == 1
|
|
|
+ && ccCallTask.getIfcall() == 1
|
|
|
+ && ccCallTask.getAutoStop() == 0) {
|
|
|
+ ids.add(ccCallTask.getBatchName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!ids.isEmpty()) {
|
|
|
+ return String.format("%s%s%s", "请先暂停任务:", StringUtils.join(ids, ","), ",再修改该配置,修改完成后再启动任务");
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析当前租户可见的模型ID:
|
|
|
+ * - 指定 companyId 时,仅返回该公司绑定的模型
|
|
|
+ * - 未指定时,返回当前租户下所有公司已绑定的模型(company_bind_ai_model 在租户库)
|
|
|
+ */
|
|
|
+ private List<Long> resolveTenantModelIds(Long companyId) {
|
|
|
+ ensureTenantDataSource();
|
|
|
+ if (companyId != null) {
|
|
|
+ return companyBindAiModelService.selectModelIdsByCompanyId(companyId);
|
|
|
+ }
|
|
|
+ return companyBindAiModelService.selectCompanyBindAiModelList(new CompanyBindAiModel())
|
|
|
+ .stream()
|
|
|
+ .map(CompanyBindAiModel::getModelId)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 填充模型绑定的公司ID、公司名称(多对多,逗号分隔展示)
|
|
|
+ */
|
|
|
+ private void fillCompanyBindInfo(List<CcLlmAgentAccount> records) {
|
|
|
+ if (records == null || records.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ensureTenantDataSource();
|
|
|
+ List<CompanyBindAiModel> allBinds =
|
|
|
+ companyBindAiModelService.selectCompanyBindAiModelList(new CompanyBindAiModel());
|
|
|
+ Map<Long, List<Long>> modelCompanyMap = allBinds.stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ CompanyBindAiModel::getModelId,
|
|
|
+ Collectors.mapping(CompanyBindAiModel::getCompanyId, Collectors.toList())));
|
|
|
+
|
|
|
+ for (CcLlmAgentAccount account : records) {
|
|
|
+ List<Long> companyIds = modelCompanyMap.getOrDefault(
|
|
|
+ account.getId().longValue(), Collections.emptyList());
|
|
|
+ account.setCompanyIds(companyIds);
|
|
|
+ if (companyIds.isEmpty()) {
|
|
|
+ account.setCompanyId("");
|
|
|
+ account.setCompanyName("");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ List<String> companyNames = new ArrayList<>();
|
|
|
+ for (Long cid : companyIds) {
|
|
|
+ String name = companyCacheService.selectCompanyNameById(cid);
|
|
|
+ companyNames.add(StringUtils.isNotBlank(name) ? name : String.valueOf(cid));
|
|
|
+ }
|
|
|
+ account.setCompanyId(companyIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
|
|
+ account.setCompanyName(String.join(",", companyNames));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * company_bind_ai_model 在租户库;查询 EASYCALL 后 @DataSource 切面会 clear 数据源,需重新切回租户库。
|
|
|
+ */
|
|
|
+ private void ensureTenantDataSource() {
|
|
|
+ LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
|
|
|
+ if (loginUser != null && loginUser.getTenantId() != null) {
|
|
|
+ tenantDataSourceManager.ensureSwitchByTenantId(loginUser.getTenantId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|