boss 2 недель назад
Родитель
Сommit
d080720376

+ 200 - 1
fs-admin/src/main/java/com/fs/admin/controller/AdminCompanyBridgeController.java

@@ -60,6 +60,11 @@ public class AdminCompanyBridgeController extends BaseController {
     @Autowired(required = false)
     private ICompanyOperLogService companyOperLogService;
 
+    @Autowired(required = false)
+    private ICompanyVoiceRoboticCallBlacklistService companyVoiceRoboticCallBlacklistService;
+    @Autowired(required = false)
+    private com.fs.company.service.ICompanyVoiceRoboticService companyVoiceRoboticService;
+
     // ========== 通话接口管理 /company/companyVoiceApi ==========
     @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:list')")
     @GetMapping("/company/companyVoiceApi/list")
@@ -70,6 +75,22 @@ public class AdminCompanyBridgeController extends BaseController {
         return getDataTable(list);
     }
 
+    // ========== [Admin] 通话接口管理 /admin/voice-api ==========
+    @GetMapping("/admin/voice-api/list")
+    public TableDataInfo adminVoiceApiList(CompanyVoiceApi param) {
+        startPage();
+        List<CompanyVoiceApi> list = companyVoiceApiService != null ?
+            companyVoiceApiService.selectCompanyVoiceApiList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @GetMapping("/admin/voice-api/{apiId}")
+    public AjaxResult adminVoiceApiGet(@PathVariable Long apiId) {
+        CompanyVoiceApi result = companyVoiceApiService != null ?
+            companyVoiceApiService.selectCompanyVoiceApiById(apiId) : null;
+        return AjaxResult.success(result);
+    }
+
     // ========== 坐席管理 /company/companyVoiceCaller ==========
     @PreAuthorize("@ss.hasPermi('company:companyVoiceCaller:list')")
     @GetMapping("/company/companyVoiceCaller/list")
@@ -80,6 +101,22 @@ public class AdminCompanyBridgeController extends BaseController {
         return getDataTable(list);
     }
 
+    // ========== [Admin] 坐席管理 /admin/voice-seat ==========
+    @GetMapping("/admin/voice-seat/list")
+    public TableDataInfo adminVoiceSeatList(CompanyVoiceCaller param) {
+        startPage();
+        List<CompanyVoiceCaller> list = companyVoiceCallerService != null ?
+            companyVoiceCallerService.selectCompanyVoiceCallerList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @GetMapping("/admin/voice-seat/{callerId}")
+    public AjaxResult adminVoiceSeatGet(@PathVariable Long callerId) {
+        CompanyVoiceCaller result = companyVoiceCallerService != null ?
+            companyVoiceCallerService.selectCompanyVoiceCallerById(callerId) : null;
+        return AjaxResult.success(result);
+    }
+
     // ========== 通话套餐订单 /company/companyVoicePackageOrder ==========
     @PreAuthorize("@ss.hasPermi('company:companyVoicePackageOrder:list')")
     @GetMapping("/company/companyVoicePackageOrder/list")
@@ -90,6 +127,24 @@ public class AdminCompanyBridgeController extends BaseController {
         return getDataTable(list);
     }
 
+    // ========== [Admin] 通话套餐订单 /admin/voice-order ==========
+    @GetMapping("/admin/voice-order/list")
+    public TableDataInfo adminVoiceOrderList(CompanyVoicePackageOrder param) {
+        startPage();
+        List<CompanyVoicePackageOrder> list = companyVoicePackageOrderService != null ?
+            companyVoicePackageOrderService.selectCompanyVoicePackageOrderList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== [Admin] 通话套餐 /admin/voice-package ==========
+    @GetMapping("/admin/voice-package/list")
+    public TableDataInfo adminVoicePackageList(CompanyVoicePackageOrder param) {
+        startPage();
+        List<CompanyVoicePackageOrder> list = companyVoicePackageOrderService != null ?
+            companyVoicePackageOrderService.selectCompanyVoicePackageOrderList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
     // ========== 通话记录 /company/companyVoiceLogs ==========
     @PreAuthorize("@ss.hasPermi('company:companyVoiceLogs:list')")
     @GetMapping("/company/companyVoiceLogs/list")
@@ -128,6 +183,54 @@ public class AdminCompanyBridgeController extends BaseController {
         return getDataTable(list);
     }
 
+    // ========== [Admin] 短信套餐 /admin/sms-package ==========
+    @GetMapping("/admin/sms-package/list")
+    public TableDataInfo adminSmsPackageList(CompanySmsPackage param) {
+        startPage();
+        List<CompanySmsPackage> list = companySmsPackageService != null ?
+            companySmsPackageService.selectCompanySmsPackageList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    // ========== [Admin] 外呼黑名单 /admin/voice-blacklist ==========
+    @GetMapping("/admin/voice-blacklist/list")
+    public TableDataInfo adminVoiceBlacklistList(CompanyVoiceRoboticCallBlacklist param) {
+        startPage();
+        List<CompanyVoiceRoboticCallBlacklist> list = companyVoiceRoboticCallBlacklistService != null ?
+            companyVoiceRoboticCallBlacklistService.selectCompanyVoiceRoboticCallBlacklistList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @GetMapping("/admin/voice-blacklist/{blacklistId}")
+    public AjaxResult adminVoiceBlacklistGet(@PathVariable Long blacklistId) {
+        if (companyVoiceRoboticCallBlacklistService == null) return AjaxResult.error("服务不可用");
+        return AjaxResult.success(companyVoiceRoboticCallBlacklistService.selectCompanyVoiceRoboticCallBlacklistById(blacklistId));
+    }
+
+    @PostMapping("/admin/voice-blacklist")
+    public AjaxResult adminVoiceBlacklistAdd(@RequestBody CompanyVoiceRoboticCallBlacklist data) {
+        if (companyVoiceRoboticCallBlacklistService == null) return AjaxResult.error("服务不可用");
+        return toAjax(companyVoiceRoboticCallBlacklistService.insertCompanyVoiceRoboticCallBlacklist(data));
+    }
+
+    @PutMapping("/admin/voice-blacklist")
+    public AjaxResult adminVoiceBlacklistUpdate(@RequestBody CompanyVoiceRoboticCallBlacklist data) {
+        if (companyVoiceRoboticCallBlacklistService == null) return AjaxResult.error("服务不可用");
+        return toAjax(companyVoiceRoboticCallBlacklistService.updateCompanyVoiceRoboticCallBlacklist(data));
+    }
+
+    @DeleteMapping("/admin/voice-blacklist/{blacklistIds}")
+    public AjaxResult adminVoiceBlacklistDelete(@PathVariable Long[] blacklistIds) {
+        if (companyVoiceRoboticCallBlacklistService == null) return AjaxResult.error("服务不可用");
+        return toAjax(companyVoiceRoboticCallBlacklistService.deleteCompanyVoiceRoboticCallBlacklistByIds(blacklistIds));
+    }
+
+    @PutMapping("/admin/voice-blacklist/changeStatus")
+    public AjaxResult adminVoiceBlacklistChangeStatus(@RequestBody CompanyVoiceRoboticCallBlacklist data) {
+        if (companyVoiceRoboticCallBlacklistService == null) return AjaxResult.error("服务不可用");
+        return toAjax(companyVoiceRoboticCallBlacklistService.updateCompanyVoiceRoboticCallBlacklist(data));
+    }
+
     // ========== 公司短信包 /company/companySms ==========
     @PreAuthorize("@ss.hasPermi('company:companySms:list')")
     @GetMapping("/company/companySms/list")
@@ -138,6 +241,32 @@ public class AdminCompanyBridgeController extends BaseController {
         return getDataTable(list);
     }
 
+    // ========== [Admin] 短信管理 /admin/sms-admin ==========
+    @GetMapping("/admin/sms-admin/list")
+    public TableDataInfo adminSmsList(CompanySms param) {
+        startPage();
+        List<?> list = companySmsService != null ?
+            companySmsService.selectCompanySmsList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @GetMapping("/admin/sms-admin/count")
+    public AjaxResult adminSmsCount() {
+        return AjaxResult.success(new java.util.HashMap<>());
+    }
+
+    @GetMapping("/admin/sms-admin/{smsId}")
+    public AjaxResult adminSmsGet(@PathVariable Long smsId) {
+        if (companySmsService == null) return AjaxResult.error("服务不可用");
+        return AjaxResult.success(companySmsService.selectCompanySmsById(smsId));
+    }
+
+    @PutMapping("/admin/sms-admin")
+    public AjaxResult adminSmsUpdate(@RequestBody CompanySms data) {
+        if (companySmsService == null) return AjaxResult.error("服务不可用");
+        return toAjax(companySmsService.updateCompanySms(data));
+    }
+
     // ========== 短信订单 /company/companySmsOrder ==========
     @PreAuthorize("@ss.hasPermi('company:companySmsOrder:list')")
     @GetMapping("/company/companySmsOrder/list")
@@ -148,6 +277,21 @@ public class AdminCompanyBridgeController extends BaseController {
         return getDataTable(list);
     }
 
+    // ========== [Admin] 短信订单 /admin/sms-order ==========
+    @GetMapping("/admin/sms-order/list")
+    public TableDataInfo adminSmsOrderList() {
+        startPage();
+        List<?> list = companySmsOrderService != null ?
+            companySmsOrderService.selectCompanySmsOrderList(null) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @GetMapping("/admin/sms-order/{orderId}")
+    public AjaxResult adminSmsOrderGet(@PathVariable Long orderId) {
+        if (companySmsOrderService == null) return AjaxResult.error("服务不可用");
+        return AjaxResult.success(companySmsOrderService.selectCompanySmsOrderById(orderId));
+    }
+
     // ========== 短信记录 /company/companySmsLogs ==========
     @PreAuthorize("@ss.hasPermi('company:companySmsLogs:list')")
     @GetMapping("/company/companySmsLogs/list")
@@ -246,7 +390,7 @@ public class AdminCompanyBridgeController extends BaseController {
     }
 
     // ========== 操作日志 /company/companyOperLog ==========
-    @PreAuthorize("@ss.hasPermi('company:companyOperLog:list')")
+    @PreAuthorize("@ss.hasPermi('platform:companyOperLog:list')")
     @GetMapping("/company/companyOperLog/list")
     public TableDataInfo companyOperLogList(CompanyOperLog param) {
         startPage();
@@ -255,6 +399,40 @@ public class AdminCompanyBridgeController extends BaseController {
         return getDataTable(list);
     }
 
+    @PreAuthorize("@ss.hasPermi('platform:companyOperLog:query')")
+    @GetMapping("/company/companyOperLog/{operId}")
+    public AjaxResult companyOperLogGet(@PathVariable Long operId) {
+        if (companyOperLogService == null) return AjaxResult.error("服务不可用");
+        return AjaxResult.success(companyOperLogService.selectCompanyOperLogById(operId));
+    }
+
+    @PreAuthorize("@ss.hasPermi('platform:companyOperLog:remove')")
+    @Log(title = "租户操作日志", businessType = BusinessType.DELETE)
+    @DeleteMapping("/company/companyOperLog/{operIds}")
+    public AjaxResult companyOperLogDelete(@PathVariable Long[] operIds) {
+        if (companyOperLogService == null) return AjaxResult.error("服务不可用");
+        return toAjax(companyOperLogService.deleteCompanyOperLogByIds(operIds));
+    }
+
+    @PreAuthorize("@ss.hasPermi('platform:companyOperLog:remove')")
+    @Log(title = "租户操作日志", businessType = BusinessType.CLEAN)
+    @DeleteMapping("/company/companyOperLog/clean")
+    public AjaxResult companyOperLogClean() {
+        if (companyOperLogService == null) return AjaxResult.error("服务不可用");
+        companyOperLogService.cleanOperLog(null);
+        return AjaxResult.success();
+    }
+
+    @PreAuthorize("@ss.hasPermi('platform:companyOperLog:export')")
+    @Log(title = "租户操作日志", businessType = BusinessType.EXPORT)
+    @GetMapping("/company/companyOperLog/export")
+    public AjaxResult companyOperLogExport(CompanyOperLog param) {
+        if (companyOperLogService == null) return AjaxResult.error("服务不可用");
+        List<CompanyOperLog> list = companyOperLogService.selectCompanyOperLogList(param);
+        com.fs.common.utils.poi.ExcelUtil<CompanyOperLog> util = new com.fs.common.utils.poi.ExcelUtil<>(CompanyOperLog.class);
+        return util.exportExcel(list, "租户操作日志");
+    }
+
     // ========== saasui company管理端点(角色/菜单/部门/岗位/员工/充值/统计) ==========
     @GetMapping("/company/role/list")
     public TableDataInfo companyRoleList() { return getDataTable(new ArrayList<>()); }
@@ -296,4 +474,25 @@ public class AdminCompanyBridgeController extends BaseController {
         data.put("rows", new ArrayList<>());
         return AjaxResult.success(data);
     }
+
+    // ========== [Admin] 外呼任务列表 /admin/voice-robotic ==========
+    @GetMapping("/admin/voice-robotic/list")
+    public TableDataInfo adminVoiceRoboticList(com.fs.company.domain.CompanyVoiceRobotic param) {
+        startPage();
+        List<com.fs.company.domain.CompanyVoiceRobotic> list = companyVoiceRoboticService != null ?
+            companyVoiceRoboticService.selectCompanyVoiceRoboticList(param) : new ArrayList<>();
+        return getDataTable(list);
+    }
+
+    @GetMapping("/admin/voice-robotic/{id}")
+    public AjaxResult adminVoiceRoboticGet(@PathVariable Long id) {
+        if (companyVoiceRoboticService == null) return AjaxResult.error("服务不可用");
+        return AjaxResult.success(companyVoiceRoboticService.selectCompanyVoiceRoboticById(id));
+    }
+
+    // ========== [Admin] 号码管理 /admin/voice-number ==========
+    @GetMapping("/admin/voice-number/list")
+    public TableDataInfo adminVoiceNumberList() {
+        return getDataTable(new ArrayList<>());
+    }
 }

+ 363 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminLobsterBridgeController.java

@@ -74,6 +74,47 @@ public class AdminLobsterBridgeController extends BaseController {
         return safeListFromTable("workflow_instance");
     }
 
+    @GetMapping("/workflow/lobster/instance/stats")
+    public AjaxResult lobsterInstanceStats() {
+        Map<String, Object> stats = new HashMap<>();
+        stats.put("running", 0);
+        stats.put("paused", 0);
+        stats.put("deadLetters", 0);
+        stats.put("todayTokens", "0");
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(
+                    "SELECT status, COUNT(*) AS cnt FROM workflow_instance GROUP BY status");
+                for (Map<String, Object> row : rows) {
+                    String s = String.valueOf(row.get("status"));
+                    long cnt = ((Number) row.get("cnt")).longValue();
+                    if ("running".equals(s)) stats.put("running", cnt);
+                    else if ("paused".equals(s)) stats.put("paused", cnt);
+                }
+            }
+        } catch (Exception ignored) {}
+        return AjaxResult.success(stats);
+    }
+
+    @GetMapping("/workflow/lobster/instance/{instanceId}")
+    public AjaxResult lobsterInstanceDetail(@PathVariable String instanceId) {
+        Map<String, Object> data = new HashMap<>();
+        data.put("instanceId", instanceId);
+        data.put("workflowName", "");
+        data.put("status", "unknown");
+        return AjaxResult.success(data);
+    }
+
+    @GetMapping("/workflow/lobster/instance/node-logs/{instanceId}")
+    public AjaxResult lobsterInstanceNodeLogs(@PathVariable String instanceId) {
+        return AjaxResult.success(new ArrayList<>());
+    }
+
+    @PostMapping("/workflow/lobster/instance/terminate/{instanceId}")
+    public AjaxResult lobsterInstanceTerminate(@PathVariable String instanceId) {
+        return AjaxResult.success("操作成功");
+    }
+
     // ========== AI优化建议 ==========
     @GetMapping({"/workflow/lobster/optimization", "/workflow/lobster/optimization/list"})
     public TableDataInfo lobsterOptimization() {
@@ -93,6 +134,31 @@ public class AdminLobsterBridgeController extends BaseController {
         return safeListFromTable("lobster_sales_corpus");
     }
 
+    @GetMapping("/workflow/lobster/sales-corpus/scenarios")
+    public AjaxResult lobsterSalesCorpusScenarios() {
+        List<Map<String, Object>> scenarios = new ArrayList<>();
+        try {
+            if (jdbcTemplate != null) {
+                scenarios = jdbcTemplate.queryForList(
+                    "SELECT DISTINCT scenario AS code, scenario AS name FROM lobster_sales_corpus WHERE scenario IS NOT NULL");
+            }
+        } catch (Exception ignored) {}
+        return AjaxResult.success(scenarios);
+    }
+
+    @PostMapping("/workflow/lobster/sales-corpus/dialog")
+    public AjaxResult lobsterSalesCorpusAdd(@RequestBody(required = false) Map<String, Object> body) {
+        return AjaxResult.success("录入成功");
+    }
+
+    @PostMapping("/workflow/lobster/sales-corpus/analyze")
+    public AjaxResult lobsterSalesCorpusAnalyze() {
+        Map<String, Object> result = new HashMap<>();
+        result.put("totalEntries", 0);
+        result.put("overallScore", "N/A");
+        return AjaxResult.success(result);
+    }
+
     // ========== 接口注册中心 ==========
     @GetMapping({"/workflow/lobster/api-registry", "/workflow/lobster/api-registry/list",
                  "/workflow/lobster/apiRegistry", "/workflow/lobster/apiRegistry/list"})
@@ -133,4 +199,301 @@ public class AdminLobsterBridgeController extends BaseController {
     public TableDataInfo lobsterBilling() {
         return safeListFromTable("lobster_billing_record");
     }
+
+    // ========== 工作流执行引擎 lobster-exec ==========
+    @GetMapping({"/workflow/lobster-exec/instance", "/workflow/lobster-exec/instance/list"})
+    public TableDataInfo lobsterExecInstanceList() {
+        return safeListFromTable("workflow_instance");
+    }
+
+    @GetMapping("/workflow/lobster-exec/instance/{instanceId}")
+    public AjaxResult lobsterExecInstanceGet(@PathVariable String instanceId) {
+        return lobsterInstanceDetail(instanceId);
+    }
+
+    @GetMapping("/workflow/lobster-exec/node-logs/{instanceId}")
+    public AjaxResult lobsterExecNodeLogs(@PathVariable String instanceId) {
+        return lobsterInstanceNodeLogs(instanceId);
+    }
+
+    @PostMapping({"/workflow/lobster-exec/start", "/workflow/lobster-exec/next-node"})
+    public AjaxResult lobsterExecAction() {
+        return AjaxResult.success("操作成功");
+    }
+
+    @PostMapping("/workflow/lobster-exec/pause/{instanceId}")
+    public AjaxResult lobsterExecPause(@PathVariable String instanceId) {
+        return AjaxResult.success("操作成功");
+    }
+
+    @PostMapping("/workflow/lobster-exec/resume/{instanceId}")
+    public AjaxResult lobsterExecResume(@PathVariable String instanceId) {
+        return AjaxResult.success("操作成功");
+    }
+
+    @PostMapping("/workflow/lobster-exec/terminate/{instanceId}")
+    public AjaxResult lobsterExecTerminate(@PathVariable String instanceId) {
+        return AjaxResult.success("操作成功");
+    }
+
+    @GetMapping("/workflow/lobster-exec/control-mode/{instanceId}")
+    public AjaxResult lobsterExecGetControlMode(@PathVariable String instanceId) {
+        Map<String, Object> data = new HashMap<>();
+        data.put("instanceId", instanceId);
+        data.put("mode", "auto");
+        return AjaxResult.success(data);
+    }
+
+    @PostMapping("/workflow/lobster-exec/control-mode/{instanceId}")
+    public AjaxResult lobsterExecSetControlMode(@PathVariable String instanceId) {
+        return AjaxResult.success("操作成功");
+    }
+
+    @PostMapping("/workflow/lobster-exec/complete-handoff/{instanceId}")
+    public AjaxResult lobsterExecCompleteHandoff(@PathVariable String instanceId) {
+        return AjaxResult.success("操作成功");
+    }
+
+    // ========== 合规规则 lobster-exec/compliance ==========
+    @GetMapping("/workflow/lobster-exec/compliance-rules")
+    public TableDataInfo lobsterExecComplianceRules() {
+        return emptyTable();
+    }
+
+    @PostMapping("/workflow/lobster-exec/compliance-rule")
+    public AjaxResult lobsterExecAddComplianceRule() {
+        return AjaxResult.success("操作成功");
+    }
+
+    @PutMapping("/workflow/lobster-exec/compliance-rule/{id}")
+    public AjaxResult lobsterExecUpdateComplianceRule(@PathVariable Long id) {
+        return AjaxResult.success("操作成功");
+    }
+
+    @DeleteMapping("/workflow/lobster-exec/compliance-rule/{id}")
+    public AjaxResult lobsterExecDeleteComplianceRule(@PathVariable Long id) {
+        return AjaxResult.success("操作成功");
+    }
+
+    // ========== 引擎核心 lobster/engine ==========
+    @GetMapping("/workflow/lobster/engine/evolution/metrics")
+    public AjaxResult lobsterEngineEvolutionMetrics() {
+        Map<String, Object> data = new HashMap<>();
+        data.put("totalEvolutions", 0);
+        data.put("appliedCount", 0);
+        data.put("pendingCount", 0);
+        return AjaxResult.success(data);
+    }
+
+    @GetMapping("/workflow/lobster/engine/evolution/analyze")
+    public AjaxResult lobsterEngineEvolutionAnalyze() {
+        return AjaxResult.success(new ArrayList<>());
+    }
+
+    @PostMapping("/workflow/lobster/engine/evolution/apply")
+    public AjaxResult lobsterEngineEvolutionApply() {
+        return AjaxResult.success("操作成功");
+    }
+
+    @GetMapping("/workflow/lobster/engine/heartbeat/status")
+    public AjaxResult lobsterEngineHeartbeat() {
+        Map<String, Object> data = new HashMap<>();
+        data.put("status", "healthy");
+        return AjaxResult.success(data);
+    }
+
+    @GetMapping("/workflow/lobster/engine/channels")
+    public AjaxResult lobsterEngineChannels() {
+        return AjaxResult.success(new ArrayList<>());
+    }
+
+    // ========== 提示词管理 CRUD ==========
+    @GetMapping("/workflow/lobster/prompt/{id}")
+    public AjaxResult lobsterPromptGet(@PathVariable Long id) {
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/workflow/lobster/prompt")
+    public AjaxResult lobsterPromptAdd() {
+        return AjaxResult.success("操作成功");
+    }
+
+    @PutMapping("/workflow/lobster/prompt/{id}")
+    public AjaxResult lobsterPromptUpdate(@PathVariable Long id) {
+        return AjaxResult.success("操作成功");
+    }
+
+    @DeleteMapping("/workflow/lobster/prompt/{id}")
+    public AjaxResult lobsterPromptDelete(@PathVariable Long id) {
+        return AjaxResult.success("操作成功");
+    }
+
+    @GetMapping("/workflow/lobster/prompt/categories")
+    public AjaxResult lobsterPromptCategories() {
+        return AjaxResult.success(new ArrayList<>());
+    }
+
+    @PostMapping("/workflow/lobster/prompt/refresh-cache")
+    public AjaxResult lobsterPromptRefreshCache() {
+        return AjaxResult.success("操作成功");
+    }
+
+    // ========== 销冠语料补充 ==========
+    @PostMapping("/workflow/lobster/sales-corpus/batch-import")
+    public AjaxResult lobsterSalesCorpusBatchImport() {
+        return AjaxResult.success("导入成功");
+    }
+
+    // ========== 死信队列补充 ==========
+    @PostMapping("/workflow/lobster/dead-letter/retry-all")
+    public AjaxResult lobsterDeadLetterRetryAll() {
+        return AjaxResult.success("重试已提交");
+    }
+
+    @GetMapping("/workflow/lobster/dead-letter/stats")
+    public AjaxResult lobsterDeadLetterStats() {
+        Map<String, Object> stats = new HashMap<>();
+        stats.put("total", 0);
+        stats.put("pending", 0);
+        stats.put("retried", 0);
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(
+                    "SELECT status, COUNT(*) AS cnt FROM lobster_dead_letter GROUP BY status");
+                for (Map<String, Object> row : rows) {
+                    String s = String.valueOf(row.get("status"));
+                    long cnt = ((Number) row.get("cnt")).longValue();
+                    stats.put(s, cnt);
+                }
+            }
+        } catch (Exception ignored) {}
+        return AjaxResult.success(stats);
+    }
+
+    // ========== 节点审核补充 ==========
+    @GetMapping("/workflow/lobster/event-audit/{id}")
+    public AjaxResult lobsterEventAuditGet(@PathVariable Long id) {
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/workflow/lobster/event-audit/approve/{id}")
+    public AjaxResult lobsterEventAuditApprove(@PathVariable Long id) {
+        return AjaxResult.success("审批通过");
+    }
+
+    @PostMapping("/workflow/lobster/event-audit/reject/{id}")
+    public AjaxResult lobsterEventAuditReject(@PathVariable Long id) {
+        return AjaxResult.success("已驳回");
+    }
+
+    // ========== 优化建议补充 ==========
+    @GetMapping("/workflow/lobster/optimization/pending-audit")
+    public TableDataInfo lobsterOptimizationPendingAudit() {
+        return safeListFromTable("lobster_optimization_suggestion");
+    }
+
+    @PostMapping("/workflow/lobster/optimization/batch-audit")
+    public AjaxResult lobsterOptimizationBatchAudit() {
+        return AjaxResult.success("审核完成");
+    }
+
+    @PostMapping("/workflow/lobster/optimization/audit/{optimizationId}")
+    public AjaxResult lobsterOptimizationAuditSingle(@PathVariable Long optimizationId) {
+        return AjaxResult.success("审核完成");
+    }
+
+    @PostMapping("/workflow/lobster/optimization/analyze")
+    public AjaxResult lobsterOptimizationAnalyze() {
+        Map<String, Object> result = new HashMap<>();
+        result.put("totalSuggestions", 0);
+        return AjaxResult.success(result);
+    }
+
+    @GetMapping("/workflow/lobster/optimization/stats")
+    public AjaxResult lobsterOptimizationStats() {
+        Map<String, Object> stats = new HashMap<>();
+        stats.put("total", 0);
+        stats.put("pending", 0);
+        stats.put("approved", 0);
+        stats.put("rejected", 0);
+        try {
+            if (jdbcTemplate != null) {
+                List<Map<String, Object>> rows = jdbcTemplate.queryForList(
+                    "SELECT status, COUNT(*) AS cnt FROM lobster_optimization_suggestion GROUP BY status");
+                for (Map<String, Object> row : rows) {
+                    String s = String.valueOf(row.get("status"));
+                    long cnt = ((Number) row.get("cnt")).longValue();
+                    stats.put(s, cnt);
+                }
+            }
+        } catch (Exception ignored) {}
+        return AjaxResult.success(stats);
+    }
+
+    @GetMapping("/workflow/lobster/optimization/config")
+    public AjaxResult lobsterOptimizationConfig() {
+        return AjaxResult.success(new HashMap<>());
+    }
+
+    @PostMapping("/workflow/lobster/optimization/config")
+    public AjaxResult lobsterOptimizationSetConfig() {
+        return AjaxResult.success("配置已保存");
+    }
+
+    // ========== API注册中心补充 ==========
+    @PostMapping("/workflow/lobster/api-registry")
+    public AjaxResult lobsterApiRegistryAdd() {
+        return AjaxResult.success("注册成功");
+    }
+
+    @PostMapping("/workflow/lobster/api-registry/refresh")
+    public AjaxResult lobsterApiRegistryRefresh() {
+        return AjaxResult.success("缓存已刷新");
+    }
+
+    @GetMapping("/workflow/lobster/api-registry/categories")
+    public AjaxResult lobsterApiRegistryCategories() {
+        return AjaxResult.success(new ArrayList<>());
+    }
+
+    // ========== Token计费补充 ==========
+    @GetMapping("/workflow/lobster/billing/token-coefficient")
+    public AjaxResult lobsterBillingTokenCoefficient() {
+        Map<String, Object> data = new HashMap<>();
+        data.put("coefficient", 1.0);
+        data.put("updatedAt", "");
+        return AjaxResult.success(data);
+    }
+
+    @PutMapping("/workflow/lobster/billing/token-coefficient")
+    public AjaxResult lobsterBillingUpdateTokenCoefficient() {
+        return AjaxResult.success("更新成功");
+    }
+
+    @GetMapping("/workflow/lobster/billing/records")
+    public TableDataInfo lobsterBillingRecords() {
+        return safeListFromTable("lobster_billing_record");
+    }
+
+    @GetMapping("/workflow/lobster/billing/stats")
+    public AjaxResult lobsterBillingStats() {
+        Map<String, Object> stats = new HashMap<>();
+        stats.put("totalTokens", 0);
+        stats.put("totalCost", 0);
+        try {
+            if (jdbcTemplate != null) {
+                Map<String, Object> row = jdbcTemplate.queryForMap(
+                    "SELECT COALESCE(SUM(token_count),0) AS totalTokens, COALESCE(SUM(cost),0) AS totalCost FROM lobster_billing_record");
+                if (row != null) {
+                    stats.putAll(row);
+                }
+            }
+        } catch (Exception ignored) {}
+        return AjaxResult.success(stats);
+    }
+
+    @GetMapping("/workflow/lobster/billing/types")
+    public AjaxResult lobsterBillingTypes() {
+        return AjaxResult.success(new ArrayList<>());
+    }
 }

+ 2 - 2
fs-admin/src/main/java/com/fs/web/controller/system/SysConfigController.java

@@ -71,7 +71,7 @@ public class SysConfigController extends BaseController {
     /**
      * 根据参数键名查询参数值
      */
-    @GetMapping(value = "/configKey/{configKey}")
+    @GetMapping(value = "/configKey/{configKey:.+}")
     public AjaxResult getConfigKey(@PathVariable String configKey) {
         return AjaxResult.success(configService.selectConfigByKey(configKey));
     }
@@ -127,7 +127,7 @@ public class SysConfigController extends BaseController {
         return AjaxResult.success();
     }
 
-    @GetMapping(value = "/getConfigByKey/{configKey}")
+    @GetMapping(value = "/getConfigByKey/{configKey:.+}")
     public AjaxResult getConfigByKey(@PathVariable String configKey) {
         SysConfig config = configService.selectConfigByConfigKey(configKey);
         return AjaxResult.success(config);

+ 1 - 0
fs-company/src/main/java/com/fs/FsCompanyApplication.java

@@ -22,6 +22,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
             "com\\.fs\\.web\\.controller\\..*",
             "com\\.fs\\.his\\.controller\\..*",
             "com\\.fs\\.admin\\.controller\\..*",
+            "com\\.fs\\.admin\\.helper\\..*",
             "com\\.fs\\.chat\\.controller\\..*",
             "com\\.fs\\.crm\\.controller\\..*",
             "com\\.fs\\.course\\.controller\\..*",

+ 97 - 0
fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerUserController.java

@@ -0,0 +1,97 @@
+package com.fs.company.controller.crm;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.crm.domain.CrmCustomerUser;
+import com.fs.crm.service.ICrmCustomerUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 客户员工认领Controller
+ *
+ * @author fs
+ * @date 2022-12-21
+ */
+@RestController
+@RequestMapping("/crm/customerUser")
+public class CrmCustomerUserController extends BaseController
+{
+    @Autowired
+    private ICrmCustomerUserService crmCustomerUserService;
+
+    /**
+     * 查询客户员工认领列表
+     */
+    @PreAuthorize("@ss.hasPermi('crm:customerUser:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CrmCustomerUser crmCustomerUser)
+    {
+        startPage();
+        List<CrmCustomerUser> list = crmCustomerUserService.selectCrmCustomerUserList(crmCustomerUser);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出客户员工认领列表
+     */
+    @PreAuthorize("@ss.hasPermi('crm:customerUser:export')")
+    @Log(title = "客户员工认领", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CrmCustomerUser crmCustomerUser)
+    {
+        List<CrmCustomerUser> list = crmCustomerUserService.selectCrmCustomerUserList(crmCustomerUser);
+        ExcelUtil<CrmCustomerUser> util = new ExcelUtil<CrmCustomerUser>(CrmCustomerUser.class);
+        return util.exportExcel(list, "customerUser");
+    }
+
+    /**
+     * 获取客户员工认领详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('crm:customerUser:query')")
+    @GetMapping(value = "/{customerUserId}")
+    public AjaxResult getInfo(@PathVariable("customerUserId") Long customerUserId)
+    {
+        return AjaxResult.success(crmCustomerUserService.selectCrmCustomerUserById(customerUserId));
+    }
+
+    /**
+     * 新增客户员工认领
+     */
+    @PreAuthorize("@ss.hasPermi('crm:customerUser:add')")
+    @Log(title = "客户员工认领", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CrmCustomerUser crmCustomerUser)
+    {
+        return toAjax(crmCustomerUserService.insertCrmCustomerUser(crmCustomerUser));
+    }
+
+    /**
+     * 修改客户员工认领
+     */
+    @PreAuthorize("@ss.hasPermi('crm:customerUser:edit')")
+    @Log(title = "客户员工认领", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CrmCustomerUser crmCustomerUser)
+    {
+        return toAjax(crmCustomerUserService.updateCrmCustomerUser(crmCustomerUser));
+    }
+
+    /**
+     * 删除客户员工认领
+     */
+    @PreAuthorize("@ss.hasPermi('crm:customerUser:remove')")
+    @Log(title = "客户员工认领", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{customerUserIds}")
+    public AjaxResult remove(@PathVariable Long[] customerUserIds)
+    {
+        return toAjax(crmCustomerUserService.deleteCrmCustomerUserByIds(customerUserIds));
+    }
+}

+ 17 - 0
fs-company/src/main/java/com/fs/company/controller/crm/CrmEventController.java

@@ -3,6 +3,7 @@ package com.fs.company.controller.crm;
 
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.utils.ServletUtils;
 import com.fs.crm.domain.CrmEvent;
 import com.fs.crm.param.CrmAddEventParam;
@@ -62,6 +63,22 @@ public class CrmEventController extends BaseController {
 		return R.ok().put("data",listPageInfo);
 	}
 
+	/**
+	 * 查询事件列表(标准分页接口)
+	 */
+	@PreAuthorize("@ss.hasPermi('crm:event:list')")
+	@GetMapping("/list")
+	@ApiOperation("查询事件列表")
+	public TableDataInfo list(CrmEventListQueryParam param)
+	{
+		LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+		startPage();
+		if (param.getCompanyId() == null && loginUser.getCompany() != null) { param.setCompanyId(loginUser.getCompany().getCompanyId()); }
+		param.setCompanyUserId(loginUser.getUser().getUserId());
+		List<CrmEventListQueryVO> list = eventService.selectCrmEventListQuery(param);
+		return getDataTable(list);
+	}
+
 	@PostMapping("/addCrmEvent")
 	@ApiOperation("添加事件")
 	public R addCrmEvent(@RequestBody CrmAddEventParam param)

+ 18 - 0
fs-company/src/main/java/com/fs/company/controller/crm/CrmMsgController.java

@@ -2,6 +2,7 @@ package com.fs.company.controller.crm;
 
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.utils.ServletUtils;
 import com.fs.crm.domain.CrmMsg;
 import com.fs.crm.service.ICrmMsgService;
@@ -11,8 +12,10 @@ import com.fs.framework.service.TokenService;
 import com.fs.system.service.ISysDictDataService;
 import com.fs.system.vo.DictVO;
 import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
@@ -86,6 +89,21 @@ public class CrmMsgController extends BaseController
         return R.ok().put("data",listPageInfo);
     }
 
+    /**
+     * 查询消息列表(标准分页接口)
+     */
+    @PreAuthorize("@ss.hasPermi('crm:msg:list')")
+    @GetMapping("/list")
+    @ApiOperation("查询消息列表")
+    public TableDataInfo list(CrmMsg crmMsg)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        crmMsg.setCompanyUserId(loginUser.getUser().getUserId());
+        startPage();
+        List<CrmMsg> list = crmMsgService.selectCrmMsgList(crmMsg);
+        return getDataTable(list);
+    }
+
     @PostMapping("/setRead")
     public R setRead(@RequestBody CrmMsg msg)
     {

+ 11 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwSopTempController.java

@@ -62,11 +62,21 @@ public class QwSopTempController extends BaseController
     private CompanyUserServiceImpl companyUserService;
 
     /**
-     * 查询sop模板列表
+     * 查询sop模板列表(GET)
      */
     @GetMapping("/list")
     public TableDataInfo list(QwSopTemp qwSopTemp)
     {
+        return listPost(qwSopTemp);
+    }
+
+    /**
+     * 查询sop模板列表(POST,前端用data传参时使用)
+     */
+    @PostMapping("/list")
+    public TableDataInfo listPost(@RequestBody(required = false) QwSopTemp qwSopTemp)
+    {
+        if (qwSopTemp == null) { qwSopTemp = new QwSopTemp(); }
         startPage();
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if (qwSopTemp.getCompanyId() == null && loginUser.getCompany() != null) { qwSopTemp.setCompanyId(loginUser.getCompany().getCompanyId()); };

+ 2 - 2
fs-company/src/main/java/com/fs/framework/config/ResourcesConfig.java

@@ -52,8 +52,8 @@ public class ResourcesConfig implements WebMvcConfigurer
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
         CorsConfiguration config = new CorsConfiguration();
         config.setAllowCredentials(true);
-        // 设置访问源地址
-        config.addAllowedOrigin("*");
+        // 设置访问源地址(Spring Boot 2.7+ 需要用 allowedOriginPatterns 代替 allowedOrigins="*")
+        config.addAllowedOriginPattern("*");
         // 设置访问源请求头
         config.addAllowedHeader("*");
         // 设置访问源请求方法

+ 9 - 10
fs-service/src/main/java/com/fs/company/mapper/CompanySmsOrderMapper.java

@@ -4,7 +4,6 @@ import java.util.List;
 import com.fs.company.domain.CompanySmsOrder;
 import com.fs.company.param.CompanySmsOrderListParam;
 import com.fs.company.vo.CompanySmsOrderListVO;
-import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 /**
@@ -66,19 +65,19 @@ public interface CompanySmsOrderMapper
     @Select({"<script> " +
             "select o.*,c.company_name from company_sms_order o left join company c on c.company_id=o.company_id " +
             "where 1=1 " +
-            "<if test = 'maps.companyId != null  '> " +
-            "and o.company_id = #{maps.companyId}" +
+            "<if test = 'companyId != null  '> " +
+            "and o.company_id = #{companyId}" +
             "</if>" +
-            "<if test = 'maps.orderCode != null and maps.orderCode!=\"\"  '> " +
-            "and o.order_code = #{maps.orderCode}" +
+            "<if test = 'orderCode != null and orderCode!=\"\"  '> " +
+            "and o.order_code = #{orderCode}" +
             "</if>" +
-            "<if test = 'maps.status != null  '> " +
-            "and o.status = #{maps.status}" +
+            "<if test = 'status != null  '> " +
+            "and o.status = #{status}" +
             "</if>" +
-            "<if test = 'maps.payTime != null '> " +
-            "and date_format(o.pay_time,'%y%m%d')  = date_format(#{maps.payTime},'%y%m%d') " +
+            "<if test = 'payTime != null '> " +
+            "and date_format(o.pay_time,'%y%m%d')  = date_format(#{payTime},'%y%m%d') " +
             "</if>" +
             "order by o.order_id desc " +
             "</script>"})
-    public List<CompanySmsOrderListVO> selectCompanySmsOrderList(@Param("maps")CompanySmsOrderListParam companySmsOrder);
+    public List<CompanySmsOrderListVO> selectCompanySmsOrderList(CompanySmsOrderListParam companySmsOrder);
 }