新火炬后端单体项目初始化代码
安灯发起-工单-飞书发送消息(一级响应/二级响应/三级响应)
飞书安灯消息模板相关代码
已重命名1个文件
已添加3个文件
已修改16个文件
已删除1个文件
1751 ■■■■ 文件已修改
src/main/java/org/jeecg/modules/andon/controller/AndonButtonConfigController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/controller/AndonOrderController.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/controller/AndonResponseConfigController.java 283 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/dto/AndonButtonDTO.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/entity/AndonButtonConfig.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/entity/AndonResponseConfig.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/mapper/AndonButtonConfigMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/mapper/AndonResponseConfigMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/mapper/xml/AndonButtonConfigMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/mapper/xml/AndonResponseConfigMapper.xml 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/service/IAndonButtonConfigService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/service/IAndonOrderService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/service/IAndonResponseConfigService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/service/impl/AndonButtonConfigServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/service/impl/AndonOrderServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/service/impl/AndonResponseConfigServiceImpl.java 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/feishu/job/FeishuSyncTask.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/feishu/service/FeishuUserService.java 635 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/mes/entity/MessageResponse.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/mes/job/FeishuUserService.java 327 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/org/jeecg/modules/andon/controller/AndonButtonConfigController.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -12,7 +13,9 @@
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.dto.AndonButtonDTO;
import org.jeecg.modules.andon.entity.AndonButtonConfig;
import org.jeecg.modules.andon.entity.AndonOrder;
import org.jeecg.modules.andon.service.IAndonButtonConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -21,6 +24,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
/**
* @Description: å®‰ç¯æŒ‰é’®é…ç½®
@@ -61,34 +65,22 @@
    /**
     * APP分页列表查询
     * APP安灯按钮列表查询
     *
     * @param andonButtonConfig
     * @param pageNo
     * @param pageSize
     * @param req
     * @param factoryId
     * @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);
    @GetMapping(value = "/queryUserAndonButtonList")
        public Result<List<AndonButtonDTO>> queryUserAndonButtonList(@RequestParam("factoryId") String factoryId) {
        List<AndonButtonDTO> list = andonButtonConfigService.queryUserAndonButtonList(factoryId);
        return Result.OK(list);
    }
   /**
    *   æ·»åŠ 
    *
src/main/java/org/jeecg/modules/andon/controller/AndonOrderController.java
@@ -56,20 +56,23 @@
       return Result.OK(pageList);
   }
   /**
    *   æ·»åŠ 
    *
    * @param andonOrder
    * @return
    */
   @AutoLog(value = "安灯工单-添加")
   @ApiOperation(value="安灯工单-添加", notes="安灯工单-添加")
   //@RequiresPermissions("org.jeecg.modules:andon_order:add")
   @PostMapping(value = "/add")
   public Result<String> add(@RequestBody AndonOrder andonOrder) {
       andonOrderService.save(andonOrder);
       return Result.OK("添加成功!");
   }
    /**
     * æ·»åŠ 
     *
     * @param andonOrder
     * @return
     */
    @AutoLog(value = "安灯工单-添加")
    @ApiOperation(value="安灯工单-添加", notes="安灯工单-添加")
    @PostMapping(value = "/add")
    public Result<String> add(@RequestBody AndonOrder andonOrder) {
        andonOrderService.save(andonOrder);
        return Result.OK("添加成功!");
    }
   /**
    *  ç¼–辑
src/main/java/org/jeecg/modules/andon/controller/AndonResponseConfigController.java
@@ -10,6 +10,7 @@
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.modules.andon.dto.AndonButtonDTO;
import org.jeecg.modules.andon.entity.AndonResponseConfig;
import org.jeecg.modules.andon.service.IAndonResponseConfigService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -19,143 +20,179 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
/**
* @Description: å®‰ç¯å“åº”配置
* @Author: jeecg-boot
* @Date:   2025-07-10
* @Version: V1.0
*/
@Api(tags="安灯响应配置")
 * @Description: å®‰ç¯å“åº”配置
 * @Author: jeecg-boot
 * @Date: 2025-07-10
 * @Version: V1.0
 */
