钉钉自动化PDF加密数字证书签名#
背景#
前面python 自动化pdf数字证书签名我们已经实现了通过python实现PDF加密和数字证书签名,但是还需要依赖电脑,当我们正在公园玩耍时,这时候如果有PDF文件处理需求怎么办?难道要背着电脑去玩耍?或者跑回家处理?No! 可以通过钉钉/微信/飞书等机器人在云端服务器处理,然后以消息的形式回复回来。目前发现微信机器人不支持接收文件,或许非认证用户没有权限。
基本步骤#
发送文件给钉钉
然后接收文件存储到云端服务器
然后通过已经做好的python代码对文件进行PDF加密和数字证书签名
然后将处理后的文件再通过机器人回复
参考代码#
难点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();
}
}
}