Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

yys 2 дней назад
Родитель
Сommit
203b097d70
17 измененных файлов с 693 добавлено и 2 удалено
  1. 29 1
      fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java
  2. 178 0
      fs-admin/src/main/java/com/fs/tenant/TenantSqlExecuteRecordController.java
  3. 26 0
      fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java
  4. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  5. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyUserService.java
  6. 5 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyUserServiceImpl.java
  7. 9 0
      fs-service/src/main/java/com/fs/tenant/domain/TenantExecuteTask.java
  8. 4 0
      fs-service/src/main/java/com/fs/tenant/domain/TenantInfo.java
  9. 58 0
      fs-service/src/main/java/com/fs/tenant/domain/TenantSqlExecuteRecord.java
  10. 2 0
      fs-service/src/main/java/com/fs/tenant/mapper/TenantInfoMapper.java
  11. 61 0
      fs-service/src/main/java/com/fs/tenant/mapper/TenantSqlExecuteRecordMapper.java
  12. 61 0
      fs-service/src/main/java/com/fs/tenant/service/ITenantSqlExecuteRecordService.java
  13. 70 0
      fs-service/src/main/java/com/fs/tenant/service/TenantAsyncService.java
  14. 91 0
      fs-service/src/main/java/com/fs/tenant/service/impl/TenantSqlExecuteRecordServiceImpl.java
  15. 4 0
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  16. 11 1
      fs-service/src/main/resources/mapper/tenant/TenantInfoMapper.xml
  17. 80 0
      fs-service/src/main/resources/mapper/tenant/TenantSqlExecuteRecordMapper.xml

+ 29 - 1
fs-admin/src/main/java/com/fs/his/controller/FsCompanyController.java

@@ -7,6 +7,7 @@ import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.enums.DataSourceType;
 import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
@@ -21,10 +22,14 @@ import com.fs.company.service.*;
 import com.fs.company.vo.CompanyVO;
 import com.fs.core.utils.OrderCodeUtils;
 import com.fs.course.config.CourseConfig;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.framework.datasource.TenantDataSourceManager;
 import com.fs.framework.web.service.TokenService;
 import com.fs.his.mapper.FsDoctorMapper;
 import com.fs.his.vo.OptionsVO;
 import com.fs.system.service.ISysConfigService;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.mapper.TenantInfoMapper;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
@@ -43,6 +48,8 @@ import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.core.page.TableDataInfo;
 
+import javax.servlet.http.HttpServletRequest;
+
 /**
  * 诊所管理Controller
  *
@@ -70,6 +77,10 @@ public class FsCompanyController extends BaseController {
     private ISysConfigService configService;
     @Autowired
     private ICompanyDivConfigService companyDivConfigService;
+    @Autowired
+    private TenantDataSourceManager tenantDataSourceManager;
+    @Autowired
+    private TenantInfoMapper tenantInfoMapper;
     /**
      * 查询诊所管理列表
      */
@@ -147,7 +158,7 @@ public class FsCompanyController extends BaseController {
     @PreAuthorize("@ss.hasPermi('his:company:add')")
     @Log(title = "诊所管理", businessType = BusinessType.INSERT)
     @PostMapping
-    public R add(@RequestBody Company company)
+    public R add(@RequestBody Company company, HttpServletRequest request)
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         String json = configService.selectConfigByKey("course.config");
@@ -155,9 +166,26 @@ public class FsCompanyController extends BaseController {
         if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
             company.setDeptId(loginUser.getDeptId());
         }
+
+        if (isQuantityExceeded(request)){
+            return R.error("公司数量已达上限");
+        }
         return companyService.insertCompany(company);
     }
 
+    /**
+     *
+     * 判断公司数量是否超出租户配置的数量
+     */
+    private boolean isQuantityExceeded(HttpServletRequest request){
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        String code = request.getHeader("tenant-code");
+        TenantInfo tenantInfo = tenantInfoMapper.getTenByCode(code);
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        Integer count = companyService.selectCompanyCount();
+        return count >= tenantInfo.getCompanyNum();
+    }
+
     /**
      * 修改诊所管理
      */