@Api(tags = "安灯响应配置")
@RestController
@RequestMapping("/andonresponseconfig/andonResponseConfig")
@Slf4j
public class AndonResponseConfigController extends JeecgController<AndonResponseConfig, IAndonResponseConfigService> {
   @Autowired
   private IAndonResponseConfigService andonResponseConfigService;
    @Autowired
    private IAndonResponseConfigService andonResponseConfigService;
   /**
    * åˆ†é¡µåˆ—表查询
    *
    * @param andonResponseConfig
    * @param pageNo
    * @param pageSize
    * @param req
    * @return
    */
   //@AutoLog(value = "安灯响应配置-分页列表查询")
   @ApiOperation(value="安灯响应配置-分页列表查询", notes="安灯响应配置-分页列表查询")
   @GetMapping(value = "/list")
   public Result<IPage<AndonResponseConfig>> queryPageList(AndonResponseConfig andonResponseConfig,
                                  @RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
                                  @RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
                                  HttpServletRequest req) {
       QueryWrapper<AndonResponseConfig> queryWrapper = QueryGenerator.initQueryWrapper(andonResponseConfig, req.getParameterMap());
       Page<AndonResponseConfig> page = new Page<AndonResponseConfig>(pageNo, pageSize);
       IPage<AndonResponseConfig> pageList = andonResponseConfigService.page(page, queryWrapper);
       return Result.OK(pageList);
   }
    /**
     * åˆ†é¡µåˆ—表查询
     *
     * @param andonResponseConfig
     * @param pageNo
     * @param pageSize
     * @param req
     * @return
     */
    //@AutoLog(value = "安灯响应配置-分页列表查询")
    @ApiOperation(value = "安灯响应配置-分页列表查询", notes = "安灯响应配置-分页列表查询")
    @GetMapping(value = "/list")
    public Result<IPage<AndonResponseConfig>> queryPageList(AndonResponseConfig andonResponseConfig,
                                                            @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                                            @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
                                                            HttpServletRequest req) {
        QueryWrapper<AndonResponseConfig> queryWrapper = QueryGenerator.initQueryWrapper(andonResponseConfig, req.getParameterMap());
        Page<AndonResponseConfig> page = new Page<AndonResponseConfig>(pageNo, pageSize);
        IPage<AndonResponseConfig> pageList = andonResponseConfigService.page(page, queryWrapper);
        return Result.OK(pageList);
    }
   /**
    *   æ·»åŠ 
    *
    * @param andonResponseConfig
    * @return
    */
   @AutoLog(value = "安灯响应配置-添加")
   @ApiOperation(value="安灯响应配置-添加", notes="安灯响应配置-添加")
   //@RequiresPermissions("org.jeecg.modules:andon_response_config:add")
   @PostMapping(value = "/add")
   public Result<String> add(@RequestBody AndonResponseConfig andonResponseConfig) {
       andonResponseConfigService.save(andonResponseConfig);
       return Result.OK("添加成功!");
   }
   /**
    *  ç¼–辑
    *
    * @param andonResponseConfig
    * @return
    */
   @AutoLog(value = "安灯响应配置-编辑")
   @ApiOperation(value="安灯响应配置-编辑", notes="安灯响应配置-编辑")
   //@RequiresPermissions("org.jeecg.modules:andon_response_config:edit")
   @RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
   public Result<String> edit(@RequestBody AndonResponseConfig andonResponseConfig) {
       andonResponseConfigService.updateById(andonResponseConfig);
       return Result.OK("编辑成功!");
   }
    /**
     * å‘送安灯消息
     *
     * @param andonButtonDTO
     * @return
     */
    @AutoLog(value = "安灯工单-发送飞书消息")
    @ApiOperation(value = "安灯工单-发送飞书消息", notes = "安灯工单-发送飞书消息")
    @PostMapping(value = "/sendMessage")
    public Result<String> sendMessage(@RequestBody AndonButtonDTO andonButtonDTO) {
        try {
            andonButtonDTO.setBlinkingFlag(1);
            // è°ƒç”¨æœåŠ¡å±‚å¤„ç†ä¸šåŠ¡é€»è¾‘
            andonResponseConfigService.sendAndonNotification(andonButtonDTO);
            return Result.OK("添加成功!");
        } catch (Exception e) {
            log.error("安灯工单[{}]飞书通知发送失败", andonButtonDTO.getId(), e);
            return Result.error("发送消息失败: " + e.getMessage());
        }
    }
   /**
    *   é€šè¿‡id删除
    *
    * @param id
    * @return
    */
   @AutoLog(value = "安灯响应配置-通过id删除")
   @ApiOperation(value="安灯响应配置-通过id删除", notes="安灯响应配置-通过id删除")
   //@RequiresPermissions("org.jeecg.modules:andon_response_config:delete")
   @DeleteMapping(value = "/delete")
   public Result<String> delete(@RequestParam(name="id",required=true) String id) {
       andonResponseConfigService.removeById(id);
       return Result.OK("删除成功!");
   }
    /**
     * æ·»åŠ 
     *
     * @param andonResponseConfig
     * @return
     */
    @AutoLog(value = "安灯响应配置-添加")
    @ApiOperation(value = "安灯响应配置-添加", notes = "安灯响应配置-添加")
    @PostMapping(value = "/add")
    public Result<String> add(@RequestBody AndonResponseConfig andonResponseConfig) {
        andonResponseConfigService.save(andonResponseConfig);
        return Result.OK("添加成功!");
    }
   /**
    *  æ‰¹é‡åˆ é™¤
    *
    * @param ids
    * @return
    */
   @AutoLog(value = "安灯响应配置-批量删除")
   @ApiOperation(value="安灯响应配置-批量删除", notes="安灯响应配置-批量删除")
   //@RequiresPermissions("org.jeecg.modules:andon_response_config:deleteBatch")
   @DeleteMapping(value = "/deleteBatch")
   public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
       this.andonResponseConfigService.removeByIds(Arrays.asList(ids.split(",")));
       return Result.OK("批量删除成功!");
   }
    /**
     * ç¼–辑
     *
     * @param andonResponseConfig
     * @return
     */
    @AutoLog(value = "安灯响应配置-编辑")
    @ApiOperation(value = "安灯响应配置-编辑", notes = "安灯响应配置-编辑")
    //@RequiresPermissions("org.jeecg.modules:andon_response_config:edit")
    @RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
    public Result<String> edit(@RequestBody AndonResponseConfig andonResponseConfig) {
        andonResponseConfigService.updateById(andonResponseConfig);
        return Result.OK("编辑成功!");
    }
   /**
    * é€šè¿‡id查询
    *
    * @param id
    * @return
    */
   //@AutoLog(value = "安灯响应配置-通过id查询")
   @ApiOperation(value="安灯响应配置-通过id查询", notes="安灯响应配置-通过id查询")
   @GetMapping(value = "/queryById")
   public Result<AndonResponseConfig> queryById(@RequestParam(name="id",required=true) String id) {
       AndonResponseConfig andonResponseConfig = andonResponseConfigService.getById(id);
       if(andonResponseConfig==null) {
           return Result.error("未找到对应数据");
       }
       return Result.OK(andonResponseConfig);
   }
    /**
     * é€šè¿‡id删除
     *
     * @param id
     * @return
     */
    @AutoLog(value = "安灯响应配置-通过id删除")
    @ApiOperation(value = "安灯响应配置-通过id删除", notes = "安灯响应配置-通过id删除")
    //@RequiresPermissions("org.jeecg.modules:andon_response_config:delete")
    @DeleteMapping(value = "/delete")
    public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
        andonResponseConfigService.removeById(id);
        return Result.OK("删除成功!");
    }
   /**
   * å¯¼å‡ºexcel
   *
   * @param request
   * @param andonResponseConfig
   */
   //@RequiresPermissions("org.jeecg.modules:andon_response_config:exportXls")
   @RequestMapping(value = "/exportXls")
   public ModelAndView exportXls(HttpServletRequest request, AndonResponseConfig andonResponseConfig) {
       return super.exportXls(request, andonResponseConfig, AndonResponseConfig.class, "安灯响应配置");
   }
    /**
     * æ‰¹é‡åˆ é™¤
     *
     * @param ids
     * @return
     */
    @AutoLog(value = "安灯响应配置-批量删除")
    @ApiOperation(value = "安灯响应配置-批量删除", notes = "安灯响应配置-批量删除")
    //@RequiresPermissions("org.jeecg.modules:andon_response_config:deleteBatch")
    @DeleteMapping(value = "/deleteBatch")
    public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
        this.andonResponseConfigService.removeByIds(Arrays.asList(ids.split(",")));
        return Result.OK("批量删除成功!");
    }
   /**
    /**
     * é€šè¿‡id查询
     *
     * @param id
     * @return
     */
    //@AutoLog(value = "安灯响应配置-通过id查询")
    @ApiOperation(value = "安灯响应配置-通过id查询", notes = "安灯响应配置-通过id查询")
    @GetMapping(value = "/queryById")
    public Result<AndonResponseConfig> queryById(@RequestParam(name = "id", required = true) String id) {
        AndonResponseConfig andonResponseConfig = andonResponseConfigService.getById(id);
        if (andonResponseConfig == null) {
            return Result.error("未找到对应数据");
        }
        return Result.OK(andonResponseConfig);
    }
    /**
     * å¯¼å‡ºexcel
     *
     * @param request
     * @param andonResponseConfig
     */
    //@RequiresPermissions("org.jeecg.modules:andon_response_config:exportXls")
    @RequestMapping(value = "/exportXls")
    public ModelAndView exportXls(HttpServletRequest request, AndonResponseConfig andonResponseConfig) {
        return super.exportXls(request, andonResponseConfig, AndonResponseConfig.class, "安灯响应配置");
    }
    /**
     * é€šè¿‡excel导入数据
   *
   * @param request
   * @param response
   * @return
   */
   //@RequiresPermissions("andon_response_config:importExcel")
   @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
   public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
       return super.importExcel(request, response, AndonResponseConfig.class);
   }
     *
     * @param request
     * @param response
     * @return
     */
    //@RequiresPermissions("andon_response_config:importExcel")
    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
        return super.importExcel(request, response, AndonResponseConfig.class);
    }
    /**
     * APP安灯按钮列表查询
     *
     * @param factoryId
     * @return
     */
    @ApiOperation(value = "安灯按钮配置-列表查询", notes = "安灯按钮配置-列表查询")
    @GetMapping(value = "/queryAndonButtonList")
    public Result<List<AndonButtonDTO>> queryAndonButtonList(@RequestParam("factoryId") String factoryId) {
        List<AndonButtonDTO> list = andonResponseConfigService.queryAndonButtonList(factoryId);
        return Result.OK(list);
    }
}
src/main/java/org/jeecg/modules/andon/dto/AndonButtonDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
package org.jeecg.modules.andon.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.jeecg.common.aspect.annotation.Dict;
import org.jeecgframework.poi.excel.annotation.Excel;
@Data
public class AndonButtonDTO {
    /** å“åº”ID */
    @ApiModelProperty(value = "响应ID")
    private String id;
    /** æŒ‰é’®ID */
    @ApiModelProperty(value = " æŒ‰é’®ID ")
    private String buttonId;
    /**安灯名称*/
    @ApiModelProperty(value = "安灯名称")
    private String buttonName;
    /**安灯编码*/
    @ApiModelProperty(value = "安灯编码")
    private String buttonCode;
    /** ä¸€çº§å“åº”人 */
    @ApiModelProperty(value = "一级响应人")
    private String responder;
    @ApiModelProperty(value = "二级响应人")
    private String secondResponder;
    /**产线名称*/
    @ApiModelProperty(value = "产线名称")
    private String factoryName;
    /**产线名称*/
    @ApiModelProperty(value = "工厂名称")
    private String parentFactoryName;
    /**一级响应时长*/
    @ApiModelProperty(value = "一级响应时长")
    private Integer upgradeResponseDuration;
    /**二级响应时长*/
    @ApiModelProperty(value = "二级响应时长")
    private Integer secondUpgradeResponseDuration;
    /**三级级响应时长*/
    @ApiModelProperty(value = "三级响应时长")
    private Integer thirdUpgradeResponseDuration;
    /**
     * æ˜¯å¦é—ªçƒ
     * å¯¹åº”数据库表中 is_blinking å­—段(大于0 æœ‰å¾…处理的工单闪,相反不闪烁)
     */
    @ApiModelProperty(value = "标识闪烁")
    private Integer blinkingFlag;
    @ApiModelProperty(value = "一级响应人openId")
    private String responderOpenId;
    @ApiModelProperty(value = "二级响应人openId")
    private String secondResponderOpenId;
    @ApiModelProperty(value = "三级响应人openId")
    private String thirdResponderOpenId;
    @ApiModelProperty(value = "三级响应人")
    private String thirdResponder;
    @ApiModelProperty(value = "工单ID")
    private String orderIds;
    @ApiModelProperty(value = "工单状态")
    private String orderStatus;
}
src/main/java/org/jeecg/modules/andon/entity/AndonButtonConfig.java
@@ -1,12 +1,10 @@
package org.jeecg.modules.andon.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import liquibase.pro.packaged.B;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@@ -89,4 +87,13 @@
    @Excel(name = "备注", width = 15)
    @ApiModelProperty(value = "备注")
    private String remark;
    /**
     * æ˜¯å¦é—ªçƒ
     * å¯¹åº”数据库表中 is_blinking å­—段(tinyint类型,默认值0)
     */
    @ApiModelProperty(value = "标识闪烁")
    @TableField(exist = false)
    private boolean blinkingFlag;
}
src/main/java/org/jeecg/modules/andon/entity/AndonResponseConfig.java
@@ -78,4 +78,7 @@
    @Excel(name = "三级响应人(升级两次)", width = 15)
    @ApiModelProperty(value = "三级响应人(升级两次)")
    private String thirdResponder;
}
src/main/java/org/jeecg/modules/andon/mapper/AndonButtonConfigMapper.java
@@ -2,7 +2,10 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.andon.dto.AndonButtonDTO;
import org.jeecg.modules.andon.entity.AndonButtonConfig;
import java.util.List;
/**
 * @Description: å®‰ç¯æŒ‰é’®é…ç½®
@@ -12,4 +15,10 @@
 */
