ct 1 неделя назад
Родитель
Сommit
76126a81f2

+ 307 - 0
fs-admin-saas/src/main/java/com/fs/company/CompanyController.java

@@ -0,0 +1,307 @@
+package com.fs.company;
+
+import cn.hutool.json.JSONUtil;
+import com.fs.common.annotation.Log;
+import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.ParseUtils;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.utils.sign.Md5Utils;
+import com.fs.company.domain.*;
+import com.fs.company.param.*;
+import com.fs.company.service.*;
+import com.fs.company.vo.CompanyCrmVO;
+import com.fs.company.vo.CompanyVO;
+import com.fs.company.vo.CompanyVoiceCallerListVO;
+import com.fs.core.utils.OrderCodeUtils;
+import com.fs.course.config.CourseConfig;
+import com.fs.framework.web.service.TokenService;
+import com.fs.his.vo.OptionsVO;
+import com.fs.system.service.ISysConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.text.ParseException;
+import java.util.List;
+
+/**
+ * 企业Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/company")
+public class CompanyController extends BaseController
+{
+    @Autowired
+    private TokenService tokenService;
+    @Autowired
+    private ICompanyService companyService;
+    @Autowired
+    private ICompanyUserService userService;
+    @Autowired
+    private ICompanyRechargeService rechargeService;
+    @Autowired
+    private ICompanyDeductService deductService;
+    @Autowired
+    private ICompanyVoiceCallerService callerService;
+    @Autowired
+    private ISysConfigService configService;
+
+    /**
+     * 查询企业列表
+     */
+    @PreAuthorize("@ss.hasPermi('live:liveMiniLives:list')")
+    @GetMapping("/liveShowList")
+    public TableDataInfo liveShowList(CompanyParam param) throws ParseException {
+        startPage();
+        List<CompanyVO> list = companyService.liveShowList(param);
+        for (CompanyVO vo : list){
+            vo.setCompanyMobile(ParseUtils.parsePhone(vo.getCompanyMobile()));
+        }
+        return getDataTable(list);
+    }
+
+    @PostMapping(value = "/batchUpdateLiveShow")
+    public R batchUpdateLiveShow(@RequestBody CompanyLiveShowParam param) {
+        if (param.getIds().isEmpty()) {
+            return R.error("请选择要操作的记录");
+        }
+        companyService.batchUpdateLiveShow(param);
+        return R.ok();
+    }
+
+    /**
+     * 查询企业列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
+            param.setDeptId(loginUser.getDeptId());
+        }
+        List<CompanyVO> list = companyService.selectCompanyVOList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出企业列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:export')")
+    @Log(title = "企业", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyParam company)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
+            company.setDeptId(loginUser.getDeptId());
+        }
+        List<CompanyVO> list = companyService.selectCompanyVOList(company);
+        ExcelUtil<CompanyVO> util = new ExcelUtil<CompanyVO>(CompanyVO.class);
+        return util.exportExcel(list, "company");
+    }
+
+    /**
+     * 获取企业详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:query')")
+    @GetMapping(value = "/{companyId}")
+    public AjaxResult getInfo(@PathVariable("companyId") Long companyId)
+    {
+        return AjaxResult.success(companyService.selectCompanyById(companyId));
+    }
+
+    /**
+     * 新增企业
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:add')")
+    @Log(title = "企业", businessType = BusinessType.INSERT)
+    @PostMapping
+    public R add(@RequestBody Company company)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        if(company.getDeptId() != null){
+            company.setDeptId(loginUser.getDeptId());
+        }
+        company.setPassword(SecurityUtils.encryptPassword(company.getPassword()));
+        company.setAppId(Md5Utils.hash(company.getUserName()));
+        company.setAppKey(Md5Utils.hash(company.getPassword()));
+        company.setOmsCode("SF.1");
+        return companyService.insertCompany(company);
+    }
+
+    /**
+     * 修改企业
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:edit')")
+    @Log(title = "企业", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody Company company)
+    {
+        CompanyUser companyUser = new CompanyUser();
+        if (company.getStatus()==0){
+            companyUser.setStatus("1");
+            companyUser.setCompanyId(company.getCompanyId());
+            companyUser.setUserType("00");
+            userService.updateAllCompanyUser(companyUser);
+            CompanyVoiceCallerParam param = new CompanyVoiceCallerParam();
+            param.setCompanyId(company.getCompanyId());
+            List<CompanyVoiceCallerListVO> list = callerService.selectCompanyVoiceCallerListVO(param);
+            for (CompanyVoiceCallerListVO vo : list){
+                CompanyVoiceCaller caller=callerService.selectCompanyVoiceCallerById(vo.getCallerId());
+                caller.setCompanyId(0L);
+                caller.setCompanyUserId(0L);
+                caller.setMobile("");
+                caller.setStatus(1);
+                caller.setBindTime(null);
+                callerService.updateCompanyVoiceCaller(caller);
+            }
+        }
+        company.setUpdateMiniApp(true);
+        return toAjax(companyService.updateCompany(company));
+    }
+
+
+
+    /**
+     * 删除企业
+     */
+    @PreAuthorize("@ss.hasPermi('company:company:remove')")
+    @Log(title = "企业", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{companyIds}")
+    public AjaxResult remove(@PathVariable Long[] companyIds)
+    {
+        return toAjax(companyService.deleteCompanyByIds(companyIds));
+    }
+
+    @GetMapping("/getCompanyList")
+    public R getCompanyList()
+    {
+        Company map=new Company();
+        map.setIsDel(0);
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
+            map.setDeptId(loginUser.getDeptId());
+        }
+        List<Company> list = companyService.selectCompanyList(map);
+        return R.ok().put("data",list);
+    }
+
+
+//  @PreAuthorize("@ss.hasPermi('company:company:crmDayCountlist')")
+    @GetMapping("/crmDayCountlist")
+    public TableDataInfo companyCrmDayCountList(CompanyParam param)
+    {
+        startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
+            param.setDeptId(loginUser.getDeptId());
+        }
+        List<CompanyCrmVO> list = companyService.selectCompanyCrmDayCountList(param);
+        return getDataTable(list);
+    }
+
+
+
+    @PreAuthorize("@ss.hasPermi('company:company:resetPwd')")
+    @PostMapping("/resetPwd/{companyId}")
+    public AjaxResult resetPwd(@PathVariable Long companyId)
+    {
+        Company company=companyService.selectCompanyById(companyId);
+        return toAjax(userService.resetUserPwdByUserId(company.getUserId(),SecurityUtils.encryptPassword("cq654321!!")));
+    }
+
+//    @PreAuthorize("@ss.hasPermi('company:company:resetMoney')")
+//    @PostMapping("/resetMoney/{companyId}")
+//    public R resetMoney(@PathVariable Long companyId)
+//    {
+//        //companyService.resetMoney(companyId);
+//        return R.ok("功能已停用");
+//    }
+
+
+    @PreAuthorize("@ss.hasPermi('company:company:recharge')")
+    @Log(title = "企业转账", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/recharge")
+    @Transactional
+    @RepeatSubmit
+    public R recharge(@RequestBody CompanyRechargeParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        CompanyRecharge recharge=new CompanyRecharge();
+        String orderSn =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(orderSn)){
+            return R.error("订单生成失败,请重试");
+        }
+        recharge.setRechargeNo(orderSn);
+        recharge.setCompanyId(param.getCompanyId());
+        recharge.setMoney(param.getMoney());
+        recharge.setCreateUserId(loginUser.getUser().getUserId());
+        recharge.setIsAudit(0);
+        recharge.setStatus(0);
+        recharge.setRemark(param.getRemark());
+        recharge.setPayType(3);
+        rechargeService.insertCompanyRecharge(recharge);
+        return R.ok("提交成功,等待审核");
+
+    }
+
+    @PreAuthorize("@ss.hasPermi('company:company:deduct')")
+    @Log(title = "企业扣款", businessType = BusinessType.INSERT)
+    @PostMapping(value = "/deduct")
+    @Transactional
+    @RepeatSubmit
+    public R deduct(@RequestBody CompanyDeductParam param)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        CompanyDeduct deduct=new CompanyDeduct();
+        String orderSn =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(orderSn)){
+            return R.error("订单生成失败,请重试");
+        }
+        deduct.setDeductNo(orderSn);
+        deduct.setCompanyId(param.getCompanyId());
+        deduct.setMoney(param.getMoney());
+        deduct.setCreateUserId(loginUser.getUser().getUserId());
+        deduct.setIsAudit(0);
+        deduct.setRemark(param.getRemark());
+        deductService.insertCompanyDeduct(deduct);
+        return R.ok("提交成功,等待审核");
+    }
+
+    @GetMapping("/allList")
+    public TableDataInfo getHospital()
+    {
+        Long deptId = null;
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
+            deptId = loginUser.getDeptId();
+        }
+        List<OptionsVO> list = companyService.selectAllCompanyList(deptId);
+        return getDataTable(list);
+    }
+}

