Ver Fonte

Merge remote-tracking branch 'origin/master'

吴树波 há 2 meses atrás
pai
commit
1012e3a7c7

+ 14 - 1
fs-admin/src/main/java/com/fs/qw/controller/QwCompanyController.java

@@ -8,6 +8,7 @@ import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.utils.ServletUtils;
 import com.fs.course.config.CourseConfig;
 import com.fs.framework.web.service.TokenService;
+import com.fs.qw.param.QwCompanyTenantSetParam;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.system.service.ISysConfigService;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -53,7 +54,7 @@ public class QwCompanyController extends BaseController
     @GetMapping("/list")
     public TableDataInfo list(QwCompany qwCompany)
     {
-        startPage();
+
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         String json = configService.selectConfigByKey("course.config");
         CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
@@ -61,6 +62,7 @@ public class QwCompanyController extends BaseController
             qwCompany.setCreateDeptId(loginUser.getDeptId());
             qwCompany.setCreateUserId(loginUser.getUserId());
         }
+        startPage();
         List<QwCompany> list = qwCompanyService.selectQwCompanyList(qwCompany);
         return getDataTable(list);
     }
@@ -142,4 +144,15 @@ public class QwCompanyController extends BaseController
     {
         return toAjax(qwCompanyService.deleteQwCompanyByIds(ids));
     }
+
+    /**
+     * 租户配置
+     */
+    @PreAuthorize("@ss.hasPermi('qw:qwCompany:tenant')")
+    @Log(title = "企微主体租户配置", businessType = BusinessType.UPDATE, isStoreLog = true)
+    @PostMapping("/setTenant")
+    public R setTenant(@RequestBody QwCompanyTenantSetParam param)
+    {
+        return qwCompanyService.setTenant(param);
+    }
 }

+ 10 - 0
fs-admin/src/main/java/com/fs/tenant/TenantInfoController.java

@@ -43,6 +43,16 @@ public class TenantInfoController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询所有租户id以及租户名称 租户编码
+     */
+    @PreAuthorize("@ss.hasPermi('tenant:tenant:list')")
+    @GetMapping("/tenantList")
+    public R tenantList(TenantInfo tenantInfo)
+    {
+        return R.ok().put("rows",tenantInfoService.tenantList(tenantInfo));
+    }
+
     /**
      * 导出租户基础信息列表
      */

+ 78 - 0
fs-qw-api/src/main/java/com/fs/app/service/QwDataCallbackService.java

@@ -16,6 +16,8 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.param.QwAutoRulesTagsParams;
 import com.fs.qw.service.*;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.mapper.TenantInfoMapper;
 import com.fs.qwApi.Result.QwGroupChatDetailsResult;
 import com.fs.qwApi.Result.QwOpenidResult;
 import com.fs.qwApi.Result.QwPermanentCodeResult;