public interface AndonButtonConfigMapper extends BaseMapper<AndonButtonConfig> {
    AndonButtonConfig getAndonButtonById(@Param("id") String id);
    /**
     * å®‰ç¯æŒ‰é’®åˆ—表
     * @param factoryId
     * @return
     */
    List<AndonButtonDTO> queryUserAndonButtonList(String factoryId);
}
src/main/java/org/jeecg/modules/andon/mapper/AndonResponseConfigMapper.java
@@ -2,7 +2,10 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.andon.dto.AndonButtonDTO;
import org.jeecg.modules.andon.entity.AndonResponseConfig;
import java.util.List;
/**
 * @Description: å®‰ç¯å“åº”配置
@@ -12,4 +15,5 @@
 */
public interface AndonResponseConfigMapper extends BaseMapper<AndonResponseConfig> {
    AndonResponseConfig getAndonResponseConfigByFactoryIdAndButtonId(@Param("factoryId") String factoryId, @Param("buttonId") String buttonId);
}
src/main/java/org/jeecg/modules/andon/mapper/xml/AndonButtonConfigMapper.xml
@@ -4,4 +4,24 @@
    <select id="getAndonButtonById" resultType="org.jeecg.modules.andon.entity.AndonButtonConfig">
        SELECT * FROM andon_button_config WHERE id = #{id} and del_flag = 0 and button_status = '启用'
    </select>
    <select id="queryUserAndonButtonList" resultType="org.jeecg.modules.andon.dto.AndonButtonDTO">
        select arc.id,
               abc.id as button_id,
               abc.button_name,
               abc.button_code,
               (select count(1)
                from andon_order ao
                where ao.button_id = arc.button_id
                  and ao.factory_id = arc.factory_id
                  and ao.order_status != '3') as blinking_flag,
            STUFF((SELECT ',' + CAST(ao.id AS VARCHAR)
            FROM andon_order ao
            WHERE ao.button_id = arc.button_id
            and ao.factory_id = arc.factory_id
            FOR XML PATH('')), 1, 1, '') as order_ids
        from andon_response_config arc
            left join andon_button_config abc
        on arc.button_id = abc.id
        where arc.factory_id=#{factoryId};
    </select>
