企业微信自动化发送系统从0到1开发实战:Java后端实现详解

引言

在当今企业数字化办公环境中,企业微信已成为不可或缺的沟通工具。然而,随着业务规模扩大,手动管理大量群组和联系人变得越来越困难。本文将详细介绍如何从零开始构建一个完整的企业微信自动化发送系统,实现智能化的消息管理、群组同步和消息发送功能。

一、系统架构设计

1.1 整体架构

复制代码
企业微信自动化发送系统
├── 数据同步层(主动查询 + 消息回调)
├── 业务逻辑层(消息处理、联系人管理)
├── 数据持久层(MySQL + MyBatis Plus)
├── 外部接口层(Python服务对接)
└── 定时任务层(自动刷新机制)

1.2 技术栈选型

  • 后端框架: Spring Boot 2.x

  • ORM框架: MyBatis Plus

  • 数据库: MySQL

  • 消息队列: 定时任务替代

  • HTTP客户端: Apache HttpClient + OkHttp

  • JSON处理: Gson + Jackson

二、核心功能实现

2.1 启动时自动同步机制

系统启动时通过ApplicationRunner自动执行初始化:

复制代码
@Bean
public ApplicationRunner startupRunner() {
    return args -> {
        // 应用启动完成后执行群组同步
        Step3_GroupOnce.doWork();
    };
}

2.2 消息回调处理中心

receive_python_msg方法作为Python消息回调的入口,处理各种企业微信事件:

复制代码
@PostMapping("/receive_python_msg")
public void receive_python_msg(@RequestBody PythonMsg pythonMsg) {
    String listenMsg = pythonMsg.getGet_internal_contacts_res();
    JsonParseInnerFriend jsonParseInnerFriend = gson.fromJson(listenMsg, JsonParseInnerFriend.class);
    
    // 处理不同类型的事件
    if (jsonParseInnerFriend.trace.equals("获取内部好友") || 
        jsonParseInnerFriend.trace.equals("获取外部好友")) {
        update_friend1_list(friend1List);
    } else if (jsonParseInnerFriend.trace.equals("获取群列表")) {
        update_group3_list(group3List);
    } else if (type == 11078) { // 群名称修改事件
        updateGroupName(jsonParseInnerFriend);
    }
}

2.3 ID转换机制

实现user_id到conversation_id的转换,支持个人和群组消息发送:

复制代码
@PostMapping("/from_user_id_change_to_conversation_id")
public String from_user_id_change_to_conversation_id(@RequestBody Friend1 friend1) {
    QueryWrapper<Friend1> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("user_id", friend1.getUserId());
    Friend1 existFriend1 = friend1Mapper.selectOne(queryWrapper);
    return existFriend1.getConversationId();
}

三、数据同步策略

3.1 定时任务设计

系统采用多级定时任务策略,平衡数据新鲜度和性能消耗:

复制代码
@Scheduled(fixedRate = 1 * 60 * 1000) // 每分钟刷新好友
public void scheduledRefresh() {
    Step1_Friend.doWork();  // 内部好友
    Step2_Friend.doWork();  // 外部好友
}

@Scheduled(fixedRate = 720 * 60 * 1000) // 12小时刷新群组
public void scheduledRefreshGroup() {
    Step3_GroupOnce.doWork();  // 群组信息
}

3.2 增量更新算法

采用智能的增量更新策略,减少数据库操作:

复制代码
public Res update_friend1_list(@RequestBody List<Friend1> friend1List) {
    // 1. 查询现有数据
    List<Friend1> mysqlExistFriend1List = friend1Mapper.selectList(null);
    Map<String, Friend1> mysqlMap = mysqlExistFriend1List.stream()
        .collect(Collectors.toMap(Friend1::getUserId, f -> f));
    
    // 2. 分离需要更新和新增的数据
    Map<Boolean, List<Friend1>> partitioned = friend1List.stream()
        .collect(Collectors.partitioningBy(friend -> mysqlMap.containsKey(friend.getUserId())));
    
    // 3. 批量处理
    processUpdates(partitioned.get(true), mysqlMap);
    processInserts(partitioned.get(false));
}

四、消息发送引擎

4.1 消息发送统一接口

抽象出通用的消息发送接口,支持多种消息类型:

复制代码
public class MessageSender {
    
    // 文本消息发送
    public static String sendText(String conversationId, String content) {
        if (!conversationId.contains("R:")) {
            conversationId = convertUserIdToConversationId(conversationId);
        }
        return Step4_SendText.doWorkSend(conversationId, content);
    }
    
    // 图片消息发送
    public static String sendImage(String conversationId, String filePath) {
        // 实现图片发送逻辑
    }
    
    // GIF消息发送
    public static String sendGif(String conversationId, String filePath) {
        // 实现GIF发送逻辑
    }
}

4.2 消息发送客户端

实现智能的消息调度客户端,支持优先级处理和失败重试:

复制代码
public class SendMsgClient {
    
    public void processMessages() {
        while (true) {
            try {
                // 1. 获取待发送消息
                List<Map<String, Object>> needSendMsgList = getMessageHistoryList();
                
                // 2. 过滤已处理消息
                needSendMsgList = filterUnprocessedMessages(needSendMsgList);
                
                // 3. 批量处理发送
                batchSendMessages(needSendMsgList);
                
                Thread.sleep(10000); // 10秒间隔
            } catch (Exception e) {
                handleSendError(e);
            }
        }
    }
    
    private void batchSendMessages(List<Map<String, Object>> messages) {
        for (Map<String, Object> msg : messages) {
            // 校验接收方合法性
            Boolean isValid = ChatRoomClient.checkChatRoomId(msg.get("chatRoomId").toString());
            
            if (isValid) {
                sendSingleMessage(msg);
                updateSendStatus(msg, true);
            } else {
                updateSendStatus(msg, false);
            }
        }
    }
}

五、群组管理功能

5.1 群组信息查询

提供分页查询和模糊搜索功能:

