吴树波 пре 3 дана
родитељ
комит
8f7a22aa88

+ 1 - 1
fs-comm-gateway/src/main/java/com/fs/comm/service/CommCallService.java

@@ -94,7 +94,7 @@ public class CommCallService {
             commMetricsService.increment("call.success");
             return response;
         } catch (ServiceException ex) {
-            boolean limitHit = ex.getMessage() != null && ex.getMessage().contains("限制");
+            boolean limitHit = CommGatewayApiLogRecorder.isLimitFailure(ex);
             recordResult = limitHit
                     ? commGatewayApiLogRecorder.buildLimitFailure(ex, calleePhone, callerKey, gatewayId)
                     : commGatewayApiLogRecorder.buildFailure(ex, calleePhone, callerKey, gatewayId);

+ 83 - 9
fs-comm-gateway/src/main/java/com/fs/comm/service/CommGatewayApiLogRecorder.java

@@ -7,10 +7,13 @@ import com.fs.comm.domain.CommGatewayApiLog;
 import com.fs.comm.dto.CommCallSendRequest;
 import com.fs.comm.dto.CommSmsSendRequest;
 import com.fs.comm.model.CommGatewayBillingQuote;
+import com.fs.comm.support.CommTenantDataSourceHelper;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.ip.IpUtils;
+import com.fs.company.domain.CompanyCommGatewayLog;
+import com.fs.company.service.ICompanyCommGatewayLogService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -20,7 +23,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 /**
- * 通讯网关 API 调用日志记录(主库)
+ * 通讯网关 API 调用日志记录(主库 + 租户库
  */
 @Slf4j
 @Service
@@ -29,9 +32,15 @@ public class CommGatewayApiLogRecorder {
     @Autowired
     private ICommGatewayApiLogService commGatewayApiLogService;
 
+    @Autowired
+    private ICompanyCommGatewayLogService companyCommGatewayLogService;
+
     @Autowired
     private CommGatewayBillingService commGatewayBillingService;
 
+    @Autowired
+    private CommTenantDataSourceHelper commTenantDataSourceHelper;
+
     public void recordCallAttempt(Long companyId, Long tenantId, CommCallSendRequest request,
                                   CommApiRecordResult result, String calleePhone, String callerPhone,
                                   Long gatewayId, long startMs) {
@@ -58,7 +67,7 @@ public class CommGatewayApiLogRecorder {
             logEntity.setCallerAccount(session.getAccount());
             logEntity.setAuthScope(session.getScope());
         }
-        fillCompanyUserIdFromRequest(logEntity, request);
+        fillIdentityFromRequest(logEntity, request);
         logEntity.setApiType(apiType);
         logEntity.setApiPath(apiPath);
         logEntity.setRequestBody(JSON.toJSONString(request));
@@ -111,14 +120,23 @@ public class CommGatewayApiLogRecorder {
         logEntity.setBillingAmount(quote.getBillingAmount());
     }
 
-    private void fillCompanyUserIdFromRequest(CommGatewayApiLog logEntity, Object request) {
-        if (logEntity.getCompanyUserId() != null || request == null) {
-            return;
-        }
+    private void fillIdentityFromRequest(CommGatewayApiLog logEntity, Object request) {
         if (request instanceof CommCallSendRequest) {
-            logEntity.setCompanyUserId(((CommCallSendRequest) request).getCompanyUserId());
+            CommCallSendRequest callRequest = (CommCallSendRequest) request;
+            if (callRequest.getCompanyUserId() != null) {
+                logEntity.setCompanyUserId(callRequest.getCompanyUserId());
+            }
         } else if (request instanceof CommSmsSendRequest) {
-            logEntity.setCompanyUserId(((CommSmsSendRequest) request).getCompanyUserId());
+            CommSmsSendRequest smsRequest = (CommSmsSendRequest) request;
+            if (smsRequest.getCompanyId() != null) {
+                logEntity.setCompanyId(smsRequest.getCompanyId());
+            }
+            if (smsRequest.getCompanyUserId() != null) {
+                logEntity.setCompanyUserId(smsRequest.getCompanyUserId());
+            }
+        }
+        if (logEntity.getCompanyUserId() == null) {
+            logEntity.setCompanyUserId(CommAuthContext.getCompanyUserId());
         }
     }
 
@@ -135,6 +153,7 @@ public class CommGatewayApiLogRecorder {
         logEntity.setCostPrice(BigDecimal.ZERO);
         logEntity.setCalcPrice(BigDecimal.ZERO);
         logEntity.setBillingQuantity(0);
+        logEntity.setBillingUnit(logEntity.getApiType());
         Map<String, Object> body = new HashMap<>();
         body.put("code", 500);
         body.put("msg", message);
@@ -142,11 +161,66 @@ public class CommGatewayApiLogRecorder {
     }
 
     private void record(CommGatewayApiLog logEntity) {
+        Long masterLogId = null;
         try {
             commGatewayApiLogService.saveLog(logEntity);
+            masterLogId = logEntity.getLogId();
         } catch (Exception ex) {
-            log.error("写入通讯网关调用日志失败", ex);
+            log.error("写入主库通讯网关调用日志失败", ex);
+        }
+        saveTenantLog(logEntity, masterLogId);
+    }
+
+    private void saveTenantLog(CommGatewayApiLog logEntity, Long masterLogId) {
+        if (logEntity == null || logEntity.getTenantId() == null) {
+            return;
+        }
+        try {
+            commTenantDataSourceHelper.ensureTenant(logEntity.getTenantId());
+            companyCommGatewayLogService.saveLog(toTenantLog(logEntity, masterLogId));
+        } catch (Exception ex) {
+            log.error("写入租户通讯网关调用日志失败 tenantId={}, companyId={}",
+                    logEntity.getTenantId(), logEntity.getCompanyId(), ex);
+        }
+    }
+
+    private CompanyCommGatewayLog toTenantLog(CommGatewayApiLog source, Long masterLogId) {
+        CompanyCommGatewayLog target = new CompanyCommGatewayLog();
+        target.setMasterLogId(masterLogId);
+        target.setCompanyId(source.getCompanyId());
+        target.setCompanyUserId(source.getCompanyUserId());
+        target.setCallerAccount(source.getCallerAccount());
+        target.setApiType(source.getApiType());
+        target.setApiPath(source.getApiPath());
+        target.setRequestBody(source.getRequestBody());
+        target.setResponseBody(source.getResponseBody());
+        target.setResultCode(source.getResultCode());
+        target.setResultMsg(source.getResultMsg());
+        target.setSuccess(source.getSuccess());
+        target.setLimitHit(source.getLimitHit());
+        target.setLimitReason(source.getLimitReason());
+        target.setCalleePhone(source.getCalleePhone());
+        target.setCallerPhone(source.getCallerPhone());
+        target.setGatewayId(source.getGatewayId());
+        target.setBillingAmount(source.getBillingAmount());
+        target.setCostPrice(source.getCostPrice());
+        target.setCalcPrice(source.getCalcPrice());
+        target.setBillingQuantity(source.getBillingQuantity());
+        target.setBillingUnit(source.getBillingUnit());
+        target.setClientIp(source.getClientIp());
+        target.setAuthScope(source.getAuthScope());
+        target.setDurationMs(source.getDurationMs());
+        target.setCreateTime(source.getCreateTime());
+        return target;
+    }
+
+    /** 是否为频率/QPS 类限制失败 */
+    public static boolean isLimitFailure(ServiceException ex) {
+        if (ex == null || StringUtils.isBlank(ex.getMessage())) {
+            return false;
         }
+        String message = ex.getMessage();
+        return message.contains("限制") || message.contains("超限") || message.contains("频率");
     }
 
     public CommApiRecordResult buildLimitFailure(ServiceException ex, String calleePhone, String callerPhone, Long gatewayId) {

+ 6 - 1
fs-comm-gateway/src/main/java/com/fs/comm/service/CommSmsService.java

@@ -50,6 +50,11 @@ public class CommSmsService {
             if (companyId == null) {
                 throw new ServiceException("未获取到公司信息");
             }
+            commTenantDataSourceHelper.ensureTenant(tenantId);
+            if (request != null) {
+                calleePhone = commVoiceLimitService.resolveSmsCalleePhone(
+                        request.getCalleeId(), request.getCustomerId(), request.getPhone());
+            }
             callerKey = commVoiceLimitService.buildCompanyCallerKey(companyId, null);
 
             commRateLimitService.checkTenantQps(tenantId);
@@ -80,7 +85,7 @@ public class CommSmsService {
             commMetricsService.increment("sms.success");
             return response;
         } catch (ServiceException ex) {
-            boolean limitHit = ex.getMessage() != null && ex.getMessage().contains("限制");
+            boolean limitHit = CommGatewayApiLogRecorder.isLimitFailure(ex);
             recordResult = limitHit
                     ? commGatewayApiLogRecorder.buildLimitFailure(ex, calleePhone, callerKey, null)
                     : commGatewayApiLogRecorder.buildFailure(ex, calleePhone, callerKey, null);

+ 10 - 6
fs-comm-gateway/对接文档.md

@@ -562,13 +562,17 @@ location /comm/ {
 
 ## 11. 附录:相关数据表
 
-| 表名 | 说明 |
-|------|------|
-| company_voice_robotic_call_log_callphone | AI 外呼执行日志 |
-| company_voice_robotic_call_log_sendmsg | 短信发送日志 |
-| company_voice_robotic_call_log_addwx | 加微执行日志(其他模块写入) |
+| 表名 | 库 | 说明 |
+|------|-----|------|
+| comm_gateway_api_log | 主库 | 网关 API 调用日志(频率计数、Admin 查询) |
+| company_comm_gateway_log | 租户库 | 网关 API 调用记录(成功/失败/限频均写入) |
+| company_voice_robotic_call_log_callphone | 租户库 | AI 外呼执行日志 |
+| company_voice_robotic_call_log_sendmsg | 租户库 | 短信发送日志 |
+| company_voice_robotic_call_log_addwx | 租户库 | 加微执行日志(其他模块写入) |
+
+外呼/短信业务日志与 `company_comm_gateway_log` 均写入**当前租户库**;`comm_gateway_api_log` 写入主库用于全平台统计与频率限制计数。
 
-日志写入均路由至**当前租户库**,非主库。
+存量租户需执行:`fs-service/src/main/resources/db/20250604-company-comm-gateway-log.sql`
 
 ---
 

+ 47 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyCommGatewayLogController.java

@@ -0,0 +1,47 @@
+package com.fs.company.controller.company;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.company.domain.CompanyCommGatewayLog;
+import com.fs.company.service.ICompanyCommGatewayLogService;
+import com.fs.framework.security.SecurityUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 通讯网关 API 调用记录(租户库)
+ */
+@RestController
+@RequestMapping("/company/commGatewayLog")
+public class CompanyCommGatewayLogController extends BaseController {
+
+    @Autowired
+    private ICompanyCommGatewayLogService companyCommGatewayLogService;
+
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyCommGatewayLog query) {
+        query.setCompanyId(currentCompanyId());
+        startPage();
+        List<CompanyCommGatewayLog> list = companyCommGatewayLogService.selectList(query);
+        return getDataTable(list);
+    }
+
+    @GetMapping("/{logId}")
+    public AjaxResult getInfo(@PathVariable Long logId) {
+        CompanyCommGatewayLog log = companyCommGatewayLogService.selectById(logId);
+        if (log == null || !currentCompanyId().equals(log.getCompanyId())) {
+            return AjaxResult.error("记录不存在");
+        }
+        return AjaxResult.success(log);
+    }
+
+    private Long currentCompanyId() {
+        return SecurityUtils.getLoginUser().getUser().getCompanyId();
+    }
+}

+ 5 - 0
fs-service/src/main/java/com/fs/comm/service/CommVoiceLimitService.java

@@ -122,6 +122,11 @@ public class CommVoiceLimitService {
         return normalizePhone(PhoneUtil.decryptAutoPhone(callees.getPhone()));
     }
 
+    /** 解析短信被叫号码(供日志与限频使用) */
+    public String resolveSmsCalleePhone(Long calleeId, Long customerId, String phone) {
+        return resolveSmsPhone(calleeId, customerId, phone);
+    }
+
     private String resolveSmsPhone(Long calleeId, Long customerId, String phone) {
         if (StringUtils.isNotBlank(phone)) {
             return normalizePhone(PhoneUtil.decryptAutoPhone(phone.trim()));

+ 49 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyCommGatewayLog.java

@@ -0,0 +1,49 @@
+package com.fs.company.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 通讯网关 API 调用记录(租户库 company_comm_gateway_log)
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CompanyCommGatewayLog extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long logId;
+    /** 主库 comm_gateway_api_log.log_id */
+    private Long masterLogId;
+    private Long companyId;
+    private Long companyUserId;
+    private String callerAccount;
+    private String apiType;
+    private String apiPath;
+    private String requestBody;
+    private String responseBody;
+    private Integer resultCode;
+    private String resultMsg;
+    private Integer success;
+    private Integer limitHit;
+    private String limitReason;
+    private String calleePhone;
+    private String callerPhone;
+    private Long gatewayId;
+    private BigDecimal billingAmount;
+    private BigDecimal costPrice;
+    private BigDecimal calcPrice;
+    private Integer billingQuantity;
+    private String billingUnit;
+    private String clientIp;
+    private String authScope;
+    private Integer durationMs;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+}

+ 14 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyCommGatewayLogMapper.java

@@ -0,0 +1,14 @@
+package com.fs.company.mapper;
+
+import com.fs.company.domain.CompanyCommGatewayLog;
+
+import java.util.List;
+
+public interface CompanyCommGatewayLogMapper {
+
+    int insertCompanyCommGatewayLog(CompanyCommGatewayLog log);
+
+    CompanyCommGatewayLog selectCompanyCommGatewayLogById(Long logId);
+
+    List<CompanyCommGatewayLog> selectCompanyCommGatewayLogList(CompanyCommGatewayLog query);
+}

+ 14 - 0
fs-service/src/main/java/com/fs/company/service/ICompanyCommGatewayLogService.java

@@ -0,0 +1,14 @@
+package com.fs.company.service;
+
+import com.fs.company.domain.CompanyCommGatewayLog;
+
+import java.util.List;
+
+public interface ICompanyCommGatewayLogService {
+
+    void saveLog(CompanyCommGatewayLog log);
+
+    CompanyCommGatewayLog selectById(Long logId);
+
+    List<CompanyCommGatewayLog> selectList(CompanyCommGatewayLog query);
+}

+ 35 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyCommGatewayLogServiceImpl.java

@@ -0,0 +1,35 @@
+package com.fs.company.service.impl;
+
+import com.fs.common.utils.DateUtils;
+import com.fs.company.domain.CompanyCommGatewayLog;
+import com.fs.company.mapper.CompanyCommGatewayLogMapper;
+import com.fs.company.service.ICompanyCommGatewayLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class CompanyCommGatewayLogServiceImpl implements ICompanyCommGatewayLogService {
+
+    @Autowired
+    private CompanyCommGatewayLogMapper companyCommGatewayLogMapper;
+
+    @Override
+    public void saveLog(CompanyCommGatewayLog log) {
+        if (log.getCreateTime() == null) {
+            log.setCreateTime(DateUtils.getNowDate());
+        }
+        companyCommGatewayLogMapper.insertCompanyCommGatewayLog(log);
+    }
+
+    @Override
+    public CompanyCommGatewayLog selectById(Long logId) {
+        return companyCommGatewayLogMapper.selectCompanyCommGatewayLogById(logId);
+    }
+
+    @Override
+    public List<CompanyCommGatewayLog> selectList(CompanyCommGatewayLog query) {
+        return companyCommGatewayLogMapper.selectCompanyCommGatewayLogList(query);
+    }
+}

+ 0 - 1
fs-service/src/main/java/com/fs/company/service/workflow/contact/ContactInfo.java

@@ -40,7 +40,6 @@ public class ContactInfo {
 
     /** 手机号 */
     private String phone;
-    private Long companyId;
 
     /** 渠道特有字段(不同渠道有不同的扩展信息) */
     private Map<String, Object> extra;

+ 0 - 5
fs-service/src/main/java/com/fs/company/service/workflow/impl/SensitiveWordServiceImpl.java

@@ -131,11 +131,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
         return result;
     }
 
-    @Override
-    public boolean isHighRiskSensitiveWord(String content, Long companyId) {
-        return false;
-    }
-
     private void loadFromOtherSources(List<Map<String, Object>> result) {
         if (fastGptChatReplaceWordsMapper != null) {
             try {

+ 45 - 0
fs-service/src/main/resources/db/20250604-company-comm-gateway-log.sql

@@ -0,0 +1,45 @@
+-- 租户库:通讯网关 API 调用记录(存量租户执行,幂等)
+SET @dbname = DATABASE();
+SET @tablename = 'company_comm_gateway_log';
+
+SET @preparedStatement = (SELECT IF(
+  (SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES
+   WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename) > 0,
+  'SELECT 1',
+  'CREATE TABLE `company_comm_gateway_log` (
+    `log_id` bigint NOT NULL AUTO_INCREMENT COMMENT ''主键'',
+    `master_log_id` bigint DEFAULT NULL COMMENT ''主库日志ID'',
+    `company_id` bigint DEFAULT NULL COMMENT ''公司ID'',
+    `company_user_id` bigint DEFAULT NULL COMMENT ''调用人用户ID'',
+    `caller_account` varchar(100) DEFAULT NULL COMMENT ''调用人账号'',
+    `api_type` varchar(20) NOT NULL COMMENT ''接口类型 call/sms'',
+    `api_path` varchar(200) DEFAULT NULL COMMENT ''请求路径'',
+    `request_body` mediumtext COMMENT ''请求参数'',
+    `response_body` mediumtext COMMENT ''返回参数'',
+    `result_code` int DEFAULT NULL COMMENT ''业务code'',
+    `result_msg` varchar(500) DEFAULT NULL COMMENT ''结果说明'',
+    `success` tinyint(1) DEFAULT 0 COMMENT ''是否成功 0否1是'',
+    `limit_hit` tinyint(1) DEFAULT 0 COMMENT ''是否触发频率限制 0否1是'',
+    `limit_reason` varchar(200) DEFAULT NULL COMMENT ''限制原因'',
+    `callee_phone` varchar(32) DEFAULT NULL COMMENT ''被叫号码'',
+    `caller_phone` varchar(32) DEFAULT NULL COMMENT ''主叫号码/线路标识'',
+    `gateway_id` bigint DEFAULT NULL COMMENT ''外呼线路ID'',
+    `billing_amount` decimal(12,4) DEFAULT 0.0000 COMMENT ''计费金额'',
+    `cost_price` decimal(12,4) DEFAULT 0.0000 COMMENT ''成本价(单价)'',
+    `calc_price` decimal(12,4) DEFAULT 0.0000 COMMENT ''计算价/售价(单价)'',
+    `billing_quantity` int DEFAULT 1 COMMENT ''计费数量'',
+    `billing_unit` varchar(20) DEFAULT NULL COMMENT ''计费单位 call/sms'',
+    `client_ip` varchar(64) DEFAULT NULL COMMENT ''客户端IP'',
+    `auth_scope` varchar(20) DEFAULT NULL COMMENT ''鉴权范围 external/internal'',
+    `duration_ms` int DEFAULT NULL COMMENT ''耗时毫秒'',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT ''创建时间'',
+    PRIMARY KEY (`log_id`),
+    KEY `idx_company_callee_time` (`company_id`,`callee_phone`,`create_time`),
+    KEY `idx_company_caller_time` (`company_id`,`caller_phone`,`create_time`),
+    KEY `idx_company_api_time` (`company_id`,`api_type`,`create_time`),
+    KEY `idx_master_log_id` (`master_log_id`)
+  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=''通讯网关API调用记录(租户库)'''
+));
+PREPARE createIfNotExists FROM @preparedStatement;
+EXECUTE createIfNotExists;
+DEALLOCATE PREPARE createIfNotExists;

+ 38 - 0
fs-service/src/main/resources/db/tenant-initTable.sql

@@ -17954,6 +17954,44 @@ CREATE TABLE `company_ai_workflow`
     INDEX             `index_del`(`del_flag`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
 -- ----------------------------
+-- Table structure for company_comm_gateway_log
+-- ----------------------------
+DROP TABLE IF EXISTS `company_comm_gateway_log`;
+CREATE TABLE `company_comm_gateway_log`
+(
+    `log_id`           bigint         NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `master_log_id`    bigint         DEFAULT NULL COMMENT '主库日志ID',
+    `company_id`       bigint         DEFAULT NULL COMMENT '公司ID',
+    `company_user_id`  bigint         DEFAULT NULL COMMENT '调用人用户ID',
+    `caller_account`   varchar(100)   DEFAULT NULL COMMENT '调用人账号',
+    `api_type`         varchar(20)    NOT NULL COMMENT '接口类型 call/sms',
+    `api_path`         varchar(200)   DEFAULT NULL COMMENT '请求路径',
+    `request_body`     mediumtext     COMMENT '请求参数',
+    `response_body`    mediumtext     COMMENT '返回参数',
+    `result_code`      int            DEFAULT NULL COMMENT '业务code',
+    `result_msg`       varchar(500)   DEFAULT NULL COMMENT '结果说明',
+    `success`          tinyint(1)     DEFAULT 0 COMMENT '是否成功 0否1是',
+    `limit_hit`        tinyint(1)     DEFAULT 0 COMMENT '是否触发频率限制 0否1是',
+    `limit_reason`     varchar(200)   DEFAULT NULL COMMENT '限制原因',
+    `callee_phone`     varchar(32)    DEFAULT NULL COMMENT '被叫号码',
+    `caller_phone`     varchar(32)    DEFAULT NULL COMMENT '主叫号码/线路标识',
+    `gateway_id`       bigint         DEFAULT NULL COMMENT '外呼线路ID',
+    `billing_amount`   decimal(12, 4) DEFAULT 0.0000 COMMENT '计费金额',
+    `cost_price`       decimal(12, 4) DEFAULT 0.0000 COMMENT '成本价(单价)',
+    `calc_price`       decimal(12, 4) DEFAULT 0.0000 COMMENT '计算价/售价(单价)',
+    `billing_quantity` int            DEFAULT 1 COMMENT '计费数量',
+    `billing_unit`     varchar(20)    DEFAULT NULL COMMENT '计费单位 call/sms',
+    `client_ip`        varchar(64)    DEFAULT NULL COMMENT '客户端IP',
+    `auth_scope`       varchar(20)    DEFAULT NULL COMMENT '鉴权范围 external/internal',
+    `duration_ms`      int            DEFAULT NULL COMMENT '耗时毫秒',
+    `create_time`      datetime       DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`log_id`) USING BTREE,
+    INDEX              `idx_company_callee_time`(`company_id`, `callee_phone`, `create_time`) USING BTREE,
+    INDEX              `idx_company_caller_time`(`company_id`, `caller_phone`, `create_time`) USING BTREE,
+    INDEX              `idx_company_api_time`(`company_id`, `api_type`, `create_time`) USING BTREE,
+    INDEX              `idx_master_log_id`(`master_log_id`) USING BTREE
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '通讯网关API调用记录(租户库)' ROW_FORMAT = DYNAMIC;
+-- ----------------------------
 -- Table structure for company_bind_gateway
 -- ----------------------------
 DROP TABLE IF EXISTS `company_bind_gateway`;

+ 80 - 0
fs-service/src/main/resources/mapper/company/CompanyCommGatewayLogMapper.xml

@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyCommGatewayLogMapper">
+
+    <resultMap id="CompanyCommGatewayLogResult" type="com.fs.company.domain.CompanyCommGatewayLog">
+        <result property="logId" column="log_id"/>
+        <result property="masterLogId" column="master_log_id"/>
+        <result property="companyId" column="company_id"/>
+        <result property="companyUserId" column="company_user_id"/>
+        <result property="callerAccount" column="caller_account"/>
+        <result property="apiType" column="api_type"/>
+        <result property="apiPath" column="api_path"/>
+        <result property="requestBody" column="request_body"/>
+        <result property="responseBody" column="response_body"/>
+        <result property="resultCode" column="result_code"/>
+        <result property="resultMsg" column="result_msg"/>
+        <result property="success" column="success"/>
+        <result property="limitHit" column="limit_hit"/>
+        <result property="limitReason" column="limit_reason"/>
+        <result property="calleePhone" column="callee_phone"/>
+        <result property="callerPhone" column="caller_phone"/>
+        <result property="gatewayId" column="gateway_id"/>
+        <result property="billingAmount" column="billing_amount"/>
+        <result property="costPrice" column="cost_price"/>
+        <result property="calcPrice" column="calc_price"/>
+        <result property="billingQuantity" column="billing_quantity"/>
+        <result property="billingUnit" column="billing_unit"/>
+        <result property="clientIp" column="client_ip"/>
+        <result property="authScope" column="auth_scope"/>
+        <result property="durationMs" column="duration_ms"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <sql id="selectCompanyCommGatewayLogVo">
+        select log_id, master_log_id, company_id, company_user_id, caller_account, api_type, api_path,
+               request_body, response_body, result_code, result_msg, success, limit_hit, limit_reason,
+               callee_phone, caller_phone, gateway_id, billing_amount, cost_price, calc_price, billing_quantity,
+               billing_unit, client_ip, auth_scope, duration_ms, create_time
+        from company_comm_gateway_log
+    </sql>
+
+    <select id="selectCompanyCommGatewayLogList" resultMap="CompanyCommGatewayLogResult">
+        <include refid="selectCompanyCommGatewayLogVo"/>
+        <where>
+            <if test="companyId != null">and company_id = #{companyId}</if>
+            <if test="companyUserId != null">and company_user_id = #{companyUserId}</if>
+            <if test="apiType != null and apiType != ''">and api_type = #{apiType}</if>
+            <if test="success != null">and success = #{success}</if>
+            <if test="limitHit != null">and limit_hit = #{limitHit}</if>
+            <if test="calleePhone != null and calleePhone != ''">and callee_phone like concat('%', #{calleePhone}, '%')</if>
+            <if test="callerAccount != null and callerAccount != ''">and caller_account like concat('%', #{callerAccount}, '%')</if>
+            <if test="params.beginTime != null and params.beginTime != ''">
+                and create_time &gt;= #{params.beginTime}
+            </if>
+            <if test="params.endTime != null and params.endTime != ''">
+                and create_time &lt;= #{params.endTime}
+            </if>
+        </where>
+        order by log_id desc
+    </select>
+
+    <select id="selectCompanyCommGatewayLogById" resultMap="CompanyCommGatewayLogResult">
+        <include refid="selectCompanyCommGatewayLogVo"/>
+        where log_id = #{logId}
+    </select>
+
+    <insert id="insertCompanyCommGatewayLog" useGeneratedKeys="true" keyProperty="logId">
+        insert into company_comm_gateway_log
+        (master_log_id, company_id, company_user_id, caller_account, api_type, api_path,
+         request_body, response_body, result_code, result_msg, success, limit_hit, limit_reason,
+         callee_phone, caller_phone, gateway_id, billing_amount, cost_price, calc_price, billing_quantity,
+         billing_unit, client_ip, auth_scope, duration_ms, create_time)
+        values
+        (#{masterLogId}, #{companyId}, #{companyUserId}, #{callerAccount}, #{apiType}, #{apiPath},
+         #{requestBody}, #{responseBody}, #{resultCode}, #{resultMsg}, #{success}, #{limitHit}, #{limitReason},
+         #{calleePhone}, #{callerPhone}, #{gatewayId}, #{billingAmount}, #{costPrice}, #{calcPrice}, #{billingQuantity},
+         #{billingUnit}, #{clientIp}, #{authScope}, #{durationMs}, #{createTime})
+    </insert>
+
+</mapper>