</mapper>
src/main/java/org/jeecg/modules/andon/mapper/xml/AndonResponseConfigMapper.xml
@@ -1,7 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.jeecg.modules.andon.mapper.AndonResponseConfigMapper">
    <select id="getAndonResponseConfigByFactoryIdAndButtonId" resultType="org.jeecg.modules.andon.entity.AndonResponseConfig">
        SELECT * FROM andon_response_config WHERE factory_id = #{factoryId} AND button_id = #{buttonId} AND del_flag = 0
    <select id="getAndonResponseConfigByFactoryIdAndButtonId"
            resultType="org.jeecg.modules.andon.entity.AndonResponseConfig">
        SELECT *
        FROM andon_response_config
        WHERE factory_id = #{factoryId}
          AND button_id = #{buttonId}
          AND del_flag = 0
    </select>
</mapper>
</mapper>
src/main/java/org/jeecg/modules/andon/service/IAndonButtonConfigService.java
@@ -1,7 +1,10 @@
package org.jeecg.modules.andon.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.andon.dto.AndonButtonDTO;
import org.jeecg.modules.andon.entity.AndonButtonConfig;
import java.util.List;
/**
 * @Description: å®‰ç¯æŒ‰é’®é…ç½®
@@ -11,4 +14,11 @@
 */
public interface IAndonButtonConfigService extends IService<AndonButtonConfig> {
    AndonButtonConfig getAndonButtonById(String id);
    /**
     * æŸ¥è¯¢ç”¨æˆ·å®‰ç¯æŒ‰é’®åˆ—表
     * @param factoryId
     * @return
     */
    List<AndonButtonDTO> queryUserAndonButtonList(String factoryId);
}
src/main/java/org/jeecg/modules/andon/service/IAndonOrderService.java
@@ -14,4 +14,12 @@
public interface IAndonOrderService extends IService<AndonOrder> {
    //已完成状态除外
    List<AndonOrder> getAndonOrdersExceptYWC();
    /**
     * èŽ·å–å·¥å•çš„åˆå§‹å“åº”äºº
     * @param andonOrder
     * @return
     */
    String getPrimaryResponder(AndonOrder andonOrder);
}
src/main/java/org/jeecg/modules/andon/service/IAndonResponseConfigService.java
@@ -1,7 +1,10 @@
package org.jeecg.modules.andon.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.jeecg.modules.andon.dto.AndonButtonDTO;
import org.jeecg.modules.andon.entity.AndonResponseConfig;
import java.util.List;
/**
 * @Description: å®‰ç¯å“åº”配置
@@ -11,4 +14,19 @@
 */
public interface IAndonResponseConfigService extends IService<AndonResponseConfig> {
    AndonResponseConfig getAndonResponseConfigByFactoryIdAndButtonId(String factoryId, String buttonId);
    /**
     * æ ¹æ®å·¥åŽ‚id查询按钮列表
     * @param factoryId å·¥åŽ‚id
     * @return
     */
    List<AndonButtonDTO> queryAndonButtonList(String factoryId);
    /**
     * å‘送安灯通知
     * @param andonButtonDTO å®‰ç¯æŒ‰é’®DTO
     * @return ç»“æžœ
     */
    void sendAndonNotification(AndonButtonDTO andonButtonDTO);
}
src/main/java/org/jeecg/modules/andon/service/impl/AndonButtonConfigServiceImpl.java
@@ -1,10 +1,14 @@
package org.jeecg.modules.andon.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.modules.andon.dto.AndonButtonDTO;
import org.jeecg.modules.andon.entity.AndonButtonConfig;
import org.jeecg.modules.andon.mapper.AndonButtonConfigMapper;
import org.jeecg.modules.andon.service.IAndonButtonConfigService;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
/**
 * @Description: å®‰ç¯æŒ‰é’®é…ç½®
@@ -19,4 +23,11 @@
    public AndonButtonConfig getAndonButtonById(String id) {
        return baseMapper.getAndonButtonById(id);
    }
    @Override
    public List<AndonButtonDTO> queryUserAndonButtonList(String factoryId) {
        return baseMapper.queryUserAndonButtonList(factoryId);
    }
}
src/main/java/org/jeecg/modules/andon/service/impl/AndonOrderServiceImpl.java
@@ -1,9 +1,16 @@
package org.jeecg.modules.andon.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.jeecg.modules.andon.entity.AndonResponseConfig;
import org.jeecg.modules.andon.mapper.AndonResponseConfigMapper;
import org.jeecg.modules.andon.service.IAndonOrderService;
import org.jeecg.modules.andon.entity.AndonOrder;
import org.jeecg.modules.andon.mapper.AndonOrderMapper;
import org.jeecg.modules.feishu.service.FeishuUserService;
import org.jeecg.modules.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -11,14 +18,46 @@
/**
 * @Description: å®‰ç¯å·¥å•
 * @Author: jeecg-boot
 * @Date:   2025-07-10
 * @Date: 2025-07-10
 * @Version: V1.0
 */
@Service
public class AndonOrderServiceImpl extends ServiceImpl<AndonOrderMapper, AndonOrder> implements IAndonOrderService {
    @Autowired
    private FeishuUserService feishuService;
    @Autowired
    private ISysUserService sysUserService;
    @Autowired
    private AndonResponseConfigMapper andonResponseConfigMapper;
    @Override
    public List<AndonOrder> getAndonOrdersExceptYWC() {
        return baseMapper.getAndonOrdersExceptYWC();
    }
    @Override
    public String getPrimaryResponder(AndonOrder andonOrder) {
        // 1. æ ¡éªŒå¿…要参数
        if (StringUtils.isBlank(andonOrder.getFactoryId()) || StringUtils.isBlank(andonOrder.getButtonId())) {
            return null;
        }
        // 2. æŸ¥è¯¢å“åº”人配置(假设存在响应人配置实体和Mapper)
        // æž„建查询条件:匹配产线ID、安灯类型ID,且配置为启用状态
        QueryWrapper<AndonResponseConfig> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("factory_id", andonOrder.getFactoryId())
                .eq("button_id", andonOrder.getButtonId())
                .eq("status", 1) // 1表示启用状态
                .last("limit 1"); // åªå–一条有效配置
        // 3. æ‰§è¡ŒæŸ¥è¯¢
        AndonResponseConfig config = andonResponseConfigMapper.selectOne(queryWrapper);
        if (config == null) {
            return null;
        }
        return config.getFirsterResponder();
    }
}
src/main/java/org/jeecg/modules/andon/service/impl/AndonResponseConfigServiceImpl.java
@@ -1,22 +1,204 @@
package org.jeecg.modules.andon.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang.StringUtils;
import org.jeecg.modules.andon.dto.AndonButtonDTO;
import org.jeecg.modules.andon.entity.AndonButtonConfig;
import org.jeecg.modules.andon.entity.AndonOrder;
import org.jeecg.modules.andon.entity.AndonResponseConfig;
import org.jeecg.modules.andon.mapper.AndonResponseConfigMapper;
import org.jeecg.modules.andon.service.IAndonButtonConfigService;
import org.jeecg.modules.andon.service.IAndonOrderService;
import org.jeecg.modules.andon.service.IAndonResponseConfigService;
import org.jeecg.modules.base.entity.Factory;
import org.jeecg.modules.base.service.IFactoryService;
import org.jeecg.modules.feishu.service.FeishuUserService;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
/**
 * @Description: å®‰ç¯å“åº”配置
 * @Author: jeecg-boot
 * @Date:   2025-07-10
 * @Date: 2025-07-10
 * @Version: V1.0
 */
