飞书自动化PDF加密数字证书签名#

背景#

前面已经完成了钉钉自动化PDF加密数字证书签名,飞书和企业微信也应该同步跟进。可惜的是企业微信机器人限制普通机器人和使用URL回调接入的机器人不支持接收文件,只能是长连接智能机器人支持接收文件。

效果展示#

核心代码#

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import com.alibaba.fastjson.JSONObject;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;

public class FeishuUpDownUtil {
	
	/**
	 * 下载用户上传的文件
	 * @param msgId 消息ID
	 * @param fileKey 事件内的file_key
	 * @param saveName 保存文件名
	 * @return 本地绝对路径
	 */
	public String downloadFeishuFile(String token,String msgId, String fileKey, String savePath) {
	    String url = "https://open.feishu.cn/open-apis/im/v1/messages/" + msgId + "/resources/" + fileKey+"?type=file";

	    try (CloseableHttpClient client = HttpClients.createDefault()) {
	        HttpGet httpGet = new HttpGet(url);
	        httpGet.setHeader("Authorization", "Bearer " + token);
	        CloseableHttpResponse resp = client.execute(httpGet);
	        InputStream is = resp.getEntity().getContent();

	        // 写入本地
	        File outFile = new File(savePath);
	        try (FileOutputStream fos = new FileOutputStream(outFile)) {
	            byte[] buf = new byte[4096];
	            int len;
	            while ((len = is.read(buf)) != -1) {
	                fos.write(buf, 0, len);
	            }
	        }
	        resp.close();
	    } catch (IOException e) {
	    	System.out.println("下载文件失败");
			e.printStackTrace();
		}
	    return savePath;
	}
	
	/**
	 * 上传本地文件,返回可发消息的file_key
	 */
	public String uploadFeishuFile(String token,String localFilePath) {
	    String url = "https://open.feishu.cn/open-apis/im/v1/files";
	    File uploadFile = new File(localFilePath);

	    try (CloseableHttpClient client = HttpClients.createDefault()) {
	        HttpPost httpPost = new HttpPost(url);
	        httpPost.setHeader("Authorization", "Bearer " + token);

	        // 严格按照飞书要求构造multipart
	        MultipartEntityBuilder builder = MultipartEntityBuilder.create();
	        // 手动固定boundary,避免复杂符号解析失败
	        String boundary = "----feishu-file-upload-boundary-123456";
	        httpPost.setHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
	        builder.setBoundary(boundary);
	        
	        // 1. 文件类型 普通文件类型 固定小写file,强制UTF8
	        builder.addTextBody("file_type", "pdf",ContentType.TEXT_PLAIN.withCharset(java.nio.charset.StandardCharsets.UTF_8));
	        // 2. 文件名称
	        builder.addTextBody("file_name", uploadFile.getName(),ContentType.TEXT_PLAIN.withCharset(java.nio.charset.StandardCharsets.UTF_8));
	        // 3. 文件二进制
	        builder.addBinaryBody(
	                "file",
	                new FileInputStream(uploadFile),
	                ContentType.APPLICATION_OCTET_STREAM,
	                uploadFile.getName()
	        );
	        
	        HttpEntity multipart = builder.build();
	        httpPost.setEntity(multipart);

	        CloseableHttpResponse resp = client.execute(httpPost);
	        String respJson = EntityUtils.toString(resp.getEntity(), "UTF-8");
	        System.out.println("upload feishu file: "+respJson);
	        resp.close();

	        // 解析返回file_key
	        JSONObject resObj = JSONObject.parseObject(respJson);
	        JSONObject data = resObj.getJSONObject("data");
	        return data.getString("file_key");
	    } catch (IOException e) {
	    	System.out.println("上传文件失败");
			e.printStackTrace();
		}
		return null;
		
	}
	
	/**
	 * 会话内回复文件消息,替代文本发送
	 * @param chatId 会话ID
	 * @param fileKey 上传接口返回的file_key
	 * @param showFileName 前端展示文件名
	 */
	public void replyFeishuFile(String token,String chatId, String fileKey, String showFileName) {
	    String url = "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id";

	    // 构造文件消息体
	    JSONObject fileContent = new JSONObject();
	    fileContent.put("file_key", fileKey);
	    fileContent.put("file_name", showFileName);

	    JSONObject body = new JSONObject();
	    body.put("receive_id_type", "chat_id");
	    body.put("receive_id", chatId);
	    body.put("msg_type", "file");
	    body.put("content", fileContent.toString());
	    String reqJson=body.toString();
	    System.out.println(reqJson);

	    try (CloseableHttpClient client = HttpClients.createDefault()) {
	        HttpPost httpPost = new HttpPost(url);
	        httpPost.setHeader("Authorization", "Bearer " + token);
	        httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
	        StringEntity reqEntity=new StringEntity(reqJson, java.nio.charset.StandardCharsets.UTF_8);
	        httpPost.setEntity(reqEntity);

	        CloseableHttpResponse resp = client.execute(httpPost);
	        System.out.println("发送文件返回:" + respStr);
	        resp.close();
	    } catch (IOException e) {
	    	System.out.println("回复文件消息失败");
			e.printStackTrace();
		}
	}
}

注意: 豆包给的参考代码 downloadFeishuFile replyFeishuFile,参数是错误的,飞书毕竟是自己的东西,豆包都能搞错。 type=filereceive_id_type=chat_id 是URL参数,不是header