Typora 瘦身 + 标题编号 + 图片同步

分手后的思念是犯贱 2022-12-08 04:49 260阅读 0赞

Typora 瘦身 + 标题编号 + 图片同步

1、Why???

  1. 之前使用 PicGo 插件上传图片至阿里云 OSS 服务器,结果导致所有的图片顺序全部乱掉了,于是我直接疯掉了~~~一张一张,修改图片的链接
  2. 话说,咱又不是没那技术,自己写个小工具,还能拓展一些自己想要实现的功能,何乐而不为呢
  3. 说做就做,这个小工具我已经使用并测试了很久,应该没什么大问题,但是这篇博文因为种种原因,却耽搁了许久
  4. 项目 GitHub 地址:oneby1314/typora-tools

2、代码思路

2.1、图片瘦身

【图片瘦身的功能】参考我的这篇博文:typora 瘦身,该博文主要思想为:利用正则表达式匹配文中所用到的图片标签:![](),进而获取文中所引用的图片名称,然后与本地图片对比,删除本地的无用图片

2.2、标题编号

【标题自动编号功能】参考我的这篇博文:Typora 博文标题自动编号,该博文主要思想为:利用 Markdown 语法中,各级标题的 # 语法,实现对标题的自动编号功能

2.3、图片同步

【图片同步】参考我的这篇博文:自己写个简易版 PicGo,该博文主要思想为:利用正则表达式匹配文中所用到的图片标签:![](),如果匹配到的图片为本地链接,则将其上传到阿里云 OSS 服务器上,并修改图片路径为网络 URL 地址

2.4、思路总结

我编写的小工具是如上三个功能的汇总版本,提供了配置文件,用户可自行修改配置文件,选择自己需要的功能

3、代码实现

3.1、项目结构

项目结构如下

  1. HttpUtils:阿里云 OSS 必须依赖于该 HttpUtils 工具类
  2. ResultEntity:实体类,用于封装请求的返回结果
  3. OSSConfigOSS 配置文件所对应的实体类
  4. TyporaToolConfigTypora 小工具配置文件所对应的实体类
  5. TyporaTools:程序入口
  6. OSSUtil:阿里云 OSS 上传文件的工具类
  7. TyporaFileRwUtilTypora 文件读写工具类
  8. TyporaOSSPicSyncUtil:图片同步至 OSS 的工具类
  9. TyporaPicCleanUtil:图片瘦身的工具类
  10. TyporaTiltleAutoNoUtil:标题自动编号的工具类
  11. typora-tool.properties:配置文件
  12. pom.xmlpom 依赖

image-20200916155459609

3.2、引入依赖

pom 文件中引入项目所需的依赖

image-20200916160102066

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.heygo.typora</groupId>
  7. <artifactId>typora-tools</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <dependencies>
  10. <!-- OSS客户端SDK -->
  11. <dependency>
  12. <groupId>com.aliyun.oss</groupId>
  13. <artifactId>aliyun-sdk-oss</artifactId>
  14. <version>3.5.0</version>
  15. </dependency>
  16. <!-- HttpUtil 工具类所需依赖 -->
  17. <dependency>
  18. <groupId>commons-lang</groupId>
  19. <artifactId>commons-lang</artifactId>
  20. <version>2.6</version>
  21. </dependency>
  22. <!-- Lombok 插件 -->
  23. <dependency>
  24. <groupId>org.projectlombok</groupId>
  25. <artifactId>lombok</artifactId>
  26. <version>1.16.10</version>
  27. </dependency>
  28. <!-- Junit -->
  29. <dependency>
  30. <groupId>junit</groupId>
  31. <artifactId>junit</artifactId>
  32. <version>4.0</version>
  33. </dependency>
  34. </dependencies>
  35. </project>

3.3、引入 HttpUtils

阿里云 OSS 客户端依赖于 HttpUtils 工具类,我们在项目下创建此工具类,emmm,这个工具类翻文档,就能在 GitHub 上找到对应的源代码