@Service
public class AndonResponseConfigServiceImpl extends ServiceImpl<AndonResponseConfigMapper, AndonResponseConfig> implements IAndonResponseConfigService {
    @Autowired
    private IFactoryService factoryService;
    @Autowired
    private ISysUserService iSysUserService;
    @Autowired
    private FeishuUserService feishuUserService;
    @Autowired
    private IAndonButtonConfigService andonButtonConfigService;
    @Autowired
    private IAndonOrderService andonOrderService;
    @Override
    public AndonResponseConfig getAndonResponseConfigByFactoryIdAndButtonId(String factoryId, String buttonId) {
        return baseMapper.getAndonResponseConfigByFactoryIdAndButtonId(factoryId,buttonId);
        return baseMapper.getAndonResponseConfigByFactoryIdAndButtonId(factoryId, buttonId);
    }
    @Override
    public List<AndonButtonDTO> queryAndonButtonList(String factoryId) {
        return Collections.emptyList();
    }
    @Override
    public void sendAndonNotification(AndonButtonDTO andonButtonDTO) {
        /**
         * 1. éªŒè¯å·¥å•状态
         */
        if (andonButtonDTO == null) {
            log.warn("请求参数为空");
            throw new IllegalArgumentException("请求参数不能为空");
        }
        /**
         * 2. èŽ·å–å“åº”é…ç½®
         */
        AndonResponseConfig andonResponseConfig = this.getById(andonButtonDTO.getId());
        AndonOrder andonOrder = andonOrderService.getById(andonButtonDTO.getOrderIds());
        if (andonOrder == null) {
            log.warn("未找到ID为[{}]的安灯订单");
            throw new IllegalArgumentException("未找到对应的安灯订单");
        }
        String orderStatus = andonOrder.getOrderStatus();
        andonButtonDTO.setOrderStatus(orderStatus);
        if (andonResponseConfig == null) {
            log.warn("未找到ID为[{}]的响应配置");
            throw new IllegalArgumentException("未找到对应的响应配置");
        }
        /**
         * 3. è®¾ç½®äºŒçº§å“åº”人
         */
        String secondResponder = andonResponseConfig.getSecondResponder();
        if (StringUtils.isNotBlank(secondResponder)) {
            andonButtonDTO.setSecondResponder(secondResponder);
        }
        /**
         * 4. èŽ·å–å·¥åŽ‚ä¿¡æ¯
         */
        String factoryId = andonResponseConfig.getFactoryId();
        if (factoryId == null || factoryId.isEmpty()) {
            log.warn("响应配置[{}]未设置工厂ID");
            throw new IllegalArgumentException("响应配置未设置工厂信息");
        }
        Factory factory = factoryService.getOne(new QueryWrapper<Factory>()
                .eq("id", factoryId)
                .eq("del_flag", 0));
        if (factory == null) {
            log.warn("未找到ID为[{}]的工厂");
            throw new IllegalArgumentException("未找到对应的工厂信息");
        }
        /**
         * èŽ·å–äº§çº¿åç§°
         */
        String factoryName = factory.getFactoryName();
        andonButtonDTO.setFactoryName(factoryName);
        /**
         * 6. è®¾ç½®å·¥åŽ‚åç§°
         */
        String parentId = factory.getParentId();
        if (parentId != null && !parentId.isEmpty()) {
            Factory parentFactory = factoryService.getOne(new QueryWrapper<Factory>()
                    .eq("id", parentId)
                    .eq("del_flag", 0));
            if (parentFactory != null) {
                andonButtonDTO.setParentFactoryName(parentFactory.getFactoryName());
            }
        }
        /**
         * 6. èŽ·å–å®‰ç¯æŒ‰é’®é…ç½®
         */
        AndonButtonConfig andonButtonById = andonButtonConfigService.getAndonButtonById(andonButtonDTO.getButtonId());
        if (andonButtonById == null) {
            log.warn("未找到ID为[{}]的安灯按钮");
            throw new IllegalArgumentException("未找到对应的安灯按钮信息");
        }
        /**
         * 7. è®¾ç½®å“åº”æ—¶é—´
         */
        Integer upgradeResponseDuration = andonButtonById.getUpgradeResponseDuration();
        andonButtonDTO.setUpgradeResponseDuration(upgradeResponseDuration);
        Integer secondUpgradeResponseDuration = andonButtonById.getSecondUpgradeResponseDuration();
        andonButtonDTO.setSecondUpgradeResponseDuration(secondUpgradeResponseDuration);
        /**
         * 8. èŽ·å–å“åº”äººä¿¡æ¯
         */
        String firstResponder = andonResponseConfig.getFirsterResponder();
        String threeResponder = andonResponseConfig.getThirdResponder();
        SysUser thirdUser = iSysUserService.getUserByName(threeResponder);
        SysUser userByName = iSysUserService.getUserByName(firstResponder);
        SysUser secondUser = null;
        if (StringUtils.isNotBlank(secondResponder)) {
            secondUser = iSysUserService.getUserByName(secondResponder);
        }
        /**
         * 9. éªŒè¯å¹¶è®¾ç½®å“åº”人信息
         */
        if (userByName == null) {
            log.warn("用户[{}]未找到,无法发送通知");
            throw new IllegalArgumentException("用户[{}]未找到,无法发送通知");
        }
        if (secondUser == null && StringUtils.isNotBlank(secondResponder)) {
            log.warn("用户[{}]未找到,无法发送通知");
            throw new IllegalArgumentException("用户[{}]未找到,无法发送通知");
        }
        if (threeResponder == null) {
            StringUtils.isNotBlank(null);
        }
        //
        /**
         * 10. è®¾ç½®å“åº”人相关信息
         */
        andonButtonDTO.setResponderOpenId(userByName.getOpenId());
        andonButtonDTO.setResponder(userByName.getRealname());
        log.warn("用户[{}]已找到,开始发送通知");
        if (secondUser != null) {
            andonButtonDTO.setSecondResponderOpenId(secondUser.getOpenId());
            andonButtonDTO.setSecondResponder(secondUser.getRealname());
            log.warn("用户[{}]已找到,开始发送通知");
        }
        if (thirdUser != null) {
            andonButtonDTO.setThirdResponderOpenId(thirdUser.getOpenId());
            andonButtonDTO.setThirdResponder(thirdUser.getRealname());
            log.warn("用户[{}]已找到,开始发送通知");
        }
        /**
         *  11. å‘送飞书通知
         */
        if (StringUtils.isNotBlank(firstResponder)) {
            String feishuAccessToken = feishuUserService.getFeishuAccessToken();
            feishuUserService.sendAndonNotification(feishuAccessToken, andonButtonDTO);
        } else {
            log.warn("工单[{}]未找到对应响应人,无法发送通知");
        }
    }
}
src/main/java/org/jeecg/modules/feishu/job/FeishuSyncTask.java
ÎļþÃû´Ó src/main/java/org/jeecg/modules/mes/job/FeishuSyncTask.java ÐÞ¸Ä
@@ -1,7 +1,8 @@
package org.jeecg.modules.mes.job;
package org.jeecg.modules.feishu.job;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.feishu.service.FeishuUserService;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
@@ -17,7 +18,7 @@
public class FeishuSyncTask implements Job {
    @Autowired
    private org.jeecg.modules.mes.job.FeishuUserService feishuUserService;
    private FeishuUserService feishuUserService;
    @Value("${feishu.sync.departmentId:0}")
    private String departmentId;
src/main/java/org/jeecg/modules/feishu/service/FeishuUserService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,635 @@
package org.jeecg.modules.feishu.service;
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.andon.dto.AndonButtonDTO;
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.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class FeishuUserService {
    // ç±»ä¸­å®šä¹‰æ¨¡æ¿å¸¸é‡ï¼ˆä¾¿äºŽç»´æŠ¤ï¼‰
    private static final String FIRST_LEVEL_TEMPLATE = "【工艺安灯】\n%s Â· %s å‘起了工艺安灯,请%d分钟内在PDA上进行响应处理!!!";
    private static final String SECOND_LEVEL_TEMPLATE = "【工艺安灯】\n%s Â· %s å‘起的工艺安灯,%s未在%d分钟内响应,请尽快督促进行响应处理!!!";
    @Resource
    private ISysUserService sysUserService;
    @Resource
    private RestTemplate restTemplate;
    @Value("${feishu.appId:}")
    private String appId;
    @Value("${feishu.appSecret:}")
    private String appSecret;
    @Value("${feishu.url:}")
    private String feishuUrl;
    // å®šæ—¶ä»»åŠ¡çº¿ç¨‹æ± ï¼ˆæ”¯æŒå¤šçº§å“åº”è°ƒåº¦ï¼‰
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);
    private final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    /**
     * åŒæ­¥é£žä¹¦éƒ¨é—¨ç”¨æˆ·åˆ°ç³»ç»Ÿç”¨æˆ·è¡¨
     */
    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());
        }
    }
    public 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;
        }
    }
    /**
     * å‘送安灯通知消息(支持一、二、三级响应)
     * æµç¨‹ï¼š
     * 1. APP点击发送一级响应(初级响应)
     * 2. ä¸€çº§å“åº”时长后检查状态,未处理则发送二级
     * 3. äºŒçº§å“åº”时长后检查状态,未处理则发送三级
     */
    public boolean sendAndonNotification(String accessToken, AndonButtonDTO andonOrde) {
        try {
            String currentTime = sdf.format(new Date());
            log.info("【{}】触发一级响应(初级响应),安灯ID: {}", currentTime, andonOrde.getId());
            // 1. å‚数验证
            if (!validateNotificationParams(andonOrde)) {
                return false;
            }
            if (accessToken == null || accessToken.isEmpty()) {
                log.error("访问令牌为空,无法发送安灯通知");
                return false;
            }
            // 2. å‘送一级响应通知(即时发送)
            boolean firstLevelResult = sendLevelNotification(accessToken, andonOrde, 1);
            // 3. è°ƒåº¦äºŒçº§å“åº”通知(延迟时间=一级响应时长)
            if (firstLevelResult) {
                int secondDelay = andonOrde.getUpgradeResponseDuration();
                String secondTime = sdf.format(new Date(System.currentTimeMillis() + secondDelay * 60 * 1000L));
                log.info("【{}】一级响应调度二级通知,延迟{}分钟,预计发送时间: {}",
                        currentTime, secondDelay, secondTime);
                scheduleNextLevelNotification(accessToken, andonOrde, 1, secondDelay);
            } else {
                log.warn("一级通知发送失败,终止后续通知流程,安灯ID: {}", andonOrde.getId());
            }
            return firstLevelResult;
        } catch (Exception e) {
            log.error("处理安灯通知异常,安灯ID: {}", andonOrde != null ? andonOrde.getId() : "未知", e);
            return false;
        }
    }
    /**
     * å‘送指定级别的通知
     * @param level 1-一级,2-二级,3-三级
     */
    private boolean sendLevelNotification(String accessToken, AndonButtonDTO andonOrde, int level) {
        try {
            String levelDesc = getLevelDesc(level);
            String openId = getResponderOpenId(andonOrde, level);
            String currentTime = sdf.format(new Date());
            log.info("【{}】开始发送{}级安灯通知,接收人openId: {}, å®‰ç¯ID: {}",
                    currentTime, levelDesc, openId, andonOrde.getId());
            // æž„建请求URL
            String url = feishuUrl + "open-apis/im/v1/messages?receive_id_type=open_id";
            log.info("{}级通知请求URL: {}", levelDesc, url);
            // è®¾ç½®è¯·æ±‚头
            HttpHeaders headers = new HttpHeaders();
            String authHeader = "Bearer " + accessToken;
            headers.add("Authorization", authHeader);
            headers.add("Content-Type", "application/json; charset=utf-8");
            log.info("设置{}级通知请求头: Authorization = {}", levelDesc,
                    authHeader.substring(0, Math.min(30, authHeader.length())) + "...");
            // æž„建消息内容
            JSONObject content = new JSONObject();
            String notificationContent = buildNotificationContent(andonOrde, level);
            content.put("text", notificationContent);
            log.debug("【{}】{}级通知内容: {}", currentTime, levelDesc, notificationContent);
            // æž„建请求体
            JSONObject requestBody = new JSONObject();
            requestBody.put("receive_id", openId);
            requestBody.put("msg_type", "text");
            requestBody.put("content", content.toJSONString());
            requestBody.put("uuid", "andon_" + andonOrde.getId() + "_level_" + level + "_" + System.currentTimeMillis());
            // å‘送请求
            HttpEntity<String> requestEntity = new HttpEntity<>(requestBody.toJSONString(), headers);
            ResponseEntity<String> response = restTemplate.exchange(
                    url, HttpMethod.POST, requestEntity, String.class
            );
            // è§£æžå“åº”
            JSONObject result = JSONObject.parseObject(response.getBody());
            log.debug("{}级通知API响应: {}", levelDesc, result != null ? result.toJSONString() : "null");
            if (result != null && result.getInteger("code") == 0) {
                JSONObject data = result.getJSONObject("data");
                if (data != null) {
                    log.info("【{}】{}级安灯通知发送成功,消息ID: {}, å®‰ç¯ID: {}",
                            currentTime, levelDesc, data.getString("message_id"), andonOrde.getId());
                    return true;
                } else {
                    log.warn("{}级通知响应data为空,安灯ID: {}", levelDesc, andonOrde.getId());
                    return false;
                }
            } else {
                log.error("发送{}级安灯通知失败,安灯ID: {}", levelDesc, andonOrde.getId());
                if (result != null) {
                    log.error("错误码: {}, é”™è¯¯ä¿¡æ¯: {}", result.getInteger("code"), result.getString("msg"));
                }
                return false;
            }
        } catch (Exception e) {
            log.error("发送{}级安灯通知异常,安灯ID: {}", getLevelDesc(level), andonOrde.getId(), e);
            return false;
        }
    }
    /**
     * è°ƒåº¦ä¸‹ä¸€çº§é€šçŸ¥ï¼ˆ1→2→3)
     * @param currentLevel å½“前级别
     * @param delayMinutes å»¶è¿Ÿåˆ†é’Ÿæ•°ï¼ˆç”±å½“前级别响应时长决定)
     */
    private void scheduleNextLevelNotification(String accessToken, AndonButtonDTO andonOrde, int currentLevel, int delayMinutes) {
        int nextLevel = currentLevel + 1;
        if (nextLevel > 3) {
            log.info("已到达最高级别通知,无需继续调度,安灯ID: {}", andonOrde.getId());
            return;
        }
        String currentLevelDesc = getLevelDesc(currentLevel);
        String nextLevelDesc = getLevelDesc(nextLevel);
        String scheduleTime = sdf.format(new Date());
        String executeTime = sdf.format(new Date(System.currentTimeMillis() + delayMinutes * 60 * 1000L));
        log.info("【{}】调度{}通知,延迟{}分钟(当前级别{}时长),预计执行时间: {}",
                scheduleTime, nextLevelDesc, delayMinutes, currentLevelDesc, executeTime);
        scheduler.schedule(() -> {
            try {
                String currentExecuteTime = sdf.format(new Date());
                log.info("【{}】开始执行{}通知检查,安灯ID: {}", currentExecuteTime, nextLevelDesc, andonOrde.getId());
                // æ£€æŸ¥è®¢å•状态:3表示已响应,无需发送下一级
                if (andonOrde.getOrderStatus() != null && "3".equals(andonOrde.getOrderStatus())) {
                    log.info("【{}】订单已响应(status=3),取消{}通知发送,安灯ID: {}",
                            currentExecuteTime, nextLevelDesc, andonOrde.getId());
                    return;
                }
                // æ£€æŸ¥ä¸‹ä¸€çº§å“åº”人是否有效
                if (!hasValidResponder(andonOrde, nextLevel)) {
                    log.warn("【{}】{}响应人信息无效,取消通知发送,安灯ID: {}",
                            currentExecuteTime, nextLevelDesc, andonOrde.getId());
                    return;
                }
                // å‘送下一级通知
                boolean nextLevelResult = sendLevelNotification(accessToken, andonOrde, nextLevel);
                if (nextLevelResult && nextLevel < 3) {
                    int nextDelay = getResponseDuration(andonOrde, nextLevel);
                    String nextExecuteTime = sdf.format(new Date(System.currentTimeMillis() + nextDelay * 60 * 1000L));
                    log.info("【{}】{}通知调度三级通知,延迟{}分钟,预计发送时间: {}",
                            currentExecuteTime, nextLevelDesc, nextDelay, nextExecuteTime);
                    scheduleNextLevelNotification(accessToken, andonOrde, nextLevel, nextDelay);
                } else if (!nextLevelResult) {
                    log.warn("【{}】{}通知发送失败,终止后续调度,安灯ID: {}",
                            currentExecuteTime, nextLevelDesc, andonOrde.getId());
                }
            } catch (Exception e) {
                log.error("执行{}调度任务异常,安灯ID: {}", nextLevelDesc, andonOrde.getId(), e);
            }
        }, delayMinutes, TimeUnit.MINUTES);
    }
    /**
     * æž„建通知内容(优化分隔符,提升可读性)
     */
    private String buildNotificationContent(AndonButtonDTO andonOrde, int level) {
        // èŽ·å–å·¥åŽ‚åç§°å’Œäº§çº¿åç§°ï¼Œåšç©ºå€¼ä¿æŠ¤
        String parentFactoryName = andonOrde.getParentFactoryName() != null ? andonOrde.getParentFactoryName() : "未知工厂";
        String factoryName = andonOrde.getFactoryName() != null ? andonOrde.getFactoryName() : "未知产线";
        String content;
        if (level == 1) {
            // ä¸€çº§æ¨¡æ¿ï¼šä½¿ç”¨"·"分隔工厂和产线
            int firstDuration = andonOrde.getUpgradeResponseDuration() != null ? andonOrde.getUpgradeResponseDuration() : 0;
            content = String.format(FIRST_LEVEL_TEMPLATE, parentFactoryName, factoryName, firstDuration);
        } else {
            // äºŒçº§æ¨¡æ¿ï¼šä¿æŒä¸€è‡´çš„分隔风格
            String prevResponder = getResponderName(andonOrde, level - 1);
            int prevDuration = getResponseDuration(andonOrde, level - 1);
            prevResponder = oConvertUtils.isEmpty(prevResponder) ? "未知人员" : prevResponder;
            prevDuration = Math.max(prevDuration, 0);
            content = String.format(SECOND_LEVEL_TEMPLATE, parentFactoryName, factoryName, prevResponder, prevDuration);
        }
        log.debug("构建{}级通知内容: {}", level, content);
        return content;
    }
