代码仓库:BatchImgUploadCSDNAndReplaceUtil.java
配套视频:https://www.bilibili.com/video/BV1md4y1n76L
1.背景
习惯用Typora
写md文档,但是导入csdn之后,图片就丢了,要浪费时间去一张一张手动上传是不可能的。
果断baidu了一下,最后虽然没有找到java
版的上传csdn的现成工具,但找到了一些python
写的上传其他网站的博客,也了解了一些相关知识。
主要两点:
-
1.如何读取md文件中的图片元素?
-
2.如何替换图片地址?
想了想,好像自己可以写出来的样子。
先用F12找出相关图片上传接口。
然后话不多说,开干。
2.主要代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 将md文件中所有本地图片,上传csdn,并替换url,最终输出为新文件
*/
public class uploadAndReplaceImgUtil {
private static String PROTOCOL = "file:" + File.separator;
// 源.md文件
private static String ORIGIN_FILE_PATH = "D:\\数据\\源文件.md";
// 新文件
private static String TARGET_FILE_PATH = "D:\\数据\\生成的新文件.md";
// csdn图片服务器地址
// 在csdn编辑界面,打开F12,上传一张图片,拿到的相关url和cookie
private static String IMG_SERVICE_URL = "https://imgservice.csdn.net/direct/v1.0/image/upload?watermark=&type=blog&rtype=markdown";
private static String IMG_OSS_URL = "https://csdn-img-blog.oss-cn-beijing.aliyuncs.com/";
// cookie
private static String COOKIE = "uuid_tt_dd=......";
private static HttpClientUtil httpClientUtil = null;
public static void main(String[] args) throws Exception {
// http请求工具类
httpClientUtil = new HttpClientUtil();
// 读取一个文件,获取到内容
String newContent = readFile(ORIGIN_FILE_PATH);
// 写入到目标文件
writeFile(newContent, TARGET_FILE_PATH);
CloseableHttpClient httpClient = httpClientUtil.getHttpClient();
if (httpClient != null) {
httpClient.close();
}
System.out.println("处理完成!");
}
/**
* 通过文件名读取一个markdown文件
*/
private static String readFile(String originFile) throws Exception {
FileInputStream fis = new FileInputStream(originFile);
final InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(isr);
String content = "";
String line = null;
while ((line = bufferedReader.readLine()) != null) {
// 按行读取
if (!line.isEmpty()) {
// 处理图片
line = handleImg(line);
}
content += line + "\r\n";
}
bufferedReader.close();
return content;
}
/**
* 判断该行是否有图片,有则上传并替换
*/
private static String handleImg(String line) throws Exception {
System.out.println(line);
System.out.println("--------line--------");
// 处理图片类型1: ![]()
// copy至大佬的代码,原来不用正则也可以处理啊
for (int i = 0; i < line.length(); i++) {
if (i < line.length() - 4 && line.charAt(i) == '!' && line.charAt(i + 1) == '[') {
int index1 = line.indexOf(']', i + 1);
if (index1 != -1 && line.charAt(index1 + 1) == '(' && line.indexOf(')', index1 + 2) != -1) {
int index2 = line.indexOf(')', index1 + 2);
// [图片描述]
String picName = line.substring(i + 2, index1);
// (图片路径)
String picPath = line.substring(index1 + 2, index2).trim();
line = toUploadImg(line, picPath);
}
}
}
// 处理图片类型2: <img src="../../../图片/我是图片.png" style="zoom:25%;" />
line = handleImgTag(line);
return line;
}
/**
* 处理html类型的img标签
* 使用正则提取
*/
private static String handleImgTag(String line) throws Exception {
Matcher mImage = Pattern.compile("<img.*?src\\s*=\\s*(.*?)[^>]*?>", Pattern.CASE_INSENSITIVE).matcher(line);
while (mImage.find()) {
// 得到<img />数据
String imgTagStr = mImage.group();
// 匹配<img>中的src数据
Matcher m = Pattern.compile("\\s{1}src=[\'\"]([^\'\"]+)[\'\"]").matcher(imgTagStr);
while (m.find()) {
String picPath = m.group(1);
System.out.println("提取img标签图片:" + picPath);
line = toUploadImg(line, picPath);
}
}
return line;
}
/**
* 上传图片之前拿到本地图片文件
* 1.以http开头的不用上传
* 2.将相对路径转为绝对路径
* 3.拿不到图片文件的不用上传
*/
private static String toUploadImg(String line, String picPath) throws Exception {
// 保存原始地址,替换时使用
String originPicPath = picPath;
if (!picPath.isEmpty() && !picPath.startsWith("http")) {
// 判断是相对路径,还是绝对路径
// https://www.jb51.net/article/64518.htm
if (picPath.startsWith("/") || picPath.indexOf(":") == 1) {
// 绝对路径
} else {
// 相对路径转绝对路径
// 使用该方法需要有协议头,比如http
String absUrl = getAbsUrl(PROTOCOL + ORIGIN_FILE_PATH, picPath);
// 切掉协议头
picPath = absUrl.substring(PROTOCOL.length());
}
System.out.println("图片绝对路径:" + picPath);
File file = new File(picPath);
if (file == null || !file.exists() || file.isDirectory()) {
// 可能url被编码过
picPath = java.net.URLDecoder.decode(picPath, "UTF-8");
file = new File(picPath);
if (file == null || !file.exists() || file.isDirectory()) {
return line;
}
}
System.out.println("图片需要上传:" + picPath);
// 上传图片
line = uploadImg(line, originPicPath, file);
}
return line;
}
/**
* 上传图片到csdn,并替换图片路径
*/
private static String uploadImg(String line, String originPicPath, File file) {
// 获取oss凭据
// 请求头
Map<String, String> headerMap = new HashMap<>();
headerMap.put("cookie", COOKIE);
headerMap.put("origin", "https://editor.csdn.net");
headerMap.put("referer", "https://editor.csdn.net/");
headerMap.put("x-image-app", "direct_blog");
headerMap.put("x-image-dir", "direct");
headerMap.put("x-image-suffix", "jpeg");
String result = httpClientUtil.sendHttpGet(IMG_SERVICE_URL, null, headerMap);
// {"code":200,"msg":"success","message":null,
// "data":{
// "accessId":"LTAI5......",
// "policy":"eyJle......",
// "signature":"Zdxo......",
// "dir":null,
// "expire":"1650621246217", // 过期时间,时间戳,ms
// "host":"https://csdn-img-blog.oss-cn-beijing.aliyuncs.com",
// "callbackUrl":"eyJjYWxsYmFja0JvZHl......", // base64编码的数据
// "filePath":"7.....12.png"}
// }
System.out.println(result);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject != null && jsonObject.get("code") != null && "200".equals(jsonObject.get("code").toString())) {
Object dataObject = jsonObject.get("data");
if (dataObject != null) {
JSONObject data = (JSONObject) JSONObject.toJSON(dataObject);
Map<String, String> paramMap = new HashMap<>();
paramMap.put("key", data.get("filePath").toString());
paramMap.put("policy", data.get("policy").toString());
paramMap.put("OSSAccessKeyId", data.get("accessId").toString());
paramMap.put("success_action_status", "200");
paramMap.put("signature", data.get("signature").toString());
paramMap.put("callback", data.get("callbackUrl").toString());
// 上传oss
String result2 = httpClientUtil.sendHttpPostAndFile(IMG_OSS_URL, paramMap, headerMap, Arrays.asList(file));
// {"code":200,"data":{"imageUrl":"https://img-blog.csdnimg.cn/7......589.png"},"msg":"success"}
System.out.println(result2);
if (result2 != null) {
JSONObject jsonObject2 = JSON.parseObject(result2);
if (jsonObject2 != null && jsonObject2.get("code") != null && "200".equals(jsonObject2.get("code").toString())) {
Object dataObject2 = jsonObject2.get("data");
if (dataObject != null) {
JSONObject data2 = (JSONObject) JSONObject.toJSON(dataObject2);
line = line.replace(originPicPath, data2.get("imageUrl").toString());
}
}
}
}
}
return line;
}
/**
* 相对路径转为绝对路径
*
* @param absolutePath 绝对路径
* @param relativePath 相对路径
* @return 绝对路径
*/
private static String getAbsUrl(String absolutePath, String relativePath) throws Exception {
URL absoluteUrl = new URL(absolutePath);
URL parseUrl = new URL(absoluteUrl, relativePath);
return parseUrl.toString();
}
/**
* 将内容写到目标文件
*/
private static void writeFile(String newContent, String targetFile) throws Exception {
File file = new File(targetFile);
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
osw.write(newContent);
osw.flush();
osw.close();
}
}
3.pom依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
4.http请求工具类
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
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.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* HttpClient请求工具类
*/
public class HttpClientUtil {
// 缓存大小
public static final int cache = 10 * 1024;
CloseableHttpClient httpClient = null;
// 配置
private static RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(15000) // 返回等待时间
.setConnectTimeout(15000) // 连接超时
.setConnectionRequestTimeout(15000)
.build();
public HttpClientUtil() {
this.httpClient = HttpClients.createDefault();
}
public CloseableHttpClient getHttpClient() {
return httpClient;
}
/**
* 发送get请求
*
* @param httpUrl 请求地址
* @param paramMap 参数,url后面
* @param headerMap 请求头
*/
public String sendHttpGet(String httpUrl, Map<String, String> paramMap, Map<String, String> headerMap) {
// 创建get请求
HttpGet httpGet = null;
if (paramMap != null && paramMap.size() > 0) {
// 封装请求参数
List<BasicNameValuePair> list = new ArrayList<>();
for (String key : paramMap.keySet()) {
list.add(new BasicNameValuePair(key, paramMap.get(key)));
}
// 转化参数
String params = null;
try {
params = EntityUtils.toString(new UrlEncodedFormEntity(list, Consts.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(params);
if (params != null) {
httpUrl = httpUrl + "?" + params;
}
}
httpGet = new HttpGet(httpUrl);
// 设置请求头
if (headerMap != null && headerMap.size() > 0) {
for (String key : headerMap.keySet()) {
httpGet.addHeader(key, headerMap.get(key));
}
}
CloseableHttpResponse response = null;
HttpEntity entity = null;
String responseContent = null;
try {
httpGet.setConfig(requestConfig);
// 执行请求
response = httpClient.execute(httpGet);
entity = response.getEntity();
responseContent = EntityUtils.toString(entity, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 关闭连接,释放资源
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return responseContent;
}
/**
* 发送form-data格式的post请求,可以上传附件
*
* @param httpUrl 地址
* @param paramMap 参数,body里面
* @param headerMap 请求头
* @param files 附件
*/
public String sendHttpPostAndFile(String httpUrl, Map<String, String> paramMap, Map<String, String> headerMap, List<File> files) {
// 创建httpPost
HttpPost httpPost = new HttpPost(httpUrl);
httpPost.setConfig(requestConfig);
// 设置参数 编码
ContentType contentType = ContentType.create(HTTP.PLAIN_TEXT_TYPE, HTTP.UTF_8);
MultipartEntityBuilder meBuilder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532);
for (String key : paramMap.keySet()) {
meBuilder.addPart(key, new StringBody(paramMap.get(key), contentType));
}
// 设置请求头
if (paramMap != null && paramMap.size() > 0) {
for (String key : headerMap.keySet()) {
httpPost.addHeader(key, headerMap.get(key));
}
}
// 附件
if (files != null && files.size() > 0) {
// 单附件或多附件
String fileKey = files.size() > 1 ? "files" : "file";
for (File file : files) {
FileBody fileBody = new FileBody(file);
meBuilder.addPart(fileKey, fileBody);
}
}
// 创建HttpEntity
HttpEntity reqEntity = meBuilder.build();
httpPost.setEntity(reqEntity);
return sendHttpPost(httpPost);
}
/**
* 发送Post请求
*/
private String sendHttpPost(HttpPost httpPost) {
CloseableHttpResponse response = null;
HttpEntity entity = null;
String responseContent = null;
try {
// 执行请求
response = httpClient.execute(httpPost);
entity = response.getEntity();
responseContent = EntityUtils.toString(entity, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 关闭连接,释放资源
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return responseContent;
}
}