新火炬后端单体项目初始化代码
cuilei
2 天以前 c71714508fbe3ace3543423c7700d7bbcca90056
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
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;
        }
    }
}