+ 104 - 0
fs-admin-saas/src/main/java/com/fs/company/CompanyVoiceApiController.java

@@ -0,0 +1,104 @@
+package com.fs.company;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.CompanyVoiceApi;
+import com.fs.company.service.ICompanyVoiceApiService;
+import com.fs.voice.service.IVoiceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 呼叫接口Controller
+ *
+ * @author fs
+ * @date 2021-10-04
+ */
+@RestController
+@RequestMapping("/company/companyVoiceApi")
+public class CompanyVoiceApiController extends BaseController {
+    @Autowired
+    private ICompanyVoiceApiService companyVoiceApiService;
+    @Autowired
+    private IVoiceService voiceService;
+
+    /**
+     * 查询呼叫接口列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(CompanyVoiceApi companyVoiceApi) {
+        startPage();
+        List<CompanyVoiceApi> list = companyVoiceApiService.selectCompanyVoiceApiList(companyVoiceApi);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出呼叫接口列表
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:export')")
+    @Log(title = "呼叫接口", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(CompanyVoiceApi companyVoiceApi) {
+        List<CompanyVoiceApi> list = companyVoiceApiService.selectCompanyVoiceApiList(companyVoiceApi);
+        ExcelUtil<CompanyVoiceApi> util = new ExcelUtil<CompanyVoiceApi>(CompanyVoiceApi.class);
+        return util.exportExcel(list, "companyVoiceApi");
+    }
+
+    /**
+     * 获取呼叫接口详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:query')")
+    @GetMapping(value = "/{apiId}")
+    public AjaxResult getInfo(@PathVariable("apiId") Long apiId) {
+        return AjaxResult.success(companyVoiceApiService.selectCompanyVoiceApiById(apiId));
+    }
+
+    /**
+     * 新增呼叫接口
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:add')")
+    @Log(title = "呼叫接口", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody CompanyVoiceApi companyVoiceApi) {
+        return toAjax(companyVoiceApiService.insertCompanyVoiceApi(companyVoiceApi));
+    }
+
+    /**
+     * 修改呼叫接口
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:edit')")
+    @Log(title = "呼叫接口", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody CompanyVoiceApi companyVoiceApi) {
+        return toAjax(companyVoiceApiService.updateCompanyVoiceApi(companyVoiceApi));
+    }
+
+    /**
+     * 删除呼叫接口
+     */
+    @PreAuthorize("@ss.hasPermi('company:companyVoiceApi:remove')")
+    @Log(title = "呼叫接口", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{apiIds}")
+    public AjaxResult remove(@PathVariable Long[] apiIds) {
+        return toAjax(companyVoiceApiService.deleteCompanyVoiceApiByIds(apiIds));
+    }
+
+    @GetMapping("/getVoiceApiList")
+    public R getVoiceApiList() {
+        CompanyVoiceApi map = new CompanyVoiceApi();
+        map.setStatus(1);
+        List<CompanyVoiceApi> list = companyVoiceApiService.selectCompanyVoiceApiList(map);
+        return R.ok().put("data", list);
+    }
+
+
+}

+ 9 - 10
fs-admin-saas/src/main/java/com/fs/his/controller/FsCompanyController.java

@@ -103,16 +103,15 @@ public class FsCompanyController extends BaseController {
     public R companyList()
     {
 
-//        com.fs.framework.security.LoginUser loginUser = (com.fs.framework.security.LoginUser) tokenService.getLoginUser(ServletUtils.getRequest());
-//        Long depId = null;
-//        String json = configService.selectConfigByKey("course.config");
-//        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-//        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
-//            depId = loginUser.getDeptId();
-//        }
-//        List<OptionsVO> list = companyService.selectAllCompanyList(depId);
-//        return R.ok().put("data",list);
-        throw new RuntimeException("未实现");
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        Long depId = null;
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        if(!loginUser.isAdmin() && config.getDept() != null && config.getDept()){
+            depId = loginUser.getDeptId();
+        }
+        List<OptionsVO> list = companyService.selectAllCompanyList(depId);
+        return R.ok().put("data",list);
     }
     /**
      * 导出诊所管理列表

+ 219 - 0
fs-admin-saas/src/main/java/com/fs/qw/controller/QwFsCourseWatchLogController.java

@@ -0,0 +1,219 @@
+package com.fs.qw.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.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.param.FsCourseOverParam;
+import com.fs.course.param.FsCourseWatchLogListParam;
+import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
+import com.fs.course.param.PeriodStatisticCountParam;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.vo.FsCourseOverVO;
+import com.fs.course.vo.FsCourseWatchLogListVO;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListByCompanyVO;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
+import com.fs.qw.param.QwWatchLogStatisticsListParam;
+import com.fs.qw.service.IQwWatchLogService;
+import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 短链课程看课记录Controller
+ *
+ * @author fs
+ * @date 2024-10-24
+ */
+@RestController
+@RequestMapping("/qw/course/courseWatchLog")
+public class QwFsCourseWatchLogController extends BaseController
+{
+    @Value("${cloud_host.company_name:}")
+    private String signProjectName;
+
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
+    @Autowired
+    private IQwWatchLogService qwWatchLogService;
+    /**
+     * 查询短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseWatchLogListParam param)
+    {
+        startPage();
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        return getDataTable(list);
+    }
+
+    @GetMapping("/qwWatchLogAllStatisticsList")
+    public TableDataInfo qwWatchLogAllStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        logger.info("企微课程数据汇总 参数:{}",param);
+        startPage();
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<QwWatchLogAllStatisticsListVO> list = qwWatchLogService.selectQwWatchLogAllStatisticsListVO(param);
+        return getDataTable(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsList')")
+    @GetMapping("/statisticsList")
+    public TableDataInfo statisticsList(FsCourseWatchLogStatisticsListParam param)
+    {
+        startPage();
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        param.setSendType(2); //企微
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVO(param);
+        if("济南联志健康".equals(signProjectName)){
+            FsCourseWatchLogStatisticsListVO totalData = fsCourseWatchLogService.getTotalDataAddItem(param);
+            list.add(totalData);
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 会员看课统计导出
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:statisticsExport')")
+    @Log(title = "企微看课统计导出", businessType = BusinessType.EXPORT)
+    @PostMapping("/statisticsExport")
+    public AjaxResult statisticsExport(@RequestBody FsCourseWatchLogStatisticsListParam param)
+    {
+
+        if (param.getSTime()==null||param.getETime()==null){
+            throw new CustomException("必须选择开始时间和结束时间!");
+        }
+
+        param.setSendType(2); //企微
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListVO(param);
+        if("济南联志健康".equals(signProjectName)){
+            FsCourseWatchLogStatisticsListVO totalData = fsCourseWatchLogService.getTotalDataAddItem(param);
+            list.add(totalData);
+        }
+
+        ExcelUtil<FsCourseWatchLogStatisticsListVO> util = new ExcelUtil<FsCourseWatchLogStatisticsListVO>(FsCourseWatchLogStatisticsListVO.class);
+
+        return util.exportExcel(list, "企微看课统计");
+    }
+
+
+
+    @GetMapping("/getSignProjectName")
+    public R getSignProjectName(){
+        return R.ok().put("signProjectName", signProjectName);
+    }
+
+    @GetMapping("/statisticsListByCompany")
+    public R statisticsListByCompany(FsCourseWatchLogStatisticsListParam param)
+    {
+        if (param.getSTime()==null||param.getETime()==null){
+            return R.ok().put("rows", new ArrayList<>());
+        }
+        param.setSendType(2); //企微
+        List<FsCourseWatchLogStatisticsListByCompanyVO> list = fsCourseWatchLogService.selectFsCourseWatchLogStatisticsListByCompanyVO(param);
+        return R.ok().put("rows", list);
+    }
+    @GetMapping("/qwWatchLogStatisticsList")
+    public TableDataInfo qwWatchLogStatisticsList(QwWatchLogStatisticsListParam param)
+    {
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        if(param.getCompanyId() == null){
+            throw new CustomException("必须选择公司!");
+        }
+        return qwWatchLogService.selectQwWatchLogStatisticsListVO(param);
+    }
+    /**
+     * 导出短链课程看课记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:export')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseWatchLogListParam param)
+    {
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
+        return util.exportExcel(list, "短链课程看课记录数据");
+    }
+
+    /**
+     * 获取短链课程看课记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+    {
+        return AjaxResult.success(fsCourseWatchLogService.selectFsCourseWatchLogByLogId(logId));
+    }
+
+    /**
+     * 新增短链课程看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:add')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseWatchLog fsCourseWatchLog)
+    {
+        return toAjax(fsCourseWatchLogService.insertFsCourseWatchLog(fsCourseWatchLog));
+    }
+
+    /**
+     * 修改短链课程看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:edit')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseWatchLog fsCourseWatchLog)
+    {
+        return toAjax(fsCourseWatchLogService.updateFsCourseWatchLog(fsCourseWatchLog));
+    }
+
+    /**
+     * 删除短链课程看课记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:remove')")
+    @Log(title = "短链课程看课记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{logIds}")
+    public AjaxResult remove(@PathVariable Long[] logIds)
+    {
+        return toAjax(fsCourseWatchLogService.deleteFsCourseWatchLogByLogIds(logIds));
+    }
+
+//    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:list')")
+    @GetMapping("/listBytrainingCampId")
+    public TableDataInfo listBytrainingCampId(PeriodStatisticCountParam param)
+    {
+        startPage();
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectListBytrainingCampId(param);
+        return getDataTable(list);
+    }
+
+    @GetMapping("/watchLogStatistics")
+    public TableDataInfo watchLogStatistics(FsCourseOverParam param)
+    {
+        startPage();
+        if (param.getSTime()==null||param.getETime()==null){
+            return getDataTable(new ArrayList<>());
+        }
+        List<FsCourseOverVO> list = fsCourseWatchLogService.selectFsCourseWatchLogOverStatisticsListVO(param);
+        return getDataTable(list);
+    }
+}

+ 0 - 46
fs-company-app/src/main/java/com/fs/app/controller/qw/QwFsCourseWatchLogController.java

@@ -1,46 +0,0 @@
-package com.fs.app.controller.qw;
-
-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.exception.CustomException;
-import com.fs.common.utils.poi.ExcelUtil;
-import com.fs.course.domain.FsCourseWatchLog;
-import com.fs.course.param.FsCourseWatchLogListParam;
-import com.fs.course.param.FsCourseWatchLogStatisticsListParam;
-import com.fs.course.param.PeriodStatisticCountParam;
-import com.fs.course.service.IFsCourseWatchLogService;
-import com.fs.course.vo.FsCourseWatchLogListVO;
-import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
-import com.fs.qw.param.QwWatchLogStatisticsListParam;
-import com.fs.qw.service.IQwWatchLogService;
-import com.fs.qw.vo.QwWatchLogAllStatisticsListVO;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * 短链课程看课记录Controller
- *
- * @author fs
- * @date 2024-10-24
- */
-@RestController
-@RequestMapping("/qw/course/courseWatchLog")
-public class QwFsCourseWatchLogController extends BaseController {
-    @Autowired
-    private IFsCourseWatchLogService fsCourseWatchLogService;
-
-    //    @PreAuthorize("@ss.hasPermi('course:courseWatchLog:list')")
-    @GetMapping("/listBytrainingCampId")
-    public TableDataInfo listBytrainingCampId(PeriodStatisticCountParam param) {
-        startPage();
-        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectListBytrainingCampId(param);
-        return getDataTable(list);
-    }
-}

+ 58 - 57
fs-company/src/main/java/com/fs/company/controller/company/CompanyUserController.java

@@ -198,72 +198,72 @@ public class CompanyUserController extends BaseController {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         if (user.getCompanyId() == null && loginUser.getCompany() != null) { user.setCompanyId(loginUser.getCompany().getCompanyId()); }
         List<CompanyUserQwListVO> list = companyUserService.selectCompanyUserQwListVO(user);
-        if (!list.isEmpty()){
-            String json = configService.selectConfigByKey("course.config");
-            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-            Long sendDelayTime = config.getSendDelayTime();
-            List<CompletableFuture<Void>> futures = new ArrayList<>();
-            for (CompanyUserQwListVO companyUserQwListVO : list) {
-                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
-                    CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
-                    if (ObjectUtil.isEmpty(companyUserDelayTime)) {
-                        companyUserQwListVO.setSendDelayTime(sendDelayTime);
-                    } else {
-                        companyUserQwListVO.setSendDelayTime(companyUserDelayTime.getSendDelayTime());
-                    }
-                    //是否绑定
-                    if(QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()){
-                        if(!StringUtil.strIsNullOrEmpty(companyUserQwListVO.getQwUserId())){
-                            Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
-                                    .map(Long::parseLong)
-                                    .toArray(Long[]::new);
-                            List<QwUserVO> qwUserVOS = qwUserService.selectQwUserVOByIds(ids);
-                            companyUserQwListVO.setQwUsers(qwUserVOS);
-                        }
-                    }
-                });
-                futures.add(future);
-            }
-            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-        }
+        enrichQwListAsync(list, loginUser);
         return getDataTable(list);
     }
 
     @GetMapping("/myQwList")
     public TableDataInfo myQwList(CompanyUserQwParam user) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-//        user.setCompanyId(loginUser.getCompany().getCompanyId());
         user.setUserId(loginUser.getUser().getUserId());
         List<CompanyUserQwListVO> list = companyUserService.selectCompanyUserQwListVO(user);
-        if (!list.isEmpty()){
-            String json = configService.selectConfigByKey("course.config");
-            CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
-            Long sendDelayTime = config.getSendDelayTime();
-            List<CompletableFuture<Void>> futures = new ArrayList<>();
-            for (CompanyUserQwListVO companyUserQwListVO : list) {
-                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
-                    CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
-                    if (ObjectUtil.isEmpty(companyUserDelayTime)) {
-                        companyUserQwListVO.setSendDelayTime(sendDelayTime);
-                    } else {
-                        companyUserQwListVO.setSendDelayTime(companyUserDelayTime.getSendDelayTime());
-                    }
-                    //是否绑定
-                    if(QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()){
-                        if(!StringUtil.strIsNullOrEmpty(companyUserQwListVO.getQwUserId())){
-                            Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
-                                    .map(Long::parseLong)
-                                    .toArray(Long[]::new);
-                            List<QwUserVO> qwUserVOS = qwUserService.selectQwUserVOByIds(ids);
-                            companyUserQwListVO.setQwUsers(qwUserVOS);
-                        }
-                    }
-                });
-                futures.add(future);
+        enrichQwListAsync(list, loginUser);
+        return getDataTable(list);
+    }
+
+    private void enrichQwListAsync(List<CompanyUserQwListVO> list, LoginUser loginUser) {
+        if (list == null || list.isEmpty()) {
+            return;
+        }
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        Long sendDelayTime = config.getSendDelayTime();
+        Long tenantId = resolveRequestTenantId(loginUser);
+        List<CompletableFuture<Void>> futures = new ArrayList<>();
+        for (CompanyUserQwListVO companyUserQwListVO : list) {
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() ->
+                    tenantDataSourceManager.runWithTenant(tenantId, () -> fillQwListExtra(companyUserQwListVO, sendDelayTime)));
+            futures.add(future);
+        }
+        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+    }
+
+    private void fillQwListExtra(CompanyUserQwListVO companyUserQwListVO, Long sendDelayTime) {
+        CompanyUserDelayTime companyUserDelayTime = companyUserDelayTimeService.selectCompanyUserDelayTimeByCompanyUser(companyUserQwListVO.getCompanyId(), companyUserQwListVO.getUserId());
+        if (ObjectUtil.isEmpty(companyUserDelayTime)) {
+            companyUserQwListVO.setSendDelayTime(sendDelayTime);
+        } else {
+            companyUserQwListVO.setSendDelayTime(companyUserDelayTime.getSendDelayTime());
+        }
+        if (QwStatusEnum.BOUND.getCode() == companyUserQwListVO.getQwStatus()) {
+            if (!StringUtil.strIsNullOrEmpty(companyUserQwListVO.getQwUserId())) {
+                Long[] ids = Arrays.stream(companyUserQwListVO.getQwUserId().split(","))
+                        .map(Long::parseLong)
+                        .toArray(Long[]::new);
+                List<QwUserVO> qwUserVOS = qwUserService.selectQwUserVOByIds(ids);
+                companyUserQwListVO.setQwUsers(qwUserVOS);
             }
-            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
         }
-        return getDataTable(list);
+    }
+
+    private Long resolveRequestTenantId(LoginUser loginUser) {
+        if (loginUser != null && loginUser.getTenantId() != null) {
+            return loginUser.getTenantId();
+        }
+        HttpServletRequest request = ServletUtils.getRequest();
+        if (request == null) {
+            return null;
+        }
+        String tenantCode = request.getHeader("tenant-code");
+        if (StringUtils.isBlank(tenantCode)) {
+            return null;
+        }
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        TenantInfo tenant = tenantInfoMapper.selectTenantInfoByCode(tenantCode);
+        if (tenant != null && tenant.getStatus() != null && tenant.getStatus() == 1) {
+            return tenant.getId();
+        }
+        return null;
     }
 
     @Log(title = "用户管理导出", businessType = BusinessType.EXPORT)
