yh 1 месяц назад
Родитель
Сommit
001dafd6cc

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

@@ -1,6 +1,7 @@
 package com.fs.company.controller.qw;
 package com.fs.company.controller.qw;
 
 
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.Log;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.annotation.RepeatSubmit;
@@ -37,6 +38,7 @@ import com.fs.qw.service.IQwUserService;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwOptionsVO;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.qw.vo.QwUserVO;
 import com.fs.qw.vo.UpdateSendTypeVo;
 import com.fs.qw.vo.UpdateSendTypeVo;
+import com.fs.qwApi.config.OpenQwConfig;
 import com.fs.qwApi.domain.QwExternalContactAllListResult;
 import com.fs.qwApi.domain.QwExternalContactAllListResult;
 import com.fs.qwApi.domain.inner.ExternalContact;
 import com.fs.qwApi.domain.inner.ExternalContact;
 import com.fs.qwApi.domain.inner.ExternalContactInfo;
 import com.fs.qwApi.domain.inner.ExternalContactInfo;
@@ -624,8 +626,14 @@ public class QwUserController extends BaseController
         for (String string : strings) {
         for (String string : strings) {
 
 
             if (string.equals(corpId)){
             if (string.equals(corpId)){
-                qwUserService.syncQwUser(string);
-                qwDeptService.insertOrUpdateQwDept(string);
+//                qwUserService.syncQwUser(string);
+//                qwDeptService.insertOrUpdateQwDept(string);
+
+                Map<String, Object> paramMap = new HashMap<>();
+                paramMap.put("tenantId", loginUser.getTenantId());
+                paramMap.put("corpId", string);
+                HttpUtil.post(OpenQwConfig.baseApi + "/getSyncQwUser", paramMap);
+                HttpUtil.post(OpenQwConfig.baseApi + "/getSyncQwDept", paramMap);
                 logger.info("同步完成");
                 logger.info("同步完成");
             }
             }
         }
         }

+ 52 - 0
fs-qw-api/src/main/java/com/fs/app/controller/OpenQwApiController.java

@@ -0,0 +1,52 @@
+package com.fs.app.controller;
+
+import com.fs.app.service.OpenQwApiService;
+import com.fs.common.core.domain.R;
+import com.fs.framework.datasource.TenantDataSourceManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/open/qwapi")
+public class OpenQwApiController {
+
+    @Autowired
+    private OpenQwApiService openQwApiService;
+
+    @Autowired
+    private TenantDataSourceManager tenantDataSourceManager;
+
+    /**
+     * 同步企微员工
+     * @param tenantId
+     * @param corpId
+     * @return
+     */
+    @PostMapping("/getSyncQwUser")
+    public R getSyncQwUser(@RequestParam Long tenantId, @RequestParam String corpId){
+        tenantDataSourceManager.ensureSwitchByTenantId(tenantId);
+        try {
+            return openQwApiService.getSyncQwUser(corpId);
+        } finally {
+            tenantDataSourceManager.clear();
+        }
+    }
+
+
+    /**
+     * 同步企微部门
+     * @param tenantId
+     * @param corpId
+     * @return
+     */
+    @PostMapping("/getSyncQwDept")
+    public R getSyncQwDept(@RequestParam Long tenantId, @RequestParam String corpId){
+        tenantDataSourceManager.ensureSwitchByTenantId(tenantId);
+        try {
+            return openQwApiService.getSyncQwDept(corpId);
+        } finally {
+            tenantDataSourceManager.clear();
+        }
+    }
+}
+

+ 9 - 0
fs-qw-api/src/main/java/com/fs/app/service/OpenQwApiService.java

@@ -0,0 +1,9 @@
+package com.fs.app.service;
+
+import com.fs.common.core.domain.R;
+
+public interface OpenQwApiService {
+    R getSyncQwUser(String corpId);
+
+    R getSyncQwDept(String corpId);
+}

+ 91 - 0
fs-qw-api/src/main/java/com/fs/app/service/impl/OpenQwApiServiceImpl.java

@@ -0,0 +1,91 @@
+package com.fs.app.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.app.service.OpenQwApiService;
+import com.fs.common.core.domain.R;
+import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwDept;
+import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwDeptMapper;
+import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.IQwCompanyService;
+import com.fs.qwApi.Result.QwOpenidResult;
+import com.fs.qwApi.domain.QwDeptResult;
+import com.fs.qwApi.domain.QwUserIdResult;
+import com.fs.qwApi.domain.inner.Department;
+import com.fs.qwApi.domain.inner.DeptUser;
+import com.fs.qwApi.param.QwOpenidByUserParams;
+import com.fs.qwApi.service.QwApiService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Slf4j
+@Service
+public class OpenQwApiServiceImpl implements OpenQwApiService {
+    @Autowired
+    private QwApiService qwApiService;
+
+    @Autowired
+    private IQwCompanyService iQwCompanyService;
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private QwDeptMapper qwDeptMapper;
+
+    @Override
+    public R getSyncQwUser(String corpId) {
+        QwUserIdResult userList = qwApiService.getUserList(corpId);
+        List<DeptUser> deptUser = userList.getDept_user();
+        log.info("返回数据:{}", JSON.toJSONString(userList));
+        log.info("同步用户数量:{}", deptUser.size());
+        QwCompany qwCompany = iQwCompanyService.selectQwCompanyByCorpId(corpId);
+        for (DeptUser user : deptUser) {
+            QwUser qw = qwUserMapper.selectQwUserByCorpIdAndUserId(corpId, user.getUserid());
+            String serverQwUserName = qwApiService.getServerQwUserName(corpId, qwCompany.getOpenSecret(), user.getUserid(), qwCompany.getPermanentCode());
+            log.info("同步用户名称:{}", serverQwUserName);
+            QwUser qwUser = new QwUser();
+            qwUser.setQwUserId(user.getUserid());
+            qwUser.setDepartment(user.getDepartment().toString());
+            qwUser.setQwUserName(serverQwUserName);
+            qwUser.setCorpId(corpId);
+            QwOpenidByUserParams param = new QwOpenidByUserParams();
+            param.setUserid(user.getUserid());
+            QwOpenidResult qwOpenidResult = qwApiService.useridToOpenid(param, corpId);
+            qwUser.setOpenid(qwOpenidResult.getOpenid());
+            if (qw == null) {
+                qwUserMapper.insertQwUser(qwUser);
+            } else {
+                qwUser.setId(qw.getId());
+                qwUser.setIsDel(0);
+                qwUserMapper.updateQwUser(qwUser);
+            }
+        }
+        return R.ok();
+    }
+
+    @Override
+    public R getSyncQwDept(String corpId) {
+        QwDeptResult departmentList = qwApiService.getDepartmentList(corpId);
+
+        if (departmentList.getErrcode() == 0) {
+            List<Department> department = departmentList.getDepartment();
+            for (Department dept : department) {
+                QwDept qwDept = new QwDept();
+                qwDept.setDeptId(Long.valueOf(dept.getId()));
+                qwDept.setDeptName(dept.getName());
+                qwDept.setCorpId(corpId);
+                qwDept.setParentid(Long.valueOf(dept.getParentid()));
+                int i = qwDeptMapper.insertOrUpdateQwDept(qwDept);
+            }
+        }else {
+            return R.error("同步失败:"+departmentList.getErrmsg());
+        }
+
+        return null;
+    }
+}

+ 141 - 0
fs-qw-api/src/main/java/com/fs/framework/datasource/TenantDataSourceManager.java

@@ -0,0 +1,141 @@
+package com.fs.framework.datasource;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.fs.common.enums.DataSourceType;
+import com.fs.tenant.domain.TenantInfo;
+import com.fs.tenant.service.TenantInfoService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import javax.sql.DataSource;
+import java.lang.reflect.Field;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class TenantDataSourceManager {
+
+    private static final Logger log = LoggerFactory.getLogger(TenantDataSourceManager.class);
+
+    @Resource
+    private DynamicDataSource dynamicDataSource;
+
+    @Resource
+    private TenantInfoService tenantInfoService;
+
+    /**
+     * 租户数据源缓存
+     */
+    private static final Map<String, DataSource> TENANT_DS_CACHE = new ConcurrentHashMap<>();
+
+    /**
+     * 切换到租户数据源(不存在则创建)
+     */
+    public void switchTenant(TenantInfo tenantInfo) {
+
+        // 用租户主键作为唯一标识
+        String tenantKey = buildTenantKey(tenantInfo.getId());
+
+        if (!TENANT_DS_CACHE.containsKey(tenantKey)) {
+            synchronized (this) {
+                if (!TENANT_DS_CACHE.containsKey(tenantKey)) {
+
+                    DataSource tenantDs = createTenantDataSource(tenantInfo);
+                    TENANT_DS_CACHE.put(tenantKey, tenantDs);
+
+                    // 动态追加到已解析的数据源
+                    Map<Object, DataSource> resolvedMap = getResolvedDataSources();
+                    resolvedMap.put(tenantKey, tenantDs);
+                }
+            }
+        }
+
+        // ThreadLocal 切库
+        DynamicDataSourceContextHolder.setDataSourceType(tenantKey);
+    }
+
+    private String buildTenantKey(Long tenantId) {
+        return "tenant:" + tenantId;
+    }
+
+    /**
+     * 清理 ThreadLocal
+     */
+    public void clear() {
+        DynamicDataSourceContextHolder.clearDataSourceType();
+    }
+
+    /**
+     * 根据租户ID确保数据源已注册并切换(用于 Filter/拦截器等非登录场景)
+     * 解决 JVM 重启后 TENANT_DS_CACHE 被清空,导致 resolvedDataSources 中找不到租户数据源的问题
+     *
+     * @param tenantId 租户ID
+     */
+    public void ensureSwitchByTenantId(Long tenantId) {
+        String tenantKey = buildTenantKey(tenantId);
+
+        if (TENANT_DS_CACHE.containsKey(tenantKey)) {
+            DynamicDataSourceContextHolder.setDataSourceType(tenantKey);
+            log.debug("[TenantDS] 数据源已缓存,直接切换: {}", tenantKey);
+            return;
+        }
+
+        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        try {
+            TenantInfo tenantInfo = tenantInfoService.getById(tenantId);
+            if (tenantInfo == null) {
+                log.warn("[TenantDS] 租户ID={} 在主库中不存在,回退到主库", tenantId);
+                DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+                return;
+            }
+            if (!tenantInfo.getStatus().equals(1)) {
+                log.warn("[TenantDS] 租户ID={} 已禁用,回退到主库", tenantId);
+                DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+                return;
+            }
+            switchTenant(tenantInfo);
+            log.info("[TenantDS] 动态注册并切换数据源: key={}, url={}", tenantKey, tenantInfo.getDbUrl());
+        } catch (Exception e) {
+            log.error("[TenantDS] 动态注册租户数据源失败, tenantId={}, 回退到主库", tenantId, e);
+            DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
+        }
+    }
+
+    /**
+     * 创建租户数据源(MySQL + Druid)
+     */
+    private DataSource createTenantDataSource(TenantInfo tenant) {
+
+        DruidDataSource ds = new DruidDataSource();
+        ds.setUrl(tenant.getDbUrl());
+        ds.setUsername(tenant.getDbAccount());
+        ds.setPassword(tenant.getDbPwd());
+
+        // 统一 MySQL
+        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
+
+        ds.setInitialSize(5);
+        ds.setMinIdle(10);
+        ds.setMaxActive(20);
+        ds.setMaxWait(60000);
+
+        return ds;
+    }
+
+    /**
+     * 反射获取 AbstractRoutingDataSource.resolvedDataSources
+     */
+    @SuppressWarnings("unchecked")
+    private Map<Object, DataSource> getResolvedDataSources() {
+        try {
+            Field field = org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
+                    .class.getDeclaredField("resolvedDataSources");
+            field.setAccessible(true);
+            return (Map<Object, DataSource>) field.get(dynamicDataSource);
+        } catch (Exception e) {
+            throw new IllegalStateException("获取 resolvedDataSources 失败", e);
+        }
+    }
+}

+ 5 - 0
fs-service/src/main/java/com/fs/qwApi/config/OpenQwConfig.java

@@ -0,0 +1,5 @@
+package com.fs.qwApi.config;
+
+public interface OpenQwConfig {
+    String baseApi ="http://127.0.0.1:8007/open/qwapi";
+}