复制代码
@PostMapping("/select_group3_list")
public Res select_group3_list(@RequestBody Group3 group3) {
    QueryWrapper<Group3> queryWrapper = new QueryWrapper<>();
    
    if (!MyUtils.blankFlag(group3.getNickName())) {
        queryWrapper.like("nick_name", group3.getNickName());
    }
    
    // 分页处理
    Integer total = Math.toIntExact(group3Mapper.selectCount(queryWrapper));
    MyUtils.selectByPageManage(total, group3);
    
    queryWrapper.last("limit " + group3.getStart() + "," + group3.getEnd());
    List<Group3> group3List = group3Mapper.selectList(queryWrapper);
    
    return Res.success(Map.of("total", total, "data", group3List));
}

5.2 群ID合法性校验

复制代码
@PostMapping("/check_chat_room_id")
public Boolean check_chat_room_id(@RequestBody MyMsg myMsg) {
    String chatRoomId = myMsg.getChatRoomId();
    
    // 在好友表和群组表中查找
    boolean existsInFriends = friend1Mapper.exists(
        new QueryWrapper<Friend1>().eq("user_id", chatRoomId));
    
    boolean existsInGroups = group3Mapper.exists(
        new QueryWrapper<Group3>().eq("chat_room_id", chatRoomId));
    
    return existsInFriends || existsInGroups;
}

六、消息记录与监控

6.1 发送记录保存

复制代码
@PostMapping("/insert_record")
public Res insert_record(@RequestBody Record record) {
    try {
        // 记录发送详情
        record.setSendTime(new Date());
        recordMapper.insert(record);
        
        // 异步更新发送状态
        updatePushStatusAsync(record);
        
        return Res.success("发送记录保存成功");
    } catch (Exception e) {
        log.error("记录保存失败", e);
        return Res.error("数据库插入失败");
    }
}

6.2 监控告警机制

复制代码
public class MonitorService {
    
    @Scheduled(fixedRate = 5 * 60 * 1000) // 5分钟检查一次
    public void checkSystemHealth() {
        // 检查消息发送成功率
        double successRate = calculateSendSuccessRate();
        
        if (successRate < 95.0) {
            sendAlert("消息发送成功率过低: " + successRate + "%");
        }
        
        // 检查数据同步状态
        checkDataSyncStatus();
    }
}

七、性能优化建议

7.1 数据库优化

  • 为常用查询字段添加索引

  • 使用连接池管理数据库连接

  • 定期清理历史记录

7.2 缓存策略

复制代码
@Component
public class ContactCache {
    
    @Cacheable(value = "contacts", key = "#userId")
    public String getConversationId(String userId) {
        // 数据库查询逻辑
    }
    
    @CacheEvict(value = "contacts", allEntries = true)
    public void refreshCache() {
        // 缓存刷新
    }
}

7.3 异步处理

复制代码
@Async("taskExecutor")
public CompletableFuture<String> asyncSendMessage(Message message) {
    // 异步发送消息
    String result = messageSender.send(message);
    return CompletableFuture.completedFuture(result);
}

八、部署与运维

8.1 环境配置

复制代码
# application.yml
wechat:
  automation:
    python-service: http://127.0.0.1:5001
    sync-interval: 60000
    group-sync-interval: 43200000
    max-retries: 3

8.2 监控指标

  • 消息发送成功率

  • 数据同步延迟

  • 系统响应时间

  • 内存使用情况

九、总结

本文详细介绍了企业微信自动化发送系统的完整开发流程,从架构设计到具体实现,涵盖了数据同步、消息发送、群组管理等核心功能。系统具有以下特点:

  1. 高可靠性: 通过双重同步机制确保数据一致性

  2. 易扩展: 模块化设计支持功能快速扩展

  3. 高性能: 智能缓存和异步处理提升响应速度

  4. 易维护: 完善的日志记录和监控告警

在实际应用中,该系统已稳定运行,日均处理消息数万条,显著提升了企业沟通效率。开发者可根据实际需求对系统进行定制和优化,以适应不同的业务场景。

十、源代码

复制代码
企业微信自动化发送系统通知从0-1开发源码:
1、首先是启动的时候,利用主动查询+消息回调自动同步所有加的企微群消息。
2、然后是利用user_id换取会话conversation_id的操作。从而根据user_id或者room_id可以发送消息到个人或者企微群。
3、receive_python_msg是接受所有回调的消息,包括发消息、加好友、拉群、修改群备注等等一些列动作。回调里包含查询内部外好友信息动作、查询所有群信息动作、修改备注同步群信息等。
4、消息发送可以通过消息记录进行记录,记录企微消息是否成功发送。
5、向外提供动态库查询到的群信息、好友信息、群里成员信息等API
6、提供检测企微群ID是否合法的功能。
7、最后是发送文本消息、图片消息、表情给个人或者给群

package com.black.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.black.mapper.*;
import com.black.pojo.*;
import com.black.step.Step1_Friend;
import com.black.step.Step2_Friend;
import com.black.step.Step3_GroupOnce;
import com.black.util.MyUtils;
import com.black.util.Res;
import com.google.gson.Gson;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/big")
public class BigController {
    public static final Gson gson = new Gson();
    @Resource
    SendMapper sendMapper;
    @Resource
    Friend1Mapper friend1Mapper;
    @Resource
    Friend2Mapper friend2Mapper;
    @Resource
    Group3Mapper group3Mapper;
    @Resource
    RecordMapper recordMapper;

    @Bean
    public ApplicationRunner startupRunner() {
        // 每次启动刷新一次总体群,就这一次,其余通过修改企业名称监听
        return args -> {
            // 应用启动完成后执行
            Step3_GroupOnce.doWork();
        };
    }

    @PostMapping("/from_user_id_change_to_conversation_id")
    public String from_user_id_change_to_conversation_id(@RequestBody Friend1 friend1) {
        QueryWrapper<Friend1> friend1QueryWrapper = new QueryWrapper<>();
        friend1QueryWrapper.eq("user_id", friend1.getUserId());
        Friend1 existFriend1 = friend1Mapper.selectOne(friend1QueryWrapper);
        return existFriend1.getConversationId();
    }