@@ -398,8 +398,9 @@ public class CompanyUserController extends BaseController {
         if (tenantInfo == null) {
             return false;
         }
+        Long companyId = loginUser.getCompany().getCompanyId();
         tenantDataSourceManager.switchTenant(tenantInfo);
-        Integer count = companyUserService.getUserCount();
+        Integer count = companyUserService.selectCompanyUserCountByCompanyId(companyId);
         Integer accountNum = tenantInfo.getAccountNum();
         if (accountNum == null || count == null) {
             return false;

+ 3 - 2
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -427,14 +427,15 @@ public class QwUserController extends BaseController
         }
         catch (Exception e)
         {
+            Long tenantId = SecurityUtils.getTenantId();
             if (e instanceof BadCredentialsException)
             {
-                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,param.getCompanyAdmin(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(tenantId, 0L, param.getCompanyAdmin(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                 throw new UserPasswordNotMatchException();
             }
             else
             {
-                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,param.getCompanyAdmin(), Constants.LOGIN_FAIL, e.getMessage()));
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(tenantId, 0L, param.getCompanyAdmin(), Constants.LOGIN_FAIL, e.getMessage()));
                 throw new ServiceException(e.getMessage());
             }
         }

+ 3 - 2
fs-company/src/main/java/com/fs/framework/aspectj/LogAspect.java

@@ -114,8 +114,9 @@ public class LogAspect
             operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
             // 处理设置注解上的参数
             getControllerMethodDescription(joinPoint, controllerLog, operLog);
-            // 保存数据库
-            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
+            // 保存数据库(异步任务内按 tenantId 切租户库)
+            Long tenantId = loginUser != null ? loginUser.getTenantId() : null;
+            AsyncManager.me().execute(AsyncFactory.recordOper(tenantId, operLog));
         }
         catch (Exception exp)
         {

+ 21 - 0
fs-company/src/main/java/com/fs/framework/datasource/TenantDataSourceManager.java

@@ -106,6 +106,27 @@ public class TenantDataSourceManager {
         }
     }
 
