boss 2 недель назад
Родитель
Сommit
ec056f75a8
33 измененных файлов с 1410 добавлено и 15 удалено
  1. 52 0
      fs-admin/src/main/java/com/fs/admin/controller/AdminAdController.java
  2. 31 0
      fs-admin/src/main/java/com/fs/admin/controller/AdminCommissionRecordController.java
  3. 52 0
      fs-admin/src/main/java/com/fs/admin/controller/AdminConsumeRecordController.java
  4. 49 0
      fs-admin/src/main/java/com/fs/admin/controller/AdminCrmController.java
  5. 13 1
      fs-admin/src/main/java/com/fs/admin/controller/AdminModuleUsageController.java
  6. 5 5
      fs-admin/src/main/java/com/fs/admin/controller/AdminProxyController.java
  7. 58 1
      fs-admin/src/main/java/com/fs/admin/controller/AdminRechargeRecordController.java
  8. 50 0
      fs-admin/src/main/java/com/fs/admin/controller/AdminSopController.java
  9. 57 0
      fs-admin/src/main/java/com/fs/admin/controller/AdminStoreOrderAdminController.java
  10. 17 3
      fs-admin/src/main/java/com/fs/admin/controller/AdminWithdrawalController.java
  11. 12 0
      fs-admin/src/main/java/com/fs/admin/controller/AiChatQualityController.java
  12. 54 0
      fs-admin/src/main/java/com/fs/admin/controller/ArticleAdminController.java
  13. 16 0
      fs-admin/src/main/java/com/fs/admin/controller/CallRecordAdminController.java
  14. 13 0
      fs-admin/src/main/java/com/fs/admin/controller/CompanyAdminController.java
  15. 15 0
      fs-admin/src/main/java/com/fs/admin/controller/CompanyUserAdminController.java
  16. 52 0
      fs-admin/src/main/java/com/fs/admin/controller/CourseAdminController.java
  17. 54 0
      fs-admin/src/main/java/com/fs/admin/controller/LiveAdminController.java
  18. 56 0
      fs-admin/src/main/java/com/fs/admin/controller/ProductAdminController.java
  19. 61 0
      fs-admin/src/main/java/com/fs/admin/controller/ProxyOperLogController.java
  20. 13 0
      fs-admin/src/main/java/com/fs/admin/controller/QwExternalContactAdminController.java
  21. 41 0
      fs-admin/src/main/java/com/fs/admin/vo/ConsumeRecordExportVO.java
  22. 41 0
      fs-admin/src/main/java/com/fs/admin/vo/RechargeRecordExportVO.java
  23. 41 0
      fs-common/src/main/java/com/fs/common/annotation/ProxyLog.java
  24. 243 0
      fs-framework/src/main/java/com/fs/framework/aspectj/ProxyLogAspect.java
  25. 2 0
      fs-service/src/main/java/com/fs/company/aspectj/LiveControllerAspect.java
  26. 5 5
      fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java
  27. 2 0
      fs-service/src/main/java/com/fs/live/aspectj/LiveControllerAspect.java
  28. 95 0
      fs-service/src/main/java/com/fs/proxy/domain/ProxyOperLog.java
  29. 38 0
      fs-service/src/main/java/com/fs/proxy/mapper/ProxyOperLogMapper.java
  30. 38 0
      fs-service/src/main/java/com/fs/proxy/service/IProxyOperLogService.java
  31. 46 0
      fs-service/src/main/java/com/fs/proxy/service/impl/ProxyOperLogServiceImpl.java
  32. 65 0
      fs-service/src/main/resources/mapper/proxy/ProxyOperLogMapper.xml
  33. 23 0
      sql/proxy_oper_log.sql

+ 52 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminAdController.java