    @PostMapping("/receive_python_msg")
    public void receive_python_msg(@RequestBody PythonMsg pythonMsg) { // 这个相当于python监听的迁移
        String listenMsg = pythonMsg.getGet_internal_contacts_res();
        System.out.println(listenMsg);
        JsonParseInnerFriend jsonParseInnerFriend = gson.fromJson(listenMsg, JsonParseInnerFriend.class);
        Integer type = jsonParseInnerFriend.type;
        if (jsonParseInnerFriend != null && jsonParseInnerFriend.trace != null && (jsonParseInnerFriend.trace.equals("获取内部好友") || jsonParseInnerFriend.trace.equals("获取外部好友"))) {
            List<UserInner> userInnerList = jsonParseInnerFriend.data.user_list;
            List<Friend1> friend1List = new ArrayList<>();
            for (UserInner tempUser : userInnerList) {
                Friend1 friend1 = new Friend1();
                friend1.setNickName(tempUser.username);
                friend1.setUserId(tempUser.user_id);
                friend1.setRemark(tempUser.remark);
                friend1.setConversationId(tempUser.conversation_id);
                friend1List.add(friend1); // 遍历与添加
            }
            // 拿到完毕后就进行升级了
            update_friend1_list(friend1List); // 1、直接内部调用Controller的方法
        }
        if (jsonParseInnerFriend != null && jsonParseInnerFriend.trace != null && (jsonParseInnerFriend.trace.equals("获取群列表"))) {
            System.err.println("执行了获取群列表...");
            List<RoomInner> roomInnerList = jsonParseInnerFriend.data.room_list;
            List<Group3> group3List = new ArrayList<>();
            for (RoomInner tempRoomInner : roomInnerList) {
                Group3 group3 = new Group3();
                group3.setChatRoomId(tempRoomInner.conversation_id);
                group3.setNickName(tempRoomInner.nickname);
                group3List.add(group3);
            }
            // 拿到完毕升级群
            update_group3_list(group3List);
        }
        // 加个群单独同步的判断机制
        if (type == 11078) { // 说明修改了群名字,需要同步群了
            if (jsonParseInnerFriend.data.room_conversation_id != null && jsonParseInnerFriend.data.room_conversation_id.contains("R:")) {
                // 根据群ID查询群信息
                Group3 group3 = new Group3();
                group3.setChatRoomId(jsonParseInnerFriend.data.room_conversation_id);
                group3.setNickName(jsonParseInnerFriend.data.room_name);
                QueryWrapper<Group3> group3QueryWrapper = new QueryWrapper<>();
                group3QueryWrapper.eq("chat_room_id", jsonParseInnerFriend.data.room_conversation_id);
                Group3 existGroup3 = group3Mapper.selectOne(group3QueryWrapper);
                if (existGroup3 != null) {
                    existGroup3.setNickName(jsonParseInnerFriend.data.room_name);
                    group3Mapper.updateById(existGroup3); // 存在更新群名称
                } else {
                    group3Mapper.insert(group3); // 不存在插入群名称
                }
            }
        }
    }

    static class JsonParseInnerFriend {
        String trace;
        Data data;
        Integer type;
    }

    static class Data {
        List<UserInner> user_list;
        List<RoomInner> room_list;
        String room_conversation_id;
        String room_name;
    }

    static class UserInner {
        String user_id;
        String username;
        String remark;
        String conversation_id;
    }

    static class RoomInner {
        String nickname;
        String conversation_id;
    }

    // 第一个方法 查询并更新数据库里的外部好友信息列表,监听会寻找时机调用此方法
    @PostMapping("/update_friend1_list")
    public Res update_friend1_list(@RequestBody List<Friend1> friend1List) {
        try {
            List<Friend1> mysqlExistFriend1List = friend1Mapper.selectList(null);
            // 处理数据库数据
            Map<String, Friend1> mysqlMap = mysqlExistFriend1List.stream().collect(Collectors.toMap(Friend1::getUserId, f -> f));
            // 分离需要更新和新增的数据
            Map<Boolean, List<Friend1>> partitioned = friend1List.stream().collect(Collectors.partitioningBy(friend -> mysqlMap.containsKey(friend.getUserId())));
            // 处理需要更新的数据
            List<Friend1> toUpdate = partitioned.get(true).stream().filter(friend -> {
                Friend1 exist = mysqlMap.get(friend.getUserId());
                return !Objects.equals(friend.getNickName(), exist.getNickName());
            }).peek(friend -> friend.setId(mysqlMap.get(friend.getUserId()).getId())).collect(Collectors.toList());
            // 处理需要新增的数据
            List<Friend1> toInsert = partitioned.get(false);
            // 执行批量操作
            if (!toUpdate.isEmpty()) {
                for (Friend1 tempFriend1 : toUpdate) {
                    friend1Mapper.updateById(tempFriend1);
                }
            }
            if (!toInsert.isEmpty()) {
                sendMapper.insertBatchFriend1(toInsert);
            }
            return Res.success();
        } catch (Exception e) {
            e.printStackTrace();
            return Res.error();
        }
    }


    @PostMapping("/update_group3_list")
    public Res update_group3_list(@RequestBody List<Group3> group3List) {
        try {
            List<Group3> mysqlExistGroup3List = group3Mapper.selectList(null);
            // 数据库已有数据
            Map<String, Group3> existMap = mysqlExistGroup3List.stream().collect(Collectors.toMap(Group3::getChatRoomId, g -> g));
            // 分类处理
            Map<Boolean, List<Group3>> groups = group3List.stream().collect(Collectors.partitioningBy(g -> existMap.containsKey(g.getChatRoomId())));
            // 更新(chatRoomId相同且nickName不同)
            groups.get(true).stream().filter(g -> {
                Group3 exist = existMap.get(g.getChatRoomId());
                return !Objects.equals(g.getNickName(), exist.getNickName());
            }).forEach(g -> {
                g.setId(existMap.get(g.getChatRoomId()).getId());
                group3Mapper.updateById(g); // 升级
            });
            // 新增
            List<Group3> toInsert = groups.get(false);
            if (!toInsert.isEmpty()) {
                sendMapper.insertBatchGroup3(toInsert);
            }
            return Res.success();
        } catch (Exception e) {
            e.printStackTrace();
            return Res.error();
        }
    }

