新火炬后端单体项目初始化代码
飞书同步用户/安灯获取产线  用户表增加 飞书用户open_id(后期发送消息标识)
已添加4个文件
已修改5个文件
494 ■■■■■ 文件已修改
src/main/java/org/jeecg/modules/andon/controller/AndonButtonConfigController.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/base/controller/FactoryController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/base/model/FactoryModel.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/mes/entity/FeishuUser.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/mes/job/FeishuSyncTask.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/mes/job/FeishuUserService.java 324 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/system/entity/SysUser.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/system/service/ISysUserService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/controller/AndonButtonConfigController.java
@@ -6,10 +6,12 @@
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.modules.andon.entity.AndonButtonConfig;
import org.jeecg.modules.andon.service.IAndonButtonConfigService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -56,6 +58,37 @@
       return Result.OK(pageList);
   }
    /**
     * APP分页列表查询
     *
     * @param andonButtonConfig
     * @param pageNo
     * @param pageSize
     * @param req
     * @return
     */
    @ApiOperation(value="安灯按钮配置-分页列表查询", notes="安灯按钮配置-分页列表查询")
    @GetMapping(value = "/App/list")
    public Result<IPage<AndonButtonConfig>> queryPageAppList(AndonButtonConfig andonButtonConfig,
                                                             @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
                                                             @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
                                                             HttpServletRequest req) {
        QueryWrapper<AndonButtonConfig> queryWrapper = QueryGenerator.initQueryWrapper(andonButtonConfig, req.getParameterMap());
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (sysUser != null) {
            // å¢žåŠ é€šè¿‡ç³»ç»Ÿç™»å½•äººè¿›è¡Œè¿‡æ»¤
            queryWrapper.eq("create_by", sysUser.getUsername());
        }
        Page<AndonButtonConfig> page = new Page<AndonButtonConfig>(pageNo, pageSize);
        IPage<AndonButtonConfig> pageList = andonButtonConfigService.page(page, queryWrapper);
        return Result.OK(pageList);
    }
   /**
    *   æ·»åŠ 
    *
src/main/java/org/jeecg/modules/base/controller/FactoryController.java
@@ -13,6 +13,7 @@
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.base.entity.Factory;
import org.jeecg.modules.base.model.FactoryIdModel;
import org.jeecg.modules.base.model.FactoryModel;
import org.jeecg.modules.base.service.IFactoryService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -220,6 +221,30 @@
         return result;
     }
     @ApiOperation(value = "产线表集合", notes = "产线表集合")
     @GetMapping(value = "/queryTreeAppList")
     public Result<List<FactoryModel>> queryTreeAppList() {
         Result<List<FactoryModel>> result = new Result<>();
         try {
             List<Factory> factoryList = factoryService.list(new LambdaQueryWrapper<Factory>()
                     .eq(Factory::getDelFlag, CommonConstant.DEL_FLAG_0)
                     .orderByAsc(Factory::getSorter));
             List<FactoryModel> factoryModels = factoryList.stream()
                     .map(factory -> new FactoryModel(factory.getId(), factory.getFactoryName()))
                     .collect(Collectors.toList());
             result.setSuccess(true);
             result.setResult(factoryModels);
         } catch (Exception e) {
             log.error(e.getMessage(), e);
         }
         return result;
     }
     //@AutoLog(value = "产线表-查询树形结构所有产线名称")
     @ApiOperation(value = "产线表-查询树形结构所有产线名称", notes = "产线表-查询树形结构所有产线名称")
     @GetMapping(value = "/queryIdTree")
src/main/java/org/jeecg/modules/base/model/FactoryModel.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package org.jeecg.modules.base.model;
import lombok.Data;
@Data
public class FactoryModel {
    private String value;  // äº§çº¿ID
    private String text;   // äº§çº¿åç§°
    public FactoryModel() {}
    public FactoryModel(String value, String text) {
        this.value = value;
        this.text = text;
    }
    // getter和setter方法
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
}
src/main/java/org/jeecg/modules/mes/entity/FeishuUser.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package org.jeecg.modules.mes.entity;
import lombok.Data;
@Data
public class FeishuUser {
    private String openId;
    private String name;
    private String username;
    private String realname;
    private String workNo;
    private String password;
    private String userId;      // å¯¹åº” open_id
    private String unionId;     // å¯¹åº” union_id
    private String enName;      // å¯¹åº” en_name
    private String description; // å¯¹åº” description
    private String avatar240;   // å¯¹åº” avatar.avatar_240
    private String avatar640;   // å¯¹åº” avatar.avatar_640
    private String avatar72;    // å¯¹åº” avatar.avatar_72
    private String avatarOrigin;// å¯¹åº” avatar.avatar_origin
    private String email;
    private String mobile;
    private String employeeNo;
    private Integer status;
    private Boolean mobileVisible; // å¯¹åº” mobile_visible
}
src/main/java/org/jeecg/modules/mes/job/FeishuSyncTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package org.jeecg.modules.mes.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
 * é£žä¹¦ç”¨æˆ·åŒæ­¥å®šæ—¶ä»»åŠ¡
 */
@Component
@Slf4j
public class FeishuSyncTask implements Job {
    @Autowired
    private org.jeecg.modules.mes.job.FeishuUserService feishuUserService;
    @Value("${feishu.sync.departmentId:0}")
    private String departmentId;
    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        log.info("开始执行飞书用户同步任务");
        try {
            if (departmentId != null && !"0".equals(departmentId)) {
                feishuUserService.syncFeishuDepartmentUsers(departmentId);
                log.info("飞书用户同步任务执行完成");
            } else {
                log.warn("未配置飞书同步部门ID,跳过同步任务");
            }
        } catch (Exception e) {
            log.error("飞书用户同步任务执行失败", e);
            throw new JobExecutionException(e);
        }
    }
}
src/main/java/org/jeecg/modules/mes/job/FeishuUserService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,324 @@
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;
    /**
     * åŒæ­¥é£žä¹¦éƒ¨é—¨ç”¨æˆ·åˆ°ç³»ç»Ÿç”¨æˆ·è¡¨
     * @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 = "https://open.feishu.cn/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 = "https://open.feishu.cn/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;
        }
    }
}
src/main/java/org/jeecg/modules/system/entity/SysUser.java
@@ -125,6 +125,12 @@
    @Excel(name = "工号", width = 15)
    private String workNo;
    /**
     * é£žä¹¦id,唯一键
     */
    @Excel(name = "飞书id", width = 15)
    private String openId;
    /**
     * èŒåŠ¡ï¼Œå…³è”èŒåŠ¡è¡¨
     */
src/main/java/org/jeecg/modules/system/service/ISysUserService.java
@@ -363,4 +363,6 @@
     * @return
     */
    IPage<SysUser> getUserByFactoryId(Page<SysUser> page, String factoryId, String username);
    void editUser(SysUser sysUser);
}
src/main/java/org/jeecg/modules/system/service/impl/SysUserServiceImpl.java
@@ -832,4 +832,9 @@
    public IPage<SysUser> getUserByFactoryId(Page<SysUser> page, String factoryId, String username) {
        return userMapper.getUserByFactoryId(page,factoryId,username);
    }
    @Override
    public void editUser(SysUser sysUser) {
        userMapper.updateById(sysUser);
    }
}