@@ -37,6 +39,7 @@ import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.w3c.dom.Document;
@@ -103,6 +106,12 @@ public class QwDataCallbackService {
     private RedisCache redisCache;
     @Autowired
     private QwCompanyMapper qwCompanyMapper;
+
+    @Autowired
+    private TenantInfoMapper tenantInfoMapper;
+
+    @Autowired
+    private ApplicationContext applicationContext;
     @Autowired
     private ILeadService leadService;
 
@@ -164,6 +173,75 @@ public class QwDataCallbackService {
         map.setPermanentCode(permanentCode.getPermanent_code());
         int rows = qwCompanyMapper.updateQwCompany(map);
         logger.info("已更新 qw_company 永久授权码, corpId={}, qwCompanyId={}, rows={}", corpId, qwCompany.getId(), rows);
+
+        // 同步到主库上绑定了 tenantId 的租户库对应主体
+        syncPermanentCodeToTenantDatabase(permanentCode.getPermanent_code());
+    }
+
+    /**
+     * 将主库 {@code qw_company.permanent_code} 同步到“主库绑定了 tenantId 的租户库”对应主体。
+     *
+     * <p>当同一服务商永久授权码在多个应用中相同,会触发多次回调;本方法会将最新 permanent_code 写回租户库对应 corpId 的那条记录。</p>
+     */
+    private void syncPermanentCodeToTenantDatabase(String permanentCodeValue) {
+
+        List<QwCompany> qwCompanies = qwCompanyMapper.selectQwCompanyList(new QwCompany());
+        if (qwCompanies == null || qwCompanies.isEmpty()) {
+            return;
+        }
+        for (QwCompany qwCompany : qwCompanies) {
+            Long tenantId = qwCompany.getTenantId();
+            if (tenantId == null) {
+                return;
+            }
+
+            TenantInfo tenantInfo;
+            try {
+                tenantInfo = tenantInfoMapper.selectTenantInfoById(tenantId.toString());
+            } catch (Exception e) {
+                logger.error("查询租户信息失败 tenantId={}", tenantId, e);
+                return;
+            }
+            if (tenantInfo == null) {
+                logger.warn("同步租户永久授权码失败:租户不存在 tenantId={}", tenantId);
+                return;
+            }
+
+            if (!switchToTenantDataSource(tenantInfo)) {
+                return;
+            }
+            String corpId = qwCompany.getCorpId();
+            try {
+                int tenantRows = qwCompanyMapper.updatePermanentCodeByCorpId(corpId, permanentCodeValue);
+                logger.info("已同步租户库 qw_company 永久授权码, tenantId={}, corpId={}, rows={}",
+                        tenantId, corpId, tenantRows);
+            } catch (Exception e) {
+                logger.error("同步租户库 qw_company 永久授权码失败 tenantId={}, corpId={}", tenantId, corpId, e);
+            } finally {
+                clearTenantDataSourceType();
+            }
+        }
+
+    }
+
+    private boolean switchToTenantDataSource(TenantInfo tenantInfo) {
+        try {
+            Class<?> mgrClass = Class.forName("com.fs.framework.datasource.TenantDataSourceManager");
+            Object manager = applicationContext.getBean(mgrClass);
+            mgrClass.getMethod("switchTenant", TenantInfo.class).invoke(manager, tenantInfo);
+            return true;
+        } catch (Exception e) {
+            logger.warn("无法切换租户数据源 tenantId={}, error={}", tenantInfo.getId(), e.toString());
+            return false;
+        }
+    }
+
+    private void clearTenantDataSourceType() {
+        try {
+            Class<?> holderClass = Class.forName("com.fs.framework.datasource.DynamicDataSourceContextHolder");
+            holderClass.getMethod("clearDataSourceType").invoke(null);
+        } catch (Exception ignore) {
+        }
     }
 
     @Async

+ 5 - 0
fs-service/src/main/java/com/fs/qw/domain/QwCompany.java

@@ -101,4 +101,9 @@ public class QwCompany extends BaseEntity
      * 服务商永久授权码
      */
     private String permanentCode;
+
+    /**
+     * 租户id
+     */
+    private Long tenantId;
 }

+ 9 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwCompanyMapper.java

@@ -73,6 +73,15 @@ public interface QwCompanyMapper
     @Select("SELECT * from qw_company where corp_id=#{corpId} limit 1")
     QwCompany selectQwCompanyByCorpId(String corpId);
 
+    /**
+     * 按 corpId 更新永久授权码(用于租户库同步)。
+     *
+     * @param corpId 企业微信 corpId
+     * @param permanentCode 永久授权码
+     * @return 影响行数
+     */
+    int updatePermanentCodeByCorpId(@Param("corpId") String corpId, @Param("permanentCode") String permanentCode);
+
     List<QwOptionsVO> selectQwCompanyListOptionsVO(@Param("userId") Long userId, @Param("deptId") Long deptId);
 
     List<QwCompany> selectByCorpIds(@Param("corpIds") List<String> corpIds);

+ 13 - 0
fs-service/src/main/java/com/fs/qw/param/QwCompanyTenantSetParam.java

@@ -0,0 +1,13 @@
+package com.fs.qw.param;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+@Data
+public class QwCompanyTenantSetParam {
+    /** 企微公司id */
+    private Long qwCompanyId;
+    /** 租户id */
+    private Long tenantId;
+
+}

+ 4 - 0
fs-service/src/main/java/com/fs/qw/service/IQwCompanyService.java

@@ -1,6 +1,8 @@
 package com.fs.qw.service;
 
+import com.fs.common.core.domain.R;
 import com.fs.qw.domain.QwCompany;
+import com.fs.qw.param.QwCompanyTenantSetParam;
 import com.fs.qw.vo.QwOptionsVO;
 
 import java.util.List;
@@ -67,4 +69,6 @@ public interface IQwCompanyService
     List<QwOptionsVO> selectQwCompanyListOptionsVO(Long userId, Long deptId);
 
     List<String>  selectQwCompanyListFormCorpId();