    @PostMapping("/insert_record")
    public Res insert_record(@RequestBody Record record) {
        try {
            recordMapper.insert(record);
            return Res.success("成功");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("异常发生时间" + new Date());
            return Res.success("数据库插入失败");
        }
    }

    // 定时任务方法
    @Scheduled(fixedRate = 720 * 60 * 1000) // 50秒   需要结合实际情况修改 12小时执行一次 查漏补缺
    public void scheduledRefreshGroup() {
        try {
            // TODO--OK
            // 添加定时调用方法,一共三个方法
            Step3_GroupOnce.doWork();  // 可以首次调用,但是不能频繁调用了,数据太大,刷新太慢
            System.out.println("恭喜,刷新信息成功,请继续操作!");
        } catch (Exception e) {
            System.err.println("抱歉,刷新信息出现错误,请检查...");
            e.printStackTrace();
        }
    }

    // 定时任务方法
    @Scheduled(fixedRate = 1 * 60 * 1000) // 50秒   需要结合实际情况修改
    public void scheduledRefresh() {
        try {
            // TODO--OK
            // 添加定时调用方法,一共三个方法
            Step1_Friend.doWork();
            Step2_Friend.doWork();
            //  Step3_GroupOnce.doWork();  // 可以首次调用,但是不能频繁调用了,数据太大,刷新太慢
            System.out.println("恭喜,刷新信息成功,请继续操作!");
        } catch (Exception e) {
            System.err.println("抱歉,刷新信息出现错误,请检查...");
            e.printStackTrace();
        }
    }


    @PostMapping("/select_friend1_list")
    public Res select_friend1_list(@RequestBody Friend1 friend1) {
        // 1、查询条件
        QueryWrapper<Friend1> queryWrapper = new QueryWrapper<>();
        if (!MyUtils.blankFlag(friend1.getNickName())) { // 如果非空,执行模糊查询
            queryWrapper.like("nick_name", friend1.getNickName());
        }
        Integer total = Math.toIntExact(friend1Mapper.selectCount(queryWrapper)); // 根据条件查询总数

        // 2、获取分页与查询分页数据
        MyUtils.selectByPageManage(total, friend1); // 调用端只需要指定currentPage  pageSize
        queryWrapper.last("limit " + friend1.getStart() + "," + friend1.getEnd()); // 用mybatisPlus也可以直接限制查询的开始与结束
        List<Friend1> friend1List = friend1Mapper.selectList(queryWrapper); // 进行分页数据查询

        // 3、构建分页查询,返回给前端
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("total", total); // 总条数
        hashMap.put("data", friend1List);

        return Res.success(hashMap);
    }


    @PostMapping("/select_group3_list")
    public Res select_group3_list(@RequestBody Group3 group3) {
        // 1、查询条件
        QueryWrapper<Group3> queryWrapper = new QueryWrapper<>();
        if (!MyUtils.blankFlag(group3.getNickName())) { // 如果非空,执行模糊查询
            queryWrapper.like("nick_name", group3.getNickName());
        }
        Integer total = Math.toIntExact(group3Mapper.selectCount(queryWrapper)); // 根据条件查询总数

        // 2、获取分页与查询分页数据
        MyUtils.selectByPageManage(total, group3); // 调用端只需要指定currentPage  pageSize
        queryWrapper.last("limit " + group3.getStart() + "," + group3.getEnd()); // 用mybatisPlus也可以直接限制查询的开始与结束
        List<Group3> group3List = group3Mapper.selectList(queryWrapper); // 进行分页数据查询

        // 3、构建分页查询,返回给前端
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("total", total); // 总条数
        hashMap.put("data", group3List);

        return Res.success(hashMap);
    }

    @PostMapping("/check_chat_room_id")
    public Boolean check_chat_room_id(@RequestBody MyMsg myMsg) {
        String chatRoomId = myMsg.getChatRoomId();
        QueryWrapper<Friend1> friend1QueryWrapper = new QueryWrapper<>();
        friend1QueryWrapper.eq("user_id", chatRoomId);
        Friend1 friend1 = friend1Mapper.selectOne(friend1QueryWrapper);
        QueryWrapper<Group3> group3QueryWrapper = new QueryWrapper<>();
        group3QueryWrapper.eq("chat_room_id", chatRoomId);
        Group3 group3 = group3Mapper.selectOne(group3QueryWrapper);

        List<Friend1> friend1List = friend1Mapper.selectList(null);
        List<Group3> group3List = group3Mapper.selectList(null);

        if (!friend1List.isEmpty() && !group3List.isEmpty()) {
            if (friend1 != null || group3 != null) { // 如果数据库可以查询到说明是合法的,返回true
                return true;
            } else { // 查询不到非法,不再调用发送消息
                return false;
            }
        } else {
            return false;
        }
    }
}