+ 178 - 0
fs-admin/src/main/java/com/fs/tenant/TenantSqlExecuteRecordController.java

@@ -0,0 +1,178 @@
+package com.fs.tenant;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.date.DateUtil;
+import com.fs.common.utils.DateUtils;
+import com.fs.framework.datasource.TenantDataSourceManager;
+import com.fs.tenant.domain.TenantExecuteTask;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.service.TenantAsyncService;
+import com.fs.tenant.service.TenantInfoService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.enums.BusinessType;
+import com.fs.tenant.domain.TenantSqlExecuteRecord;
+import com.fs.tenant.service.ITenantSqlExecuteRecordService;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.core.page.TableDataInfo;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 租户SQL执行记录Controller
+ * 
+ * @author fs
+ * @date 2026-04-21
+ */
+@RestController
+@RequestMapping("/tenant/record")
+public class TenantSqlExecuteRecordController extends BaseController
+{
+    @Autowired
+    private ITenantSqlExecuteRecordService tenantSqlExecuteRecordService;
+    @Autowired
+    private TenantDataSourceManager tenantDataSourceManager;
+    @Autowired
+    private TenantInfoService tenantInfoService;
+    @Autowired
+    private TenantAsyncService tenantAsyncService;
+
+    /**
+     * 查询租户SQL执行记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('tenant:record:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(TenantSqlExecuteRecord tenantSqlExecuteRecord)
+    {
+        startPage();
+        List<TenantSqlExecuteRecord> list = tenantSqlExecuteRecordService.selectTenantSqlExecuteRecordList(tenantSqlExecuteRecord);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出租户SQL执行记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('tenant:record:export')")
+    @Log(title = "租户SQL执行记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(TenantSqlExecuteRecord tenantSqlExecuteRecord)
+    {
+        List<TenantSqlExecuteRecord> list = tenantSqlExecuteRecordService.selectTenantSqlExecuteRecordList(tenantSqlExecuteRecord);
+        ExcelUtil<TenantSqlExecuteRecord> util = new ExcelUtil<TenantSqlExecuteRecord>(TenantSqlExecuteRecord.class);
+        return util.exportExcel(list, "租户SQL执行记录数据");
+    }
+
+    /**
+     * 获取租户SQL执行记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('tenant:record:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(tenantSqlExecuteRecordService.selectTenantSqlExecuteRecordById(id));
+    }
+
+    /**
+     * 新增租户SQL执行记录
+     */
+    @PreAuthorize("@ss.hasPermi('tenant:record:add')")
+    @Log(title = "租户SQL执行记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody TenantSqlExecuteRecord tenantSqlExecuteRecord)
+    {
+        return toAjax(tenantSqlExecuteRecordService.insertTenantSqlExecuteRecord(tenantSqlExecuteRecord));
+    }
+
+    /**
+     * 上传 SQL 脚本文件
+     */
+    @Log(title = "上传SQL脚本", businessType = BusinessType.INSERT)
+    @PostMapping("/uploadScript")
+    public AjaxResult uploadScript(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam(value = "tenantId", required = false) Integer tenantId) throws Exception {
+
+        // 1. 基础校验
+        if (file.isEmpty()) {
+            return AjaxResult.error("请上传SQL脚本文件");
+        }
+        String filename = file.getOriginalFilename();
+        if (!filename.toLowerCase().endsWith(".sql")) {
+            return AjaxResult.error("只能上传 .sql 后缀的脚本文件");
+        }
+
+        // 2. 获取要执行的租户列表
+        List<TenantInfo> tenantInfos;
+        if (tenantId != null) {
+            // 单个租户
+            TenantInfo tenantInfo = tenantInfoService.selectTenantInfoById(tenantId.toString());
+            tenantInfos = Collections.singletonList(tenantInfo);
+        } else {
+            // 全部租户
+            tenantInfos = tenantInfoService.selectTenantInfoList(null);
+        }
+
+        // 3. 构建执行记录
+        List<TenantSqlExecuteRecord> recordList = new ArrayList<>();
+        for (TenantInfo info : tenantInfos) {
+            TenantSqlExecuteRecord record = new TenantSqlExecuteRecord();
+            record.setTenantId(info.getId().toString());
+            record.setTenantCode(info.getTenantCode());
+            record.setDbName(info.getDbName());
+            record.setSqlFile(filename);
+            record.setStatus(0);
+            record.setExecuteTime(DateUtils.getNowDate());
+            recordList.add(record);
+        }
+
+        // 4. 批量插入记录(自动回填ID)
+        tenantSqlExecuteRecordService.saveBatch(recordList);
+
+        // 5. 构建任务列表
+        List<TenantExecuteTask> tasks = new ArrayList<>();
+        for (int i = 0; i < recordList.size(); i++) {
+            TenantExecuteTask task = new TenantExecuteTask();
+            task.setRecordId(recordList.get(i).getId());
+            task.setTenantInfo(tenantInfos.get(i));
+            tasks.add(task);
+        }
+
+        // 6. 异步执行
+        tenantAsyncService.executeSqlScriptAsync(file, tasks);
+
+        return AjaxResult.success("上传成功,后台正在执行SQL脚本...");
+    }
+
+
+    /**
+     * 修改租户SQL执行记录
+     */
+    @PreAuthorize("@ss.hasPermi('tenant:record:edit')")
+    @Log(title = "租户SQL执行记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody TenantSqlExecuteRecord tenantSqlExecuteRecord)
+    {
+        return toAjax(tenantSqlExecuteRecordService.updateTenantSqlExecuteRecord(tenantSqlExecuteRecord));
+    }
+
+    /**
+     * 删除租户SQL执行记录
+     */
+    @PreAuthorize("@ss.hasPermi('tenant:record:remove')")
+    @Log(title = "租户SQL执行记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(tenantSqlExecuteRecordService.deleteTenantSqlExecuteRecordByIds(ids));
+    }
+}

+ 26 - 0
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -12,6 +12,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.enums.BusinessType;
+import com.fs.common.enums.DataSourceType;
 import com.fs.common.utils.PatternUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
@@ -31,6 +32,8 @@ import com.fs.company.vo.CompanyUserVO;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.config.saas.ProjectConfig;
 import com.fs.course.config.CourseConfig;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.framework.datasource.TenantDataSourceManager;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.framework.service.TokenService;
@@ -48,6 +51,8 @@ import com.fs.qw.vo.QwUserVO;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.service.ISysConfigService;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.mapper.TenantInfoMapper;
 import com.fs.voice.utils.StringUtil;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
@@ -61,6 +66,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletRequest;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.*;
@@ -121,6 +127,11 @@ public class CompanyUserController extends BaseController {
 
     private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
 
+    @Autowired
+    private TenantDataSourceManager tenantDataSourceManager;
+    @Autowired
+    private TenantInfoMapper tenantInfoMapper;
+
     /**
      * 获取用户列表
      */
@@ -353,9 +364,24 @@ public class CompanyUserController extends BaseController {
         user.setCreateTime(new Date());
         user.setUserType("01");//一般用户
         user.setIsAudit(1);
+        if (isQuantityExceeded(loginUser)){
+            return AjaxResult.error("账号数量已达上限");
+        }
         return toAjax(companyUserService.insertUser(user));
     }
 
+    /**
+     *
+     * 判断公司数量是否超出租户配置的数量
+     */
+    private boolean isQuantityExceeded(LoginUser loginUser){
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(loginUser.getTenantId().toString());
+        tenantDataSourceManager.switchTenant(tenantInfo);
+        Integer count = companyUserService.getUserCount();
+        return count >= tenantInfo.getAccountNum();
+    }
+
     /**
     * 生成创建销售的二维码
     */

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java

@@ -362,4 +362,6 @@ public interface CompanyUserMapper
             "update company_user set ai_sip_call_user_id=#{aiSipCallId} where user_id=#{companyUserId} " +
             "</script>")
     public int updateCompanyUserByAiSipCall(@Param("companyUserId") Long companyUserId, @Param("aiSipCallId") Long aiSipCallId);
+
+    Integer getUserCount();
 }

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

@@ -273,4 +273,6 @@ public interface ICompanyUserService {
      * @return
      */
     List<CompanyUser> getDataScopeCompanyUser(Long companyUserId);
+
+    Integer getUserCount();
 }

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

@@ -1120,4 +1120,9 @@ public class CompanyUserServiceImpl implements ICompanyUserService
         }
         return companyUsers;
     }
