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

389 lines
16 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 com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.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;
@Service
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不能为空");
return result;
}
if (entity.getDeptName() == null || entity.getDeptName().trim().isEmpty()) {
result.put("success", false);
result.put("message", "deptName不能为空");
return result;
}
LambdaQueryWrapper<QweiDepartment> dup = new LambdaQueryWrapper<QweiDepartment>()
.eq(QweiDepartment::getDeptId, entity.getDeptId());
if (this.count(dup) > 0) {
result.put("success", false);
result.put("message", "deptId已存在");
return result;
}
entity.setId(UUID.randomUUID().toString());
entity.setTenantId(tenantId);
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(QweiDepartment 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;
}
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, Long deptId, String deptName, Long parentDeptId) {
Map<String, Object> result = new HashMap<>();
try {
if (current == null || current < 1) {
current = 1;
}
if (size == null || size < 1) {
size = 10;
}
Page<QweiDepartment> page = new Page<>(current, size);
LambdaQueryWrapper<QweiDepartment> q = new LambdaQueryWrapper<>();
if (deptId != null) {
q.eq(QweiDepartment::getDeptId, deptId);
}
if (deptName != null && !deptName.trim().isEmpty()) {
q.like(QweiDepartment::getDeptName, deptName.trim());
}
if (parentDeptId != null) {
q.eq(QweiDepartment::getParentDeptId, parentDeptId);
}
q.orderByAsc(QweiDepartment::getOrderNum).orderByDesc(QweiDepartment::getUpdatedAt);
Page<QweiDepartment> 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<>();
try {
QiWeiConfig cfg = loadQiWeiConfig();
String tenantId = resolveTenantId(tenantIdParam);
// 确保后续对 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 = 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 List<QweiDepartment> fetchDepartments(String token) throws Exception {
String url = QiWeiApiConstants.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");
}
}