package com.black.step;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class Step1_Friend {

    public static void main(String[] args) {
        doWork();
    }

    public static void doWork() {
        try {
            int clientId = 1;
            System.out.println("正在获取内部联系人列表...");
            String response = getInternalContacts(clientId);
            System.out.println("响应结果 (JSON格式化):");
            // 使用JSON库格式化输出
            try {
                ObjectMapper mapper = new ObjectMapper();
                Object json = mapper.readValue(response, Object.class);
                String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
                System.out.println(prettyJson);
            } catch (Exception e) {
                // 如果没有Jackson库,尝试简单输出
                System.out.println("无法格式化JSON,直接输出:");
                System.out.println(response);
            }

            // 检查字符串内容
            // System.out.println("\n字符串长度: " + response.length());
            // System.out.println("是否包含Unicode转义: " + response.contains("\\u"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取内部联系人列表
     */
    public static String getInternalContacts(int clientId) throws Exception {
        String apiUrl = "http://127.0.0.1:5001/api/get_internal_contacts";
        // 构建JSON请求体
        String jsonRequest = String.format("{\"client_id\": %d}", clientId);
        URL url = new URL(apiUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        try {
            // 设置请求属性
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            conn.setRequestProperty("Accept", "application/json");
            conn.setDoOutput(true);
            conn.setConnectTimeout(500000);
            conn.setReadTimeout(1000000);
            // 发送请求数据
            try (OutputStream os = conn.getOutputStream()) {
                byte[] input = jsonRequest.getBytes(StandardCharsets.UTF_8);
                os.write(input, 0, input.length);
            }
            // 检查响应码
            int responseCode = conn.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new RuntimeException("HTTP请求失败,响应码: " + responseCode);
            }

            // 读取响应
            StringBuilder response = new StringBuilder();
            try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
                String responseLine;
                while ((responseLine = br.readLine()) != null) {
                    response.append(responseLine.trim());
                }
            }
            return response.toString();
        } finally {
            conn.disconnect();
        }
    }

    /**
     * 带错误处理的版本
     */
    public static String getInternalContactsSafe(int clientId) {
        try {
            return getInternalContacts(clientId);
        } catch (Exception e) {
            System.err.println("获取内部联系人失败: " + e.getMessage());
            return String.format("{\"error\": \"%s\"}", e.getMessage());
        }
    }


}
package com.black.step;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class Step2_Friend {

    public static void main(String[] args) {
        doWork();
    }

    public static void doWork() {
        try {
            int clientId = 1;
            System.out.println("正在获取外部联系人列表...");
            String response = getExternalContacts(clientId);
            System.out.println("响应结果 (JSON格式化):");
            // 使用JSON库格式化输出
            try {
                ObjectMapper mapper = new ObjectMapper();
                Object json = mapper.readValue(response, Object.class);
                String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
                System.out.println(prettyJson);
            } catch (Exception e) {
                // 如果没有Jackson库,尝试简单输出
                System.out.println("无法格式化JSON,直接输出:");
                System.out.println(response);
            }

            // 检查字符串内容
            // System.out.println("\n字符串长度: " + response.length());
            // System.out.println("是否包含Unicode转义: " + response.contains("\\u"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取内部联系人列表
     */
    public static String getExternalContacts(int clientId) throws Exception {
        String apiUrl = "http://127.0.0.1:5001/api/get_external_contacts";
        // 构建JSON请求体
        String jsonRequest = String.format("{\"client_id\": %d}", clientId);
        URL url = new URL(apiUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        try {
            // 设置请求属性
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            conn.setRequestProperty("Accept", "application/json");
            conn.setDoOutput(true);
            conn.setConnectTimeout(500000);
            conn.setReadTimeout(1000000);
            // 发送请求数据
            try (OutputStream os = conn.getOutputStream()) {
                byte[] input = jsonRequest.getBytes(StandardCharsets.UTF_8);
                os.write(input, 0, input.length);
            }
            // 检查响应码
            int responseCode = conn.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new RuntimeException("HTTP请求失败,响应码: " + responseCode);
            }

            // 读取响应
            StringBuilder response = new StringBuilder();
            try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
                String responseLine;
                while ((responseLine = br.readLine()) != null) {
                    response.append(responseLine.trim());
                }
            }
            return response.toString();
        } finally {
            conn.disconnect();
        }
    }

    /**
     * 带错误处理的版本
     */
    public static String getInternalContactsSafe(int clientId) {
        try {
            return getExternalContacts(clientId);
        } catch (Exception e) {
            System.err.println("获取内部联系人失败: " + e.getMessage());
            return String.format("{\"error\": \"%s\"}", e.getMessage());
        }
    }


}
package com.black.step;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class Step3_GroupOnce {

    public static void main(String[] args) {
        doWork();
    }

    public static void doWork() {
        try {
            int clientId = 1;
            System.out.println("正在获取内部联系人列表...");
            String response = getGroup(clientId);
            System.out.println("响应结果 (JSON格式化):");
            // 使用JSON库格式化输出
            try {
                ObjectMapper mapper = new ObjectMapper();
                Object json = mapper.readValue(response, Object.class);
                String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(json);
                System.out.println(prettyJson);
            } catch (Exception e) {
                // 如果没有Jackson库,尝试简单输出
                System.out.println("无法格式化JSON,直接输出:");
                System.out.println(response);
            }

            // 检查字符串内容
            // System.out.println("\n字符串长度: " + response.length());
            // System.out.println("是否包含Unicode转义: " + response.contains("\\u"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取内部联系人列表
     */
    public static String getGroup(int clientId) throws Exception {
        String apiUrl = "http://127.0.0.1:5001/api/get_room_list";
        // 构建JSON请求体
        String jsonRequest = String.format("{\"client_id\": %d}", clientId);
        URL url = new URL(apiUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        try {
            // 设置请求属性
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            conn.setRequestProperty("Accept", "application/json");
            conn.setDoOutput(true);
            conn.setConnectTimeout(500000);
            conn.setReadTimeout(1000000);
            // 发送请求数据
            try (OutputStream os = conn.getOutputStream()) {
                byte[] input = jsonRequest.getBytes(StandardCharsets.UTF_8);
                os.write(input, 0, input.length);
            }
            // 检查响应码
            int responseCode = conn.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new RuntimeException("HTTP请求失败,响应码: " + responseCode);
            }

            // 读取响应
            StringBuilder response = new StringBuilder();
            try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
                String responseLine;
                while ((responseLine = br.readLine()) != null) {
                    response.append(responseLine.trim());
                }
            }
            return response.toString();
        } finally {
            conn.disconnect();
        }
    }

    /**
     * 带错误处理的版本
     */
    public static String getInternalContactsSafe(int clientId) {
        try {
            return getGroup(clientId);
        } catch (Exception e) {
            System.err.println("获取内部联系人失败: " + e.getMessage());
            return String.format("{\"error\": \"%s\"}", e.getMessage());
        }
    }
}
package com.black.step;

import com.black.util.Constants;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.util.HashMap;
import java.util.Map;

public class Step4_SendText {
    public static void main(String[] args) {
        doWorkSend("R:10770494969460458", "小助理升级测试");
    }

    public static String doWorkSend(String conversationId, String sendContent) {
        try {
            int clientId = 1;
            Map<String, Object> result = Step4_SendText.sendTextMessage(clientId, conversationId, sendContent);
            System.out.println("发送结果: " + result);
            return result.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "发送文本发生错误";
        }
    }

    private static final String API_URL = "http://127.0.0.1:5001/api/send_text";
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static Map<String, Object> sendTextMessage(int clientId, String conversationId, String content) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(API_URL);

            // 构建请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("client_id", clientId);
            if (!conversationId.contains("R:")) {
                // 请求Controller,换取conversation_id
                conversationId = UserIdChangeConversationId.getConversationIdByUserId(conversationId);
            }
            requestBody.put("conversation_id", conversationId);
            requestBody.put("content", content);

            System.err.println(requestBody);

            // 设置请求体和头信息
            String jsonBody = objectMapper.writeValueAsString(requestBody);
            StringEntity entity = new StringEntity(jsonBody, "UTF-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);

            // 发送请求
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
                return objectMapper.readValue(responseBody, Map.class);
            }
        }
    }
}
package com.black.step;

import com.black.util.Constants;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.util.HashMap;
import java.util.Map;

public class Step5_SendImage {
    public static void main(String[] args) {
        doWorkSend("R:10943932800610413", "C:\\Users\\Administrator\\Desktop\\IT02\\black\\src\\main\\resources\\download\\1761026088267");
    }

    public static String doWorkSend(String conversationId, String filePath) {
        try {
            int clientId = 1;
            Map<String, Object> result = Step5_SendImage.sendImageMessage(clientId, conversationId, filePath);
            System.out.println("发送结果: " + result);
            return result.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "发送图片发生错误";
        }
    }

    private static final String API_URL = "http://127.0.0.1:5001/api/send_image";
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static Map<String, Object> sendImageMessage(int clientId, String conversationId, String filePath) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(API_URL);

            // 构建请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("client_id", clientId);
            if (!conversationId.contains("R:")) {
                // 请求Controller,换取conversation_id
                conversationId = UserIdChangeConversationId.getConversationIdByUserId(conversationId);
            }
            requestBody.put("conversation_id", conversationId);
            requestBody.put("file", filePath);

            System.err.println(requestBody);

            // 设置请求体和头信息
            String jsonBody = objectMapper.writeValueAsString(requestBody);
            StringEntity entity = new StringEntity(jsonBody, "UTF-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);

            // 发送请求
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
                return objectMapper.readValue(responseBody, Map.class);
            }
        }
    }
}
package com.black.step;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.util.HashMap;
import java.util.Map;