image-20200916160201666

  1. package com.aliyun.api.gateway.demo.util;
  2. import java.io.UnsupportedEncodingException;
  3. import java.net.URLEncoder;
  4. import java.security.KeyManagementException;
  5. import java.security.NoSuchAlgorithmException;
  6. import java.security.cert.X509Certificate;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. import java.util.Map;
  10. import javax.net.ssl.SSLContext;
  11. import javax.net.ssl.TrustManager;
  12. import javax.net.ssl.X509TrustManager;
  13. import org.apache.commons.lang.StringUtils;
  14. import org.apache.http.HttpResponse;
  15. import org.apache.http.NameValuePair;
  16. import org.apache.http.client.HttpClient;
  17. import org.apache.http.client.entity.UrlEncodedFormEntity;
  18. import org.apache.http.client.methods.HttpDelete;
  19. import org.apache.http.client.methods.HttpGet;
  20. import org.apache.http.client.methods.HttpPost;
  21. import org.apache.http.client.methods.HttpPut;
  22. import org.apache.http.conn.ClientConnectionManager;
  23. import org.apache.http.conn.scheme.Scheme;
  24. import org.apache.http.conn.scheme.SchemeRegistry;
  25. import org.apache.http.conn.ssl.SSLSocketFactory;
  26. import org.apache.http.entity.ByteArrayEntity;
  27. import org.apache.http.entity.StringEntity;
  28. import org.apache.http.impl.client.DefaultHttpClient;
  29. import org.apache.http.message.BasicNameValuePair;
  30. public class HttpUtils {
  31. /**
  32. * get
  33. *
  34. * @param host
  35. * @param path
  36. * @param method
  37. * @param headers
  38. * @param querys
  39. * @return
  40. * @throws Exception
  41. */
  42. public static HttpResponse doGet(String host, String path, String method, Map<String, String> headers,
  43. Map<String, String> querys) throws Exception {
  44. HttpClient httpClient = wrapClient(host);
  45. HttpGet request = new HttpGet(buildUrl(host, path, querys));
  46. for (Map.Entry<String, String> e : headers.entrySet()) {
  47. request.addHeader(e.getKey(), e.getValue());
  48. }
  49. return httpClient.execute(request);
  50. }
  51. /**
  52. * post form
  53. *
  54. * @param host
  55. * @param path
  56. * @param method
  57. * @param headers
  58. * @param querys
  59. * @param bodys
  60. * @return
  61. * @throws Exception
  62. */
  63. public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers,
  64. Map<String, String> querys, Map<String, String> bodys) throws Exception {
  65. HttpClient httpClient = wrapClient(host);
  66. HttpPost request = new HttpPost(buildUrl(host, path, querys));
  67. for (Map.Entry<String, String> e : headers.entrySet()) {
  68. request.addHeader(e.getKey(), e.getValue());
  69. }
  70. if (bodys != null) {
  71. List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
  72. for (String key : bodys.keySet()) {
  73. nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
  74. }
  75. UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
  76. formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
  77. request.setEntity(formEntity);
  78. }
  79. return httpClient.execute(request);
  80. }
  81. /**
  82. * Post String
  83. *
  84. * @param host
  85. * @param path
  86. * @param method
  87. * @param headers
  88. * @param querys
  89. * @param body
  90. * @return
  91. * @throws Exception
  92. */
  93. public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers,
  94. Map<String, String> querys, String body) throws Exception {
  95. HttpClient httpClient = wrapClient(host);
  96. HttpPost request = new HttpPost(buildUrl(host, path, querys));
  97. for (Map.Entry<String, String> e : headers.entrySet()) {
  98. request.addHeader(e.getKey(), e.getValue());
  99. }
  100. if (StringUtils.isNotBlank(body)) {
  101. request.setEntity(new StringEntity(body, "utf-8"));
  102. }
  103. return httpClient.execute(request);
  104. }
  105. /**
  106. * Post stream
  107. *
  108. * @param host
  109. * @param path
  110. * @param method
  111. * @param headers
  112. * @param querys
  113. * @param body
  114. * @return
  115. * @throws Exception
  116. */
  117. public static HttpResponse doPost(String host, String path, String method, Map<String, String> headers,
  118. Map<String, String> querys, byte[] body) throws Exception {
  119. HttpClient httpClient = wrapClient(host);
  120. HttpPost request = new HttpPost(buildUrl(host, path, querys));
  121. for (Map.Entry<String, String> e : headers.entrySet()) {
  122. request.addHeader(e.getKey(), e.getValue());
  123. }
  124. if (body != null) {
  125. request.setEntity(new ByteArrayEntity(body));
  126. }
  127. return httpClient.execute(request);
  128. }
  129. /**
  130. * Put String
  131. *
  132. * @param host
  133. * @param path
  134. * @param method
  135. * @param headers
  136. * @param querys
  137. * @param body
  138. * @return
  139. * @throws Exception
  140. */
  141. public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers,
  142. Map<String, String> querys, String body) throws Exception {
  143. HttpClient httpClient = wrapClient(host);
  144. HttpPut request = new HttpPut(buildUrl(host, path, querys));
  145. for (Map.Entry<String, String> e : headers.entrySet()) {
  146. request.addHeader(e.getKey(), e.getValue());
  147. }
  148. if (StringUtils.isNotBlank(body)) {
  149. request.setEntity(new StringEntity(body, "utf-8"));
  150. }
  151. return httpClient.execute(request);
  152. }
  153. /**
  154. * Put stream
  155. *
  156. * @param host
  157. * @param path
  158. * @param method
  159. * @param headers
  160. * @param querys
  161. * @param body
  162. * @return
  163. * @throws Exception
  164. */
  165. public static HttpResponse doPut(String host, String path, String method, Map<String, String> headers,
  166. Map<String, String> querys, byte[] body) throws Exception {
  167. HttpClient httpClient = wrapClient(host);
  168. HttpPut request = new HttpPut(buildUrl(host, path, querys));
  169. for (Map.Entry<String, String> e : headers.entrySet()) {
  170. request.addHeader(e.getKey(), e.getValue());
  171. }
  172. if (body != null) {
  173. request.setEntity(new ByteArrayEntity(body));
  174. }
  175. return httpClient.execute(request);
  176. }
  177. /**
  178. * Delete
  179. *
  180. * @param host
  181. * @param path
  182. * @param method
  183. * @param headers
  184. * @param querys
  185. * @return
  186. * @throws Exception
  187. */
  188. public static HttpResponse doDelete(String host, String path, String method, Map<String, String> headers,
  189. Map<String, String> querys) throws Exception {
  190. HttpClient httpClient = wrapClient(host);
  191. HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
  192. for (Map.Entry<String, String> e : headers.entrySet()) {
  193. request.addHeader(e.getKey(), e.getValue());
  194. }
  195. return httpClient.execute(request);
  196. }
  197. private static String buildUrl(String host, String path, Map<String, String> querys)
  198. throws UnsupportedEncodingException {
  199. StringBuilder sbUrl = new StringBuilder();
  200. sbUrl.append(host);
  201. if (!StringUtils.isBlank(path)) {
  202. sbUrl.append(path);
  203. }
  204. if (null != querys) {
  205. StringBuilder sbQuery = new StringBuilder();
  206. for (Map.Entry<String, String> query : querys.entrySet()) {
  207. if (0 < sbQuery.length()) {
  208. sbQuery.append("&");
  209. }
  210. if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
  211. sbQuery.append(query.getValue());
  212. }
  213. if (!StringUtils.isBlank(query.getKey())) {
  214. sbQuery.append(query.getKey());
  215. if (!StringUtils.isBlank(query.getValue())) {
  216. sbQuery.append("=");
  217. sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
  218. }
  219. }
  220. }
  221. if (0 < sbQuery.length()) {
  222. sbUrl.append("?").append(sbQuery);
  223. }
  224. }
  225. return sbUrl.toString();
  226. }
  227. private static HttpClient wrapClient(String host) {
  228. HttpClient httpClient = new DefaultHttpClient();
  229. if (host.startsWith("https://")) {
  230. sslClient(httpClient);
  231. }
  232. return httpClient;
  233. }
  234. private static void sslClient(HttpClient httpClient) {
  235. try {
  236. SSLContext ctx = SSLContext.getInstance("TLS");
  237. X509TrustManager tm = new X509TrustManager() {
  238. public X509Certificate[] getAcceptedIssuers() {
  239. return null;
  240. }
  241. public void checkClientTrusted(X509Certificate[] xcs, String str) {
  242. }
  243. public void checkServerTrusted(X509Certificate[] xcs, String str) {
  244. }
  245. };
  246. ctx.init(null, new TrustManager[] {
  247. tm }, null);
  248. SSLSocketFactory ssf = new SSLSocketFactory(ctx);
  249. ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
  250. ClientConnectionManager ccm = httpClient.getConnectionManager();
  251. SchemeRegistry registry = ccm.getSchemeRegistry();
  252. registry.register(new Scheme("https", 443, ssf));
  253. } catch (KeyManagementException ex) {
  254. throw new RuntimeException(ex);
  255. } catch (NoSuchAlgorithmException ex) {
  256. throw new RuntimeException(ex);
  257. }
  258. }
  259. }