+
+    @Override
+    public Integer getUserCount() {
+        return companyUserMapper.getUserCount();
+    }
 }

+ 9 - 0
fs-service/src/main/java/com/fs/tenant/domain/TenantExecuteTask.java

@@ -0,0 +1,9 @@
+package com.fs.tenant.domain;
+
+import lombok.Data;
+
+@Data
+public class TenantExecuteTask {
+    private Long recordId;      // 关键:执行记录ID
+    private TenantInfo tenantInfo;
+}

+ 4 - 0
fs-service/src/main/java/com/fs/tenant/domain/TenantInfo.java

@@ -92,4 +92,8 @@ public class TenantInfo {
      * 联系人
      */
     private String contactName;
+
+    private Integer companyNum;
+
+    private Integer accountNum;
 }

+ 58 - 0
fs-service/src/main/java/com/fs/tenant/domain/TenantSqlExecuteRecord.java

@@ -0,0 +1,58 @@
+package com.fs.tenant.domain;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 租户SQL执行记录对象 tenant_sql_execute_record
+ *
+ * @author fs
+ * @date 2026-04-21
+ */
+@Data
+public class TenantSqlExecuteRecord{
+
+    /** 主键ID */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 租户ID */
+    @Excel(name = "租户ID")
+    private String tenantId;
+
+    /**
+     *  租户编码
+     */
+    @Excel(name = "租户编码")
+    private String tenantCode;
+
+    /** 数据库名 */
+    @Excel(name = "数据库名")
+    private String dbName;
+
+    /** 执行的SQL文件 */
+    @Excel(name = "执行的SQL文件")
+    private String sqlFile;
+
+    /** 执行状态:0=待执行,1=执行成功,2=执行失败 */
+    @Excel(name = "执行状态:0=待执行,1=执行成功,2=执行失败")
+    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 executeTime;
+
+
+}