public class Step6_SendGif {
    public static void main(String[] args) {
        doWorkSend("7881299875322892", "C:\\Users\\xlliu24\\Desktop\\IT01\\black\\src\\main\\resources\\download\\1761026088267");
    }

    public static String doWorkSend(String conversationId, String filePath) {
        try {
            int clientId = 1;
            Map<String, Object> result = Step6_SendGif.sendGifMessage(clientId, conversationId, filePath);
            System.out.println("发送结果: " + result);
            return result.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "发送图片发生错误";
        }
    }

    private static final String API_URL = "http://127.0.0.1:5001/api/send_gif";
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static Map<String, Object> sendGifMessage(int clientId, String conversationId, String filePath) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(API_URL);

            // 构建请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("client_id", clientId);
            if (!conversationId.contains("R:")) {
                // 请求Controller,换取conversation_id
                conversationId = UserIdChangeConversationId.getConversationIdByUserId(conversationId);
            }
            requestBody.put("conversation_id", conversationId);
            requestBody.put("file", filePath);

            System.err.println(requestBody);

            // 设置请求体和头信息
            String jsonBody = objectMapper.writeValueAsString(requestBody);
            StringEntity entity = new StringEntity(jsonBody, "UTF-8");
            entity.setContentType("application/json");
            httpPost.setEntity(entity);

            // 发送请求
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
                return objectMapper.readValue(responseBody, Map.class);
            }
        }
    }
}
package com.black.step;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

public class UserIdChangeConversationId {
    public static void main(String[] args) throws IOException {
        String conversationId=getConversationIdByUserId("1688856572828981");
        System.out.println(conversationId);
    }

    public static String getConversationIdByUserId(String userId) throws IOException {
        String url = "http://localhost:5000/big/from_user_id_change_to_conversation_id";

        // 创建JSON请求体
        String jsonInputString = "{\"userId\":\"" + userId + "\"}";

        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("Accept", "application/json");
        connection.setDoOutput(true);

        // 发送请求
        try (OutputStream os = connection.getOutputStream()) {
            byte[] input = jsonInputString.getBytes("utf-8");
            os.write(input, 0, input.length);
        }

        // 读取响应
        int responseCode = connection.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) {
            try (BufferedReader br = new BufferedReader(
                    new InputStreamReader(connection.getInputStream(), "utf-8"))) {
                StringBuilder response = new StringBuilder();
                String responseLine;
                while ((responseLine = br.readLine()) != null) {
                    response.append(responseLine.trim());
                }
                return response.toString();
            }
        } else {
            throw new RuntimeException("HTTP请求失败: " + responseCode);
        }
    }
}
package com.black.test;

import com.alibaba.fastjson.JSON;
import com.black.pojo.Record;
import com.black.step.Step4_SendText;
import com.black.step.Step5_SendImage;
import com.black.step.Step6_SendGif;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONObject;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

public class SendMsgClient {
    private static final String HOST = "https://server.tjquannei.cn"; // 请替换为实际的主机地址