+
+    R setTenant(QwCompanyTenantSetParam param);
 }

+ 124 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwCompanyServiceImpl.java

@@ -1,14 +1,20 @@
 package com.fs.qw.service.impl;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.fs.qw.domain.QwCompany;
 import com.fs.qw.mapper.QwCompanyMapper;
+import com.fs.qw.param.QwCompanyTenantSetParam;
 import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.vo.QwOptionsVO;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.mapper.TenantInfoMapper;
 import com.fs.voice.utils.StringUtil;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Service;
 
 import java.util.Collections;
@@ -21,6 +27,7 @@ import java.util.concurrent.TimeUnit;
  * @author fs
  * @date 2024-10-09
  */
+@Slf4j
 @Service
 public class QwCompanyServiceImpl implements IQwCompanyService
 {
@@ -138,4 +145,121 @@ public class QwCompanyServiceImpl implements IQwCompanyService
     public List<String> selectQwCompanyListFormCorpId() {
         return qwCompanyMapper.selectQwCompanyListFormCorpId();
     }
+
+    @Autowired
+    private TenantInfoMapper tenantInfoMapper;
+
+    @Autowired
+    private ApplicationContext applicationContext;
+
+    @Override
+    public R setTenant(QwCompanyTenantSetParam param) {
+        Long qwCompanyId = param.getQwCompanyId();
+        Long tenantId = param.getTenantId();
+        if (qwCompanyId == null || tenantId == null) {
+            return R.error("未选择所配置的企微主体/租户");
+        }
+        TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(tenantId.toString());
+        if (tenantInfo == null) {
+            return R.error("所配置租户不存在");
+        }
+        QwCompany qwCompany = qwCompanyMapper.selectQwCompanyById(qwCompanyId);
+        if (qwCompany == null) {
+            return R.error("所选企微主体不存在");
+        }
+        qwCompany.setTenantId(tenantId);
+        try {
+            if (!upsertQwCompanyInTenantDatabase(tenantInfo, qwCompany)) {
+                return R.error("更新租户企微主体失败");
+            }
+        } catch (Exception e) {
+            log.error("更新租户企微主体失败:{}",e.getMessage());
+            return R.error("更新租户企微主体失败:"+e.getMessage());
+        }
+        updateQwCompany(qwCompany);
+        return R.ok();
+    }
+
+    /**
+     * 在租户库中按 corpId 同步企微主体:已存在则更新,否则新增。
+     * 直接走 Mapper,避免复用 updateQwCompany 时用租户库主键覆盖按 corpId 缓存的 Redis。
+     */
+    private boolean upsertQwCompanyInTenantDatabase(TenantInfo tenantInfo, QwCompany masterCompany) {
+        if (!switchToTenantDataSource(tenantInfo)) {
+            return false;
+        }
+        boolean flag = false;
+        try {
+            String corpId = masterCompany.getCorpId();
+            QwCompany tenantRow = qwCompanyMapper.selectQwCompanyByCorpId(corpId);
+            Long tenantPk = tenantInfo.getId();
+            if (tenantRow != null) {
+                tenantRow.setId(tenantPk);
+                tenantRow.setCorpId(masterCompany.getCorpId());
+                tenantRow.setCorpName(masterCompany.getCorpName());
+                tenantRow.setOpenSecret(masterCompany.getOpenSecret());
+                tenantRow.setOpenCorpId(masterCompany.getOpenCorpId());
+                tenantRow.setServerAgentId(masterCompany.getServerAgentId());
+                tenantRow.setServerBookCorpId(masterCompany.getServerBookCorpId());
+                tenantRow.setServerBookSecret(masterCompany.getServerBookSecret());
+                tenantRow.setToken(masterCompany.getToken());
+                tenantRow.setEncodingAesKey(masterCompany.getEncodingAesKey());
+                tenantRow.setProviderSecret(masterCompany.getProviderSecret());
+                tenantRow.setRealmNameUrl(masterCompany.getRealmNameUrl());
+                tenantRow.setNotifyUrl(masterCompany.getNotifyUrl());
+                tenantRow.setUpdateBy("master");
+                tenantRow.setUpdateTime(DateUtils.getNowDate());
+                tenantRow.setAgentId(masterCompany.getAgentId());
+                tenantRow.setPermanentCode(masterCompany.getPermanentCode());
+
+                qwCompanyMapper.updateQwCompany(tenantRow);
+            } else {
+                QwCompany toInsert = new QwCompany();
+                toInsert.setCorpId(masterCompany.getCorpId());
+                toInsert.setCorpName(masterCompany.getCorpName());
+                toInsert.setOpenSecret(masterCompany.getOpenSecret());
+                toInsert.setOpenCorpId(masterCompany.getOpenCorpId());
+                toInsert.setServerAgentId(masterCompany.getServerAgentId());
+                toInsert.setServerBookCorpId(masterCompany.getServerBookCorpId());
+                toInsert.setServerBookSecret(masterCompany.getServerBookSecret());
+                toInsert.setToken(masterCompany.getToken());
+                toInsert.setEncodingAesKey(masterCompany.getEncodingAesKey());
+                toInsert.setProviderSecret(masterCompany.getProviderSecret());
+                toInsert.setRealmNameUrl(masterCompany.getRealmNameUrl());
+                toInsert.setNotifyUrl(masterCompany.getNotifyUrl());
+                toInsert.setCreateBy("master");
+                toInsert.setCreateTime(DateUtils.getNowDate());
+                toInsert.setAgentId(masterCompany.getAgentId());
+                toInsert.setPermanentCode(masterCompany.getPermanentCode());
+                toInsert.setStatus(1L);
+                qwCompanyMapper.insertQwCompany(toInsert);
+            }
+            flag = true;
+        } catch (Exception e) {
+            log.error("同步 qw_company 到租户库失败 tenantId={}, corpId={}", tenantInfo.getId(), masterCompany.getCorpId(), e);
+        } finally {
+            clearTenantDataSourceType();
+        }
+        return flag;
+    }
+
+    private boolean switchToTenantDataSource(TenantInfo tenantInfo) {
+        try {
+            Class<?> mgrClass = Class.forName("com.fs.framework.datasource.TenantDataSourceManager");
+            Object manager = applicationContext.getBean(mgrClass);
+            mgrClass.getMethod("switchTenant", TenantInfo.class).invoke(manager, tenantInfo);
+            return true;
+        } catch (Exception e) {
+            log.warn("无法切换租户数据源,跳过租户库 qw_company 同步: {}", e.toString());
+            return false;
+        }
+    }
+
+    private void clearTenantDataSourceType() {
+        try {
+            Class<?> holderClass = Class.forName("com.fs.framework.datasource.DynamicDataSourceContextHolder");
+            holderClass.getMethod("clearDataSourceType").invoke(null);
+        } catch (Exception ignore) {
+        }
+    }
 }

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