//    /**
//     * æž„建通知内容(按级别使用不同模板)
//     */
//    private String buildNotificationContent(AndonButtonDTO andonOrde, int level) {
//        String factoryName = andonOrde.getFactoryName() != null ? andonOrde.getFactoryName() : "未知产线";
//
//        if (level == 1) {
//            // ä¸€çº§æ¨¡æ¿ï¼šã€å·¥è‰ºå®‰ç¯ã€‘XXX产线发起了工艺安灯,请xx分钟内在PDA上进行响应处理!!!
//            return String.format("【工艺安灯】\n%s产线发起了工艺安灯,请%d分钟内在PDA上进行响应处理!!!",
//                    factoryName,
//                    andonOrde.getUpgradeResponseDuration());
//        } else {
//            // äºŒ/三级模板:【工艺安灯】XXX(产线)发起的工艺安灯,xxx(上一级响应人)未在xx分钟内响应,请尽快督促进行响应处理!!!
//            String prevLevelDesc = getLevelDesc(level - 1);
//            String prevResponder = getResponderName(andonOrde, level - 1);
//            int prevDuration = getResponseDuration(andonOrde, level - 1);
//
//            return String.format("【工艺安灯】\n%s(产线)发起的工艺安灯,%s(%s响应人)未在%d分钟内响应,请尽快督促进行响应处理!!!",
//                    factoryName,
//                    prevResponder,
//                    prevLevelDesc,
//                    prevDuration);
//        }
//    }
    /**
     * éªŒè¯é€šçŸ¥ç›¸å…³å‚æ•°
     */
    private boolean validateNotificationParams(AndonButtonDTO andonOrde) {
        if (andonOrde == null) {
            log.error("安灯订单信息为空");
            return false;
        }
        if (andonOrde.getId() == null) {
            log.error("安灯ID为空");
            return false;
        }
        // ä¸€çº§å“åº”校验(必选)
        if (andonOrde.getResponderOpenId() == null || andonOrde.getResponderOpenId().isEmpty()
                || andonOrde.getUpgradeResponseDuration() == null || andonOrde.getUpgradeResponseDuration() <= 0) {
            log.error("一级响应人信息无效(openId或时长缺失),安灯ID: {}", andonOrde.getId());
            return false;
        }
        // äºŒçº§å“åº”校验(若存在则必选完整)
        if (andonOrde.getSecondResponderOpenId() != null && !andonOrde.getSecondResponderOpenId().isEmpty()) {
            if (andonOrde.getSecondUpgradeResponseDuration() == null || andonOrde.getSecondUpgradeResponseDuration() <= 0) {
                log.error("二级响应人存在但时长无效,安灯ID: {}", andonOrde.getId());
                return false;
            }
        }
        // ä¸‰çº§å“åº”校验(若存在则必选完整)
        if (andonOrde.getThirdResponderOpenId() != null && !andonOrde.getThirdResponderOpenId().isEmpty()) {
            if (andonOrde.getSecondUpgradeResponseDuration() == null || andonOrde.getSecondUpgradeResponseDuration() <= 0) {
                log.error("三级响应人存在但二级时长无效(三级依赖二级时长),安灯ID: {}", andonOrde.getId());
                return false;
            }
        }
        return true;
    }
    // å·¥å…·æ–¹æ³•:获取级别描述
    private String getLevelDesc(int level) {
        switch (level) {
            case 1: return "一级";
            case 2: return "二级";
            case 3: return "三级";
            default: return "未知级别";
        }
    }
    // å·¥å…·æ–¹æ³•:获取响应人openId
    private String getResponderOpenId(AndonButtonDTO andonOrde, int level) {
        switch (level) {
            case 1: return andonOrde.getResponderOpenId();
            case 2: return andonOrde.getSecondResponderOpenId();
            case 3: return andonOrde.getThirdResponderOpenId();
            default: return null;
        }
    }
    // å·¥å…·æ–¹æ³•:获取响应人名称
    private String getResponderName(AndonButtonDTO andonOrde, int level) {
        String name = null;
        switch (level) {
            case 1:
                name = andonOrde.getResponder();
                break;
            case 2:
                name = andonOrde.getSecondResponder();
                break;
            case 3:
                name = andonOrde.getThirdResponder();
                break;
            default:
                name = null;
        }
        return name != null ? name : "未知响应人";
    }
    // å·¥å…·æ–¹æ³•:获取响应时长(一级=二级延迟,二级=三级延迟)
    private int getResponseDuration(AndonButtonDTO andonOrde, int level) {
        int duration = 0;
        switch (level) {
            case 1:
                duration = andonOrde.getUpgradeResponseDuration() != null ? andonOrde.getUpgradeResponseDuration() : 0;
                break;
            case 2:
                duration = andonOrde.getSecondUpgradeResponseDuration() != null ? andonOrde.getSecondUpgradeResponseDuration() : 0;
                break;
            case 3:
                duration = 0; // ä¸‰çº§æ˜¯æœ€åŽä¸€çº§ï¼Œæ— éœ€å»¶è¿Ÿ
                break;
            default:
                duration = 0;
        }
        return duration;
    }
    // å·¥å…·æ–¹æ³•:判断指定级别响应人是否有效
    private boolean hasValidResponder(AndonButtonDTO andonOrde, int level) {
        String openId = getResponderOpenId(andonOrde, level);
        return openId != null && !openId.isEmpty();
    }
    /**
     * èŽ·å–éƒ¨é—¨ç”¨æˆ·åˆ—è¡¨ï¼ˆç›´å±žå‘˜å·¥ï¼‰
     */
    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())) + "...");
            String url = feishuUrl + "open-apis/contact/v3/users/find_by_department";
            String pageToken = null;
            int pageNumber = 1;
            do {
                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 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())) + "...");
                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();
            user.setOpenId((String) userMap.getOrDefault("open_id", ""));
            String name = (String) userMap.getOrDefault("name", "");
            if (name.contains(" ")) {
                String[] parts = name.split(" ");
                if (parts.length == 2) {
                    user.setUsername(parts[0].trim());
                    user.setRealname(parts[1].trim());
                    user.setWorkNo(parts[0].trim());
                } 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");
            user.setOpenId((String) userMap.getOrDefault("open_id", ""));
            userList.add(user);
        }
        return userList;
    }
    /**
     * åŒæ­¥å•个飞书用户到系统
     */
    private boolean syncFeishuUserToSystem(FeishuUser feishuUser) {
        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/mes/entity/MessageResponse.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package org.jeecg.modules.mes.entity;
import lombok.Data;
@Data
public class MessageResponse {
    private boolean success;
    private String message;
    private String messageId;
    private Long timestamp;
}
src/main/java/org/jeecg/modules/mes/job/FeishuUserService.java
ÎļþÒÑɾ³ý
src/main/resources/application-dev.yml
@@ -126,9 +126,10 @@
        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
      datasource:
        master:
          url: jdbc:sqlserver://192.168.1.118:1433;databasename=LXZN_TEST_XHJ;nullCatalogMeansCurrent=true
          url: jdbc:sqlserver://127.0.0.1:1433;databasename=LXZN-TEXT-XHJ;nullCatalogMeansCurrent=true
          username: sa
          password: 123
          password: 123456
          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
  #redis é…ç½®
  redis: