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 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 getDepartmentUsers(String accessToken, String departmentId) { List 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 requestEntity = new HttpEntity<>(headers); ResponseEntity 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 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 parseUsers(Object items) { Logger log = LoggerFactory.getLogger(FeishuUserService.class); List 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 userMap = (Map) 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 avatarMap = (Map) 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; } } }