@@ -2,6 +2,7 @@ package com.fs.tenant.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.vo.TenantInfoShowVo;
 
 import java.util.List;
 
@@ -59,6 +60,8 @@ public interface TenantInfoMapper extends BaseMapper<TenantInfo> {
      * @return 结果
      */
     int deleteTenantInfoByIds(String[] ids);
+
+    List<TenantInfoShowVo> tenantList(TenantInfo tenantInfo);
 }
 
 

+ 3 - 0
fs-service/src/main/java/com/fs/tenant/service/TenantInfoService.java

@@ -2,6 +2,7 @@ package com.fs.tenant.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.vo.TenantInfoShowVo;
 
 import java.util.List;
 
@@ -58,4 +59,6 @@ public interface TenantInfoService extends IService<TenantInfo> {
      * @return 结果
      */
     int deleteTenantInfoById(String id);
+
+    List<TenantInfoShowVo> tenantList(TenantInfo tenantInfo);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/tenant/service/impl/TenantInfoServiceImpl.java

@@ -8,6 +8,7 @@ import com.fs.tenant.domain.TenantInfo;
 import com.fs.tenant.mapper.TenantInfoMapper;
 import com.fs.tenant.service.TenantAsyncService;
 import com.fs.tenant.service.TenantInfoService;
+import com.fs.tenant.vo.TenantInfoShowVo;
 import com.fs.utils.TenantUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -140,6 +141,11 @@ public class TenantInfoServiceImpl extends ServiceImpl<TenantInfoMapper, TenantI
     {
         return baseMapper.deleteTenantInfoById(id);
     }
+
+    @Override
+    public List<TenantInfoShowVo> tenantList(TenantInfo tenantInfo) {
+        return baseMapper.tenantList(tenantInfo);
+    }
 }
 
 

+ 33 - 0
fs-service/src/main/java/com/fs/tenant/vo/TenantInfoShowVo.java