+    /**
+     * 在指定租户数据源上下文中执行(适用于异步线程等 ThreadLocal 丢失场景)
+     */
+    public void runWithTenant(Long tenantId, Runnable action) {
+        if (action == null) {
+            return;
+        }
+        if (tenantId == null) {
+            log.debug("[TenantDS] tenantId 为空,跳过租户切库");
+            action.run();
+            return;
+        }
+        try {
+            ensureSwitchByTenantId(tenantId);
+            action.run();
+        } finally {
+            clear();
+            DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        }
+    }
+
     /**
      * 创建租户数据源(MySQL + Druid)
      */

+ 60 - 46
fs-company/src/main/java/com/fs/framework/manager/factory/AsyncFactory.java

@@ -1,6 +1,7 @@
 package com.fs.framework.manager.factory;
 
 import com.fs.common.constant.Constants;
+import com.fs.common.enums.DataSourceType;
 import com.fs.common.utils.LogUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.ip.AddressUtils;
@@ -10,6 +11,8 @@ import com.fs.company.domain.CompanyLogininfor;
 import com.fs.company.domain.CompanyOperLog;
 import com.fs.company.service.ICompanyLogininforService;
 import com.fs.company.service.ICompanyOperLogService;
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.framework.datasource.TenantDataSourceManager;
 import eu.bitwalker.useragentutils.UserAgent;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -19,24 +22,16 @@ import java.util.TimerTask;
 
 /**
  * 异步工厂(产生任务用)
- * 
-
  */
 public class AsyncFactory
 {
     private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
 
     /**
-     * 记录登录信息
-     * 
-     * @param username 用户名
-     * @param status 状态
-     * @param message 消息
-     * @param args 列表
-     * @return 任务task
+     * 记录登录信息(写租户库 company_logininfor)
      */
-    public static TimerTask recordLogininfor(final Long companyId,final String username, final String status, final String message,
-            final Object... args)
+    public static TimerTask recordLogininfor(final Long tenantId, final Long companyId, final String username,
+            final String status, final String message, final Object... args)
     {
         final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
         final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
@@ -52,55 +47,74 @@ public class AsyncFactory
                 s.append(LogUtils.getBlock(username));
                 s.append(LogUtils.getBlock(status));
                 s.append(LogUtils.getBlock(message));
-                // 打印信息到日志
                 sys_user_logger.info(s.toString(), args);
-                // 获取客户端操作系统
-                String os = userAgent.getOperatingSystem().getName();
-                // 获取客户端浏览器
-                String browser = userAgent.getBrowser().getName();
-                // 封装对象
-                CompanyLogininfor logininfor = new CompanyLogininfor();
-                logininfor.setCompanyId(companyId);
-                logininfor.setUserName(username);
-                logininfor.setIpaddr(ip);
-                logininfor.setLoginLocation(address);
-                logininfor.setBrowser(browser);
-                logininfor.setOs(os);
-                logininfor.setMsg(message);
-                // 日志状态
-                if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status))
-                {
-                    logininfor.setStatus(Constants.SUCCESS);
-                }
-                else if (Constants.LOGIN_FAIL.equals(status))
-                {
-                    logininfor.setStatus(Constants.FAIL);
-                }
-                logininfor.setLoginTime(new Date());
-                // 插入数据
-                SpringUtils.getBean(ICompanyLogininforService.class).insertCompanyLogininfor(logininfor);
+
+                executeWithTenantDatasource(tenantId, () -> {
+                    String os = userAgent.getOperatingSystem().getName();
+                    String browser = userAgent.getBrowser().getName();
+                    CompanyLogininfor logininfor = new CompanyLogininfor();
+                    logininfor.setCompanyId(companyId);
+                    logininfor.setUserName(username);
+                    logininfor.setIpaddr(ip);
+                    logininfor.setLoginLocation(address);
+                    logininfor.setBrowser(browser);
+                    logininfor.setOs(os);
+                    logininfor.setMsg(message);
+                    if (Constants.LOGIN_SUCCESS.equals(status) || Constants.LOGOUT.equals(status))
+                    {
+                        logininfor.setStatus(Constants.SUCCESS);
+                    }
+                    else if (Constants.LOGIN_FAIL.equals(status))
+                    {
+                        logininfor.setStatus(Constants.FAIL);
+                    }
+                    logininfor.setLoginTime(new Date());
+                    SpringUtils.getBean(ICompanyLogininforService.class).insertCompanyLogininfor(logininfor);
+                });
             }
         };
     }
 
     /**
-     * 操作日志记录
-     * 
-     * @param operLog 操作日志信息
-     * @return 任务task
+     * 操作日志记录(写租户库 company_oper_log)
      */
-    public static TimerTask recordOper(final CompanyOperLog operLog)
+    public static TimerTask recordOper(final Long tenantId, final CompanyOperLog operLog)
     {
         return new TimerTask()
         {
             @Override
             public void run()
             {
-                // 远程查询操作地点
-                operLog.setOperTime(new Date());
-                operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
-                SpringUtils.getBean(ICompanyOperLogService.class).insertCompanyOperLog(operLog);
+                executeWithTenantDatasource(tenantId, () -> {
+                    operLog.setOperTime(new Date());
+                    operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
+                    SpringUtils.getBean(ICompanyOperLogService.class).insertCompanyOperLog(operLog);
+                });
             }
         };
     }
+
+    private static void executeWithTenantDatasource(Long tenantId, Runnable action)
+    {
+        if (tenantId == null)
+        {
+            sys_user_logger.debug("tenantId 为空,跳过租户库异步写入(company_logininfor/company_oper_log 不在主库)");
+            return;
+        }
+        TenantDataSourceManager tenantDataSourceManager = SpringUtils.getBean(TenantDataSourceManager.class);
+        try
+        {
+            tenantDataSourceManager.ensureSwitchByTenantId(tenantId);
+            action.run();
+        }
+        catch (Exception ex)
+        {
+            sys_user_logger.warn("租户异步写库失败 tenantId={}: {}", tenantId, ex.getMessage());
+        }
+        finally
+        {
+            tenantDataSourceManager.clear();
+            DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        }
+    }
 }

+ 2 - 2
fs-company/src/main/java/com/fs/framework/security/handle/LogoutSuccessHandlerImpl.java

@@ -48,8 +48,8 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
             // 删除用户缓存记录
             tokenService.delLoginUser(loginUser.getToken());
             // 记录用户退出日志
-            Long logoutCid = loginUser.getCompany() != null ? loginUser.getCompany().getCompanyId() : null;
-            AsyncManager.me().execute(AsyncFactory.recordLogininfor(logoutCid,userName, Constants.LOGOUT, "退出成功"));
+            Long logoutCid = loginUser.getCompany() != null ? loginUser.getCompany().getCompanyId() : 0L;
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginUser.getTenantId(), logoutCid, userName, Constants.LOGOUT, "退出成功"));
         }
         ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(HttpStatus.SUCCESS, "退出成功")));
     }

+ 25 - 9
fs-company/src/main/java/com/fs/framework/service/CompanyLoginService.java

@@ -109,17 +109,18 @@ public class CompanyLoginService
             redisCache.deleteObject(verifyKey);
             if (captcha == null)
             {
-                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(resolveTenantIdQuiet(tenantCode), 0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                 throw new CaptchaExpireException();
             }
             if (!code.equalsIgnoreCase(captcha))
             {
-                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(resolveTenantIdQuiet(tenantCode), 0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                 throw new CaptchaException();
             }
         }
 
         TenantInfo tenantInfo = null;
+        Long tenantId = null;
 
         // 默认使用主库
         DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
@@ -138,6 +139,7 @@ public class CompanyLoginService
             }
 
             // 切租户库
+            tenantId = tenantInfo.getId();
             tenantDataSourceManager.switchTenant(tenantInfo);
         }
 
@@ -154,12 +156,12 @@ public class CompanyLoginService
             {
                 if (e instanceof BadCredentialsException)
                 {
-                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(tenantId, 0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                     throw new UserPasswordNotMatchException();
                 }
                 else
                 {
-                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, e.getMessage()));
+                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(tenantId, 0L, username, Constants.LOGIN_FAIL, e.getMessage()));
                     throw new ServiceException(e.getMessage());
                 }
             }
@@ -196,7 +198,7 @@ public class CompanyLoginService
                 }
             }
 
-            AsyncManager.me().execute(AsyncFactory.recordLogininfor(companyUser.getCompanyId() != null ? companyUser.getCompanyId() : 0L, username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(tenantId, companyUser.getCompanyId() != null ? companyUser.getCompanyId() : 0L, username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
             if (companyUser.getCompanyId() != null) {
                 redisCache.setCacheObject("companyId:" + companyUser.getUserId(), companyUser.getCompanyId(), 604800, TimeUnit.SECONDS);
             }
@@ -268,17 +270,18 @@ public class CompanyLoginService
             String captcha = redisCache.getCacheObject(verifyKey);
             if (captcha == null)
             {
-                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(resolveTenantIdQuiet(tenantCode), 0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                 throw new CaptchaExpireException();
             }
             if (!code.equalsIgnoreCase(captcha))
             {
-                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(resolveTenantIdQuiet(tenantCode), 0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                 throw new CaptchaException();
             }
         }
 
         TenantInfo tenantInfo = null;
+        Long tenantId = null;
         // (查租户)
         DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
 
@@ -297,6 +300,7 @@ public class CompanyLoginService
             }
 
             // 切到租户库
+            tenantId = tenantInfo.getId();
             tenantDataSourceManager.switchTenant(tenantInfo);
         }
 
@@ -313,12 +317,12 @@ public class CompanyLoginService
             {
                 if (e instanceof BadCredentialsException)
                 {
-                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(tenantId, 0L, username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                     throw new UserPasswordNotMatchException();
                 }
                 else
                 {
-                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, e.getMessage()));
+                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(tenantId, 0L, username, Constants.LOGIN_FAIL, e.getMessage()));
                     throw new ServiceException(e.getMessage());
                 }
             }
@@ -526,4 +530,16 @@ public class CompanyLoginService
         redisCache.setCacheObject("wechat:scan:" + ticket, "ok", 30, TimeUnit.SECONDS);
     }
 
+    /** 异步写登录日志前解析租户 ID(不校验状态,仅用于切库) */
+    private Long resolveTenantIdQuiet(String tenantCode)
+    {
+        if (StringUtils.isBlank(tenantCode))
+        {
+            return null;
+        }
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        TenantInfo tenantInfo = userService.getTenantInfo(tenantCode);
+        return BeanUtil.isEmpty(tenantInfo) ? null : tenantInfo.getId();
+    }
+
 }