企业微信机器人定时推送销售报表:Spring Boot整合EasyExcel与Quartz实战 1. 企业微信机器人基础配置销售部门每天手动整理报表再发到群里这种重复性工作完全可以交给程序自动化处理。我在实际项目中帮客户实现了这个需求整套方案用Spring Boot整合EasyExcel生成报表再通过Quartz定时推送到企业微信群效果非常稳定。首先需要配置企业微信群机器人。在企业微信PC端打开目标群聊点击右上角...→添加群机器人→新建。创建后会得到一个Webhook地址格式如下https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key693axxx-4bc4-97a0-0ec2sifa5aaa这个key就是机器人的唯一标识。我建议在代码里定义成常量public class QywxConstant { public static final String WEBHOOK_URL https://qyapi.weixin.qq.com/cgi-bin/webhook/send; public static final String UPLOAD_URL https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media; }测试机器人是否可用可以用curl发送一条测试消息curl https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key693axxx \ -H Content-Type: application/json \ -d {msgtype:text,text:{content:测试消息}}2. Spring Boot集成EasyExcel生成报表销售数据通常来自数据库我用MyBatis查询后通过EasyExcel生成美观的Excel报表。EasyExcel是阿里开源的Excel工具相比POI内存占用更低。首先添加依赖dependency groupIdcom.alibaba/groupId artifactIdeasyexcel/artifactId version3.1.1/version /dependency定义数据模型用注解配置Excel表头Data public class SalesData { ExcelProperty(日期) private String date; ExcelProperty(销售额) private BigDecimal amount; ExcelProperty(门店) private String store; }核心导出逻辑public void exportDailyReport(OutputStream out) { // 1. 查询数据 ListSalesData data salesMapper.selectDailyReport(); // 2. 设置Excel样式 ExcelWriter excelWriter EasyExcel.write(out) .registerWriteHandler(new HorizontalCellStyleStrategy()) // 自动美化样式 .build(); // 3. 写入数据 WriteSheet sheet EasyExcel.writerSheet(销售日报) .head(SalesData.class) .build(); excelWriter.write(data, sheet); // 4. 添加统计行 ListSalesData summary Collections.singletonList( new SalesData(总计, calculateTotal(data), ) ); excelWriter.write(summary, sheet); excelWriter.finish(); }实际项目中我还加了这些优化动态列根据参数决定导出哪些字段多Sheet按地区拆分不同Sheet条件格式用红色标注异常数据3. 文件上传与企业微信推送生成Excel后需要先上传到企业微信服务器获取media_id。这里踩过一个坑上传接口要求用multipart/form-data格式且文件参数名必须是media。我用HttpClient封装了上传方法public String uploadFile(String key, File file) throws IOException { String url QywxConstant.UPLOAD_URL ?key key typefile; HttpPost post new HttpPost(url); FileBody fileBody new FileBody(file, ContentType.DEFAULT_BINARY); MultipartEntityBuilder builder MultipartEntityBuilder.create(); builder.addPart(media, fileBody); // 关键参数名 post.setEntity(builder.build()); HttpResponse response httpClient.execute(post); String result EntityUtils.toString(response.getEntity()); JSONObject json JSON.parseObject(result); if(json.getInteger(errcode) ! 0) { throw new RuntimeException(上传失败: json.getString(errmsg)); } return json.getString(media_id); }发送消息时要注意文件大小不超过20MBmedia_id有效期3天频率限制20次/分钟完整推送代码public void sendReportToGroup(String key) throws IOException { // 1. 生成临时文件 File report File.createTempFile(sales_report, .xlsx); try(OutputStream out new FileOutputStream(report)) { exportDailyReport(out); } // 2. 上传获取media_id String mediaId uploadFile(key, report); // 3. 构建消息体 JSONObject msg new JSONObject(); msg.put(msgtype, file); JSONObject file new JSONObject(); file.put(media_id, mediaId); msg.put(file, file); // 4. 发送 HttpUtil.post(QywxConstant.WEBHOOK_URL ?key key, msg.toJSONString()); // 5. 清理临时文件 report.delete(); }4. Quartz定时任务配置用Spring自带的Scheduled也能实现定时但我更推荐Quartz因为它支持动态调整调度计划任务持久化集群部署添加Quartz依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-quartz/artifactId /dependency定义Job类public class SalesReportJob extends QuartzJobBean { Autowired private ReportService reportService; Override protected void executeInternal(JobExecutionContext context) { String groupKey context.getJobDetail() .getJobDataMap().getString(groupKey); reportService.sendReportToGroup(groupKey); } }配置触发器每天22点执行Configuration public class QuartzConfig { Bean public JobDetail reportJobDetail() { return JobBuilder.newJob(SalesReportJob.class) .withIdentity(salesReportJob) .usingJobData(groupKey, 693axxx) .storeDurably() .build(); } Bean public Trigger reportTrigger() { return TriggerBuilder.newTrigger() .forJob(reportJobDetail()) .withSchedule(CronScheduleBuilder .dailyAtHourAndMinute(22, 0)) .build(); } }5. 生产环境注意事项在实际部署时有几个关键点需要注意临时文件管理我见过有系统因为没清理临时文件把磁盘写满的案例。建议Path tempDir Files.createTempDirectory(sales_report); try { File report tempDir.resolve(daily.xlsx).toFile(); // ...生成文件并上传... } finally { Files.walk(tempDir) .sorted(Comparator.reverseOrder()) .map(Path::toFile) .forEach(File::delete); }异常处理机制上传失败重试3次记录每次任务执行日志添加报警通知性能优化大数据量时采用分页查询使用SXSSFWorkbook模式避免OOM异步化处理流程安全措施Webhook URL不要硬编码在代码里限制临时文件访问权限启用Quartz的数据库持久化这套方案在我们客户的生产环境稳定运行了2年多每天自动推送200门店的销售报表。最大的价值是让业务人员从重复劳动中解放出来把时间花在数据分析上。