Files
smartDriveEE/src/main/java/com/rj/service/impl/QweiUserServiceImpl.java
2026-04-25 14:13:27 +08:00

795 lines
34 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 cn.felord.DefaultAgent;
import cn.felord.WeComTokenCacheable;
import cn.felord.api.ExternalContactUserApi;
import cn.felord.api.WorkWeChatApi;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.rj.common.DictItemConstants;
import com.rj.common.PasswordUtil;
import com.rj.common.QiWeiApiConstants;
import com.rj.dto.QiWeiConfig;
import com.rj.entity.DictItem;
import com.rj.entity.CustomerManagement;
import com.rj.entity.QweiUser;
import com.rj.mapper.DictItemMapper;
import com.rj.mapper.QweiUserMapper;
import com.rj.service.ICustomerManagementService;
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 okhttp3.logging.HttpLoggingInterceptor;
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;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@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;
@Autowired
private ICustomerManagementService customerManagementService;
@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不能为空");
return result;
}
LambdaQueryWrapper<QweiUser> dup = new LambdaQueryWrapper<QweiUser>()
.eq(QweiUser::getUserid, entity.getUserid().trim());
if (this.count(dup) > 0) {
result.put("success", false);
result.put("message", "userid已存在");
return result;
}
entity.setId(UUID.randomUUID().toString());
entity.setTenantId(tenantId);
entity.setUserid(entity.getUserid().trim());
if (entity.getIsDeleted() == null) {
entity.setIsDeleted(0);
}
LocalDateTime now = LocalDateTime.now();
entity.setCreatedAt(now);
entity.setUpdatedAt(now);
boolean ok = this.save(entity);
result.put("success", ok);
result.put("message", ok ? "添加成功" : "添加失败");
if (ok) {
result.put("data", entity);
}
return result;
} catch (Exception e) {
result.put("success", false);
result.put("message", "添加异常:" + e.getMessage());
return result;
}
}
@Override
public Map<String, Object> update(QweiUser entity) {
Map<String, Object> result = new HashMap<>();
try {
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
result.put("success", false);
result.put("message", "id不能为空");
return result;
}
// 更新时不允许把 tenantId 改掉;若传了就忽略
entity.setTenantId(null);
if (entity.getUserid() != null) {
entity.setUserid(entity.getUserid().trim());
}
entity.setUpdatedAt(LocalDateTime.now());
boolean ok = this.updateById(entity);
result.put("success", ok);
result.put("message", ok ? "更新成功" : "更新失败");
if (ok) {
result.put("data", this.getById(entity.getId()));
}
return result;
} catch (Exception e) {
result.put("success", false);
result.put("message", "更新异常:" + e.getMessage());
return result;
}
}
@Override
public Map<String, Object> deleteById(String id) {
Map<String, Object> result = new HashMap<>();
try {
boolean ok = this.removeById(id);
result.put("success", ok);
result.put("message", ok ? "删除成功" : "删除失败");
return result;
} catch (Exception e) {
result.put("success", false);
result.put("message", "删除异常:" + e.getMessage());
return result;
}
}
@Override
public Map<String, Object> pageQuery(Integer current,
Integer size,
String userid,
String userName,
Long mainDepartmentId,
Integer status) {
Map<String, Object> result = new HashMap<>();
try {
if (current == null || current < 1) {
current = 1;
}
if (size == null || size < 1) {
size = 10;
}
Page<QweiUser> page = new Page<>(current, size);
LambdaQueryWrapper<QweiUser> q = new LambdaQueryWrapper<>();
if (userid != null && !userid.trim().isEmpty()) {
q.eq(QweiUser::getUserid, userid.trim());
}
if (userName != null && !userName.trim().isEmpty()) {
q.like(QweiUser::getUserName, userName.trim());
}
if (mainDepartmentId != null) {
q.eq(QweiUser::getMainDepartmentId, mainDepartmentId);
}
if (status != null) {
q.eq(QweiUser::getStatus, status);
}
q.orderByDesc(QweiUser::getUpdatedAt);
Page<QweiUser> data = this.page(page, q);
result.put("success", true);
result.put("message", "查询成功");
result.put("data", data.getRecords());
result.put("total", data.getTotal());
result.put("current", data.getCurrent());
result.put("size", data.getSize());
result.put("pages", data.getPages());
return result;
} catch (Exception e) {
result.put("success", false);
result.put("message", "查询异常:" + e.getMessage());
return result;
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> syncFromQiWei(String tenantIdParam) {
Map<String, Object> result = new HashMap<>();
String tenantId = null;
try {
tenantId = resolveTenantId(tenantIdParam);
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();
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> syncCustomerFromQiWei(String tenantIdParam) {
Map<String, Object> result = new HashMap<>();
String tenantId = null;
try {
tenantId = resolveTenantId(tenantIdParam);
TenantContextHolder.setTenantId(tenantId);
QiWeiConfig cfg = loadQiWeiConfig();
String corpId = requireNonBlank(cfg.getCorpid(), "qiwei_config.corpid不能为空");
String externalContactSecret = resolveExternalContactSecret(cfg);
String contactToken = getAccessToken(corpId, externalContactSecret);
ExternalContactUserApi externalContactUserApi = createExternalContactUserApi(corpId, externalContactSecret);
int inserted = 0;
int updated = 0;
JsonNode followUsers = fetchCustomerContactFollowUsers(contactToken);
printFollowUsersToConsole(followUsers);
if (followUsers.isArray()) {
for (JsonNode followUser : followUsers) {
try {
String followUserId = trimToNull(followUser.asText(null));
if (followUserId == null) {
continue;
}
SyncCounter counter = syncCustomerByFollowUser(externalContactUserApi, contactToken, followUserId, tenantId);
inserted += counter.inserted;
updated += counter.updated;
}catch (Exception e) {
log.error("syncCustomerFromQiWei: {}", e);
e.printStackTrace();
}
}
}
result.put("success", true);
result.put("message", "同步企微客户成功");
result.put("inserted", inserted);
result.put("updated", updated);
result.put("total", inserted + 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 = QiWeiApiConstants.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 SyncCounter syncCustomerByFollowUser(ExternalContactUserApi externalContactUserApi,
String contactToken,
String followUserId,
String tenantId) throws Exception {
SyncCounter counter = new SyncCounter();
JsonNode listJson = fetchExternalContactList(externalContactUserApi, followUserId);
JsonNode externalUserIds = listJson.path("external_userid");
if (!externalUserIds.isArray()) {
return counter;
}
for (JsonNode externalUserNode : externalUserIds) {
String externalUserId = trimToNull(externalUserNode.asText(null));
if (externalUserId == null) {
continue;
}
JsonNode externalDetail = null;
try {
externalDetail = fetchExternalContactDetail(contactToken, externalUserId);
} catch (IllegalStateException ex) {
if (isNoExternalContactRelationError(ex.getMessage())) {
// 官方建议:关系不存在(84061)时应跳过该客户,继续处理后续数据
System.out.println("[QiWeiCustomerSync] skip external user due to no relation. externalUserId="
+ externalUserId + ", followUserId=" + followUserId + ", reason=" + ex.getMessage());
continue;
}
}
printExternalCustomerToConsole(externalDetail, followUserId);
CustomerManagement customer = mapToCustomerManagement(externalDetail, followUserId, tenantId);
if (customer == null) {
continue;
}
if (upsertCustomer(customer, tenantId)) {
counter.inserted++;
} else {
counter.updated++;
}
}
return counter;
}
private JsonNode fetchExternalContactList(ExternalContactUserApi externalContactUserApi, String followUserId) throws Exception {
Object sdkResp = externalContactUserApi.listByUserId(followUserId);
JsonNode json = OBJECT_MAPPER.valueToTree(sdkResp);
int errCode = json.path("errcode").asInt(0);
if (errCode != 0) {
throw new IllegalStateException("获取外部联系人列表失败: " + json);
}
if (!json.has("external_userid") && json.has("data")) {
ObjectNode adapted = OBJECT_MAPPER.createObjectNode();
adapted.set("external_userid", json.path("data"));
return adapted;
}
return json;
}
private void printExternalCustomerToConsole(JsonNode externalDetail, String followUserId) {
JsonNode externalContact = externalDetail.path("external_contact");
String externalUserId = trimToNull(externalContact.path("external_userid").asText(null));
String customerName = trimToNull(externalContact.path("name").asText(null));
String phone = null;
JsonNode followInfo = externalDetail.path("follow_user");
if (followInfo.isArray() && followInfo.size() > 0) {
JsonNode firstFollow = followInfo.get(0);
JsonNode remarkMobiles = firstFollow.path("remark_mobiles");
if (remarkMobiles.isArray() && remarkMobiles.size() > 0) {
phone = trimToNull(remarkMobiles.get(0).asText(null));
}
}
System.out.println("[QiWeiCustomerSync] externalUserId=" + (externalUserId == null ? "" : externalUserId)
+ ", customerName=" + (customerName == null ? "" : customerName)
+ ", phone=" + (phone == null ? "" : phone)
+ ", followUserId=" + (followUserId == null ? "" : followUserId));
}
private JsonNode fetchExternalContactDetail(String contactToken, String externalUserId) throws Exception {
String url = QiWeiApiConstants.EXTERNAL_CONTACT_GET + "?access_token="
+ URLEncoder.encode(contactToken, StandardCharsets.UTF_8)
+ "&external_userid=" + URLEncoder.encode(externalUserId, 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);
}
return json;
}
private CustomerManagement mapToCustomerManagement(JsonNode externalDetail, String followUserId, String tenantId) {
JsonNode externalContact = externalDetail.path("external_contact");
if (externalContact.isMissingNode() || externalContact.isNull()) {
return null;
}
String name = trimToNull(externalContact.path("name").asText(null));
if (name == null) {
return null;
}
String phone = null;
JsonNode followInfo = externalDetail.path("follow_user");
if (followInfo.isArray() && followInfo.size() > 0) {
JsonNode firstFollow = followInfo.get(0);
JsonNode remarkMobiles = firstFollow.path("remark_mobiles");
if (remarkMobiles.isArray() && remarkMobiles.size() > 0) {
phone = trimToNull(remarkMobiles.get(0).asText(null));
}
}
CustomerManagement customer = new CustomerManagement();
customer.setTenantId(tenantId);
customer.setCustomerName(name);
customer.setContact(phone);
customer.setCustomerSource("QIWEI");
customer.setSalesId(followUserId);
customer.setSalesPhone(followUserId);
LambdaQueryWrapper<QweiUser> salesQuery = new LambdaQueryWrapper<QweiUser>()
.eq(QweiUser::getUserid, followUserId);
QweiUser sales = this.getOne(salesQuery, false);
if (sales != null) {
customer.setSalesName(sales.getUserName());
customer.setSalesPhone(trimToNull(sales.getMobile()) == null ? followUserId : sales.getMobile());
customer.setDealershipId(sales.getMainDepartmentId() == null ? null : String.valueOf(sales.getMainDepartmentId()));
}
return customer;
}
private boolean upsertCustomer(CustomerManagement customer, String tenantId) {
LambdaQueryWrapper<CustomerManagement> queryWrapper = new LambdaQueryWrapper<CustomerManagement>()
.eq(CustomerManagement::getCustomerName, customer.getCustomerName());
if (trimToNull(customer.getContact()) != null) {
queryWrapper.eq(CustomerManagement::getContact, customer.getContact());
}
CustomerManagement existing = customerManagementService.getOne(queryWrapper, false);
LocalDateTime now = LocalDateTime.now();
if (existing == null) {
customer.setId(UUID.randomUUID().toString());
customer.setTenantId(tenantId);
customer.setRecordingCount(0);
customer.setContactCount(0);
customer.setCreateTime(now);
customer.setUpdateTime(now);
customerManagementService.save(customer);
return true;
}
existing.setTenantId(null);
existing.setCustomerName(customer.getCustomerName());
if (trimToNull(customer.getContact()) != null) {
existing.setContact(customer.getContact());
}
existing.setSalesId(customer.getSalesId());
existing.setSalesName(customer.getSalesName());
existing.setSalesPhone(customer.getSalesPhone());
existing.setDealershipId(customer.getDealershipId());
existing.setCustomerSource("QIWEI");
existing.setUpdateTime(now);
customerManagementService.updateById(existing);
return false;
}
private JsonNode fetchUserListId(String contactToken, String cursor, int limit) throws Exception {
String url = QiWeiApiConstants.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 JsonNode fetchCustomerContactFollowUsers(String accessToken) throws Exception {
String url = QiWeiApiConstants.EXTERNAL_CONTACT_FOLLOW_USER_LIST + "?access_token="
+ URLEncoder.encode(accessToken, 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);
}
return json.path("follow_user");
}
private void printFollowUsersToConsole(JsonNode followUsers) {
if (followUsers == null || !followUsers.isArray()) {
System.out.println("[QiWeiCustomerSync] follow_user list is empty or invalid.");
return;
}
System.out.println("[QiWeiCustomerSync] follow_user count=" + followUsers.size());
for (JsonNode followUser : followUsers) {
String followUserId = trimToNull(followUser.asText(null));
if (followUserId == null) {
continue;
}
System.out.println("[QiWeiCustomerSync] followUserId=" + followUserId);
}
}
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 = QiWeiApiConstants.GET_TOKEN + "?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 String resolveExternalContactSecret(QiWeiConfig cfg) {
String fromProp = trimToNull(System.getProperty("qiwei.externalContactSecret"));
if (fromProp != null) {
return fromProp;
}
String appSecret = trimToNull(cfg.getAppSecret());
if (appSecret != null) {
return appSecret;
}
String contactSecret = trimToNull(cfg.getContractSecret());
if (contactSecret != null) {
return contactSecret;
}
throw new IllegalStateException("外部联系人secret不能为空请配置 -Dqiwei.externalContactSecret 或 qiwei_config.appSecret/contractSecret");
}
private boolean isNoExternalContactRelationError(String message) {
if (message == null) {
return false;
}
return message.contains("\"errcode\":84061")
|| message.contains("errcode\":84061")
|| message.contains("不存在外部联系人的关系");
}
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");
}
private ExternalContactUserApi createExternalContactUserApi(String corpId, String contactSecret) {
WeComTokenCacheable cacheable = new InMemoryWeComTokenCacheable();
WorkWeChatApi workWeChatApi = new WorkWeChatApi(cacheable, HttpLoggingInterceptor.Level.NONE);
return workWeChatApi.externalContactManager(DefaultAgent.of(corpId, contactSecret, "0"))
.externalContactUserApi();
}
private static class SyncCounter {
private int inserted;
private int updated;
}
private static final class InMemoryWeComTokenCacheable implements WeComTokenCacheable {
private final ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
@Override
public String putCorpTicket(String corpId, String agentId, String corpTicket) {
map.put("ticket:corp:" + corpId + ":" + agentId, corpTicket);
return corpTicket;
}
@Override
public String getCorpTicket(String corpId, String agentId) {
return map.get("ticket:corp:" + corpId + ":" + agentId);
}
@Override
public String putAgentTicket(String corpId, String agentId, String agentTicket) {
map.put("ticket:agent:" + corpId + ":" + agentId, agentTicket);
return agentTicket;
}
@Override
public String getAgentTicket(String corpId, String agentId) {
return map.get("ticket:agent:" + corpId + ":" + agentId);
}
@Override
public String putAccessToken(String corpId, String agentId, String accessToken) {
map.put("token:" + corpId + ":" + agentId, accessToken);
return accessToken;
}
@Override
public String getAccessToken(String corpId, String agentId) {
return map.get("token:" + corpId + ":" + agentId);
}
@Override
public void clearAccessToken(String corpId, String agentId) {
map.remove("token:" + corpId + ":" + agentId);
}
}
}