@@ -3,8 +3,14 @@ package com.fs.admin.controller;
 import java.util.*;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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 lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -20,6 +26,52 @@ public class AdminAdController extends BaseController {
     @Autowired
     private AdminCrossTenantHelper crossTenantHelper;
 
+    @Log(title = "导出广告账户", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:ad:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String accountName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT * FROM ad_account WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (accountName != null && !accountName.isEmpty()) {
+                        sql.append(" AND account_name LIKE ?");
+                        params.add("%" + accountName + "%");
+                    }
+                    sql.append(" ORDER BY create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<AdExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            AdExportVO vo = new AdExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setAccountName(str(m.get("account_name")));
+            vo.setPlatform(str(m.get("platform")));
+            vo.setStatus(str(m.get("status")));
+            vo.setCostAmount(str(m.get("cost_amount")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<AdExportVO> util = new ExcelUtil<>(AdExportVO.class);
+        return util.exportExcel(voList, "广告账户数据");
+    }
+
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class AdExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "账户名称") private String accountName;
+        @Excel(name = "平台") private String platform;
+        @Excel(name = "状态") private String status;
+        @Excel(name = "消耗金额") private String costAmount;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
     @PreAuthorize("@ss.hasPermi('admin:ad:list')")
     @GetMapping("/list")
     public TableDataInfo list(@RequestParam(required = false) String accountName,

+ 31 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminCommissionRecordController.java

@@ -2,9 +2,12 @@ package com.fs.admin.controller;
 
 import java.util.List;
 
+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.proxy.domain.Proxy;
 import com.fs.proxy.domain.TenantConsumeRecord;
 import com.fs.proxy.service.ProxyService;
@@ -61,6 +64,34 @@ public class AdminCommissionRecordController extends BaseController {
         return getDataTable(list);
     }
 
+    /**
+     * 导出返佣记录
+     */
+    @Log(title = "导出返佣记录", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:commissionRecord:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String proxyName,
+                             @RequestParam(required = false) String tenantName,
+                             @RequestParam(required = false) String beginTime,
+                             @RequestParam(required = false) String endTime) {
+        TenantConsumeRecord query = new TenantConsumeRecord();
+        if (tenantName != null && !tenantName.isEmpty()) { query.setTenantName(tenantName); }
+        if (proxyName != null && !proxyName.isEmpty()) {
+            Proxy proxyQuery = new Proxy();
+            proxyQuery.setProxyName(proxyName);
+            List<Proxy> proxies = proxyService.selectProxyList(proxyQuery);
+            if (proxies.isEmpty()) {
+                return new ExcelUtil<>(TenantConsumeRecord.class).exportExcel(new java.util.ArrayList<>(), "返佣记录数据");
+            }
+            query.setProxyId(proxies.get(0).getProxyId());
+        }
+        if (beginTime != null && !beginTime.isEmpty()) { query.setBeginTime(beginTime); }
+        if (endTime != null && !endTime.isEmpty()) { query.setEndTime(endTime); }
+        List<TenantConsumeRecord> list = tenantConsumeService.selectTenantConsumeRecordList(query);
+        ExcelUtil<TenantConsumeRecord> util = new ExcelUtil<>(TenantConsumeRecord.class);
+        return util.exportExcel(list, "返佣记录数据");
+    }
+
     /**
      * 获取返佣记录详情
      */

+ 52 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminConsumeRecordController.java

@@ -4,11 +4,15 @@ import java.util.*;
 import java.util.stream.Collectors;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.admin.vo.ConsumeRecordExportVO;
 import com.fs.billing.domain.TenantWalletTxn;
 import com.fs.billing.mapper.TenantWalletTxnMapper;
+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.company.domain.Company;
 import com.fs.company.mapper.CompanyMapper;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -65,6 +69,54 @@ public class AdminConsumeRecordController extends BaseController {
         return getDataTable(resultList);
     }
 
+    /**
+     * 导出消费记录
+     */
+    @Log(title = "导出消费记录", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:consumeRecord:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String tenantName,
+                             @RequestParam(required = false) String eventType,
+                             @RequestParam(required = false) String beginTime,
+                             @RequestParam(required = false) String endTime) {
+        LambdaQueryWrapper<TenantWalletTxn> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TenantWalletTxn::getTxnType, "CONSUME");
+        wrapper.orderByDesc(TenantWalletTxn::getCreateTime);
+        if (tenantName != null && !tenantName.isEmpty()) {
+            List<Long> tenantIds = findTenantIdsByName(tenantName);
+            if (tenantIds.isEmpty()) {
+                return new ExcelUtil<>(ConsumeRecordExportVO.class).exportExcel(new ArrayList<>(), "消费记录数据");
+            }
+            wrapper.in(TenantWalletTxn::getTenantId, tenantIds);
+        }
+        if (eventType != null && !eventType.isEmpty()) { wrapper.eq(TenantWalletTxn::getBizType, eventType); }
+        if (beginTime != null && !beginTime.isEmpty()) { wrapper.ge(TenantWalletTxn::getCreateTime, beginTime + " 00:00:00"); }
+        if (endTime != null && !endTime.isEmpty()) { wrapper.le(TenantWalletTxn::getCreateTime, endTime + " 23:59:59"); }
+        List<TenantWalletTxn> txnList = tenantWalletTxnMapper.selectList(wrapper);
+        List<Long> tids = txnList.stream().map(TenantWalletTxn::getTenantId).distinct().collect(Collectors.toList());
+        Map<Long, String> nameMap = new HashMap<>();
+        if (!tids.isEmpty()) {
+            List<Company> cs = companyMapper.selectCompanyByIds(tids);
+            if (cs != null) { for (Company c : cs) { nameMap.put(c.getCompanyId(), c.getCompanyName()); } }
+        }
+        List<ConsumeRecordExportVO> exportList = new ArrayList<>();
+        for (TenantWalletTxn txn : txnList) {
+            ConsumeRecordExportVO vo = new ConsumeRecordExportVO();
+            vo.setId(txn.getId());
+            vo.setTenantId(txn.getTenantId());
+            vo.setTenantName(nameMap.getOrDefault(txn.getTenantId(), ""));
+            vo.setAmount(txn.getAmount());
+            vo.setBalanceAfter(txn.getBalanceAfter());
+            vo.setEventType(txn.getBizType());
+            vo.setBizId(txn.getBizId());
+            vo.setOccurredAt(txn.getCreateTime());
+            vo.setRemark(txn.getRemark());
+            exportList.add(vo);
+        }
+        ExcelUtil<ConsumeRecordExportVO> util = new ExcelUtil<>(ConsumeRecordExportVO.class);
+        return util.exportExcel(exportList, "消费记录数据");
+    }
+
     /**
      * 获取消费记录详情
      */

+ 49 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminCrmController.java

@@ -3,8 +3,14 @@ package com.fs.admin.controller;
 import java.util.*;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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 lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -20,6 +26,49 @@ public class AdminCrmController extends BaseController {
     @Autowired
     private AdminCrossTenantHelper crossTenantHelper;
 
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class CrmExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "客户名称") private String customerName;
+        @Excel(name = "手机号") private String customerPhone;
+        @Excel(name = "客户阶段") private String stage;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出CRM客户", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:crm:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String customerName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT c.* FROM crm_customer c WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (customerName != null && !customerName.isEmpty()) {
+                        sql.append(" AND c.customer_name LIKE ?");
+                        params.add("%" + customerName + "%");
+                    }
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<CrmExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            CrmExportVO vo = new CrmExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setCustomerName(str(m.get("customer_name")));
+            vo.setCustomerPhone(str(m.get("customer_phone")));
+            vo.setStage(str(m.get("stage")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<CrmExportVO> util = new ExcelUtil<>(CrmExportVO.class);
+        return util.exportExcel(voList, "CRM客户数据");
+    }
+
     @PreAuthorize("@ss.hasPermi('admin:crm:list')")
     @GetMapping("/list")
     public TableDataInfo list(@RequestParam(required = false) String customerName,

+ 13 - 1
fs-admin/src/main/java/com/fs/admin/controller/AdminModuleUsageController.java

@@ -10,6 +10,7 @@ import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.proxy.domain.TenantModuleUsage;
 import com.fs.proxy.service.TenantModuleUsageService;
+import com.fs.common.utils.poi.ExcelUtil;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -26,9 +27,20 @@ public class AdminModuleUsageController extends BaseController {
     @Autowired
     private TenantModuleUsageService tenantModuleUsageService;
 
+    /**
+     * 导出租户模块使用统计
+     */
+    @PreAuthorize("@ss.hasPermi('admin:moduleUsage:list')")
+    @Log(title = "导出租户模块使用统计", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(TenantModuleUsage query) {
+        List<TenantModuleUsage> list = tenantModuleUsageService.selectTenantModuleUsageList(query);
+        ExcelUtil<TenantModuleUsage> util = new ExcelUtil<>(TenantModuleUsage.class);
+        return util.exportExcel(list, "租户模块使用统计");
+    }
+
     /**
      * 查询所有租户模块使用统计列表
-     * 支持按代理ID、租户名称、日期范围筛选
      */
     @PreAuthorize("@ss.hasPermi('admin:moduleUsage:list')")
     @GetMapping("/list")

+ 5 - 5
fs-admin/src/main/java/com/fs/admin/controller/AdminProxyController.java

@@ -2,7 +2,7 @@ package com.fs.admin.controller;
 
 import java.util.List;
 
-import com.fs.common.annotation.Log;
+import com.fs.common.annotation.ProxyLog;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.page.TableDataInfo;
@@ -50,7 +50,7 @@ public class AdminProxyController extends BaseController
      * 新增代理
      */
     @PreAuthorize("@ss.hasPermi('admin:proxy:add')")
-    @Log(title = "代理管理", businessType = BusinessType.INSERT)
+    @ProxyLog(title = "代理管理", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody Proxy proxy) {
         return toAjax(proxyService.insertProxy(proxy));
@@ -60,7 +60,7 @@ public class AdminProxyController extends BaseController
      * 修改代理
      */
     @PreAuthorize("@ss.hasPermi('admin:proxy:edit')")
-    @Log(title = "代理管理", businessType = BusinessType.UPDATE)
+    @ProxyLog(title = "代理管理", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody Proxy proxy) {
         return toAjax(proxyService.updateProxy(proxy));
@@ -70,7 +70,7 @@ public class AdminProxyController extends BaseController
      * 删除代理
      */
     @PreAuthorize("@ss.hasPermi('admin:proxy:remove')")
-    @Log(title = "代理管理", businessType = BusinessType.DELETE)
+    @ProxyLog(title = "代理管理", businessType = BusinessType.DELETE)
     @DeleteMapping("/{proxyIds}")
     public AjaxResult remove(@PathVariable Long[] proxyIds) {
         return toAjax(proxyService.deleteProxyByIds(proxyIds));
@@ -80,7 +80,7 @@ public class AdminProxyController extends BaseController
      * 启用/禁用代理
      */
     @PreAuthorize("@ss.hasPermi('admin:proxy:edit')")
-    @Log(title = "代理管理", businessType = BusinessType.UPDATE)
+    @ProxyLog(title = "代理管理", businessType = BusinessType.UPDATE)
     @PutMapping("/changeStatus/{proxyId}")
     public AjaxResult changeStatus(@PathVariable Long proxyId, @RequestParam Integer status) {
         Proxy proxy = new Proxy();

+ 58 - 1
fs-admin/src/main/java/com/fs/admin/controller/AdminRechargeRecordController.java

@@ -3,11 +3,15 @@ package com.fs.admin.controller;
 import java.util.*;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.admin.vo.RechargeRecordExportVO;
 import com.fs.billing.domain.TenantWalletTxn;
 import com.fs.billing.mapper.TenantWalletTxnMapper;
+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.company.domain.Company;
 import com.fs.company.mapper.CompanyMapper;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -97,7 +101,60 @@ public class AdminRechargeRecordController extends BaseController {
     }
 
     /**
-     * 获取充值记录详情
+     * 导出充値记录
+     */
+    @Log(title = "导出充値记录", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:rechargeRecord:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String tenantName,
+                             @RequestParam(required = false) String createBy,
+                             @RequestParam(required = false) String beginTime,
+                             @RequestParam(required = false) String endTime) {
+        LambdaQueryWrapper<TenantWalletTxn> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TenantWalletTxn::getTxnType, "RECHARGE");
+        wrapper.orderByDesc(TenantWalletTxn::getCreateTime);
+        if (tenantName != null && !tenantName.isEmpty()) {
+            Company query = new Company();
+            query.setCompanyName(tenantName);
+            List<Company> companies = companyMapper.selectCompanyList(query);
+            List<Long> tenantIds = new ArrayList<>();
+            for (Company c : companies) { tenantIds.add(c.getCompanyId()); }
+            if (tenantIds.isEmpty()) {
+                return new ExcelUtil<>(RechargeRecordExportVO.class).exportExcel(new ArrayList<>(), "充値记录数据");
+            }
+            wrapper.in(TenantWalletTxn::getTenantId, tenantIds);
+        }
+        if (createBy != null && !createBy.isEmpty()) { wrapper.like(TenantWalletTxn::getCreateBy, createBy); }
+        if (beginTime != null && !beginTime.isEmpty()) { wrapper.ge(TenantWalletTxn::getCreateTime, beginTime + " 00:00:00"); }
+        if (endTime != null && !endTime.isEmpty()) { wrapper.le(TenantWalletTxn::getCreateTime, endTime + " 23:59:59"); }
+        List<TenantWalletTxn> txnList = tenantWalletTxnMapper.selectList(wrapper);
+        List<Long> tids = new ArrayList<>();
+        for (TenantWalletTxn t : txnList) { tids.add(t.getTenantId()); }
+        Map<Long, String> nameMap = new HashMap<>();
+        if (!tids.isEmpty()) {
+            List<Company> cs = companyMapper.selectCompanyByIds(tids);
+            if (cs != null) { for (Company c : cs) { nameMap.put(c.getCompanyId(), c.getCompanyName()); } }
+        }
+        List<RechargeRecordExportVO> exportList = new ArrayList<>();
+        for (TenantWalletTxn txn : txnList) {
+            RechargeRecordExportVO vo = new RechargeRecordExportVO();
+            vo.setId(txn.getId());
+            vo.setTenantId(txn.getTenantId());
+            vo.setTenantName(nameMap.getOrDefault(txn.getTenantId(), ""));
+            vo.setAmount(txn.getAmount());
+            vo.setBalanceAfter(txn.getBalanceAfter());
+            vo.setPayMethod(txn.getBizType());
+            vo.setCreateBy(txn.getCreateBy());
+            vo.setCreateTime(txn.getCreateTime());
+            vo.setRemark(txn.getRemark());
+            exportList.add(vo);
+        }
+        ExcelUtil<RechargeRecordExportVO> util = new ExcelUtil<>(RechargeRecordExportVO.class);
+        return util.exportExcel(exportList, "充値记录数据");
+    }
+    
+    /**
+     * 获取充値记录详情
      */
     @PreAuthorize("@ss.hasPermi('admin:rechargeRecord:list')")
     @GetMapping("/{id}")

+ 50 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminSopController.java

@@ -3,8 +3,14 @@ package com.fs.admin.controller;
 import java.util.*;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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 lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -20,6 +26,50 @@ public class AdminSopController extends BaseController {
     @Autowired
     private AdminCrossTenantHelper crossTenantHelper;
 
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class SopExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "SOP名称") private String name;
+        @Excel(name = "类型") private String type;
+        @Excel(name = "状态") private String status;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出SOP", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:sop:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String sopName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder("SELECT qs.* FROM qw_sop qs WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (sopName != null && !sopName.isEmpty()) {
+                        sql.append(" AND qs.name LIKE ?");
+                        params.add("%" + sopName + "%");
+                    }
+                    sql.append(" ORDER BY qs.create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<SopExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            SopExportVO vo = new SopExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setName(str(m.get("name")));
+            vo.setType(str(m.get("type")));
+            vo.setStatus(str(m.get("status")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<SopExportVO> util = new ExcelUtil<>(SopExportVO.class);
+        return util.exportExcel(voList, "SOP数据");
+    }
+
     @PreAuthorize("@ss.hasPermi('admin:sop:list')")
     @GetMapping("/list")
     public TableDataInfo list(@RequestParam(required = false) String sopName,

+ 57 - 0
fs-admin/src/main/java/com/fs/admin/controller/AdminStoreOrderAdminController.java

@@ -3,8 +3,14 @@ package com.fs.admin.controller;
 import java.util.*;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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 lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -20,6 +26,57 @@ public class AdminStoreOrderAdminController extends BaseController {
     @Autowired
     private AdminCrossTenantHelper crossTenantHelper;
 
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class StoreOrderExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "订单编号") private String orderNo;
+        @Excel(name = "订单金额") private String orderAmount;
+        @Excel(name = "支付方式") private String payType;
+        @Excel(name = "订单状态") private String orderStatus;
+        @Excel(name = "用户名") private String userName;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出商城订单", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:storeOrder:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String orderNo,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder(
+                            "SELECT o.order_id, o.order_no, o.order_amount, o.pay_type, " +
+                            "o.order_status, o.create_time, o.user_name " +
+                            "FROM fs_store_order_scrm o WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (orderNo != null && !orderNo.isEmpty()) {
+                        sql.append(" AND o.order_no LIKE ?");
+                        params.add("%" + orderNo + "%");
+                    }
+                    sql.append(" ORDER BY o.create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<StoreOrderExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            StoreOrderExportVO vo = new StoreOrderExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setOrderNo(str(m.get("order_no")));
+            vo.setOrderAmount(str(m.get("order_amount")));
+            vo.setPayType(str(m.get("pay_type")));
+            vo.setOrderStatus(str(m.get("order_status")));
+            vo.setUserName(str(m.get("user_name")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<StoreOrderExportVO> util = new ExcelUtil<>(StoreOrderExportVO.class);
+        return util.exportExcel(voList, "商城订单数据");
+    }
+
     /**
      * 查询所有租户的销售订单列表
      */

+ 17 - 3
fs-admin/src/main/java/com/fs/admin/controller/AdminWithdrawalController.java

@@ -3,11 +3,13 @@ package com.fs.admin.controller;
 import java.util.List;
 
 import com.fs.common.annotation.Log;
+import com.fs.common.annotation.ProxyLog;
 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.SecurityUtils;
+import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.proxy.domain.ProxyWithdraw;
 import com.fs.proxy.service.ProxyWithdrawService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -35,6 +37,18 @@ public class AdminWithdrawalController extends BaseController {
         return getDataTable(list);
     }
 
+    /**
+     * 导出提现申请列表
+     */
+    @Log(title = "导出提现申请", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:withdrawal:list')")
+    @GetMapping("/export")
+    public AjaxResult export(ProxyWithdraw proxyWithdraw) {
+        List<ProxyWithdraw> list = proxyWithdrawService.selectProxyWithdrawList(proxyWithdraw);
+        ExcelUtil<ProxyWithdraw> util = new ExcelUtil<>(ProxyWithdraw.class);
+        return util.exportExcel(list, "提现申请数据");
+    }
+
     /**
      * 获取提现申请详情
      */
@@ -49,7 +63,7 @@ public class AdminWithdrawalController extends BaseController {
      * 审核通过(status 0→1)
      */
     @PreAuthorize("@ss.hasPermi('admin:withdrawal:approve')")
-    @Log(title = "提现管理", businessType = BusinessType.UPDATE)
+    @ProxyLog(title = "提现管理", businessType = BusinessType.UPDATE)
     @PutMapping("/{id}/approve")
     public AjaxResult approve(@PathVariable Long id) {
         Long auditorId = SecurityUtils.getUserId();
@@ -61,7 +75,7 @@ public class AdminWithdrawalController extends BaseController {
      * 审核拒绝(status 0→2)
      */
     @PreAuthorize("@ss.hasPermi('admin:withdrawal:approve')")
-    @Log(title = "提现管理", businessType = BusinessType.UPDATE)
+    @ProxyLog(title = "提现管理", businessType = BusinessType.UPDATE)
     @PutMapping("/{id}/reject")
     public AjaxResult reject(@PathVariable Long id, @RequestParam String reason) {
         Long auditorId = SecurityUtils.getUserId();
@@ -73,7 +87,7 @@ public class AdminWithdrawalController extends BaseController {
      * 确认打款(status 1→3)
      */
     @PreAuthorize("@ss.hasPermi('admin:withdrawal:approve')")
-    @Log(title = "提现管理", businessType = BusinessType.UPDATE)
+    @ProxyLog(title = "提现管理", businessType = BusinessType.UPDATE)
     @PutMapping("/{id}/paid")
     public AjaxResult confirmPaid(@PathVariable Long id) {
         int rows = proxyWithdrawService.payWithdraw(id);

+ 12 - 0
fs-admin/src/main/java/com/fs/admin/controller/AiChatQualityController.java

@@ -1,8 +1,11 @@
 package com.fs.admin.controller;
 
+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.fastGpt.domain.FastGptChatMsg;
 import com.fs.fastGpt.domain.FastGptChatSession;
 import com.fs.fastGpt.service.IFastGptChatMsgService;
@@ -28,6 +31,15 @@ public class AiChatQualityController extends BaseController {
     @Autowired
     private AiChatQualityService aiChatQualityService;
 
+    @Log(title = "导出AI对话会话", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:list')")
+    @GetMapping("/export")
+    public AjaxResult export(FastGptChatSession session) {
+        List<FastGptChatSession> list = fastGptChatSessionService.selectFastGptChatSessionList(session);
+        ExcelUtil<FastGptChatSession> util = new ExcelUtil<>(FastGptChatSession.class);
+        return util.exportExcel(list, "AI对话质检数据");
+    }
+
     @PreAuthorize("@ss.hasPermi('admin:aiChatQuality:list')")
     @GetMapping({"/list", "/sessions"})
     public TableDataInfo listSessions(FastGptChatSession session) {

+ 54 - 0
fs-admin/src/main/java/com/fs/admin/controller/ArticleAdminController.java

@@ -3,9 +3,14 @@ package com.fs.admin.controller;
 import java.util.*;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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 lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -21,6 +26,55 @@ public class ArticleAdminController extends BaseController {
     @Autowired
     private AdminCrossTenantHelper crossTenantHelper;
 
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class ArticleExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "文章标题") private String articleTitle;
+        @Excel(name = "分类") private String categoryName;
+        @Excel(name = "作者") private String author;
+        @Excel(name = "状态") private String status;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出文章", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:article:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String articleTitle,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder(
+                            "SELECT a.article_id, a.article_title, a.category_name, a.author, " +
+                            "a.create_time, a.status " +
+                            "FROM fs_article a WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (articleTitle != null && !articleTitle.isEmpty()) {
+                        sql.append(" AND a.article_title LIKE ?");
+                        params.add("%" + articleTitle + "%");
+                    }
+                    sql.append(" ORDER BY a.create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<ArticleExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            ArticleExportVO vo = new ArticleExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setArticleTitle(str(m.get("article_title")));
+            vo.setCategoryName(str(m.get("category_name")));
+            vo.setAuthor(str(m.get("author")));
+            vo.setStatus(str(m.get("status")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<ArticleExportVO> util = new ExcelUtil<>(ArticleExportVO.class);
+        return util.exportExcel(voList, "文章数据");
+    }
+
     /**
      * 查询所有租户的文章列表
      */

+ 16 - 0
fs-admin/src/main/java/com/fs/admin/controller/CallRecordAdminController.java

@@ -1,9 +1,12 @@
 package com.fs.admin.controller;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+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.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.service.ICompanyVoiceRoboticCallLogCallphoneService;
 import com.fs.proxy.domain.CallQualityRecord;
@@ -48,6 +51,19 @@ public class CallRecordAdminController extends BaseController {
         return AjaxResult.success(callLogService.getById(logId));
     }
 
+    /**
+     * 导出通话记录
+     */
+    @Log(title = "导出通话记录", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:callRecord:list')")
+    @GetMapping("/export")
+    public AjaxResult export(CompanyVoiceRoboticCallLogCallphone param) {
+        List<CompanyVoiceRoboticCallLogCallphone> list = callLogService.list(
+            new QueryWrapper<CompanyVoiceRoboticCallLogCallphone>().orderByDesc("run_time"));
+        ExcelUtil<CompanyVoiceRoboticCallLogCallphone> util = new ExcelUtil<>(CompanyVoiceRoboticCallLogCallphone.class);
+        return util.exportExcel(list, "通话记录数据");
+    }
+
     /**
      * 根据租户ID查询外呼记录
      */

+ 13 - 0
fs-admin/src/main/java/com/fs/admin/controller/CompanyAdminController.java

@@ -6,6 +6,7 @@ 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.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.Company;
 import com.fs.company.service.ICompanyService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -38,6 +39,18 @@ public class CompanyAdminController extends BaseController {
         return getDataTable(list);
     }
 
+    /**
+     * 导出租户列表
+     */
+    @Log(title = "导出租户列表", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:company:list')")
+    @GetMapping("/export")
+    public AjaxResult export(Company company) {
+        List<Company> list = companyService.selectCompanyList(company);
+        ExcelUtil<Company> util = new ExcelUtil<>(Company.class);
+        return util.exportExcel(list, "租户列表数据");
+    }
+
     /**
      * 获取租户详细信息
      */

+ 15 - 0
fs-admin/src/main/java/com/fs/admin/controller/CompanyUserAdminController.java

@@ -3,6 +3,9 @@ package com.fs.admin.controller;
 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.annotation.Log;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.domain.CompanyUserChangeApply;
@@ -43,6 +46,18 @@ public class CompanyUserAdminController extends BaseController {
         return getDataTable(list);
     }
 
+    /**
+     * 导出员工列表
+     */
+    @Log(title = "导出员工列表", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:companyUser:list')")
+    @GetMapping("/export")
+    public AjaxResult export(CompanyUser companyUser) {
+        List<CompanyUser> list = companyUserService.selectCompanyUserList(companyUser);
+        ExcelUtil<CompanyUser> util = new ExcelUtil<>(CompanyUser.class);
+        return util.exportExcel(list, "员工账户数据");
+    }
+
     /**
      * 获取员工详细信息
      */

+ 52 - 0
fs-admin/src/main/java/com/fs/admin/controller/CourseAdminController.java

@@ -3,9 +3,14 @@ package com.fs.admin.controller;
 import java.util.*;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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 lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -21,6 +26,53 @@ public class CourseAdminController extends BaseController {
     @Autowired
     private AdminCrossTenantHelper crossTenantHelper;
 
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class CourseExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "课程名称") private String courseName;
+        @Excel(name = "播放次数") private String playCount;
+        @Excel(name = "状态") private String status;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出课程", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:course:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String courseName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder(
+                            "SELECT v.video_id, v.course_id, v.course_name, v.cover_url, " +
+                            "v.duration, v.play_count, v.create_time, v.status " +
+                            "FROM fs_user_course_video v WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (courseName != null && !courseName.isEmpty()) {
+                        sql.append(" AND v.course_name LIKE ?");
+                        params.add("%" + courseName + "%");
+                    }
+                    sql.append(" ORDER BY v.create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<CourseExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            CourseExportVO vo = new CourseExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setCourseName(str(m.get("course_name")));
+            vo.setPlayCount(str(m.get("play_count")));
+            vo.setStatus(str(m.get("status")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<CourseExportVO> util = new ExcelUtil<>(CourseExportVO.class);
+        return util.exportExcel(voList, "课程数据");
+    }
+
     /**
      * 查询所有租户的课程列表
      */

+ 54 - 0
fs-admin/src/main/java/com/fs/admin/controller/LiveAdminController.java

@@ -3,9 +3,14 @@ package com.fs.admin.controller;
 import java.util.*;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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 lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -21,6 +26,55 @@ public class LiveAdminController extends BaseController {
     @Autowired
     private AdminCrossTenantHelper crossTenantHelper;
 
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class LiveExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "直播标题") private String title;
+        @Excel(name = "状态") private String liveStatus;
+        @Excel(name = "开始时间") private String startTime;
+        @Excel(name = "结束时间") private String endTime;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出直播", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:live:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String liveName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder(
+                            "SELECT v.video_id, v.title, v.cover_url, v.live_status, " +
+                            "v.start_time, v.end_time, v.create_time " +
+                            "FROM live_video v WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (liveName != null && !liveName.isEmpty()) {
+                        sql.append(" AND v.title LIKE ?");
+                        params.add("%" + liveName + "%");
+                    }
+                    sql.append(" ORDER BY v.create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<LiveExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            LiveExportVO vo = new LiveExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setTitle(str(m.get("title")));
+            vo.setLiveStatus(str(m.get("live_status")));
+            vo.setStartTime(str(m.get("start_time")));
+            vo.setEndTime(str(m.get("end_time")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<LiveExportVO> util = new ExcelUtil<>(LiveExportVO.class);
+        return util.exportExcel(voList, "直播数据");
+    }
+
     /**
      * 查询所有租户的直播列表
      */

+ 56 - 0
fs-admin/src/main/java/com/fs/admin/controller/ProductAdminController.java

@@ -3,9 +3,14 @@ package com.fs.admin.controller;
 import java.util.*;
 
 import com.fs.admin.helper.AdminCrossTenantHelper;
+import com.fs.common.annotation.Excel;
+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 lombok.Data;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -21,6 +26,57 @@ public class ProductAdminController extends BaseController {
     @Autowired
     private AdminCrossTenantHelper crossTenantHelper;
 
+    private String str(Object o) { return o == null ? "" : o.toString(); }
+
+    @Data
+    public static class ProductExportVO {
+        @Excel(name = "租户名称") private String companyName;
+        @Excel(name = "商品名称") private String productName;
+        @Excel(name = "分类") private String categoryName;
+        @Excel(name = "价格") private String price;
+        @Excel(name = "库存") private String stock;
+        @Excel(name = "状态") private String status;
+        @Excel(name = "创建时间") private String createTime;
+    }
+
+    @Log(title = "导出商品", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:product:list')")
+    @GetMapping("/export")
+    public AjaxResult export(@RequestParam(required = false) String productName,
+                             @RequestParam(required = false) Long companyId,
+                             @RequestParam(required = false) String companyName) {
+        List<Map<String, Object>> allList = crossTenantHelper.queryAcrossTenants(
+                companyId, companyName, (tenant, jdbc) -> {
+                    StringBuilder sql = new StringBuilder(
+                            "SELECT p.product_id, p.product_name, p.product_image, p.category_name, " +
+                            "p.price, p.stock, p.create_time, p.status " +
+                            "FROM fs_store_product p WHERE 1=1");
+                    List<Object> params = new ArrayList<>();
+                    if (productName != null && !productName.isEmpty()) {
+                        sql.append(" AND p.product_name LIKE ?");
+                        params.add("%" + productName + "%");
+                    }
+                    sql.append(" ORDER BY p.create_time DESC");
+                    return params.isEmpty()
+                            ? jdbc.queryForList(sql.toString())
+                            : jdbc.queryForList(sql.toString(), params.toArray());
+                });
+        List<ProductExportVO> voList = new ArrayList<>();
+        for (Map<String, Object> m : allList) {
+            ProductExportVO vo = new ProductExportVO();
+            vo.setCompanyName(str(m.get("company_name")));
+            vo.setProductName(str(m.get("product_name")));
+            vo.setCategoryName(str(m.get("category_name")));
+            vo.setPrice(str(m.get("price")));
+            vo.setStock(str(m.get("stock")));
+            vo.setStatus(str(m.get("status")));
+            vo.setCreateTime(str(m.get("create_time")));
+            voList.add(vo);
+        }
+        ExcelUtil<ProductExportVO> util = new ExcelUtil<>(ProductExportVO.class);
+        return util.exportExcel(voList, "商品数据");
+    }
+
     /**
      * 查询所有租户的商品列表
      */

+ 61 - 0
fs-admin/src/main/java/com/fs/admin/controller/ProxyOperLogController.java

@@ -0,0 +1,61 @@
+package com.fs.admin.controller;
+
+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.proxy.domain.ProxyOperLog;
+import com.fs.proxy.service.IProxyOperLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 总后台-代理操作日志控制器
+ *
+ * @author fs
+ */
+@RestController
+@RequestMapping("/admin/proxyOperLog")
+public class ProxyOperLogController extends BaseController {
+
+    @Autowired
+    private IProxyOperLogService proxyOperLogService;
+
+    /**
+     * 查询代理操作日志列表
+     */
+    @PreAuthorize("@ss.hasPermi('admin:proxyOperLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ProxyOperLog proxyOperLog) {
+        startPage();
+        List<ProxyOperLog> list = proxyOperLogService.selectProxyOperLogList(proxyOperLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出代理操作日志
+     */
+    @Log(title = "代理操作日志导出", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:proxyOperLog:export')")
+    @GetMapping("/export")
+    public AjaxResult export(ProxyOperLog proxyOperLog) {
+        List<ProxyOperLog> list = proxyOperLogService.selectProxyOperLogList(proxyOperLog);
+        ExcelUtil<ProxyOperLog> util = new ExcelUtil<>(ProxyOperLog.class);
+        return util.exportExcel(list, "代理操作日志");
+    }
+
+    /**
+     * 删除代理操作日志
+     */
+    @Log(title = "代理操作日志", businessType = BusinessType.DELETE)
+    @PreAuthorize("@ss.hasPermi('admin:proxyOperLog:remove')")
+    @DeleteMapping("/{operIds}")
+    public AjaxResult remove(@PathVariable Long[] operIds) {
+        return toAjax(proxyOperLogService.deleteProxyOperLogByIds(operIds));
+    }
+}

+ 13 - 0
fs-admin/src/main/java/com/fs/admin/controller/QwExternalContactAdminController.java

@@ -1,8 +1,11 @@
 package com.fs.admin.controller;
 
+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.company.domain.Company;
 import com.fs.company.service.ICompanyService;
 import com.fs.qw.domain.QwExternalContact;
@@ -27,6 +30,16 @@ public class QwExternalContactAdminController extends BaseController {
     @Autowired
     private ICompanyService companyService;
 
+    /** 导出企微外部联系人 */
+    @Log(title = "导出企微外部联系人", businessType = BusinessType.EXPORT)
+    @PreAuthorize("@ss.hasPermi('admin:qwContact:list')")
+    @GetMapping("/export")
+    public AjaxResult export(QwExternalContact qwExternalContact) {
+        List<QwExternalContact> list = qwExternalContactService.selectQwExternalContactList(qwExternalContact);
+        ExcelUtil<QwExternalContact> util = new ExcelUtil<>(QwExternalContact.class);
+        return util.exportExcel(list, "企微外部联系人数据");
+    }
+
     /**
      * 查询所有租户的企微用户列表
      */

+ 41 - 0
fs-admin/src/main/java/com/fs/admin/vo/ConsumeRecordExportVO.java

@@ -0,0 +1,41 @@
+package com.fs.admin.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 消费记录导出VO
+ */
+@Data
+public class ConsumeRecordExportVO {
+
+    @Excel(name = "ID")
+    private Long id;
+
+    @Excel(name = "租户ID")
+    private Long tenantId;
+
+    @Excel(name = "租户名称")
+    private String tenantName;
+
+    @Excel(name = "消费金额")
+    private BigDecimal amount;
+
+    @Excel(name = "消费后余额")
+    private BigDecimal balanceAfter;
+
+    @Excel(name = "消费类型")
+    private String eventType;
+
+    @Excel(name = "业务ID")
+    private String bizId;
+
+    @Excel(name = "消费时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date occurredAt;
+
+    @Excel(name = "备注")
+    private String remark;
+}

+ 41 - 0
fs-admin/src/main/java/com/fs/admin/vo/RechargeRecordExportVO.java

@@ -0,0 +1,41 @@
+package com.fs.admin.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 充值记录导出VO
+ */
+@Data
+public class RechargeRecordExportVO {
+
+    @Excel(name = "ID")
+    private Long id;
+
+    @Excel(name = "租户ID")
+    private Long tenantId;
+
+    @Excel(name = "租户名称")
+    private String tenantName;
+
+    @Excel(name = "充值金额")
+    private BigDecimal amount;
+
+    @Excel(name = "充值后余额")
+    private BigDecimal balanceAfter;
+
+    @Excel(name = "支付方式")
+    private String payMethod;
+
+    @Excel(name = "操作人")
+    private String createBy;
+
+    @Excel(name = "充值时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @Excel(name = "备注")
+    private String remark;
+}

+ 41 - 0
fs-common/src/main/java/com/fs/common/annotation/ProxyLog.java

@@ -0,0 +1,41 @@
+package com.fs.common.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.fs.common.enums.BusinessType;
+import com.fs.common.enums.OperatorType;
+
+/**
+ * 代理操作日志记录注解
+ *
+ * @author fs
+ */
+@Target({ ElementType.PARAMETER, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface ProxyLog
+{
+    /**
+     * 模块
+     */
+    public String title() default "";
+
+    /**
+     * 功能
+     */
+    public BusinessType businessType() default BusinessType.OTHER;
+
+    /**
+     * 操作人类别
+     */
+    public OperatorType operatorType() default OperatorType.MANAGE;
+
+    /**
+     * 是否保存请求的参数
+     */
+    public boolean isSaveRequestData() default true;
+}

+ 243 - 0
fs-framework/src/main/java/com/fs/framework/aspectj/ProxyLogAspect.java

@@ -0,0 +1,243 @@
+package com.fs.framework.aspectj;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.fs.proxy.domain.ProxyOperLog;
+import com.fs.proxy.service.IProxyOperLogService;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.HandlerMapping;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.ProxyLog;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.enums.BusinessStatus;
+import com.fs.common.enums.HttpMethod;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.framework.manager.AsyncManager;
+
+import java.util.TimerTask;
+
+/**
+ * 代理操作日志记录处理
+ *
+ * @author fs
+ */
+@Aspect
+@Component
+public class ProxyLogAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(ProxyLogAspect.class);
+
+    // 配置织入点
+    @Pointcut("@annotation(com.fs.common.annotation.ProxyLog)")
+    public void proxyLogPointCut()
+    {
+    }
+
+    /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     * @param jsonResult 返回结果
+     */
+    @AfterReturning(pointcut = "proxyLogPointCut()", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
+    {
+        handleLog(joinPoint, null, jsonResult);
+    }
+
+    /**
+     * 拦截异常操作
+     *
+     * @param joinPoint 切点
+     * @param e 异常
+     */
+    @AfterThrowing(value = "proxyLogPointCut()", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Exception e)
+    {
+        handleLog(joinPoint, e, null);
+    }
+
+    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
+    {
+        try
+        {
+            // 获得注解
+            ProxyLog controllerLog = getAnnotationLog(joinPoint);
+            if (controllerLog == null)
+            {
+                return;
+            }
+
+            // 获取当前的用户
+            LoginUser loginUser = SecurityUtils.getLoginUser();
+
+            // *========数据库日志=========*//
+            ProxyOperLog operLog = new ProxyOperLog();
+            // 设置代理ID
+            if (loginUser != null && loginUser.getProxyId() != null)
+            {
+                operLog.setProxyId(loginUser.getProxyId());
+            }
+            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
+            // 请求的地址
+            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+            operLog.setOperIp(ip);
+            // 返回参数
+            operLog.setJsonResult(JSON.toJSONString(jsonResult));
+
+            operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
+            if (loginUser != null)
+            {
+                operLog.setOperName(loginUser.getUsername());
+            }
+
+            if (e != null)
+            {
+                operLog.setStatus(BusinessStatus.FAIL.ordinal());
+                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
+            }
+            // 设置方法名称
+            String className = joinPoint.getTarget().getClass().getName();
+            String methodName = joinPoint.getSignature().getName();
+            operLog.setMethod(className + "." + methodName + "()");
+            // 设置请求方式
+            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
+            // 设置操作时间
+            operLog.setOperTime(new java.util.Date());
+            // 处理设置注解上的参数
+            getControllerMethodDescription(joinPoint, controllerLog, operLog);
+            // 异步保存数据库
+            AsyncManager.me().execute(new TimerTask()
+            {
+                @Override
+                public void run()
+                {
+                    try
+                    {
+                        com.fs.common.utils.spring.SpringUtils.getBean(IProxyOperLogService.class).insertProxyOperLog(operLog);
+                    }
+                    catch (Exception exp)
+                    {
+                        log.error("==代理操作日志异步写入异常==");
+                        log.error("异常信息:{}", exp.getMessage());
+                    }
+                }
+            });
+        }
+        catch (Exception exp)
+        {
+            // 记录本地异常日志
+            log.error("==代理操作日志前置通知异常==");
+            log.error("异常信息:{}", exp.getMessage());
+            exp.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取注解中对方法的描述信息 用于Controller层注解
+     *
+     * @param proxyLog 日志注解
+     * @param operLog 操作日志
+     * @throws Exception
+     */
+    public void getControllerMethodDescription(JoinPoint joinPoint, ProxyLog proxyLog, ProxyOperLog operLog) throws Exception
+    {
+        // 设置action动作
+        operLog.setBusinessType(proxyLog.businessType().ordinal());
+        // 设置标题
+        operLog.setTitle(proxyLog.title());
+        // 设置操作人类别
+        operLog.setOperatorType(proxyLog.operatorType().ordinal());
+        // 是否需要保存request,参数和值
+        if (proxyLog.isSaveRequestData())
+        {
+            // 获取参数的信息,传入到数据库中。
+            setRequestValue(joinPoint, operLog);
+        }
+    }
+
+    /**
+     * 获取请求的参数,放到log中
+     *
+     * @param operLog 操作日志
+     * @throws Exception 异常
+     */
+    private void setRequestValue(JoinPoint joinPoint, ProxyOperLog operLog) throws Exception
+    {
+        String requestMethod = operLog.getRequestMethod();
+        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
+        {
+            String params = argsArrayToString(joinPoint.getArgs());
+            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
+        }
+        else
+        {
+            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+            operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private ProxyLog getAnnotationLog(JoinPoint joinPoint) throws Exception
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(ProxyLog.class);
+        }
+        return null;
+    }
+
+    /**
+     * 参数拼装
+     */
+    private String argsArrayToString(Object[] paramsArray)
+    {
+        String params = "";
+        if (paramsArray != null && paramsArray.length > 0)
+        {
+            for (int i = 0; i < paramsArray.length; i++)
+            {
+                if (!isFilterObject(paramsArray[i]))
+                {
+                    Object jsonObj = JSON.toJSON(paramsArray[i]);
+                    params += jsonObj.toString() + " ";
+                }
+            }
+        }
+        return params.trim();
+    }
+
+    /**
+     * 判断是否需要过滤的对象。
+     *
+     * @param o 对象信息。
+     * @return 如果是需要过滤的对象,则返回true;否则返回false。
+     */
+    public boolean isFilterObject(final Object o)
+    {
+        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
+    }
+}

+ 2 - 0
fs-service/src/main/java/com/fs/company/aspectj/LiveControllerAspect.java

@@ -9,10 +9,12 @@ import org.aspectj.lang.annotation.Aspect;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.stereotype.Component;
 
 @Aspect
 @Component
+@ConditionalOnClass(name = "com.fs.company.controller.live.LiveController")
 public class LiveControllerAspect {
 
     private static final Logger logger = LoggerFactory.getLogger(LiveControllerAspect.class);

+ 5 - 5
fs-service/src/main/java/com/fs/company/service/impl/CompanyServiceImpl.java

@@ -1989,18 +1989,18 @@ public class CompanyServiceImpl implements ICompanyService
     @Transactional(rollbackFor = Exception.class)
     public R rechargeCompany(Long companyId, String operateType, BigDecimal amount, String remark) {
         if (companyId == null || companyId <= 0) {
-            return R.fail("租户ID不能为空");
+            return R.error("租户ID不能为空");
         }
         if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
-            return R.fail("金额必须大于0");
+            return R.error("金额必须大于0");
         }
         if (!"recharge".equals(operateType) && !"deduct".equals(operateType)) {
-            return R.fail("操作类型只能为 recharge 或 deduct");
+            return R.error("操作类型只能为 recharge 或 deduct");
         }
 
         Company company = companyMapper.selectCompanyByIdForUpdate(companyId);
         if (company == null) {
-            return R.fail("租户不存在");
+            return R.error("租户不存在");
         }
 
         BigDecimal newBalance;
@@ -2016,7 +2016,7 @@ public class CompanyServiceImpl implements ICompanyService
         } else {
             // deduct
             if (company.getMoney().compareTo(amount) < 0) {
-                return R.fail("余额不足,当前余额: " + company.getMoney());
+                return R.error("余额不足,当前余额: " + company.getMoney());
             }
             newBalance = company.getMoney().subtract(amount);
             logMoney = amount.multiply(new BigDecimal(-1));

+ 2 - 0
fs-service/src/main/java/com/fs/live/aspectj/LiveControllerAspect.java

@@ -9,10 +9,12 @@ import org.aspectj.lang.annotation.Aspect;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.stereotype.Component;
 
 @Aspect
 @Component
+@ConditionalOnClass(name = "com.fs.live.controller.LiveController")
 public class LiveControllerAspect {
 
     private static final Logger logger = LoggerFactory.getLogger(LiveControllerAspect.class);

+ 95 - 0
fs-service/src/main/java/com/fs/proxy/domain/ProxyOperLog.java

@@ -0,0 +1,95 @@
+package com.fs.proxy.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 代理操作日志对象 proxy_oper_log
+ *
+ * @author fs
+ */
+@Data
+public class ProxyOperLog {
+    private static final long serialVersionUID = 1L;
+
+    /** 日志主键 */
+    @Excel(name = "日志主键")
+    private Long operId;
+
+    /** 代理ID */
+    @Excel(name = "代理ID")
+    private Long proxyId;
+
+    /** 代理名称 */
+    @Excel(name = "代理名称")
+    private String proxyName;
+
+    /** 模块标题 */
+    @Excel(name = "模块标题")
+    private String title;
+
+    /** 业务类型(0其它 1新增 2修改 3删除) */
+    @Excel(name = "业务类型", readConverterExp = "0=其它,1=新增,2=修改,3=删除")
+    private Integer businessType;
+
+    /** 方法名称 */
+    @Excel(name = "方法名称")
+    private String method;
+
+    /** 请求方式 */
+    @Excel(name = "请求方式")
+    private String requestMethod;
+
+    /** 操作类别(0其它 1后台用户 2手机端用户) */
+    @Excel(name = "操作类别", readConverterExp = "0=其它,1=后台用户,2=手机端用户")
+    private Integer operatorType;
+
+    /** 操作人员 */
+    @Excel(name = "操作人员")
+    private String operName;
+
+    /** 部门名称 */
+    @Excel(name = "部门名称")
+    private String deptName;
+
+    /** 请求URL */
+    @Excel(name = "请求URL")
+    private String operUrl;
+
+    /** 主机地址 */
+    @Excel(name = "主机地址")
+    private String operIp;
+
+    /** 操作地点 */
+    @Excel(name = "操作地点")
+    private String operLocation;
+
+    /** 请求参数 */
+    private String operParam;
+
+    /** 返回参数 */
+    @Excel(name = "返回参数")
+    private String jsonResult;
+
+    /** 操作状态(0正常 1异常) */
+    @Excel(name = "操作状态", readConverterExp = "0=正常,1=异常")
+    private Integer status;
+
+    /** 错误消息 */
+    @Excel(name = "错误消息")
+    private String errorMsg;
+
+    /** 操作时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date operTime;
+
+    /** 查询用临时字段-开始时间 */
+    private String beginTime;
+
+    /** 查询用临时字段-结束时间 */
+    private String endTime;
+}

+ 38 - 0
fs-service/src/main/java/com/fs/proxy/mapper/ProxyOperLogMapper.java

@@ -0,0 +1,38 @@
+package com.fs.proxy.mapper;
+
+import com.fs.proxy.domain.ProxyOperLog;
+
+import java.util.List;
+
+/**
+ * 代理操作日志Mapper接口
+ *
+ * @author fs
+ */
+public interface ProxyOperLogMapper {
+
+    /**
+     * 新增代理操作日志
+     */
+    int insertProxyOperLog(ProxyOperLog proxyOperLog);
+
+    /**
+     * 查询代理操作日志列表
+     */
+    List<ProxyOperLog> selectProxyOperLogList(ProxyOperLog proxyOperLog);
+
+    /**
+     * 查询代理操作日志详情
+     */
+    ProxyOperLog selectProxyOperLogById(Long operId);
+
+    /**
+     * 批量删除代理操作日志
+     */
+    int deleteProxyOperLogByIds(Long[] operIds);
+
+    /**
+     * 删除代理操作日志
+     */
+    int deleteProxyOperLogById(Long operId);
+}

+ 38 - 0
fs-service/src/main/java/com/fs/proxy/service/IProxyOperLogService.java

@@ -0,0 +1,38 @@
+package com.fs.proxy.service;
+
+import com.fs.proxy.domain.ProxyOperLog;
+
+import java.util.List;
+
+/**
+ * 代理操作日志Service接口
+ *
+ * @author fs
+ */
+public interface IProxyOperLogService {
+
+    /**
+     * 新增代理操作日志
+     */
+    int insertProxyOperLog(ProxyOperLog proxyOperLog);
+
+    /**
+     * 查询代理操作日志列表
+     */
+    List<ProxyOperLog> selectProxyOperLogList(ProxyOperLog proxyOperLog);
+
+    /**
+     * 查询代理操作日志详情
+     */
+    ProxyOperLog selectProxyOperLogById(Long operId);
+
+    /**
+     * 批量删除代理操作日志
+     */
+    int deleteProxyOperLogByIds(Long[] operIds);
+
+    /**
+     * 删除代理操作日志
+     */
+    int deleteProxyOperLogById(Long operId);
+}

+ 46 - 0
fs-service/src/main/java/com/fs/proxy/service/impl/ProxyOperLogServiceImpl.java

@@ -0,0 +1,46 @@
+package com.fs.proxy.service.impl;
+
+import com.fs.proxy.domain.ProxyOperLog;
+import com.fs.proxy.mapper.ProxyOperLogMapper;
+import com.fs.proxy.service.IProxyOperLogService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 代理操作日志Service实现
+ *
+ * @author fs
+ */
+@Service
+public class ProxyOperLogServiceImpl implements IProxyOperLogService {
+
+    @Autowired
+    private ProxyOperLogMapper proxyOperLogMapper;
+
+    @Override
+    public int insertProxyOperLog(ProxyOperLog log) {
+        return proxyOperLogMapper.insertProxyOperLog(log);
+    }
+
+    @Override
+    public List<ProxyOperLog> selectProxyOperLogList(ProxyOperLog log) {
+        return proxyOperLogMapper.selectProxyOperLogList(log);
+    }
+
+    @Override
+    public ProxyOperLog selectProxyOperLogById(Long operId) {
+        return proxyOperLogMapper.selectProxyOperLogById(operId);
+    }
+
+    @Override
+    public int deleteProxyOperLogByIds(Long[] operIds) {
+        return proxyOperLogMapper.deleteProxyOperLogByIds(operIds);
+    }
+
+    @Override
+    public int deleteProxyOperLogById(Long operId) {
+        return proxyOperLogMapper.deleteProxyOperLogById(operId);
+    }
+}

+ 65 - 0
fs-service/src/main/resources/mapper/proxy/ProxyOperLogMapper.xml

@@ -0,0 +1,65 @@
+<?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.proxy.mapper.ProxyOperLogMapper">
+
+    <resultMap id="ProxyOperLogResult" type="com.fs.proxy.domain.ProxyOperLog">
+        <id property="operId" column="oper_id"/>
+        <result property="proxyId" column="proxy_id"/>
+        <result property="proxyName" column="proxy_name"/>
+        <result property="title" column="title"/>
+        <result property="businessType" column="business_type"/>
+        <result property="method" column="method"/>
+        <result property="requestMethod" column="request_method"/>
+        <result property="operatorType" column="operator_type"/>
+        <result property="operName" column="oper_name"/>
+        <result property="deptName" column="dept_name"/>
+        <result property="operUrl" column="oper_url"/>
+        <result property="operIp" column="oper_ip"/>
+        <result property="operLocation" column="oper_location"/>
+        <result property="operParam" column="oper_param"/>
+        <result property="jsonResult" column="json_result"/>
+        <result property="status" column="status"/>
+        <result property="errorMsg" column="error_msg"/>
+        <result property="operTime" column="oper_time"/>
+    </resultMap>
+
+    <insert id="insertProxyOperLog" useGeneratedKeys="true" keyProperty="operId">
+        insert into proxy_oper_log(proxy_id, title, business_type, method, request_method,
+            operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location,
+            oper_param, json_result, status, error_msg, oper_time)
+        values(#{proxyId}, #{title}, #{businessType}, #{method}, #{requestMethod},
+            #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation},
+            #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{operTime})
+    </insert>
+
+    <select id="selectProxyOperLogList" resultMap="ProxyOperLogResult">
+        select ol.*, p.nick_name as proxy_name
+        from proxy_oper_log ol
+        left join proxy p on p.proxy_id = ol.proxy_id
+        <where>
+            <if test="proxyId != null">and ol.proxy_id = #{proxyId}</if>
+            <if test="title != null and title != ''">and ol.title like concat('%', #{title}, '%')</if>
+            <if test="operName != null and operName != ''">and ol.oper_name like concat('%', #{operName}, '%')</if>
+            <if test="status != null">and ol.status = #{status}</if>
+            <if test="beginTime != null and beginTime != ''">and ol.oper_time &gt;= #{beginTime}</if>
+            <if test="endTime != null and endTime != ''">and ol.oper_time &lt;= #{endTime}</if>
+        </where>
+        order by ol.oper_time desc
+    </select>
+
+    <select id="selectProxyOperLogById" resultMap="ProxyOperLogResult">
+        select ol.*, p.nick_name as proxy_name from proxy_oper_log ol
+        left join proxy p on p.proxy_id = ol.proxy_id
+        where ol.oper_id = #{operId}
+    </select>
+
+    <delete id="deleteProxyOperLogByIds">
+        delete from proxy_oper_log where oper_id in
+        <foreach collection="array" item="operId" open="(" separator="," close=")">#{operId}</foreach>
+    </delete>
+
+    <delete id="deleteProxyOperLogById">delete from proxy_oper_log where oper_id = #{operId}</delete>
+
+</mapper>

+ 23 - 0
sql/proxy_oper_log.sql

@@ -0,0 +1,23 @@
+-- ----------------------------
+-- 代理操作日志表 proxy_oper_log
+-- ----------------------------
+CREATE TABLE IF NOT EXISTS `proxy_oper_log` (
+    `oper_id`        bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键',
+    `proxy_id`       bigint NULL DEFAULT NULL COMMENT '代理ID',
+    `title`          varchar(50) NULL DEFAULT '' COMMENT '模块标题',
+    `business_type`  int NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)',
+    `method`         varchar(100) NULL DEFAULT '' COMMENT '方法名称',
+    `request_method` varchar(10) NULL DEFAULT '' COMMENT '请求方式',
+    `operator_type`  int NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)',
+    `oper_name`      varchar(50) NULL DEFAULT '' COMMENT '操作人员',
+    `dept_name`      varchar(50) NULL DEFAULT '' COMMENT '部门名称',
+    `oper_url`       varchar(2000) NULL DEFAULT '' COMMENT '请求URL',
+    `oper_ip`        varchar(50) NULL DEFAULT '' COMMENT '主机地址',
+    `oper_location`  varchar(255) NULL DEFAULT '' COMMENT '操作地点',
+    `oper_param`     longtext NULL COMMENT '请求参数',
+    `json_result`    varchar(2000) NULL DEFAULT '' COMMENT '返回参数',
+    `status`         int NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)',
+    `error_msg`      varchar(2000) NULL DEFAULT '' COMMENT '错误消息',
+    `oper_time`      datetime NULL DEFAULT NULL COMMENT '操作时间',
+    PRIMARY KEY (`oper_id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='代理操作日志' ROW_FORMAT=DYNAMIC;