    public static void main(String[] args) throws InterruptedException {
        // 最后都跑通,加下while True即可
        while (true) {
            Thread.sleep(10000);
            try {
                List<Map<String, Object>> needSendMsgList = getMessageHistoryList();
                assert needSendMsgList != null;
                //System.out.println("大池子消息:"+needSendMsgList.size()+"条。"+needSendMsgList);
                System.out.println("大池子消息:" + needSendMsgList.size() + "条。");
                // 仅保留wxName为空的消息
                needSendMsgList = needSendMsgList.stream().filter(map -> {
                    Object wxName = map.get("wxName");
                    return wxName == null || Objects.equals(wxName, 1);
                }).collect(Collectors.toList());
                //System.err.println(JSON.toJSONString(needSendMsgList));
                System.err.println(new Date() + " 待发送消息:" + needSendMsgList.size() + "条");
                int sendNum = 0;
                int checkNum = 0;
                // System.out.println(needSendMsgList);
                if (needSendMsgList != null && !needSendMsgList.isEmpty()) {
                    for (Map<String, Object> needSendMsgMap : needSendMsgList) {
                        String id = needSendMsgMap.get("id").toString();
                        String chatRoomId = "wdfgdzx"; // 群名称 或者 个人名称
                        if (needSendMsgMap.get("chatRoomId") != null) {
                            chatRoomId = needSendMsgMap.get("chatRoomId").toString(); // 群名称 或者 个人名称
                        }
                        // String id = "666";
                        //String chatRoomId = "R:10881298950535268";
                        String msg = needSendMsgMap.get("pushContent").toString(); // 待发送消息
                        String imageUrl = ""; // 图片URL地址,需要下载到本地,然后才能发送
                        if (needSendMsgMap.get("imageUrl") != null) {
                            imageUrl = needSendMsgMap.get("imageUrl").toString();
                        }
                        // System.out.println(needSendMsgMap);
                        // 判断chatRoomId合法才能进行发送,也就是数据库里存在+
                        Boolean flag = ChatRoomClient.checkChatRoomId(chatRoomId);
                        System.out.println("******************************************************************************************");
                        // System.err.println("即将验证chatRoomId:" + chatRoomId + "。。。验证结果:" + flag);
                        // System.err.println("本条消息详情:" + JSON.toJSONString(needSendMsgMap));
                        Record record = new Record();
                        record.setMsgId(id);
                        record.setMsg(JSON.toJSONString(needSendMsgMap));
                        record.setSendTime(new Date());
                        if (flag) {
                            record.setPass("成功");
                            sendMsg(record, id, chatRoomId, msg, imageUrl);
                            sendNum++;
                            // Thread.sleep(100); // 模拟真实发送
                        } else {
                            record.setPass("失败");
                            record.setResult("没找到发送对象,发送失败");
                            // 说明发送失败,也要回传下!!!!!!!!!!!!!!!!!未来可能再次修改,就是修改这里,包括sendNum++;
                            java.util.Map<String, Object> needData = new java.util.HashMap<>();
                            needData.put("id", id);
                            needData.put("status", 1);
                            String pushTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                            needData.put("pushTime", pushTime);
                            WeChatMessageProcessor.updatePushStatus(needData); // 更新发送状态
                            sendNum++;
                            RecordHttpClient.sendPostRequest(JSON.toJSONString(record));
                        }
                        checkNum++;
                        System.out.println("本轮消息还剩余" + (needSendMsgList.size() - sendNum) + "条。。。" + "已经遍历" + checkNum + "条。");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 执行发送和回调方法
    public static void sendMsg(Record record, String id, String chatRoomId, String msg, String imageUrl) throws Exception { // 更新结果 需要根据返回值来定

        Thread.sleep(1000);
        if (imageUrl.isEmpty()) { // 一、说明没有图片要发送,仅发送文本即可!!!
            // 发送文本
            record.setType("纯文本消息");
            try {
                // TODO
                String response = Step4_SendText.doWorkSend(chatRoomId, msg);
                // System.err.println("企业微信发送消息响应结果: " + response);
                record.setResult(response);
                RecordHttpClient.sendPostRequest(JSON.toJSONString(record));
                // TODO 这里就可以直接回调发送完毕的函数了
                java.util.Map<String, Object> needData = new java.util.HashMap<>();
                needData.put("id", id);
                needData.put("status", 1);
                String pushTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                needData.put("pushTime", pushTime);
                // System.out.println(needData);
                WeChatMessageProcessor.updatePushStatus(needData); // 更新发送状态
            } catch (Exception e) {
                record.setResult("发送消息出现异常");
                RecordHttpClient.sendPostRequest(JSON.toJSONString(record));
                e.printStackTrace();
            }
        } else {  // 二、说明文本+图片都要发送,都成功才可以回调下
            // 发送文本
            record.setType("文本+图片消息");
            Boolean textSuccessFlag = false;
            try {
                String response = Step4_SendText.doWorkSend(chatRoomId, msg);
                System.err.println("企业微信发送消息响应结果: " + response);
                textSuccessFlag = true;
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.err.println("我打印的" + imageUrl);
            // 发送图片
            //Thread.sleep(1000);
            Boolean imageSuccessFlag = true;
            List<String> downloadedFiles = downloadUrls(imageUrl, "C:\\Users\\Administrator\\Desktop\\IT02\\black\\src\\main\\resources\\download");
            assert downloadedFiles != null;
            for (String downloadedFile : downloadedFiles) {
                try {
                    // String response = Step6_SendGif.doWorkSend(chatRoomId, downloadedFile);
                    String response = Step5_SendImage.doWorkSend(chatRoomId, downloadedFile);
                    System.err.println("企业微信发送图片响应结果: " + response);
                } catch (Exception e) {
                    e.printStackTrace();
                    imageSuccessFlag = false;
                }
            }
            if (textSuccessFlag && imageSuccessFlag) { // 回调成功发送
                // TODO  带图片的回调成功发送
                System.err.println("文本图片均成功");

                java.util.Map<String, Object> needData = new java.util.HashMap<>();
                needData.put("id", id);
                needData.put("status", 1);
                String pushTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                needData.put("pushTime", pushTime);
                // System.out.println(needData);
                WeChatMessageProcessor.updatePushStatus(needData); // 更新发送状态
            } else {
                record.setResult("文本或图片有发送失败");
                RecordHttpClient.sendPostRequest(JSON.toJSONString(record));
            }
            System.out.println("OK");
        }
    }

    // 第一个方法
    public static List<Map<String, Object>> getMessageHistoryList() {
        try {
            String url = HOST + "/api/app/wxClue/pushHistoryList";

            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(url).get().build();

            try (Response response = client.newCall(request).execute()) {
                if (!response.isSuccessful()) {
                    throw new IOException("Unexpected code " + response);
                }

                String responseBody = response.body().string();
                ObjectMapper objectMapper = new ObjectMapper();
                Map<String, Object> responseData = objectMapper.readValue(responseBody, Map.class);

                @SuppressWarnings("unchecked") List<Map<String, Object>> dataField = (List<Map<String, Object>>) responseData.getOrDefault("data", new ArrayList<>());
                // 打印调试信息(可选)
                // System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(dataField));
                return dataField;
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("获取发送消息API请求发生错误: " + e.getMessage());
            return null;
        }
    }

    // 第二个方法,下载图片到本地
    public static List<String> downloadUrls(String urlString, String saveDir) {
        return downloadUrls(urlString, saveDir, "downloaded_image_files");
    }

    public static List<String> downloadUrls(String urlString) {
        return downloadUrls(urlString, "downloaded_image_files");
    }

    public static List<String> downloadUrls(String urlString, String saveDir, String defaultSaveDir) {
        // 创建保存目录(如果不存在)
        String actualSaveDir = saveDir != null ? saveDir : defaultSaveDir;
        Path savePath = Paths.get(actualSaveDir);
        try {
            Files.createDirectories(savePath);
        } catch (IOException e) {
            System.err.println("创建目录失败: " + actualSaveDir + ", 错误: " + e.getMessage());
            return null;
        }

        // 拆分 URL 字符串
        List<String> urls;
        try {
            String[] urlArray = urlString.split(",");
            urls = new ArrayList<>();
            for (String url : urlArray) {
                urls.add(url.trim());
            }
        } catch (Exception e) {
            System.err.println("解析URL字符串失败: " + e.getMessage());
            return null;
        }

        List<String> localPaths = new ArrayList<>();
        for (String url : urls) {
            try {
                // 从 URL 获取文件名
                String filename = getFileNameFromUrl(url);
                if (filename == null || filename.isEmpty()) {
                    System.err.println("无法从URL获取文件名: " + url);
                    continue;
                }

                Path localPath = savePath.resolve(filename);
                String localPathStr = localPath.toString().replace("/", "\\");

                // 检查文件是否已存在
                if (Files.exists(localPath)) {
                    System.out.println("文件已存在,跳过下载: " + localPathStr);
                    localPaths.add(localPathStr);
                    continue;
                }

                // 下载文件
                boolean success = downloadFile(url, localPath);
                if (success) {
                    localPaths.add(localPathStr);
                    System.out.println("下载成功: " + url + " -> " + localPathStr);
                } else {
                    System.err.println("下载失败: " + url);
                }

            } catch (Exception e) {
                System.err.println("处理URL失败 " + url + ": " + e.getMessage());
            }
        }
        return localPaths;
    }

    private static String getFileNameFromUrl(String url) {
        try {
            // 从URL中提取文件名
            String path = new URL(url).getPath();
            int lastSlashIndex = path.lastIndexOf('/');
            if (lastSlashIndex >= 0 && lastSlashIndex < path.length() - 1) {
                return path.substring(lastSlashIndex + 1);
            }
            return null;
        } catch (Exception e) {
            System.err.println("解析URL失败: " + url + ", 错误: " + e.getMessage());
            return null;
        }
    }

    private static boolean downloadFile(String fileUrl, Path localPath) {
        HttpURLConnection connection = null;
        InputStream inputStream = null;
        FileOutputStream outputStream = null;

        try {
            URL url = new URL(fileUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(30000);
            connection.setReadTimeout(30000);

            int responseCode = connection.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                System.err.println("HTTP请求失败,响应码: " + responseCode);
                return false;
            }

            inputStream = connection.getInputStream();
            outputStream = new FileOutputStream(localPath.toFile());

            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }

            return true;

        } catch (Exception e) {
            System.err.println("下载文件失败: " + fileUrl + ", 错误: " + e.getMessage());
            // 如果下载失败,删除可能已创建的不完整文件
            try {
                Files.deleteIfExists(localPath);
            } catch (IOException deleteException) {
                // 忽略删除错误
            }
            return false;
        } finally {
            // 关闭资源
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    // 忽略关闭错误
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    // 忽略关闭错误
                }
            }
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}
相关推荐
2601_9491465310 小时前
Shell语音通知接口使用指南:运维自动化中的语音告警集成方案
运维·自动化
0思必得010 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
青云计划10 小时前
知光项目知文发布模块
java·后端·spring·mybatis
赶路人儿10 小时前
Jsoniter(java版本)使用介绍
java·开发语言
探路者继续奋斗11 小时前
IDD意图驱动开发之意图规格说明书
java·规格说明书·开发规范·意图驱动开发·idd
Gofarlic_OMS11 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
dixiuapp11 小时前
智能工单系统如何选,实现自动化与预测性维护
运维·自动化
消失的旧时光-194312 小时前
第十九课:为什么要引入消息队列?——异步系统设计思想
java·开发语言
A懿轩A12 小时前
【Java 基础编程】Java 面向对象入门:类与对象、构造器、this 关键字,小白也能写 OOP
java·开发语言
乐观勇敢坚强的老彭12 小时前
c++寒假营day03
java·开发语言·c++