+ 2 - 0
fs-service/src/main/java/com/fs/tenant/mapper/TenantInfoMapper.java

@@ -123,6 +123,8 @@ public interface TenantInfoMapper extends BaseMapper<TenantInfo> {
     List<CompanyMenu> getTenComMenuByIds(List<Long> needAddIds);
 
     int addComMenu(List<CompanyMenu> addCompanyMenu);
+
+    TenantInfo getTenByCode(String code);
 }
 
 

+ 61 - 0
fs-service/src/main/java/com/fs/tenant/mapper/TenantSqlExecuteRecordMapper.java

@@ -0,0 +1,61 @@
+package com.fs.tenant.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.tenant.domain.TenantSqlExecuteRecord;
+
+/**
+ * 租户SQL执行记录Mapper接口
+ * 
+ * @author fs
+ * @date 2026-04-21
+ */
+public interface TenantSqlExecuteRecordMapper extends BaseMapper<TenantSqlExecuteRecord>{
+    /**
+     * 查询租户SQL执行记录
+     * 
+     * @param id 租户SQL执行记录主键
+     * @return 租户SQL执行记录
+     */
+    TenantSqlExecuteRecord selectTenantSqlExecuteRecordById(Long id);
+
+    /**
+     * 查询租户SQL执行记录列表
+     * 
+     * @param tenantSqlExecuteRecord 租户SQL执行记录
+     * @return 租户SQL执行记录集合
+     */
+    List<TenantSqlExecuteRecord> selectTenantSqlExecuteRecordList(TenantSqlExecuteRecord tenantSqlExecuteRecord);
+
+    /**
+     * 新增租户SQL执行记录
+     * 
+     * @param tenantSqlExecuteRecord 租户SQL执行记录
+     * @return 结果
+     */
+    int insertTenantSqlExecuteRecord(TenantSqlExecuteRecord tenantSqlExecuteRecord);
+
+    /**
+     * 修改租户SQL执行记录
+     * 
+     * @param tenantSqlExecuteRecord 租户SQL执行记录
+     * @return 结果
+     */
+    int updateTenantSqlExecuteRecord(TenantSqlExecuteRecord tenantSqlExecuteRecord);
+
+    /**
+     * 删除租户SQL执行记录
+     * 
+     * @param id 租户SQL执行记录主键
+     * @return 结果
+     */
+    int deleteTenantSqlExecuteRecordById(Long id);
+
+    /**
+     * 批量删除租户SQL执行记录
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteTenantSqlExecuteRecordByIds(Long[] ids);
+}

+ 61 - 0
fs-service/src/main/java/com/fs/tenant/service/ITenantSqlExecuteRecordService.java

@@ -0,0 +1,61 @@
+package com.fs.tenant.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.tenant.domain.TenantSqlExecuteRecord;
+
+/**
+ * 租户SQL执行记录Service接口
+ * 
+ * @author fs
+ * @date 2026-04-21
+ */
+public interface ITenantSqlExecuteRecordService extends IService<TenantSqlExecuteRecord>{
+    /**
+     * 查询租户SQL执行记录
+     * 
+     * @param id 租户SQL执行记录主键
+     * @return 租户SQL执行记录
+     */
+    TenantSqlExecuteRecord selectTenantSqlExecuteRecordById(Long id);
+
+    /**
+     * 查询租户SQL执行记录列表
+     * 
+     * @param tenantSqlExecuteRecord 租户SQL执行记录
+     * @return 租户SQL执行记录集合
+     */
+    List<TenantSqlExecuteRecord> selectTenantSqlExecuteRecordList(TenantSqlExecuteRecord tenantSqlExecuteRecord);
+
+    /**
+     * 新增租户SQL执行记录
+     * 
+     * @param tenantSqlExecuteRecord 租户SQL执行记录
+     * @return 结果
+     */
+    int insertTenantSqlExecuteRecord(TenantSqlExecuteRecord tenantSqlExecuteRecord);
+
+    /**
+     * 修改租户SQL执行记录
+     * 
+     * @param tenantSqlExecuteRecord 租户SQL执行记录
+     * @return 结果
+     */
+    int updateTenantSqlExecuteRecord(TenantSqlExecuteRecord tenantSqlExecuteRecord);
+
+    /**
+     * 批量删除租户SQL执行记录
+     * 
+     * @param ids 需要删除的租户SQL执行记录主键集合
+     * @return 结果
+     */
+    int deleteTenantSqlExecuteRecordByIds(Long[] ids);
+
+    /**
+     * 删除租户SQL执行记录信息
+     * 
+     * @param id 租户SQL执行记录主键
+     * @return 结果
+     */
+    int deleteTenantSqlExecuteRecordById(Long id);
+}

+ 70 - 0
fs-service/src/main/java/com/fs/tenant/service/TenantAsyncService.java

@@ -1,22 +1,36 @@
 package com.fs.tenant.service;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.core.domain.entity.SysMenu;
 import com.fs.company.domain.CompanyMenu;
+import com.fs.tenant.domain.TenantExecuteTask;
 import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.domain.TenantSqlExecuteRecord;
 import com.fs.tenant.mapper.TenantInfoMapper;
 import com.fs.utils.TenantUtils;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.jdbc.datasource.init.ScriptUtils;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
 import java.util.List;
 
 @Service
+@Slf4j
 public class TenantAsyncService {
 
     @Autowired
     private TenantInfoMapper tenantInfoMapper;
+    @Autowired
+    private ITenantSqlExecuteRecordService tenantSqlExecuteRecordService;
 
     @Async
     public void asyncInit(TenantInfo tenantInfo, List<SysMenu> menus,List<CompanyMenu> companyMenus) {
@@ -36,4 +50,60 @@ public class TenantAsyncService {
             throw new RuntimeException(e);
         }
     }
+
+    // 异步执行
+    @Async
+    public void executeSqlScriptAsync(MultipartFile file, List<TenantExecuteTask> tasks) {
+        try {
+            byte[] sqlBytes = file.getBytes();
+            ByteArrayResource resource = new ByteArrayResource(sqlBytes);
+
+            // 遍历任务(包含 recordId + tenantInfo)
+            for (TenantExecuteTask task : tasks) {
+                Long recordId = task.getRecordId();
+                TenantInfo info = task.getTenantInfo();
+
+                String dbIp = info.getDbIp();
+                String dbPort = info.getDbPort();
+                String dbName = info.getDbName();
+                String dbAccount = info.getDbAccount();
+                String dbPwd = info.getDbPwd();
+
+                log.info("开始异步执行SQL脚本,recordId:{},库名:{}", recordId, dbName);
+
+                Connection conn = null;
+                try {
+                    conn = TenantUtils.getConnection(dbIp, dbPort, dbName, dbAccount, dbPwd);
+                    ScriptUtils.executeSqlScript(
+                            conn,
+                            new EncodedResource(resource, StandardCharsets.UTF_8)
+                    );
+
+                    updateStatus(recordId, 1, null);
+
+                } catch (Exception e) {
+                    log.error("执行失败 recordId:{} err:{}", recordId, e.getMessage());
+                    updateStatus(recordId, 2, e.getMessage());
+                } finally {
+                    if (conn != null) conn.close();
+                }
+            }
+
+        } catch (Exception e) {
+            log.error("异步执行SQL全局异常", e);
+        }
+    }
+
+    // 更新执行状态
+    private void updateStatus(Long recordId, Integer status, String errorMsg) {
+        LambdaUpdateWrapper<TenantSqlExecuteRecord> wrapper = Wrappers.lambdaUpdate();
+        wrapper.eq(TenantSqlExecuteRecord::getId, recordId);
+
+        TenantSqlExecuteRecord record = new TenantSqlExecuteRecord();
+        record.setStatus(status);
+        record.setErrorMsg(errorMsg);
+
+        // 正确用法
+        tenantSqlExecuteRecordService.update(record, wrapper);
+    }
 }

+ 91 - 0
fs-service/src/main/java/com/fs/tenant/service/impl/TenantSqlExecuteRecordServiceImpl.java

@@ -0,0 +1,91 @@
+package com.fs.tenant.service.impl;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.tenant.mapper.TenantSqlExecuteRecordMapper;
+import com.fs.tenant.domain.TenantSqlExecuteRecord;
+import com.fs.tenant.service.ITenantSqlExecuteRecordService;
+
+/**
+ * 租户SQL执行记录Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-04-21
+ */
+@Service
+public class TenantSqlExecuteRecordServiceImpl extends ServiceImpl<TenantSqlExecuteRecordMapper, TenantSqlExecuteRecord> implements ITenantSqlExecuteRecordService {
+
+    /**
+     * 查询租户SQL执行记录
+     * 
+     * @param id 租户SQL执行记录主键
+     * @return 租户SQL执行记录
+     */
+    @Override
+    public TenantSqlExecuteRecord selectTenantSqlExecuteRecordById(Long id)
+    {
+        return baseMapper.selectTenantSqlExecuteRecordById(id);
+    }
+
+    /**
+     * 查询租户SQL执行记录列表
+     * 
+     * @param tenantSqlExecuteRecord 租户SQL执行记录
+     * @return 租户SQL执行记录
+     */
+    @Override
+    public List<TenantSqlExecuteRecord> selectTenantSqlExecuteRecordList(TenantSqlExecuteRecord tenantSqlExecuteRecord)
+    {
+        return baseMapper.selectTenantSqlExecuteRecordList(tenantSqlExecuteRecord);
+    }
+
+    /**
+     * 新增租户SQL执行记录
+     * 
+     * @param tenantSqlExecuteRecord 租户SQL执行记录
+     * @return 结果
+     */
+    @Override
+    public int insertTenantSqlExecuteRecord(TenantSqlExecuteRecord tenantSqlExecuteRecord)
+    {
+        return baseMapper.insertTenantSqlExecuteRecord(tenantSqlExecuteRecord);
+    }
+
+    /**
+     * 修改租户SQL执行记录
+     * 
+     * @param tenantSqlExecuteRecord 租户SQL执行记录
+     * @return 结果
+     */
+    @Override
+    public int updateTenantSqlExecuteRecord(TenantSqlExecuteRecord tenantSqlExecuteRecord)
+    {
+        return baseMapper.updateTenantSqlExecuteRecord(tenantSqlExecuteRecord);
+    }
+
+    /**
+     * 批量删除租户SQL执行记录
+     * 
+     * @param ids 需要删除的租户SQL执行记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTenantSqlExecuteRecordByIds(Long[] ids)
+    {
+        return baseMapper.deleteTenantSqlExecuteRecordByIds(ids);
+    }
+
+    /**
+     * 删除租户SQL执行记录信息
+     * 
+     * @param id 租户SQL执行记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteTenantSqlExecuteRecordById(Long id)
+    {
+        return baseMapper.deleteTenantSqlExecuteRecordById(id);
+    }
+}

+ 4 - 0
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -761,4 +761,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where  company_user_id = #{companyUserId}
     </select>
 
+    <select id="getUserCount" resultType="java.lang.Integer">
+        select count(1) from company_user WHERE `status` = 0 AND del_flag = 0
+    </select>
+
 </mapper>

+ 11 - 1
fs-service/src/main/resources/mapper/tenant/TenantInfoMapper.xml

@@ -20,7 +20,7 @@
     </resultMap>
 
     <sql id="selectTenantInfoVo">
-        select id, tenant_code, tenant_name, status, expire_time, db_url, db_account, db_pwd, create_time, update_time, contact_phone, contact_name,db_ip, db_port, db_name from tenant_info
+        select id, tenant_code, tenant_name, status, expire_time, db_url, db_account, db_pwd, create_time, update_time, contact_phone, contact_name,db_ip, db_port, db_name,company_num,account_num from tenant_info
     </sql>
 
     <select id="selectTenantInfoList" parameterType="TenantInfo" resultMap="TenantInfoResult">
@@ -243,6 +243,10 @@
         </foreach>
     </select>
 
+    <select id="getTenByCode" resultType="com.fs.tenant.domain.TenantInfo">
+        select * from tenant_info where tenant_code = #{tenantCode}
+    </select>
+
 
     <insert id="insertTenantInfo" parameterType="TenantInfo" useGeneratedKeys="true" keyProperty="id">
         insert into tenant_info
@@ -261,6 +265,8 @@
             <if test="updateTime != null">update_time,</if>
             <if test="contactPhone != null">contact_phone,</if>
             <if test="contactName != null">contact_name,</if>
+            <if test="companyNum != null">company_num,</if>
+            <if test="accountNum != null">account_num,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="tenantCode != null and tenantCode != ''">#{tenantCode},</if>
@@ -277,6 +283,8 @@
             <if test="updateTime != null">#{updateTime},</if>
             <if test="contactPhone != null">#{contactPhone},</if>
             <if test="contactName != null">#{contactName},</if>
+            <if test="companyNum != null">#{companyNum},</if>
+            <if test="accountNum != null">#{accountNum},</if>
         </trim>
     </insert>
 
@@ -437,6 +445,8 @@
             <if test="updateTime != null">update_time = #{updateTime},</if>
             <if test="contactPhone != null">contact_phone = #{contactPhone},</if>
             <if test="contactName != null">contact_name = #{contactName},</if>
+            <if test="companyNum != null">company_num = #{companyNum},</if>
+            <if test="accountNum != null">account_num = #{accountNum},</if>
         </trim>
         where id = #{id}
     </update>

+ 80 - 0
fs-service/src/main/resources/mapper/tenant/TenantSqlExecuteRecordMapper.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.tenant.mapper.TenantSqlExecuteRecordMapper">
+    
+    <resultMap type="TenantSqlExecuteRecord" id="TenantSqlExecuteRecordResult">
+        <result property="id"    column="id"    />
+        <result property="tenantId"    column="tenant_id"    />
+        <result property="dbName"    column="db_name"    />
+        <result property="sqlFile"    column="sql_file"    />
+        <result property="status"    column="status"    />
+        <result property="errorMsg"    column="error_msg"    />
+        <result property="executeTime"    column="execute_time"    />
+    </resultMap>
+
+    <sql id="selectTenantSqlExecuteRecordVo">
+        select id, tenant_id, db_name, sql_file, status, error_msg, execute_time,tenant_code from tenant_sql_execute_record
+    </sql>
+
+    <select id="selectTenantSqlExecuteRecordList" parameterType="TenantSqlExecuteRecord" resultMap="TenantSqlExecuteRecordResult">
+        <include refid="selectTenantSqlExecuteRecordVo"/>
+        <where>  
+            <if test="dbName != null  and dbName != ''"> and db_name like concat('%', #{dbName}, '%')</if>
+            <if test="sqlFile != null  and sqlFile != ''"> and sql_file = #{sqlFile}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="errorMsg != null  and errorMsg != ''"> and error_msg like concat('%', #{errorMsg}, '%')</if>
+            <if test="executeTime != null "> and execute_time = #{executeTime}</if>
+        </where>
+    </select>
+    
+    <select id="selectTenantSqlExecuteRecordById" parameterType="Long" resultMap="TenantSqlExecuteRecordResult">
+        <include refid="selectTenantSqlExecuteRecordVo"/>
+        where id = #{id}
+    </select>
+        
+    <insert id="insertTenantSqlExecuteRecord" parameterType="TenantSqlExecuteRecord" useGeneratedKeys="true" keyProperty="id">
+        insert into tenant_sql_execute_record
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="tenantId != null and tenantId != ''">tenant_id,</if>
+            <if test="dbName != null">db_name,</if>
+            <if test="sqlFile != null">sql_file,</if>
+            <if test="status != null">status,</if>
+            <if test="errorMsg != null">error_msg,</if>
+            <if test="executeTime != null">execute_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="tenantId != null and tenantId != ''">#{tenantId},</if>
+            <if test="dbName != null">#{dbName},</if>
+            <if test="sqlFile != null">#{sqlFile},</if>
+            <if test="status != null">#{status},</if>
+            <if test="errorMsg != null">#{errorMsg},</if>
+            <if test="executeTime != null">#{executeTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateTenantSqlExecuteRecord" parameterType="TenantSqlExecuteRecord">
+        update tenant_sql_execute_record
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="tenantId != null and tenantId != ''">tenant_id = #{tenantId},</if>
+            <if test="dbName != null">db_name = #{dbName},</if>
+            <if test="sqlFile != null">sql_file = #{sqlFile},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="errorMsg != null">error_msg = #{errorMsg},</if>
+            <if test="executeTime != null">execute_time = #{executeTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteTenantSqlExecuteRecordById" parameterType="Long">
+        delete from tenant_sql_execute_record where id = #{id}
+    </delete>
+
+    <delete id="deleteTenantSqlExecuteRecordByIds" parameterType="String">
+        delete from tenant_sql_execute_record where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>