3.4、请求结果的实体类

创建 ResultEntity 实体类,用于封装请求的返回结果

image-20200916160522009

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class ResultEntity<T> {
  5. public static final String SUCCESS = "SUCCESS";
  6. public static final String FAILED = "FAILED";
  7. // 用来封装当前请求处理的结果是成功还是失败
  8. private String result;
  9. // 请求处理失败时返回的错误消息
  10. private String message;
  11. // 要返回的数据
  12. private T data;
  13. /**
  14. * 请求处理成功且不需要返回数据时使用的工具方法
  15. *
  16. * @return
  17. */
  18. public static <Type> ResultEntity<Type> successWithoutData() {
  19. return new ResultEntity<Type>(SUCCESS, null, null);
  20. }
  21. /**
  22. * 请求处理成功且需要返回数据时使用的工具方法
  23. *
  24. * @param data 要返回的数据
  25. * @return
  26. */
  27. public static <Type> ResultEntity<Type> successWithData(Type data) {
  28. return new ResultEntity<Type>(SUCCESS, null, data);
  29. }
  30. /**
  31. * 请求处理失败后使用的工具方法
  32. *
  33. * @param message 失败的错误消息
  34. * @return
  35. */
  36. public static <Type> ResultEntity<Type> failed(String message) {
  37. return new ResultEntity<Type>(FAILED, message, null);
  38. }
  39. @Override
  40. public String toString() {
  41. return "ResultEntity [result=" + result + ", message=" + message + ", data=" + data + "]";
  42. }
  43. }

3.5、配置类与配置文件

OSSConfigTyporaToolConfig 为配置文件所对应的实体类,typora-tool.properties 为配置文件

image-20200916160628809

配置类

  1. OSSConfig:连接阿里云 OSS 所需要的配置信息

    1. /**
    2. * @ClassName OSSConfig
    3. * @Description TODO
    4. * @Author Heygo
    5. * @Date 2020/7/25 13:47
    6. * @Version 1.0
    7. */
    8. @Data
    9. @AllArgsConstructor
    10. @NoArgsConstructor
    11. public class OSSConfig {
  1. // 连接阿里云 OSS 所需要的配置信息
  2. private String endPoint;
  3. private String bucketName;
  4. private String accessKeyId;
  5. private String accessKeySecret;
  6. private String bucketDomain;
  7. // 单例对象
  8. private static OSSConfig ossConfig;
  9. // 使用静态代码块初始化,保证线程安全
  10. static {
  11. // 创建单例对象
  12. ossConfig = new OSSConfig();
  13. // 填充属性
  14. Properties prop = new Properties();
  15. try (
  16. InputStream is = TyporaToolConfig.class.getClassLoader().getResourceAsStream("typora-tool.properties");
  17. ){
  18. prop.load(is);
  19. ossConfig.setEndPoint(prop.getProperty("endPoint"));
  20. ossConfig.setBucketName(prop.getProperty("bucketName"));
  21. ossConfig.setAccessKeyId(prop.getProperty("accessKeyId"));
  22. ossConfig.setAccessKeySecret(prop.getProperty("accessKeySecret"));
  23. ossConfig.setBucketDomain(prop.getProperty("bucketDomain"));
  24. } catch (IOException e) {
  25. e.printStackTrace();
  26. System.out.println("配置文件有点问题,你去检查下哦~~~");
  27. }
  28. }
  29. // 返回单例的配置对象
  30. public static OSSConfig getOSSConfig(){
  31. return ossConfig;
  32. }
  33. }
  1. TyporaToolConfigTypora 工具所需的配置信息

    1. /**
    2. * @ClassName TyporaToolConfig
    3. * @Description TODO
    4. * @Author Heygo
    5. * @Date 2020/7/25 13:47
    6. * @Version 1.0
    7. */
    8. @Data
    9. @NoArgsConstructor
    10. @AllArgsConstructor
    11. public class TyporaToolConfig {
  1. private boolean isNeedCleanPic; // 是否需要进行图片清理
  2. private boolean isNeedTiltleAutoNo; // 是否需要进行标题编号
  3. private boolean isNeedPicSyncOSS; // 是否需要进行图片同步
  4. private String noteRootPath; // 笔记的根目录(也可以填入单个的 .md 文件)
  5. // 单例对象
  6. private static TyporaToolConfig typoraToolConfig;
  7. // 使用静态代码块初始化,保证线程安全
  8. static {
  9. // 创建单例对象
  10. typoraToolConfig = new TyporaToolConfig();
  11. // 填充属性
  12. Properties prop = new Properties();
  13. try (
  14. InputStream is = TyporaToolConfig.class.getClassLoader().getResourceAsStream("typora-tool.properties");
  15. ){
  16. prop.load(is);
  17. typoraToolConfig.isNeedCleanPic = Boolean.parseBoolean(prop.getProperty("isNeedCleanPic"));
  18. typoraToolConfig.isNeedTiltleAutoNo = Boolean.parseBoolean(prop.getProperty("isNeedTiltleAutoNo"));
  19. typoraToolConfig.isNeedPicSyncOSS = Boolean.parseBoolean(prop.getProperty("isNeedPicSyncOSS"));
  20. typoraToolConfig.setNoteRootPath(prop.getProperty("noteRootPath"));
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. System.out.println("配置文件有点问题,你去检查下哦~~~");
  24. }
  25. }
  26. // 返回单例的配置对象
  27. public static TyporaToolConfig getTyporaToolConfig(){
  28. return typoraToolConfig;
  29. }
  30. }

配置文件

