企微同步,需要有租户ID
This commit is contained in:
@@ -3,13 +3,31 @@ package com.rj.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rj.common.DictItemConstants;
|
||||
import com.rj.common.PasswordUtil;
|
||||
import com.rj.dto.QiWeiConfig;
|
||||
import com.rj.entity.DictItem;
|
||||
import com.rj.entity.QweiDepartment;
|
||||
import com.rj.mapper.DictItemMapper;
|
||||
import com.rj.mapper.QweiDepartmentMapper;
|
||||
import com.rj.service.IQweiDepartmentService;
|
||||
import com.rj.tenant.TenantContextHolder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -17,10 +35,17 @@ import java.util.UUID;
|
||||
public class QweiDepartmentServiceImpl extends ServiceImpl<QweiDepartmentMapper, QweiDepartment>
|
||||
implements IQweiDepartmentService {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
|
||||
|
||||
@Autowired
|
||||
private DictItemMapper dictItemMapper;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> add(QweiDepartment entity) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
String tenantId = resolveTenantId(entity.getTenantId());
|
||||
if (entity.getDeptId() == null) {
|
||||
result.put("success", false);
|
||||
result.put("message", "deptId不能为空");
|
||||
@@ -40,6 +65,7 @@ public class QweiDepartmentServiceImpl extends ServiceImpl<QweiDepartmentMapper,
|
||||
}
|
||||
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setTenantId(tenantId);
|
||||
if (entity.getIsDeleted() == null) {
|
||||
entity.setIsDeleted(0);
|
||||
}
|
||||
@@ -138,5 +164,224 @@ public class QweiDepartmentServiceImpl extends ServiceImpl<QweiDepartmentMapper,
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> syncFromQiWei() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
QiWeiConfig cfg = loadQiWeiConfig();
|
||||
String tenantId = resolveTenantId(null);
|
||||
// 确保后续对 qwei_department 的查询/写入都带 tenant_id 条件
|
||||
TenantContextHolder.setTenantId(tenantId);
|
||||
String corpId = requireNonBlank(cfg.getCorpid(), "qiwei_config.corpid不能为空");
|
||||
String contactSecret = requireNonBlank(cfg.getContractSecret(), "qiwei_config.contractSecret不能为空");
|
||||
String appSecret = trimToNull(cfg.getAppSecret());
|
||||
|
||||
List<QweiDepartment> remoteDepartments;
|
||||
try {
|
||||
String token = getAccessToken(corpId, contactSecret);
|
||||
remoteDepartments = fetchDepartments(token);
|
||||
} catch (Exception e) {
|
||||
String msg = e.getMessage();
|
||||
boolean contactForbidden = msg != null && (msg.contains("e=48009")
|
||||
|| msg.contains("api forbidden for contact assistant"));
|
||||
if (!contactForbidden || appSecret == null) {
|
||||
throw e;
|
||||
}
|
||||
String appToken = getAccessToken(corpId, appSecret);
|
||||
remoteDepartments = fetchDepartments(appToken);
|
||||
}
|
||||
|
||||
int inserted = 0;
|
||||
int updated = 0;
|
||||
for (QweiDepartment dept : remoteDepartments) {
|
||||
dept.setTenantId(tenantId);
|
||||
LambdaQueryWrapper<QweiDepartment> q = new LambdaQueryWrapper<QweiDepartment>()
|
||||
.eq(QweiDepartment::getDeptId, dept.getDeptId());
|
||||
QweiDepartment exists = this.getOne(q, false);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (exists == null) {
|
||||
dept.setId(UUID.randomUUID().toString());
|
||||
dept.setIsDeleted(0);
|
||||
dept.setCreatedAt(now);
|
||||
dept.setUpdatedAt(now);
|
||||
try {
|
||||
this.save(dept);
|
||||
inserted++;
|
||||
} catch (Exception insertEx) {
|
||||
// 若数据库唯一索引仍是 dept_id 单列,且历史数据 tenant_id 为空,会导致不同租户插入时撞唯一键。
|
||||
// 这里做一次“修复脏数据”:找到 dept_id 对应的旧记录,如果 tenant_id 为空则补齐为当前租户并更新。
|
||||
if (!isDuplicateDeptId(insertEx)) {
|
||||
throw insertEx;
|
||||
}
|
||||
QweiDepartment legacy = findByDeptIdIgnoreTenant(dept.getDeptId());
|
||||
if (legacy == null) {
|
||||
throw insertEx;
|
||||
}
|
||||
String legacyTenant = trimToNull(legacy.getTenantId());
|
||||
if (legacyTenant == null) {
|
||||
this.baseMapper.updateLegacyTenantAndFieldsByIdIgnoreTenant(
|
||||
legacy.getId(),
|
||||
tenantId,
|
||||
dept.getDeptName(),
|
||||
dept.getParentDeptId(),
|
||||
dept.getOrderNum(),
|
||||
0,
|
||||
now
|
||||
);
|
||||
updated++;
|
||||
} else if (!tenantId.equals(legacyTenant)) {
|
||||
throw new IllegalStateException("部门dept_id=" + dept.getDeptId()
|
||||
+ " 已存在且属于其他租户(" + legacyTenant + ")。请把唯一索引改为(tenant_id, dept_id)。");
|
||||
} else {
|
||||
// 同租户但 exists 查不到(理论上不该发生),走更新
|
||||
exists = legacy;
|
||||
exists.setDeptName(dept.getDeptName());
|
||||
exists.setParentDeptId(dept.getParentDeptId());
|
||||
exists.setOrderNum(dept.getOrderNum());
|
||||
exists.setIsDeleted(0);
|
||||
exists.setUpdatedAt(now);
|
||||
this.updateById(exists);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
exists.setDeptName(dept.getDeptName());
|
||||
exists.setParentDeptId(dept.getParentDeptId());
|
||||
exists.setOrderNum(dept.getOrderNum());
|
||||
exists.setIsDeleted(0);
|
||||
exists.setUpdatedAt(now);
|
||||
this.updateById(exists);
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "同步成功");
|
||||
result.put("inserted", inserted);
|
||||
result.put("updated", updated);
|
||||
result.put("total", remoteDepartments.size());
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "同步异常:" + e.getMessage());
|
||||
return result;
|
||||
} finally {
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private QiWeiConfig loadQiWeiConfig() throws Exception {
|
||||
// 优先使用 JVM 参数,便于联调/脚本执行:
|
||||
// -Dqiwei.corpid=xxx -Dqiwei.contactSecret=xxx
|
||||
String corpid = trimToNull(System.getProperty("qiwei.corpid"));
|
||||
String contactSecret = trimToNull(System.getProperty("qiwei.contactSecret"));
|
||||
if (corpid != null && contactSecret != null) {
|
||||
QiWeiConfig cfg = new QiWeiConfig();
|
||||
cfg.setCorpid(corpid);
|
||||
cfg.setContractSecret(contactSecret);
|
||||
cfg.setAppSecret(trimToNull(System.getProperty("qiwei.appSecret")));
|
||||
return cfg;
|
||||
}
|
||||
|
||||
String tenantId = System.getProperty("tenantId", "TENANT_ID_CST_2026");
|
||||
TenantContextHolder.setTenantId(tenantId);
|
||||
try {
|
||||
LambdaQueryWrapper<DictItem> q = new LambdaQueryWrapper<DictItem>()
|
||||
.eq(DictItem::getName, DictItemConstants.QIWEI_CONFIG)
|
||||
.orderByDesc(DictItem::getUpdatedTime)
|
||||
.orderByDesc(DictItem::getCreatedTime)
|
||||
.last("limit 1");
|
||||
DictItem item = dictItemMapper.selectOne(q);
|
||||
if (item == null || item.getValue() == null || item.getValue().trim().isEmpty()) {
|
||||
throw new IllegalStateException("未找到 qiwei_config");
|
||||
}
|
||||
String json = PasswordUtil.decryptReversibleWithDefaultSalt(item.getValue());
|
||||
return OBJECT_MAPPER.readValue(json, QiWeiConfig.class);
|
||||
} finally {
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private String getAccessToken(String corpId, String secret) throws Exception {
|
||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid="
|
||||
+ URLEncoder.encode(corpId, StandardCharsets.UTF_8)
|
||||
+ "&corpsecret=" + URLEncoder.encode(secret, StandardCharsets.UTF_8);
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
|
||||
String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
|
||||
JsonNode json = OBJECT_MAPPER.readTree(body);
|
||||
int errCode = json.path("errcode").asInt(-1);
|
||||
if (errCode != 0) {
|
||||
throw new IllegalStateException("获取access_token失败: " + body);
|
||||
}
|
||||
return json.path("access_token").asText();
|
||||
}
|
||||
|
||||
private List<QweiDepartment> fetchDepartments(String token) throws Exception {
|
||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token="
|
||||
+ URLEncoder.encode(token, StandardCharsets.UTF_8);
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
|
||||
String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
|
||||
JsonNode json = OBJECT_MAPPER.readTree(body);
|
||||
int errCode = json.path("errcode").asInt(-1);
|
||||
if (errCode != 0) {
|
||||
throw new IllegalStateException("获取部门列表失败: " + body);
|
||||
}
|
||||
|
||||
List<QweiDepartment> list = new ArrayList<>();
|
||||
JsonNode departmentNode = json.path("department");
|
||||
if (departmentNode.isArray()) {
|
||||
for (JsonNode n : departmentNode) {
|
||||
QweiDepartment dept = new QweiDepartment();
|
||||
dept.setDeptId(n.path("id").asLong());
|
||||
dept.setDeptName(n.path("name").asText(null));
|
||||
dept.setParentDeptId(n.path("parentid").asLong());
|
||||
dept.setOrderNum(n.path("order").asInt(0));
|
||||
list.add(dept);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private String requireNonBlank(String value, String msg) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private boolean isDuplicateDeptId(Exception e) {
|
||||
String msg = e.getMessage();
|
||||
return msg != null && (msg.contains("Duplicate entry") && msg.contains("uk_qwei_department_dept_id"));
|
||||
}
|
||||
|
||||
private QweiDepartment findByDeptIdIgnoreTenant(Long deptId) {
|
||||
return this.baseMapper.selectByDeptIdIgnoreTenant(deptId);
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String t = value.trim();
|
||||
return t.isEmpty() ? null : t;
|
||||
}
|
||||
|
||||
private String resolveTenantId(String tenantIdFromEntity) {
|
||||
String current = trimToNull(TenantContextHolder.getTenantId());
|
||||
if (current != null) {
|
||||
return current;
|
||||
}
|
||||
String t = trimToNull(tenantIdFromEntity);
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
String fromProp = trimToNull(System.getProperty("tenantId"));
|
||||
if (fromProp != null) {
|
||||
return fromProp;
|
||||
}
|
||||
throw new IllegalStateException("tenantId不能为空(请设置租户上下文或传入 tenantId 或 -DtenantId)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,27 @@ package com.rj.service.impl;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rj.common.DictItemConstants;
|
||||
import com.rj.common.PasswordUtil;
|
||||
import com.rj.dto.QiWeiConfig;
|
||||
import com.rj.entity.DictItem;
|
||||
import com.rj.entity.QweiUser;
|
||||
import com.rj.mapper.DictItemMapper;
|
||||
import com.rj.mapper.QweiUserMapper;
|
||||
import com.rj.service.IQweiUserService;
|
||||
import com.rj.tenant.TenantContextHolder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -16,10 +32,17 @@ import java.util.UUID;
|
||||
@Service
|
||||
public class QweiUserServiceImpl extends ServiceImpl<QweiUserMapper, QweiUser> implements IQweiUserService {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
|
||||
|
||||
@Autowired
|
||||
private DictItemMapper dictItemMapper;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> add(QweiUser entity) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
String tenantId = resolveTenantId(entity.getTenantId());
|
||||
if (entity.getUserid() == null || entity.getUserid().trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "userid不能为空");
|
||||
@@ -34,6 +57,7 @@ public class QweiUserServiceImpl extends ServiceImpl<QweiUserMapper, QweiUser> i
|
||||
}
|
||||
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
entity.setTenantId(tenantId);
|
||||
entity.setUserid(entity.getUserid().trim());
|
||||
if (entity.getIsDeleted() == null) {
|
||||
entity.setIsDeleted(0);
|
||||
@@ -64,6 +88,8 @@ public class QweiUserServiceImpl extends ServiceImpl<QweiUserMapper, QweiUser> i
|
||||
result.put("message", "id不能为空");
|
||||
return result;
|
||||
}
|
||||
// 更新时不允许把 tenantId 改掉;若传了就忽略
|
||||
entity.setTenantId(null);
|
||||
if (entity.getUserid() != null) {
|
||||
entity.setUserid(entity.getUserid().trim());
|
||||
}
|
||||
@@ -144,5 +170,290 @@ public class QweiUserServiceImpl extends ServiceImpl<QweiUserMapper, QweiUser> i
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> syncFromQiWei() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
String tenantId = null;
|
||||
try {
|
||||
tenantId = resolveTenantId(null);
|
||||
TenantContextHolder.setTenantId(tenantId);
|
||||
|
||||
QiWeiConfig cfg = loadQiWeiConfig();
|
||||
String corpId = requireNonBlank(cfg.getCorpid(), "qiwei_config.corpid不能为空");
|
||||
String contactSecret = requireNonBlank(cfg.getContractSecret(), "qiwei_config.contractSecret不能为空");
|
||||
String appSecret = trimToNull(cfg.getAppSecret());
|
||||
if (appSecret == null) {
|
||||
throw new IllegalStateException("qiwei_config.appSecret不能为空(user/get 需要 appSecret)");
|
||||
}
|
||||
|
||||
String contactToken = getAccessToken(corpId, contactSecret);
|
||||
String appToken = getAccessToken(corpId, appSecret);
|
||||
|
||||
int inserted = 0;
|
||||
int updated = 0;
|
||||
|
||||
String cursor = "";
|
||||
int limit = 100;
|
||||
while (true) {
|
||||
JsonNode listId = fetchUserListId(contactToken, cursor, limit);
|
||||
JsonNode deptUser = listId.path("dept_user");
|
||||
if (deptUser.isArray()) {
|
||||
for (JsonNode du : deptUser) {
|
||||
String userid = du.path("userid").asText(null);
|
||||
if (userid == null || userid.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
QweiUser mapped = fetchAndMapUser(appToken, userid.trim(), tenantId);
|
||||
if (upsertUser(mapped, tenantId)) {
|
||||
inserted++;
|
||||
} else {
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonNode nextCursor = listId.get("next_cursor");
|
||||
if (nextCursor == null || nextCursor.isNull()) {
|
||||
break;
|
||||
}
|
||||
String nc = nextCursor.asText("");
|
||||
if (nc.isBlank()) {
|
||||
break;
|
||||
}
|
||||
cursor = nc;
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "同步成功");
|
||||
result.put("inserted", inserted);
|
||||
result.put("updated", updated);
|
||||
result.put("tenantId", tenantId);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
result.put("success", false);
|
||||
result.put("message", "同步异常:" + e.getMessage());
|
||||
if (tenantId != null) {
|
||||
result.put("tenantId", tenantId);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean upsertUser(QweiUser user, String tenantId) {
|
||||
LambdaQueryWrapper<QweiUser> q = new LambdaQueryWrapper<QweiUser>()
|
||||
.eq(QweiUser::getUserid, user.getUserid());
|
||||
QweiUser exists = this.getOne(q, false);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (exists == null) {
|
||||
user.setId(UUID.randomUUID().toString());
|
||||
user.setTenantId(tenantId);
|
||||
user.setIsDeleted(0);
|
||||
user.setCreatedAt(now);
|
||||
user.setUpdatedAt(now);
|
||||
try {
|
||||
this.save(user);
|
||||
return true;
|
||||
} catch (Exception insertEx) {
|
||||
// 若唯一索引仍是 userid 单列,且历史数据 tenant_id 为空,会导致撞唯一键
|
||||
if (!isDuplicateUserid(insertEx)) {
|
||||
throw insertEx;
|
||||
}
|
||||
QweiUser legacy = this.baseMapper.selectByUseridIgnoreTenant(user.getUserid());
|
||||
if (legacy == null) {
|
||||
throw insertEx;
|
||||
}
|
||||
String legacyTenant = trimToNull(legacy.getTenantId());
|
||||
if (legacyTenant == null) {
|
||||
this.baseMapper.updateLegacyTenantAndFieldsByIdIgnoreTenant(
|
||||
legacy.getId(),
|
||||
tenantId,
|
||||
user.getUserName(),
|
||||
user.getMobile(),
|
||||
user.getEmail(),
|
||||
user.getBizEmail(),
|
||||
user.getTelephone(),
|
||||
user.getPosition(),
|
||||
user.getGender(),
|
||||
user.getStatus(),
|
||||
user.getMainDepartmentId(),
|
||||
user.getDepartmentIdsJson(),
|
||||
user.getAliasName(),
|
||||
user.getAvatar(),
|
||||
user.getThumbAvatar(),
|
||||
user.getAddress(),
|
||||
0,
|
||||
now
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!tenantId.equals(legacyTenant)) {
|
||||
throw new IllegalStateException("用户userid=" + user.getUserid()
|
||||
+ " 已存在且属于其他租户(" + legacyTenant + ")。请把唯一索引改为(tenant_id, userid)。");
|
||||
}
|
||||
// 同租户兜底更新
|
||||
exists = legacy;
|
||||
}
|
||||
}
|
||||
|
||||
exists.setTenantId(null); // 不允许改租户
|
||||
exists.setUserName(user.getUserName());
|
||||
exists.setMobile(user.getMobile());
|
||||
exists.setEmail(user.getEmail());
|
||||
exists.setBizEmail(user.getBizEmail());
|
||||
exists.setTelephone(user.getTelephone());
|
||||
exists.setPosition(user.getPosition());
|
||||
exists.setGender(user.getGender());
|
||||
exists.setStatus(user.getStatus());
|
||||
exists.setMainDepartmentId(user.getMainDepartmentId());
|
||||
exists.setDepartmentIdsJson(user.getDepartmentIdsJson());
|
||||
exists.setAliasName(user.getAliasName());
|
||||
exists.setAvatar(user.getAvatar());
|
||||
exists.setThumbAvatar(user.getThumbAvatar());
|
||||
exists.setAddress(user.getAddress());
|
||||
exists.setIsDeleted(0);
|
||||
exists.setUpdatedAt(now);
|
||||
this.updateById(exists);
|
||||
return false;
|
||||
}
|
||||
|
||||
private QweiUser fetchAndMapUser(String appToken, String userid, String tenantId) throws Exception {
|
||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token="
|
||||
+ URLEncoder.encode(appToken, StandardCharsets.UTF_8)
|
||||
+ "&userid=" + URLEncoder.encode(userid, StandardCharsets.UTF_8);
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
|
||||
String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
|
||||
JsonNode json = OBJECT_MAPPER.readTree(body);
|
||||
int errCode = json.path("errcode").asInt(-1);
|
||||
if (errCode != 0) {
|
||||
throw new IllegalStateException("获取用户信息失败: " + body);
|
||||
}
|
||||
|
||||
QweiUser user = new QweiUser();
|
||||
user.setTenantId(tenantId);
|
||||
user.setUserid(userid);
|
||||
user.setUserName(json.path("name").asText(null));
|
||||
user.setMobile(json.path("mobile").asText(null));
|
||||
user.setEmail(json.path("email").asText(null));
|
||||
user.setBizEmail(json.path("biz_email").asText(null));
|
||||
user.setTelephone(json.path("telephone").asText(null));
|
||||
user.setPosition(json.path("position").asText(null));
|
||||
user.setGender(json.hasNonNull("gender") ? json.path("gender").asInt() : null);
|
||||
user.setStatus(json.hasNonNull("status") ? json.path("status").asInt() : null);
|
||||
user.setAvatar(json.path("avatar").asText(null));
|
||||
user.setThumbAvatar(json.path("thumb_avatar").asText(null));
|
||||
user.setAliasName(json.path("alias").asText(null));
|
||||
user.setAddress(json.path("address").asText(null));
|
||||
user.setMainDepartmentId(json.hasNonNull("main_department") ? json.path("main_department").asLong() : null);
|
||||
JsonNode dep = json.path("department");
|
||||
if (dep.isArray()) {
|
||||
user.setDepartmentIdsJson(OBJECT_MAPPER.writeValueAsString(dep));
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private JsonNode fetchUserListId(String contactToken, String cursor, int limit) throws Exception {
|
||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token="
|
||||
+ URLEncoder.encode(contactToken, StandardCharsets.UTF_8);
|
||||
String payload = OBJECT_MAPPER.writeValueAsString(Map.of(
|
||||
"cursor", cursor == null ? "" : cursor,
|
||||
"limit", limit
|
||||
));
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(payload))
|
||||
.build();
|
||||
String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
|
||||
JsonNode json = OBJECT_MAPPER.readTree(body);
|
||||
int errCode = json.path("errcode").asInt(-1);
|
||||
if (errCode != 0) {
|
||||
throw new IllegalStateException("获取成员ID列表失败: " + body);
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
private QiWeiConfig loadQiWeiConfig() throws Exception {
|
||||
String corpid = trimToNull(System.getProperty("qiwei.corpid"));
|
||||
String contactSecret = trimToNull(System.getProperty("qiwei.contactSecret"));
|
||||
String appSecret = trimToNull(System.getProperty("qiwei.appSecret"));
|
||||
if (corpid != null && contactSecret != null && appSecret != null) {
|
||||
QiWeiConfig cfg = new QiWeiConfig();
|
||||
cfg.setCorpid(corpid);
|
||||
cfg.setContractSecret(contactSecret);
|
||||
cfg.setAppSecret(appSecret);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
String tenantId = System.getProperty("tenantId", "TENANT_ID_CST_2026");
|
||||
TenantContextHolder.setTenantId(tenantId);
|
||||
try {
|
||||
LambdaQueryWrapper<DictItem> q = new LambdaQueryWrapper<DictItem>()
|
||||
.eq(DictItem::getName, DictItemConstants.QIWEI_CONFIG)
|
||||
.orderByDesc(DictItem::getUpdatedTime)
|
||||
.orderByDesc(DictItem::getCreatedTime)
|
||||
.last("limit 1");
|
||||
DictItem item = dictItemMapper.selectOne(q);
|
||||
if (item == null || item.getValue() == null || item.getValue().trim().isEmpty()) {
|
||||
throw new IllegalStateException("未找到 qiwei_config");
|
||||
}
|
||||
String json = PasswordUtil.decryptReversibleWithDefaultSalt(item.getValue());
|
||||
return OBJECT_MAPPER.readValue(json, QiWeiConfig.class);
|
||||
} finally {
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private String getAccessToken(String corpId, String secret) throws Exception {
|
||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid="
|
||||
+ URLEncoder.encode(corpId, StandardCharsets.UTF_8)
|
||||
+ "&corpsecret=" + URLEncoder.encode(secret, StandardCharsets.UTF_8);
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
|
||||
String body = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString()).body();
|
||||
JsonNode json = OBJECT_MAPPER.readTree(body);
|
||||
int errCode = json.path("errcode").asInt(-1);
|
||||
if (errCode != 0) {
|
||||
throw new IllegalStateException("获取access_token失败: " + body);
|
||||
}
|
||||
return json.path("access_token").asText();
|
||||
}
|
||||
|
||||
private String requireNonBlank(String value, String msg) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private boolean isDuplicateUserid(Exception e) {
|
||||
String msg = e.getMessage();
|
||||
return msg != null && msg.contains("Duplicate entry");
|
||||
}
|
||||
|
||||
private String trimToNull(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String t = value.trim();
|
||||
return t.isEmpty() ? null : t;
|
||||
}
|
||||
|
||||
private String resolveTenantId(String tenantIdFromEntity) {
|
||||
String current = trimToNull(TenantContextHolder.getTenantId());
|
||||
if (current != null) {
|
||||
return current;
|
||||
}
|
||||
String t = trimToNull(tenantIdFromEntity);
|
||||
if (t != null) {
|
||||
return t;
|
||||
}
|
||||
String fromProp = trimToNull(System.getProperty("tenantId"));
|
||||
if (fromProp != null) {
|
||||
return fromProp;
|
||||
}
|
||||
throw new IllegalStateException("tenantId不能为空(请设置租户上下文或传入 tenantId 或 -DtenantId)");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user