@@ -0,0 +1,33 @@
+package com.fs.tenant.vo;
+
+import lombok.Data;
+
+
+/**
+ * 租户基础信息表
+ * @TableName tenant_info
+ */
+@Data
+public class TenantInfoShowVo {
+    /**
+     * 租户唯一ID
+     */
+    private Long id;
+
+    /**
+     * 租户编码(唯一,如企业简称/编号)
+     */
+    private String tenantCode;
+
+    /**
+     * 租户名称
+     */
+    private String tenantName;
+
+    /**
+     * 状态:1-启用,0-禁用
+     */
+    private Integer status;
+
+
+}

+ 10 - 2
fs-service/src/main/resources/mapper/qw/QwCompanyMapper.xml

@@ -34,13 +34,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="shareAgentId"    column="share_agent_id"    />
         <result property="shareSchema"    column="share_schema"    />
         <result property="permanentCode"    column="permanent_code"    />
+        <result property="tenantId"    column="tenant_id"    />
     </resultMap>
 
     <sql id="selectQwCompanyVo">
         select id, corp_id, corp_name, open_secret, open_corp_id, server_agent_id, server_book_corp_id,
                server_book_secret, token, encoding_aes_key, provider_secret, realm_name_url, notify_url,
                chat_toolbar, chat_toolbar_oauth, company_ids, status, create_time, update_time, create_by,
-               is_buy,mini_app_id,company_server_num,share_app_id,share_agent_id,share_schema,permanent_code from qw_company
+               is_buy,mini_app_id,company_server_num,share_app_id,share_agent_id,share_schema,permanent_code,tenant_id from qw_company
     </sql>
 
     <select id="selectQwCompanyList" parameterType="QwCompany" resultMap="QwCompanyResult">
@@ -107,7 +108,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="isBuy != null">is_buy,</if>
             <if test="miniAppId != null">mini_app_id,</if>
             <if test="companyServerNum != null">company_server_num,</if>
-            <if test="createUserId != null != null">create_user_id,</if>
+            <if test="createUserId != null">create_user_id,</if>
             <if test="createDeptId != null">create_dept_id,</if>
             <if test="shareAppId != null">share_app_id,</if>
             <if test="shareAgentId != null">share_agent_id,</if>
@@ -173,10 +174,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="shareAgentId != null">share_agent_id = #{shareAgentId},</if>
             <if test="shareSchema != null">share_schema = #{shareSchema},</if>
             <if test="permanentCode != null">permanent_code = #{permanentCode},</if>
+            <if test="tenantId != null">tenant_id = #{tenantId},</if>
         </trim>
         where id = #{id}
     </update>
 
+    <update id="updatePermanentCodeByCorpId">
+        update qw_company
+        set permanent_code = #{permanentCode}
+        where corp_id = #{corpId}
+    </update>
+
     <delete id="deleteQwCompanyById" parameterType="Long">
         delete from qw_company where id = #{id}
     </delete>

+ 15 - 0
fs-service/src/main/resources/mapper/tenant/TenantInfoMapper.xml

@@ -42,6 +42,21 @@
         <include refid="selectTenantInfoVo"/>
         where id = #{id}
     </select>
+    <select id="tenantList" resultType="com.fs.tenant.vo.TenantInfoShowVo">
+        select id, tenant_code, tenant_name from tenant_info
+        <where>
+            <if test="tenantCode != null  and tenantCode != ''"> and tenant_code = #{tenantCode}</if>
+            <if test="tenantName != null  and tenantName != ''"> and tenant_name like concat('%', #{tenantName}, '%')</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="expireTime != null "> and expire_time = #{expireTime}</if>
+            <if test="dbUrl != null  and dbUrl != ''"> and db_url = #{dbUrl}</if>
+            <if test="dbAccount != null  and dbAccount != ''"> and db_account = #{dbAccount}</if>
+            <if test="dbPwd != null  and dbPwd != ''"> and db_pwd = #{dbPwd}</if>
+            <if test="contactPhone != null  and contactPhone != ''"> and contact_phone = #{contactPhone}</if>
+            <if test="contactName != null  and contactName != ''"> and contact_name like concat('%', #{contactName}, '%')</if>
+        </where>
+    </select>
+
 
     <insert id="insertTenantInfo" parameterType="TenantInfo" useGeneratedKeys="true" keyProperty="id">
         insert into tenant_info