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

背景

前面python 自动化pdf数字证书签名我们已经实现了通过python实现PDF加密和数字证书签名,但是还需要依赖电脑,当我们正在公园玩耍时,这时候如果有PDF文件处理需求怎么办?难道要背着电脑去玩耍?或者跑回家处理?No! 可以通过钉钉/微信/飞书等机器人在云端服务器处理,然后以消息的形式回复回来。目前发现微信机器人不支持接收文件,或许非认证用户没有权限。

基本步骤

  1. 发送文件给钉钉
  2. 然后接收文件存储到云端服务器
  3. 然后通过已经做好的python代码对文件进行PDF加密和数字证书签名
  4. 然后将处理后的文件再通过机器人回复

参考代码

难点1:java调用python

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Array;

public class JavaCallPython {
    
   public static void main(String [] args) {
       try {
           // ===================== 必须改成你自己的路径 =====================
           String pythonPath = "/Library/Frameworks/Python.framework/Versions/3.10/bin/python3";  // macOS/Homebrew
           // String pythonPath = "python";               // Windows
           String scriptPath = "/Users/Downloads/signpdf.py";
           String param = "/Users/input.pdf"; // 要传给 Python 的参数, 文件名可能有特殊字符用引号括起来
           // ==============================================================
           
           // 构建命令:python3 脚本路径 参数1 参数2...
           String[] commandArr = new String[]{
                   pythonPath,
                   scriptPath,
                   "-f",
                   param
           };
           
           System.out.println(Arrays.toString(commandArr));

           // 执行脚本
           Process process = Runtime.getRuntime().exec(commandArr);

           // 获取 Python 输出内容
           BufferedReader reader = new BufferedReader(
                   new InputStreamReader(process.getInputStream())
           );

           // 读取每一行 print
           String line;
           System.out.println("=== Python 输出 ===");
           while ((line = reader.readLine()) != null) {
               System.out.println(line);
           }

           // 等待脚本执行完成
           int exitCode = process.waitFor();
           System.out.println("==================");
           System.out.println("Python 退出码:" + exitCode + " (0=成功)");

           reader.close();
           process.destroy();

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

提示: 我们的参数格式是 python3 signpdf.py -f file.pdf 格式, commandArr中的每一项是不能包含空格,不能把两个参数-f file.pdf 放在一个数组项中,必须单独一项。 这地方是一个坑,打印出来的命令参数没有问题,可以在终端执行,但是就是不执行。

难点2:回复文件信息

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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;
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.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

/**
 * 钉钉文件上传/下载工具类(纯HttpClient + Common IO,无SDK依赖)
 */
public class FileUpDownUtil {
	// 钉钉机器人专用下载接口
    private final String DOWNLOAD_API_URL = "https://api.dingtalk.com/v1.0/robot/messageFiles/download";

//    // ====================== 1. 获取钉钉 access_token ======================
//    public static String getAccessToken(String appKey, String appSecret) throws Exception {
//        CloseableHttpClient client = HttpClients.createDefault();
//        HttpPost post = new HttpPost("https://oapi.dingtalk.com/gettoken");
//
//        JSONObject param = new JSONObject();
//        param.put("appkey", appKey);
//        param.put("appsecret", appSecret);
//
//        post.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
//        post.setEntity(new org.apache.http.entity.StringEntity(param.toString(), StandardCharsets.UTF_8));
//
//        CloseableHttpResponse resp = client.execute(post);
//        String result = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);
//        resp.close();
//        client.close();
//
//        JSONObject json = JSONObject.parseObject(result);
//        return json.getString("access_token");
//    }

    // ====================== 2. 上传文件到钉钉(获取 media_id) ======================
    public String uploadFile(String accessToken, File file) throws Exception {
        CloseableHttpClient client = HttpClients.createDefault();
        HttpPost post = new HttpPost("https://oapi.dingtalk.com/media/upload?access_token=" + accessToken);

        HttpEntity entity = MultipartEntityBuilder.create()
                .addBinaryBody("media", file, ContentType.DEFAULT_BINARY, file.getName())
                .addTextBody("type", "file")
                .build();

        post.setEntity(entity);
        CloseableHttpResponse resp = client.execute(post);
        String result = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8);

        resp.close();
        client.close();
        System.out.println(result);

        JSONObject json = JSONObject.parseObject(result);
        return json.getString("media_id");
    }
    
   

    // ====================== 3. 下载钉钉文件(media_id → 本地文件) ======================
    public void downloadFile(String accessToken, String mediaId, File saveFile) throws Exception {
        CloseableHttpClient client = HttpClients.createDefault();
        String url = "https://oapi.dingtalk.com/media/downloadFile?access_token=" + accessToken + "&media_id=" + mediaId;
        System.out.println(url);
        HttpGet get = new HttpGet(url);
        CloseableHttpResponse resp = client.execute(get);
        InputStream in = resp.getEntity().getContent();

        // Common IO 直接保存
        FileUtils.copyInputStreamToFile(in, saveFile);

        in.close();
        resp.close();
        client.close();
    }
    
    /**
     * 调用钉钉接口获取文件临时下载链接
     */
    public String getDownloadUrl(String accessToken, String downloadCode, String robotCode) throws IOException {
        // 使用 try-with-resources 确保资源自动释放,防止连接泄漏
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(DOWNLOAD_API_URL);
            
            // 设置请求头
            httpPost.setHeader("Content-Type", "application/json");
            httpPost.setHeader("x-acs-dingtalk-access-token", accessToken);

            // 构造请求体 JSON
            String jsonBody = String.format(
                    "{\"downloadCode\":\"%s\",\"robotCode\":\"%s\"}", 
                    downloadCode, 
                    robotCode
            );
            httpPost.setEntity(new StringEntity(jsonBody, "UTF-8"));

            // 执行请求并解析响应
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                System.out.println("钉钉接口返回结果: " + result);
                
                JSONObject json=JSONObject.parseObject(result);
                
                return json.getString("downloadUrl"); 
            }
        }
    }
    public void downloadFile(String url, File saveFile) throws Exception {
        CloseableHttpClient client = HttpClients.createDefault();

        HttpGet get = new HttpGet(url);
        CloseableHttpResponse resp = client.execute(get);
        InputStream in = resp.getEntity().getContent();

        // Common IO 直接保存
        FileUtils.copyInputStreamToFile(in, saveFile);

        in.close();
        resp.close();
        client.close();
    }

