package org.jeecg.modules.mes.job;
|
|
import com.alibaba.fastjson.JSONObject;
|
import lombok.extern.slf4j.Slf4j;
|
import org.jeecg.common.util.PasswordUtil;
|
import org.jeecg.common.util.RestUtil;
|
import org.jeecg.common.util.oConvertUtils;
|
import org.jeecg.modules.mes.entity.FeishuUser;
|
import org.jeecg.modules.system.entity.SysUser;
|
import org.jeecg.modules.system.service.ISysUserService;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpMethod;
|
import org.springframework.http.ResponseEntity;
|
import org.springframework.stereotype.Service;
|
import org.springframework.web.client.RestTemplate;
|
|
import javax.annotation.Resource;
|
import java.util.ArrayList;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
@Service
|
@Slf4j
|
public class FeishuUserService {
|
|
@Resource
|
private ISysUserService sysUserService;
|
|
@Resource
|
private RestTemplate restTemplate; // 新增RestTemplate依赖注入
|
|
@Value("${feishu.appId:}")
|
private String appId;
|
|
@Value("${feishu.appSecret:}")
|
private String appSecret;
|
|
@Value("${feishu.url:}")
|
private String feishuUrl;
|
|
/**
|
* 同步飞书部门用户到系统用户表
|
* @param departmentId 飞书部门ID
|
*/
|
public void syncFeishuDepartmentUsers(String departmentId) {
|
try {
|
log.info("开始同步飞书部门用户,部门ID: {}", departmentId);
|
|
// 1. 获取飞书访问令牌
|
String accessToken = getFeishuAccessToken();
|
if (oConvertUtils.isEmpty(accessToken)) {
|
log.error("获取飞书访问令牌失败,终止同步流程");
|
return; // 令牌获取失败直接返回,避免后续无效操作
|
}
|
|
// 2. 获取部门下的用户列表
|
List<FeishuUser> feishuUsers = getDepartmentUsers(accessToken, departmentId);
|
log.info("获取到飞书部门用户数量: {}", feishuUsers.size());
|
|
// 3. 同步到系统用户表
|
int successCount = 0;
|
int updateCount = 0;
|
int addCount = 0;
|
|
for (FeishuUser feishuUser : feishuUsers) {
|
try {
|
boolean isUpdated = syncFeishuUserToSystem(feishuUser);
|
successCount++;
|
if (isUpdated) {
|
updateCount++;
|
} else {
|
addCount++;
|
}
|
} catch (Exception e) {
|
log.error("同步飞书用户失败,用户ID: {}", feishuUser.getUserId(), e);
|
}
|
}
|
|
log.info("飞书用户同步完成,总处理: {},新增: {},更新: {}", successCount, addCount, updateCount);
|
} catch (Exception e) {
|
log.error("同步飞书部门用户失败,部门ID: {}", departmentId, e);
|
throw new RuntimeException("同步飞书用户失败: " + e.getMessage());
|
}
|
}
|
|
private String getFeishuAccessToken() {
|
try {
|
String url = feishuUrl + "open-apis/auth/v3/tenant_access_token/internal";
|
|
log.info("开始获取飞书访问令牌,AppId: {}",
|
appId != null ? appId.substring(0, Math.min(appId.length(), 10)) + "..." : "null");
|
|
JSONObject params = new JSONObject();
|
params.put("app_id", appId);
|
params.put("app_secret", appSecret);
|
|
log.debug("获取令牌请求参数: {}", params.toJSONString());
|
|
JSONObject result = RestUtil.post(url, params);
|
|
log.info("获取飞书访问令牌响应: {}", result != null ? result.toJSONString() : "null");
|
|
if (result != null && result.getInteger("code") == 0) {
|
String accessToken = result.getString("tenant_access_token");
|
log.info("成功获取飞书访问令牌,令牌长度: {}", accessToken != null ? accessToken.length() : 0);
|
return accessToken;
|
} else {
|
log.error("获取飞书访问令牌失败,响应: {}", result != null ? result.toJSONString() : "null");
|
if (result != null) {
|
log.error("错误码: {}, 错误信息: {}", result.getInteger("code"), result.getString("msg"));
|
}
|
return null;
|
}
|
} catch (Exception e) {
|
log.error("获取飞书访问令牌异常", e);
|
return null;
|
}
|
}
|
|
/**
|
* 获取部门用户列表(直属员工)
|
*/
|
private List<FeishuUser> getDepartmentUsers(String accessToken, String departmentId) {
|
List<FeishuUser> userList = new ArrayList<>();
|
try {
|
log.info("开始获取飞书部门用户列表,部门ID: {}", departmentId);
|
|
// 验证访问令牌
|
if (accessToken == null || accessToken.isEmpty()) {
|
log.error("访问令牌为空,无法继续执行");
|
return userList;
|
}
|
log.info("使用的访问令牌前缀: Bearer {}", accessToken.substring(0, Math.min(20, accessToken.length())) + "...");
|
|
// 飞书API端点
|
String url = feishuUrl + "open-apis/contact/v3/users/find_by_department";
|
|
String pageToken = null;
|
int pageNumber = 1;
|
|
do {
|
// 构建请求URL和参数
|
StringBuilder urlBuilder = new StringBuilder(url);
|
urlBuilder.append("?department_id=").append(departmentId);
|
urlBuilder.append("&department_id_type=open_department_id");
|
urlBuilder.append("&page_size=50");
|
urlBuilder.append("&user_id_type=open_id");
|
|
if (pageToken != null && !pageToken.isEmpty()) {
|
urlBuilder.append("&page_token=").append(pageToken);
|
}
|
|
log.info("请求第{}页数据,URL: {}", pageNumber, urlBuilder.toString());
|
|
// 设置请求头 - 关键修复点:使用HttpHeaders确保头信息正确设置
|
HttpHeaders headers = new HttpHeaders();
|
String authHeader = "Bearer " + accessToken;
|
headers.add("Authorization", authHeader);
|
headers.add("Content-Type", "application/json; charset=utf-8");
|
log.info("设置请求头: Authorization = {}", authHeader.substring(0, Math.min(30, authHeader.length())) + "...");
|
|
// 使用RestTemplate发送请求,确保头信息被正确传递
|
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
|
ResponseEntity<String> response = restTemplate.exchange(
|
urlBuilder.toString(),
|
HttpMethod.GET,
|
requestEntity,
|
String.class
|
);
|
|
// 解析响应
|
JSONObject result = JSONObject.parseObject(response.getBody());
|
log.debug("第{}页API响应: {}", pageNumber, result != null ? result.toJSONString() : "null");
|
|
if (result != null && result.getInteger("code") == 0) {
|
JSONObject data = result.getJSONObject("data");
|
if (data != null) {
|
// 解析用户列表
|
Object items = data.get("items");
|
log.info("第{}页原始用户数据数量: {}", pageNumber, items instanceof List ? ((List<?>) items).size() : 0);
|
|
if (items != null) {
|
List<FeishuUser> pageUsers = parseUsers(items);
|
userList.addAll(pageUsers);
|
log.info("第{}页解析后用户数量: {}", pageNumber, pageUsers.size());
|
}
|
|
// 检查是否有下一页
|
pageToken = data.getString("page_token");
|
boolean hasMore = data.getBooleanValue("has_more");
|
log.info("第{}页has_more: {}, page_token: {}", pageNumber, hasMore, pageToken);
|
pageNumber++;
|
} else {
|
log.warn("第{}页data为空", pageNumber);
|
break;
|
}
|
} else {
|
log.error("获取飞书部门用户列表失败,响应: {}", result != null ? result.toJSONString() : "null");
|
if (result != null) {
|
log.error("错误码: {}, 错误信息: {}", result.getInteger("code"), result.getString("msg"));
|
}
|
break;
|
}
|
} while (pageToken != null && !pageToken.isEmpty());
|
|
log.info("获取飞书部门用户完成,总用户数量: {}", userList.size());
|
|
} catch (Exception e) {
|
log.error("获取飞书部门用户列表异常", e);
|
}
|
return userList;
|
}
|
|
/**
|
* 解析用户数据
|
*/
|
/**
|
* 解析用户数据
|
*/
|
@SuppressWarnings("unchecked")
|
private List<FeishuUser> parseUsers(Object items) {
|
Logger log = LoggerFactory.getLogger(FeishuUserService.class);
|
List<FeishuUser> userList = new ArrayList<>();
|
if (!(items instanceof List)) {
|
log.warn("解析用户数据失败,items不是列表类型: {}", items);
|
return userList;
|
}
|
List<?> userItems = (List<?>) items;
|
log.info("开始解析飞书用户列表,共{}条记录", userItems.size());
|
for (Object item : userItems) {
|
if (item == null || !(item instanceof Map)) {
|
log.warn("跳过无效用户数据项,类型不匹配: {}", item != null ? item.getClass() : "null");
|
continue;
|
}
|
Map<String, Object> userMap = (Map<String, Object>) item;
|
FeishuUser user = new FeishuUser();
|
// 解析open_id
|
user.setOpenId((String) userMap.getOrDefault("open_id", ""));
|
// 解析name并拆分
|
String name = (String) userMap.getOrDefault("name", "");
|
if (name.contains(" ")) {
|
String[] parts = name.split(" ");
|
if (parts.length == 2) {
|
user.setUsername(parts[0].trim()); // 工号部分存到username
|
user.setRealname(parts[1].trim()); // 姓名部分存到realname
|
user.setWorkNo(parts[0].trim()); // 工号也存到workNo
|
} else {
|
// 拆分异常时,默认处理
|
user.setUsername(name);
|
user.setRealname(name);
|
user.setWorkNo("");
|
log.warn("飞书用户name字段格式异常,无法拆分,原始值:{}", name);
|
}
|
} else {
|
user.setUsername(name);
|
user.setRealname(name);
|
user.setWorkNo("");
|
log.warn("飞书用户name字段无空格分隔,无法拆分,原始值:{}", name);
|
}
|
// 解析其他基本信息
|
user.setEnName((String) userMap.getOrDefault("en_name", ""));
|
user.setDescription((String) userMap.getOrDefault("description", ""));
|
Object mobileVisibleObj = userMap.get("mobile_visible");
|
user.setMobileVisible(mobileVisibleObj instanceof Boolean ? (Boolean) mobileVisibleObj : false);
|
// 解析头像信息
|
Object avatarObj = userMap.get("avatar");
|
if (avatarObj instanceof Map) {
|
Map<String, Object> avatarMap = (Map<String, Object>) avatarObj;
|
user.setAvatar240((String) avatarMap.getOrDefault("avatar_240", ""));
|
user.setAvatar640((String) avatarMap.getOrDefault("avatar_640", ""));
|
user.setAvatar72((String) avatarMap.getOrDefault("avatar_72", ""));
|
user.setAvatarOrigin((String) avatarMap.getOrDefault("avatar_origin", ""));
|
} else {
|
log.info("用户[{}]未设置头像或头像格式异常", user.getRealname());
|
}
|
// 设置默认加密密码(这里假设密码是简单加密存储,实际建议用更安全的加密方式)
|
user.setPassword("123456"); // 你要求的默认密码
|
// 将open_id存到对应的字段(根据你的用户表结构,假设用户表有open_id字段)
|
user.setOpenId((String) userMap.getOrDefault("open_id", ""));
|
userList.add(user);
|
}
|
|
return userList;
|
}
|
|
/**
|
* 同步单个飞书用户到系统
|
* @param feishuUser 飞书用户
|
* @return true表示更新,false表示新增
|
*
|
* 后期增加: 用户同步对比,飞书离职员工需要在本地用户表里面将该用户状态禁用
|
*/
|
private boolean syncFeishuUserToSystem(FeishuUser feishuUser) {
|
// 这里根据拆分后的username(工号)去查询系统用户
|
String username = feishuUser.getUsername();
|
SysUser existUser = sysUserService.getUserByName(username);
|
SysUser sysUser = new SysUser();
|
sysUser.setUsername(username);
|
sysUser.setStatus(1);
|
sysUser.setDelFlag(0);
|
sysUser.setAvatar(feishuUser.getAvatar640());
|
sysUser.setRealname(feishuUser.getRealname());
|
sysUser.setWorkNo(feishuUser.getWorkNo());
|
String password = "123456", salt = oConvertUtils.randomGen(8);
|
String passwordEncode = PasswordUtil.encrypt(sysUser.getUsername(), password, salt);
|
sysUser.setSalt(salt);
|
sysUser.setPassword(passwordEncode);
|
sysUser.setOpenId(feishuUser.getOpenId());
|
|
if (existUser == null) {
|
|
sysUserService.addUserWithRole(sysUser, "5");
|
log.info("新增用户: {}", username);
|
return false;
|
} else {
|
sysUser.setId(existUser.getId());
|
sysUserService.editUser(sysUser);
|
log.info("更新用户: {}", username);
|
return true;
|
}
|
}
|
}
|