typora-tool.properties 配置文件

  1. # 是否需要进行图片清理
  2. isNeedCleanPic=true
  3. # 是否需要进行标题编号
  4. isNeedTiltleAutoNo=true
  5. # 是否需要进行图片同步
  6. isNeedPicSyncOSS=true
  7. # 笔记的根目录(也可以填入单个的 .md 文件)
  8. noteRootPath=<输入你的笔记存储路径>
  9. # 阿里云 OSS 配置信息
  10. endPoint=<输入你的 endpoint>
  11. bucketName=<输入你的 bucketName>
  12. accessKeyId=<输入你的 accessKeyId>
  13. accessKeySecret=<输入你的 accessKeySecret>
  14. bucketDomain=<输入你的 bucketDomain>

3.6、文件读写工具类

TyporaFileRwUtil:对于 md 文件读写操作的封装

image-20200916161737355

  1. /**
  2. * @ClassName TyporaFileRwUtil
  3. * @Description TODO
  4. * @Author Heygo
  5. * @Date 2020/7/24 12:55
  6. * @Version 1.0
  7. */
  8. public class TyporaFileRwUtil {
  9. /**
  10. * 读取MD文件的内容
  11. *
  12. * @param curFile MD文件的File对象
  13. * @return MD文件的内容
  14. */
  15. public static String readMdFileContent(File curFile) {
  16. // 存储md文件内容
  17. StringBuilder sb = new StringBuilder();
  18. // 当前行内容
  19. String curLine;
  20. // 装饰者模式:FileReader无法一行一行读取,所以使用BufferedReader装饰FileReader
  21. try (
  22. FileReader fr = new FileReader(curFile);
  23. BufferedReader br = new BufferedReader(fr);
  24. ) {
  25. // 当前行有内容
  26. while ((curLine = br.readLine()) != null) {
  27. sb.append(curLine + "\r\n");
  28. }
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. // 返回md文件内容
  33. return sb.toString();
  34. }
  35. /**
  36. * 保存MD文件
  37. *
  38. * @param destMdFilePath MD文件路径
  39. * @param mdFileContent MD文件内容
  40. */
  41. public static void SaveMdContentToFile(String destMdFilePath, String mdFileContent) {
  42. // 不保存空文件
  43. if (mdFileContent == null || mdFileContent == "") {
  44. return;
  45. }
  46. // 执行保存
  47. try (FileWriter fw = new FileWriter(destMdFilePath)) {
  48. fw.write(mdFileContent);
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. }

3.7、OSS 上传工具类

OSSUtil:对于阿里云 OSS 上传文件、判断文件是否存在等操作的封装

image-20200916162023583

  1. /**
  2. * OSS 工具类
  3. */
  4. public class OSSUtil {
  5. /**
  6. * 上传文件至 OSS 服务器
  7. * @param endpoint OSS endpoint
  8. * @param accessKeyId OSS accessKeyId
  9. * @param accessKeySecret OSS accessKeySecret
  10. * @param bucketDomain OSS bucketDomain
  11. * @param bucketName OSS bucketName
  12. * @param inputStream 待上传文件的输入流对象
  13. * @param folderName OSS 上的文件夹路径(你要把文件存在那个文件夹找中)
  14. * @param originalName 文件的原始名称
  15. * @return 参考 ResultEntity
  16. */
  17. public static ResultEntity<String> uploadFileToOss(
  18. String endpoint,
  19. String accessKeyId,
  20. String accessKeySecret,
  21. String bucketDomain,
  22. String bucketName,
  23. InputStream inputStream,
  24. String folderName,
  25. String originalName) {
  26. // 创建OSSClient实例。
  27. OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
  28. // folderName + originalName 获得文件在 OSS 上的存储路径
  29. String objectName = folderName + "/" + originalName;
  30. try {
  31. // 调用OSS客户端对象的方法上传文件并获取响应结果数据
  32. PutObjectResult putObjectResult = ossClient.putObject(bucketName, objectName, inputStream);
  33. // 从响应结果中获取具体响应消息
  34. ResponseMessage responseMessage = putObjectResult.getResponse();
  35. // 根据响应状态码判断请求是否成功
  36. if (responseMessage == null) {
  37. // 获得刚刚上传的文件的路径
  38. String ossFileAccessPath = bucketDomain + "/" + objectName;
  39. // 当前方法返回成功
  40. return ResultEntity.successWithData(ossFileAccessPath);
  41. } else {
  42. // 获取响应状态码
  43. int statusCode = responseMessage.getStatusCode();
  44. // 如果请求没有成功,获取错误消息
  45. String errorMessage = responseMessage.getErrorResponseAsString();
  46. // 当前方法返回失败
  47. return ResultEntity.failed("当前响应状态码=" + statusCode + " 错误消息=" + errorMessage);
  48. }
  49. } catch (Exception e) {
  50. e.printStackTrace();
  51. // 当前方法返回失败
  52. return ResultEntity.failed(e.getMessage());
  53. } finally {
  54. if (ossClient != null) {
  55. // 关闭OSSClient。
  56. ossClient.shutdown();
  57. }
  58. }
  59. }
  60. /**
  61. * 查看文件是否已经存在于 OSS 服务器
  62. * @param endpoint OSS endpoint
  63. * @param accessKeyId OSS accessKeyId
  64. * @param accessKeySecret OSS accessKeySecret
  65. * @param bucketName OSS bucketName
  66. * @param folderName 文件夹路径
  67. * @param fileName 文件 file 对象
  68. * @return true:存在;false:不存在
  69. */
  70. public static Boolean isFileExitsOnOSS(
  71. String endpoint,
  72. String accessKeyId,
  73. String accessKeySecret,
  74. String bucketName,
  75. String folderName,
  76. String fileName
  77. ) {
  78. // 创建OSSClient实例。
  79. OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
  80. // 拼接 objectName
  81. String objectName = folderName + "/" + fileName;
  82. // 是否找到文件
  83. boolean isFound = false;
  84. try {
  85. // 判断文件是否存在。doesObjectExist还有一个参数isOnlyInOSS,如果为true则忽略302重定向或镜像;如果为false,则考虑302重定向或镜像。
  86. isFound = ossClient.doesObjectExist(bucketName, objectName);
  87. } catch (Exception e) {
  88. e.printStackTrace();
  89. // 抛异常则认为没找到
  90. isFound = false;
  91. } finally {
  92. // 关闭OSSClient。
  93. ossClient.shutdown();
  94. }
  95. // 返回查询结果
  96. return isFound;
  97. }
  98. }

3.8、图片瘦身工具类

TyporaPicCleanUtil:图片瘦身的工具类,之前我真是个铁头娃,没有用到的图片直接就删除了,回收站都找不到。。。现在我学聪明了,我在当前目录创建了一个 deletePic 目录,专门用于存放被删除的图片,这也算是一种软删除吧~~~

image-20200916163530485

  1. /**
  2. * @ClassName TyporaPicCleanUtil
  3. * @Description TODO
  4. * @Author Heygo
  5. * @Date 2020/7/24 11:47
  6. * @Version 1.0
  7. */
  8. public class TyporaPicCleanUtil {
  9. /**
  10. * 执行单个 md 文件的图片瘦身
  11. *
  12. * @param allPicFiles 图片 File 对象(md 文件对应的 {filename}.assets 文件夹中的所有图片)
  13. * @param mdFileContent md 文件的内容
  14. */
  15. public static void doSingleTyporaClean(File[] allPicFiles, String mdFileContent) {
  16. // 获取文中所用到的所有图片名称
  17. String[] usedPicNames = getUsedPicNames(mdFileContent);
  18. // 删除无用图片
  19. CleanUnusedPic(allPicFiles, usedPicNames);
  20. }
  21. /**
  22. * 获取 md 文件对应的 {filename}.assets 文件夹中的所有图片
  23. *
  24. * @param destMdFile md 文件的 File 对象
  25. * @return 所有本地图片的 File 对象数组
  26. */
  27. public static File[] getLocalPicFiles(File destMdFile) {
  28. // 获取 MD 文件对应的 assets 文件夹
  29. // MD 文件所在目录
  30. String mdFileParentDir = destMdFile.getParent();
  31. // MD 文件名
  32. String mdFileName = destMdFile.getName();
  33. // 不带扩展名的 MD 文件名
  34. String mdFileNameWithoutExt = mdFileName.substring(0, mdFileName.lastIndexOf("."));
  35. // 拼接得到 assets 文件夹的路径
  36. String assetsAbsolutePath = mdFileParentDir + "\\" + mdFileNameWithoutExt + ".assets";
  37. // assets 目录的 File 对象
  38. File assetsFile = new File(assetsAbsolutePath);
  39. // 获取 assets 文件夹中的所有图片
  40. return assetsFile.listFiles();
  41. }
  42. /**
  43. * 获取 md 文件中使用到的图片名称
  44. *
  45. * @param mdFileContent md 文件的内容
  46. * @return 使用到的图片名称
  47. */
  48. private static String[] getUsedPicNames(String mdFileContent) {
  49. // 图片名称
  50. // 图片路径存储格式:![image-20200603100128164](IDEA快捷键.assets/image-20200603100128164.png)
  51. /*
  52. \[.*\]:[image-20200603100128164]
  53. . :匹配任意字符
  54. * :出现0次或多次
  55. \(.+\):(IDEA快捷键.assets/image-20200603100128164.png)
  56. . :匹配任意字符
  57. + :出现1次或多次
  58. */
  59. String regex = "!\\[.*\\]\\(.+\\)";
  60. // 匹配文章中所有的图片标签
  61. Matcher matcher = Pattern.compile(regex).matcher(mdFileContent);
  62. // imageNames 用于存储匹配到的图片标签
  63. List<String> imageNames = new ArrayList<>();
  64. //遍历匹配项,将其添加至集合中
  65. while (matcher.find()) {
  66. // 得到当前图片标签
  67. String curImageLabel = matcher.group();
  68. // 放心大胆地使用"/"截取子串,因为文件名不能包含"/"字符
  69. Integer picNameStartIndex = curImageLabel.lastIndexOf("/") + 1;
  70. Integer picNameEndIndex = curImageLabel.length() - 1;
  71. // 得到图片名称
  72. String curImageName = curImageLabel.substring(picNameStartIndex, picNameEndIndex);
  73. // 添加至集合中
  74. imageNames.add(curImageName);
  75. }
  76. // 转换为数组返回
  77. String[] retStrs = new String[imageNames.size()];
  78. return imageNames.toArray(retStrs);
  79. }
  80. /**
  81. * 清除无用图片
  82. *
  83. * @param allPicFiles 本地所有的图片 File 对象
  84. * @param usedPicNames md 文件中使用到的图片名称
  85. */
  86. private static void CleanUnusedPic(File[] allPicFiles, String[] usedPicNames) {
  87. // assets文件夹中如果没有图片,则直接返回
  88. if (allPicFiles == null || allPicFiles.length == 0) {
  89. return;
  90. }
  91. // 临时文件夹,保存被删除的图片
  92. File deletePicDir = new File(allPicFiles[0].getParentFile().getParent() + "\\" + "deletePic");
  93. if (deletePicDir.exists() == false) {
  94. deletePicDir.mkdir();
  95. }
  96. // 获取asset文件夹的绝对路径
  97. String assetPath = allPicFiles[0].getParent();
  98. // 为了便于操作,将数组转换为List
  99. List<String> usedPicNameList = Arrays.asList(usedPicNames);
  100. // 遍历所有本地图片,看看有哪些图片没有被使用
  101. for (File curPicFile : allPicFiles) {
  102. // 如果没有被使用,则添加至unusedPicNames集合
  103. String curFileName = curPicFile.getName();
  104. boolean isUsed = usedPicNameList.contains(curFileName);
  105. if (!isUsed) {
  106. // 创建File对象,用于删除
  107. String curPicAbsolutePath = curPicFile.getAbsolutePath();
  108. // 删除文件,看看回收站还有没有,并没有。。。
  109. // curPicFile.delete();
  110. // 将待删除的图片移动至 deletePic 文件夹
  111. String destPicPath = deletePicDir.getAbsolutePath() + "\\" + curPicFile.getName();
  112. curPicFile.renameTo(new File(destPicPath));
  113. // 测试用:打印输出
  114. System.out.println("已移动无用图片至:" + destPicPath);
  115. }
  116. }
  117. }
  118. }

3.9、标题编号工具类

TyporaTiltleAutoNoUtil :标题自动编号的工具类

image-20200916163554968

  1. /**
  2. * @ClassName TyporaTiltleAutoNoUtil
  3. * @Description TODO
  4. * @Author Heygo
  5. * @Date 2020/7/24 12:43
  6. * @Version 1.0
  7. */
  8. public class TyporaTiltleAutoNoUtil {
  9. /**
  10. * 执行单个文件的标题自动编号
  11. *
  12. * @param mdFileContent md 文件的内容
  13. * @return 标题编号之后的 md 文件内容
  14. */
  15. public static String doSingleMdTitleAutoNo(String mdFileContent) {
  16. return getAutoTitledMdContent(mdFileContent);
  17. }
  18. /**
  19. * 执行单个文件的标题自动编号
  20. *
  21. * @param mdFileContent md 文件的内容
  22. * @return 标题编号之后的 md 文件内容
  23. */
  24. private static String getAutoTitledMdContent(String mdFileContent) {
  25. // 标题编号
  26. /*
  27. 标题编号规则:
  28. - 一级标题为文章的题目,不对一级标题编号
  29. - 二级、三级、四级标题需要级联编号
  30. - 五级、六级标题无需级联编号,只需看上一级标题的脸色,递增即可
  31. */
  32. Integer[] titleNumber = new Integer[]{
  33. 0, 0, 0, 0, 0};
  34. // 存储md文件内容
  35. StringBuilder sb = new StringBuilder();
  36. // 当前行内容
  37. String curLine;
  38. // 当前内容是否在代码块中
  39. boolean isCodeBLock = false;
  40. try (
  41. StringReader sr = new StringReader(mdFileContent);
  42. BufferedReader br = new BufferedReader(sr);
  43. ) {
  44. while ((curLine = br.readLine()) != null) {
  45. // 忽略代码块
  46. if (curLine.trim().startsWith("```")) {
  47. isCodeBLock = !isCodeBLock;
  48. }
  49. if (isCodeBLock == false) {
  50. // 判断是否为标题行,如果是标题,是几级标题
  51. Integer curTitleLevel = calcTitleLevel(curLine);
  52. if (curTitleLevel != -1) {
  53. // 插入标题序号
  54. curLine = insertTitleNumber(curLine, titleNumber);
  55. // 重新计算标题计数器
  56. RecalcTitleCounter(curTitleLevel, titleNumber);
  57. }
  58. }
  59. // 向缓冲区中追加内容
  60. sb.append(curLine + "\r\n");
  61. }
  62. return sb.toString();
  63. } catch (IOException e) {
  64. e.printStackTrace();
  65. return "";
  66. }
  67. }
  68. /**
  69. * 根据当前行的内容,计算标题的等级
  70. *
  71. * @param curLine 当前行的内容
  72. * @return 当前行的标题等级:-1 表示不是标题行,>=2 的正数表示标题的等级
  73. */
  74. private static Integer calcTitleLevel(String curLine) {
  75. // 由于一级标题无需编号,所以从二级标题开始判断
  76. boolean isTitle = curLine.startsWith("##");
  77. if (!isTitle) {
  78. // 返回 -1 表示非标题行
  79. return -1;
  80. }
  81. // 现在来看看是几级标题
  82. Integer titleLevel = curLine.indexOf(" ");
  83. return titleLevel;
  84. }
  85. /**
  86. * 在当前行前面插入标题的等级
  87. *
  88. * @param curLine 当前行的内容
  89. * @param titleNumber 标题计数器
  90. * @return 添加标题之后的行
  91. */
  92. private static String insertTitleNumber(String curLine, Integer[] titleNumber) {
  93. // 标题等级(以空格分隔的前提是 Typora 开启严格模式)
  94. Integer titleLevel = curLine.indexOf(" ");
  95. // 标题等级部分
  96. String titleLevelStr = curLine.substring(0, titleLevel);
  97. // 标题内容部分
  98. String titleContent = curLine.substring(titleLevel + 1);
  99. // 先去除之前的编号
  100. titleContent = RemovePreviousTitleNumber(titleContent);
  101. // 标题等级递增
  102. Integer titleIndex = titleLevel - 2;
  103. if (titleIndex > 5) {
  104. System.out.println(titleIndex);
  105. }
  106. titleNumber[titleIndex] += 1;
  107. // 标题序号
  108. String titleNumberStr = "";
  109. switch (titleLevel) {
  110. case 2:
  111. titleNumberStr = titleNumber[0].toString();
  112. break;
  113. case 3:
  114. titleNumberStr = titleNumber[0].toString() + "." + titleNumber[1];
  115. break;
  116. case 4:
  117. titleNumberStr = titleNumber[0].toString() + "." + titleNumber[1] + "." + titleNumber[2];
  118. break;
  119. case 5:
  120. titleNumberStr = titleNumber[3].toString();
  121. break;
  122. case 6:
  123. titleNumberStr = titleNumber[4].toString() + " ) ";
  124. break;
  125. }
  126. titleNumberStr += "、";
  127. // 插入标题序号
  128. titleContent = titleNumberStr + titleContent;
  129. System.out.println("已增加标题序号:" + titleContent);
  130. // 返回带序号的标题
  131. curLine = titleLevelStr + " " + titleContent;
  132. return curLine;
  133. }
  134. /**
  135. * 当上一级标题更新时,需重置子级标题计数器
  136. *
  137. * @param titleLevel 当前标题等级
  138. * @param titleNumber 标题计数器
  139. */
  140. private static void RecalcTitleCounter(Integer titleLevel, Integer[] titleNumber) {
  141. // 二级标题更新时,三级及三级以下的标题序号重置为 0
  142. Integer startIndex = titleLevel - 1;
  143. for (int i = startIndex; i < titleNumber.length; i++) {
  144. titleNumber[i] = 0;
  145. }
  146. }
  147. /**
  148. * 移除之前的标题编号
  149. * @param curLine 当前行内容
  150. * @return 移除标题编号之后的行
  151. */
  152. private static String RemovePreviousTitleNumber(String curLine) {
  153. // 寻找标题中的 、 字符
  154. Integer index = curLine.indexOf("、");
  155. if (index > 0 && index < 6) {
  156. // 之前已经进行过标号
  157. return curLine.substring(index + 1);
  158. } else {
  159. // 之前未进行过标号,直接返回
  160. return curLine;
  161. }
  162. }
  163. }

3.10、图片同步工具类

TyporaOSSPicSyncUtil:图片同步至阿里云 OSS 服务器的工具类

image-20200916163730086

  1. /**
  2. * @ClassName TyporaOSSPicSyncUtil
  3. * @Description TODO
  4. * @Author Heygo
  5. * @Date 2020/7/24 12:44
  6. * @Version 1.0
  7. */
  8. public class TyporaOSSPicSyncUtil {
  9. /**
  10. * 执行单个 md 文件的图片同步
  11. *
  12. * @param allPicFiles 本地图片的 File 对象数组
  13. * @param mdFileContent md 文件的内容
  14. * @return 图片同步之后,md 文件的内容(已经将本地图片链接替换为网路链接)
  15. */
  16. public static String doSingleMdPicSyncToOSS(File[] allPicFiles, String mdFileContent) {
  17. // 如果本地图片都没有,还同步个鸡儿
  18. if (allPicFiles == null) {
  19. return mdFileContent;
  20. }
  21. return changeLocalReferToUrlRefer(allPicFiles, mdFileContent);
  22. }
  23. /**
  24. * 执行单个 md 文件的图片同步
  25. *
  26. * @param allPicFiles 本地图片的 File 对象数组
  27. * @param mdFileContent md 文件的内容
  28. * @return 图片同步之后,md 文件的内容(已经将本地图片链接替换为网路链接)
  29. */
  30. private static String changeLocalReferToUrlRefer(File[] allPicFiles, String mdFileContent) {
  31. // 存储md文件内容
  32. StringBuilder sb = new StringBuilder();
  33. // 当前行内容
  34. String curLine;
  35. // 获取所有本地图片的名称
  36. List<String> allPicNames = new ArrayList<>();
  37. for (File curPicFile : allPicFiles) {
  38. // 获取图片名称
  39. String curPicName = curPicFile.getName();
  40. // 添加至集合中
  41. allPicNames.add(curPicName);
  42. }
  43. try (
  44. StringReader sr = new StringReader(mdFileContent);
  45. BufferedReader br = new BufferedReader(sr);
  46. ) {
  47. while ((curLine = br.readLine()) != null) {
  48. // 图片路径存储格式:![image-20200711220145723](https://heygo.oss-cn-shanghai.aliyuncs.com/Software/Typora/Typora_PicGo_CSDN.assets/image-20200711220145723.png)
  49. // 正则表达式
  50. /*
  51. ^$:匹配一行的开头和结尾
  52. \[.*\]:![image-20200711220145723]
  53. . :匹配任意字符
  54. * :出现0次或多次
  55. \(.+\):(https://heygo.oss-cn-shanghai.aliyuncs.com/Software/Typora/Typora_PicGo_CSDN.assets/image-20200711220145723.png)
  56. . :匹配任意字符
  57. + :出现1次或多次
  58. */
  59. String regex = "!\\[.*\\]\\(.+\\)";
  60. // 执行正则表达式
  61. Matcher matcher = Pattern.compile(regex).matcher(curLine);
  62. // 是否匹配到图片路径
  63. boolean isPicUrl = matcher.find();
  64. // 如果当前行是图片链接,干他
  65. if (isPicUrl) {
  66. // 检查图片是否已经是网络 URL 引用,如果已经是网络 URL 引用,则不需做任何操作
  67. Boolean isOSSUrl = curLine.contains("http://") || curLine.contains("https://");
  68. if (!isOSSUrl) {
  69. // 提取图片路径前面不变的部分
  70. Integer preStrEndIndex = curLine.indexOf("(");
  71. String preStr = curLine.substring(0, preStrEndIndex + 1);
  72. // 获取图片名称
  73. Integer picNameStartIndex = curLine.lastIndexOf("/");
  74. Integer curLineLength = curLine.length();
  75. String picName = curLine.substring(picNameStartIndex + 1, curLineLength - 1);
  76. // 拿到 URl 在 List 中的索引
  77. Integer picIndex = allPicNames.indexOf(picName);
  78. // 如果图片是真实存在于本地磁盘上的
  79. if (picIndex != -1) {
  80. // 拿到待上传的图片 File 对象
  81. File needUploadPicFile = allPicFiles[picIndex];
  82. // 检查 OSS 上是否已经有该图片的 URL
  83. String picOSSUrl = findPicOnOSS(needUploadPicFile);
  84. // 在 OSS 上找不到才执行上传
  85. if (picOSSUrl == "") {
  86. // 执行上传
  87. picOSSUrl = uploadPicToOSS(needUploadPicFile);
  88. }
  89. // 拼接得到 typora 中的图片链接
  90. curLine = preStr + picOSSUrl + ")";
  91. // 打印输出日志
  92. System.out.println("修改图片连接:" + curLine);
  93. }
  94. }
  95. }
  96. sb.append(curLine + "\r\n");
  97. }
  98. return sb.toString();
  99. } catch (IOException e) {
  100. e.printStackTrace();
  101. return "";
  102. }
  103. }
  104. /**
  105. * 上传图片至阿里云 OSS 服务器
  106. * @param curPicFile 图片 File 对象
  107. * @return 图片上传后的 URL 地址
  108. */
  109. private static String uploadPicToOSS(File curPicFile) {
  110. // 目录名称,注意:不建议使用特殊符号和中文,会进行 URL 编码
  111. String folderName = "images";
  112. // 获取 OSS 配置信息
  113. OSSConfig ossConfig = OSSConfig.getOSSConfig();
  114. // 执行上传
  115. InputStream is = null;
  116. try {
  117. // 文件输入流
  118. is = new FileInputStream(curPicFile);
  119. // 文件名称
  120. String curFileName = curPicFile.getName();
  121. // 执行上传
  122. ResultEntity<String> resultEntity = OSSUtil.uploadFileToOss(
  123. ossConfig.getEndPoint(),
  124. ossConfig.getAccessKeyId(),
  125. ossConfig.getAccessKeySecret(),
  126. ossConfig.getBucketDomain(),
  127. ossConfig.getBucketName(),
  128. is,
  129. folderName,
  130. curFileName
  131. );
  132. if (ResultEntity.SUCCESS.equals(resultEntity.getResult())) {
  133. // 上传成功:URL 格式:http://heygo.oss-cn-shanghai.aliyuncs.com/images/2020-07-12_204547.png
  134. String url = resultEntity.getData();
  135. return url;
  136. } else {
  137. // 上传失败,返回空串
  138. System.out.println(resultEntity.getMessage());
  139. return "";
  140. }
  141. } catch (FileNotFoundException e) {
  142. // 发生异常也返回空串
  143. e.printStackTrace();
  144. return "";
  145. }
  146. }
  147. /**
  148. * 在 OSS 服务器上查找是否存在目标图片
  149. * @param curPicFile 目标图片
  150. * @return 图片的网络路径:如果为"",则表示图片不存在于 OSS 服务器上;如果不为"",则表示图片在 OSS 上的路径
  151. */
  152. private static String findPicOnOSS(File curPicFile) {
  153. // 获取 OSS 配置信息
  154. OSSConfig ossConfig = OSSConfig.getOSSConfig();
  155. // 目录名称
  156. String folderName = "images";
  157. // 获取文件路径
  158. String fileName = curPicFile.getName();
  159. // 判断是否存在于 OSS 中
  160. Boolean isExist = OSSUtil.isFileExitsOnOSS(
  161. ossConfig.getEndPoint(),
  162. ossConfig.getAccessKeyId(),
  163. ossConfig.getAccessKeySecret(),
  164. ossConfig.getBucketName(),
  165. folderName,
  166. fileName
  167. );
  168. // 不存在返回空串
  169. if (!isExist) {
  170. return "";
  171. }
  172. // 拼接图片 URL 并返回
  173. String bucketDomain = ossConfig.getBucketDomain();
  174. String picUrl = bucketDomain + "/images/" + fileName;
  175. return picUrl;
  176. }
  177. }

3.11、程序主逻辑编写

TyporaTools:程序入口,递归遍历笔记的根目录(也可以是单个的 .md 文件),进行图片瘦身、标题编号、图片同步等操作

image-20200916164416338

  1. /**
  2. * @ClassName TyporaTools
  3. * @Description TODO
  4. * @Author Heygo
  5. * @Date 2020/7/24 11:44
  6. * @Version 1.0
  7. */
  8. public class TyporaTools {
  9. public static void main(String[] args) {
  10. // 笔记存储根目录
  11. String noteRootPath = TyporaToolConfig.getTyporaToolConfig().getNoteRootPath();
  12. // 根据配置需要,执行 Typora 文件瘦身、标题自动编号、图片同步至 OSS 等功能
  13. doMainBusiness(noteRootPath);
  14. }
  15. private static void doMainBusiness(String destPath) {
  16. // 获取当前路径的File对象
  17. File destPathFile = new File(destPath);
  18. // 如果是文件,执行单个md文件的图片瘦身,然后递归返回即可
  19. if (destPathFile.isFile()) {
  20. doSingleMdMainBusiness(destPathFile);
  21. return;
  22. }
  23. // 获取当前路径下所有的子文件和路径
  24. File[] allFiles = destPathFile.listFiles();
  25. // 遍历allFiles
  26. for (File curFile : allFiles) {
  27. // 获取curFile对象是否为文件夹
  28. Boolean isDirectory = curFile.isDirectory();
  29. // 获取当前curFile对象对应的绝对路径名
  30. String absolutePath = curFile.getAbsolutePath();
  31. // 如果是文件夹
  32. if (isDirectory) {
  33. // 如果是asset文件夹,则直接调过
  34. if (absolutePath.endsWith(".assets")) {
  35. continue;
  36. }
  37. }
  38. // 如果是文件夹,则继续执行递归
  39. doMainBusiness(absolutePath);
  40. }
  41. }
  42. private static void doSingleMdMainBusiness(File destMdFile) {
  43. // 如果不是 MD 文件,滚蛋
  44. Boolean isMdFile = destMdFile.getName().endsWith(".md");
  45. if (!isMdFile) {
  46. return;
  47. }
  48. // 读取 MD 文件内容
  49. String mdFileContent = TyporaFileRwUtil.readMdFileContent(destMdFile);
  50. // 获取当前 MD 文件的图片 File 对象数组
  51. File[] allPicFiles = TyporaPicCleanUtil.getLocalPicFiles(destMdFile);
  52. // 获取配置文件内容
  53. TyporaToolConfig typoraToolConfig = TyporaToolConfig.getTyporaToolConfig();
  54. // Typora 瘦身
  55. if (typoraToolConfig.isNeedCleanPic()) {
  56. System.out.println(destMdFile.getName() + " 开始执行瘦身计划...");
  57. TyporaPicCleanUtil.doSingleTyporaClean(allPicFiles, mdFileContent);
  58. System.out.println(destMdFile.getName() + " 瘦身计划执行完毕~~~");
  59. System.out.println();
  60. }
  61. // 标题自动标号
  62. if (typoraToolConfig.isNeedTiltleAutoNo()) {
  63. System.out.println(destMdFile.getName() + " 开始执行标题自动标号...");
  64. mdFileContent = TyporaTiltleAutoNoUtil.doSingleMdTitleAutoNo(mdFileContent);
  65. System.out.println(destMdFile.getName() + " 标题自动标号完成~~~");
  66. System.out.println();
  67. }
  68. // 图片同步至阿里云 OSS
  69. if (typoraToolConfig.isNeedPicSyncOSS()) {
  70. System.out.println(destMdFile.getName() + " 开始执行图片同步至 OSS...");
  71. mdFileContent = TyporaOSSPicSyncUtil.doSingleMdPicSyncToOSS(allPicFiles, mdFileContent);
  72. System.out.println(destMdFile.getName() + " 图片同步至 OSS 完成~~~");
  73. System.out.println();
  74. }
  75. // 执行保存
  76. TyporaFileRwUtil.SaveMdContentToFile(destMdFile.getPath(), mdFileContent);
  77. }
  78. }

4、结束语

有想法就一定要付诸于行动,哪怕是造轮子,造个轮子之后,你也会对轮子的结构有更深入、更清晰的了解

发表评论

表情:
评论列表 (有 0 条评论,260人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Android - App

    为什么要瘦身 安装包变大,导致很多用户不愿意安装更新 安装包变大,导致很多用户不愿意下载 安装包变大,流量使用增多,增加其他边际成本 优化方式

    相关 springboot 项目

    一、前言 [Spring Boot][Spring_Boot]部署起来虽然简单,如果服务器部署在公司内网,速度还行,但是如果部署在公网,部署起来实在头疼:编译出来的 Jar

    相关 Android-APP

           为了给用户带来更好的体验,我们应该为用户着想,首先要简化我们的app,去给我们的app瘦身,一次来减少app为用户带来的不良影响。       app瘦身有两种

    相关 spring boot

    Spring Boot 越来越流行,使用Spring Boot 技术的公司和项目也越来越多, 相比之前框架中大量的配置文件,繁琐的配置确实方便了很多,提高了开发的效率. 不同