    // ====================== 4. 发送文件消息给用户 ======================
    /**
     * 向钉钉群发送文件消息
     */

	public  void sendFileMessage(String accessToken, String robotCode, String userId, String mediaId,
			String fileName, String fileType) {
		// 1. 钉钉单聊发送消息接口 URL
		String url = "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend";

		try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
			HttpPost httpPost = new HttpPost(url);

			// 2. 设置请求头(注意:新版接口使用 x-acs-dingtalk-access-token)
			httpPost.setHeader("x-acs-dingtalk-access-token", accessToken);
			httpPost.setHeader("Content-Type", "application/json");

			// 3. 构建 msgParam JSON 字符串
			JSONObject msgParamJson = new JSONObject();
			msgParamJson.put("mediaId", mediaId);
			msgParamJson.put("fileName", fileName);
			msgParamJson.put("fileType", fileType);

			// 4. 构建请求体 JSON
			JSONObject requestBody = new JSONObject();
			requestBody.put("msgParam", msgParamJson.toJSONString());
			requestBody.put("msgKey", "sampleFile"); // 发送文件的固定 msgKey
			requestBody.put("robotCode", robotCode);
			requestBody.put("userIds", new String[] { userId }); // 接收者的 userId 数组

			// 5. 设置请求体
			StringEntity entity = new StringEntity(requestBody.toJSONString(), "UTF-8");
			httpPost.setEntity(entity);

			// 6. 执行请求并处理响应
			try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
				String result = EntityUtils.toString(response.getEntity(), "UTF-8");
				System.out.println("HTTP Status: " + response.getStatusLine().getStatusCode());
				System.out.println("Response Body: " + result);
			}

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