爬取CSDN博文到本地(包含图片,标签等信息)

爬取CSDN博文到本地(包含图片,标签等信息)

csdnToMD

项目原作者:https://gitee.com/liushili888/csdn-is---mark-down

改进后仓库地址:https://github.com/Xiamu-ssr/csdnToMD

改进

这里进行一定的改进,可以更准确获取时间,也可以选择图片的存放方式是否集中或分离到每篇文章的同名文件夹,以适应部分md扩展语法,比如{% asset_img 1.png %}

爬取结果截图

将CSDN文章转化为Markdown文档

很多情况下,我们需要将CSDN中的文章转化为markdown文档,直接复制全文是不可以的,CSDN不支持。

那有什么办法快速得到md文档?

原理:

  • 由于CSDN不是获取数据不是前后端分离的,所以无法根据接口获取文章的所有数据,它的数据是和页面元素组合在一起的,需要根据页面中的元素标签转化为markdown中的元素标签。
  • 使用jsoup解析csdn文档
  • 利用jericho-htmlflexmark-alljsoupselenium等工具将html文档转化为markdown文档

使用:

  • https://googlechromelabs.github.io/chrome-for-testing/下载chromedriver,解压后修改DynamicScraperTime函数的驱动地址。如果不下载驱动也可以,把String time = DynamicScraperTime(startUrl);的获取换成下一行被注释的就行,但是因为页面动态加载的原因,会无法获取准确的时间。
  • 然后直接将CSDN文章的url放入crawler类即可

例如:

获取单个文章markdown

1
2
3
4
5
6
7
8
public class Main {
private static final String CSDN_URL = "https://blog.csdn.net/m0_51390969/";

public static void main(String[] args) {
AbstractCrawler crawler = new CsdnCrawler();
crawler.crawlOne("https://blog.csdn.net/m0_51390969", "131172667");
}
}

获取所有的文章markdown

1
2
3
4
5
6
7
8
public class Main {
private static final String CSDN_URL = "https://blog.csdn.net/m0_51390969/";

public static void main(String[] args) {
AbstractCrawler crawler = new CsdnCrawler();
crawler.crawl(CSDN_URL);
}
}

项目中待解决的问题

TODO ‘> ’标签中包含代码块,需要处理

TODO 代码中格式待处理

TODO 增加GUI页面

TODO 公式、表格标签的处理

实现Hexo新建博文时自带随机默认封面

实现Hexo新建博文时自带随机默认封面

前提是选择的主题在Front-matter中支持cover和thumbnail,主题之间对于这两个属性的定义可能并不用,如果不适用,只需要根据逻辑修改脚本即可。

1. Hexo模版

scaffolds文件夹下,有三个md文件即模版文件,平时我们hexo new post "title"的时候就是基于post.md生成的。

1
2
3
4
scaffolds
draft.md
page.md
post.md

将其修改为如下内容,补充属性,并添加2个特殊的字符串COVER_PLACEHOLDERTHUMBNAIL_PLACEHOLDER,作为占位符,方便替换。

1
2
3
4
5
6
7
8
9
10
11
12
---
title: {{ title }}
date: {{ date }}
comments: true
cover: COVER_PLACEHOLDER
thumbnail: THUMBNAIL_PLACEHOLDER
tags:
- 未分类
categories:
- 未分类
description:
---

2. 准备封面和缩略图

在source文件夹下新建gallery文件夹,并放入5张封面图和5张缩略图,封面建议1920*1080,缩略图建议200*200 。封面和缩略图是一一对应的。

1
2
3
4
5
6
7
8
9
10
11
source\gallery
defaultCover1.png
defaultCover2.png
defaultCover3.png
defaultCover4.png
defaultCover5.png
defaultThumbnail1.png
defaultThumbnail2.png
defaultThumbnail3.png
defaultThumbnail4.png
defaultThumbnail5.png

3. 新建博文脚本

我们不再手动hexo new post来创建博文,而是使用脚本,可以在前后多一些自定义事件。

windows

windows系统可以使用以下powershell脚本来创建新博文MD,这会随机使用某一套封面和缩略图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 获取当前时间戳
$timestamp = Get-Date -Format "yyyy-MM-dd-HH-mm-ss"

# 创建带有时间戳的 Markdown 文件
hexo new post $timestamp

# 替换新创建文件中的标题
$file = "source/_posts/$timestamp.md"

# 随机选择封面和缩略图
$randomIndex = Get-Random -Minimum 1 -Maximum 6
$cover = "/gallery/defaultCover$randomIndex.png"
$thumbnail = "/gallery/defaultThumbnail$randomIndex.png"

# 读取文件内容,指定编码方式
$mdContent = Get-Content $file -Raw -Encoding utf8

# 替换标题和封面占位符
$mdContent = $mdContent -replace 'cover: COVER_PLACEHOLDER', "cover: $cover"
$mdContent = $mdContent -replace 'thumbnail: THUMBNAIL_PLACEHOLDER', "thumbnail: $thumbnail"

# 将更新后的内容写回文件,指定编码方式
Set-Content -Path $file -Value $mdContent -Encoding utf8

Write-Output "Post created: $file"
Write-Output "Cover image: $cover"
Write-Output "Thumbnail image: $thumbnail"

Linux

Liunx系统可以使用这个脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash

# 获取当前时间戳
timestamp=$(date +%Y-%m-%d-%H-%M-%S)

# 创建带有时间戳的 Markdown 文件
hexo new post "$timestamp"

# 替换新创建文件中的标题
file="source/_posts/$timestamp.md"

# 随机选择封面和缩略图
randomIndex=$(( (RANDOM % 5) + 1 ))
cover="/gallery/defaultCover${randomIndex}.png"
thumbnail="/gallery/defaultThumbnail${randomIndex}.png"

# 替换封面和缩略图占位符
sed -i "s|cover: COVER_PLACEHOLDER|cover: $cover|" "$file"
sed -i "s|thumbnail: THUMBNAIL_PLACEHOLDER|thumbnail: $thumbnail|" "$file"

echo "Post created: $file"
echo "Cover image: $cover"
echo "Thumbnail image: $thumbnail"

4. 效果展示

如何优雅的使用Github Action服务来将Hexo部署到Github Pages

如何优雅的使用Github Action服务来将Hexo部署到Github Pages

参考文章

Bilibili视频教程-9分钟零成本搭建自动化部署个人博客(Hexo + Github Action + Page)

Hexo官方文档

利用 GitHub Action 自动部署 Hexo 博客

Hexo主题-Icarus快速上手

前提条件

当前PC环境中有Node和Git。版本可以参考Hexo文档。

文章中出现的yourusername为Github用户名,your-repo为仓库名。

1. 初始化Hexo

安装脚手架,初始化hexo,这会新建blog文件夹,进入后安装依赖。

1
2
3
4
npm install -g hexo-cli
hexo init blog
cd blog
npm install

2. 初始化仓库

可以选择利用VSCode等软件直接对项目开源到github仓库。

也可以手动去github创建一个空仓库,然后手动在命令行中推送。

1
2
3
4
5
git init
git remote add origin https://github.com/yourusername/your-repo.git
git add .
git commit -m "Initial commit"
git push -u origin main

3. 创建Token

在个人设置中新增一个Personal access tokens。至少要包含repo权限,然后记住token。
这个token是给Github Action用的,Github Action会把Hexo编译部署到gh-pages分支。

随后在存放Hexo代码的仓库里把这个Token新增进去,名称为GH_TOKEN(随意,后面需要一致)。

4. 修改_config.yml

在_config.yml中修改deploy字段。指示Hexo在deploy时的推送地址。

1
2
3
4
deploy:
type: git
repo: https://github.com/yourusername/your-repo.git
branch: gh-pages

5. 配置Github Action工作流

.github文件夹下新增workflows文件夹,然后新增deploy.yml文件,内容如下。

里面有个node-version要和你本地的node一致。

步骤大致意思就是使用ubuntu-latest作为基础环境,然后安装各种依赖,随后hexo generate生成博客网站静态文件夹,
把这个文件夹推送到同一仓库的gh-pages分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
name: Deploy Hexo to GitHub Pages

on:
push:
branches:
- main # 当推送到 main 分支时触发

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
submodules: false # 禁用子模块检查

- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '18'

- name: Install Dependencies
run: npm install

- name: Install Hexo Git Deployer
run: |
npm install hexo-deployer-git --save
npm install hexo-cli -g

- name: Clean and Generate Static Files
run: |
hexo clean
hexo generate

- name: Configure Git
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'

- name: Deploy to GitHub Pages
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
cd public/
git init
git add -A
git commit -m "Create by workflows"
git remote add origin https://${{ secrets.GH_TOKEN }}@github.com/yourusername/your-repo.git
git push origin HEAD:gh-pages -f

6. 推送验证

把刚才更新的所有文件都推送一遍,github就会触发工作流,然后去网站看工作流运转的如何。
等一切运转完毕,就会发现仓库多出一个gh-pages分支。

7. 配置Github Pages

在仓库settings中配置page来源为gh-pages分支即可。等待网站部署完毕,就可以看了。网站链接可以在settings的GitHub Pages看到,也可以去action里看到。

8. 修改Hexo主题样式

以一个比较热门的主题为演示示例,参考地址https://github.com/ppoffice/hexo-theme-icarus

若要使用NPM将Icarus安装为Node包,在你的Hexo站点根目录运行如下命令:

1
npm install -S hexo-theme-icarus hexo-renderer-inferno

接下来,使用hexo命令修改主题为Icarus:

1
hexo config theme icarus

会发现多出一个_config.icarus.yml文件。这是Icarus主题的配置文件。

最后推送到仓库,等待部署后,就可以了。

10. 添加文章

你可以执行下列命令来创建一篇新文章或者新的页面。

1
$ hexo new [layout] <title>

您可以在命令中指定文章的布局(layout),默认为 post,可以通过修改 _config.yml 中的 default_layout 参数来指定默认布局。

文章添加编辑后,现在只需要推送到仓库,那么github不仅会保存你的Hexo个人博客源码,还会自动更新个人博客静态页面到gh-pages,由此触发github-page功能来更新你的个人博客网站。

遇到了一些问题和方案

1. 网站没有样式问题

在网站打开F12,发现css等样式资源无法加载,仔细查看报错原因和请求地址,发现并不是当前仓库。

缺少仓库地址,所以把请求地址复制一份,并在后面添加上仓库名即可,这需要修改_config.yml中修改url字段。yourusername似乎为小写。

推送后等待工作流执行,查看结果。

1
2
url: https://yourusername.github.io/your-repo
root: /your-repo/

2. 图片不显示

在_config.yml中设置

1
post_asset_folder: true

意思是每个md博文会单独配套一个同名文件夹,用来存放图片。形如

1
2
3
4
source/_posts/
├── my-new-post.md
└── my-new-post/
└── example.jpg

hexo提供三种语法

1
2
3
{% asset_path slug %}
{% asset_img slug [title] %}
{% asset_link slug [title] %}

那么在md中可以这样引用图片

1
{% asset_img example.jpg This is an example image %}

这样一来,部署的时候图片就不会不显示了,但是有个新的问题,
我在本地编辑md的时候无法预览图片怎么办。

建议用VSCode下载插件vscode-hexoHexo Utils,随后在左边栏目就可以看到新Hexo Utils的新菜单,只要你的VSCode当前打开的文件夹是hexo的根目录,那么插件就会自动识别到,当你对md文件使用“侧边预览”时,图片就正常显示了。

22种常用设计模式示例代码

22种常用设计模式示例代码

仓库地址https://github.com/Xiamu-ssr/DesignPatternsPractice
参考教程 refactoringguru设计模式-目录

创建型模式

软件包 复杂度 流行度
工厂方法 factorymethod ❄️ ⭐️⭐️⭐️
抽象工厂 abstractfactory ❄️❄️ ⭐️⭐️⭐️
生成器 builder ❄️❄️ ⭐️⭐️⭐️
原型 prototype ❄️ ⭐️⭐️
单例 singleton ❄️ ⭐️⭐️⭐️

结构型模式

软件包 复杂度 流行度
适配器 adapter ❄️ ⭐️⭐️⭐️
桥接 bridge ❄️❄️❄️ ⭐️
组合 composite ❄️❄️ ⭐️⭐️
装饰 decorator ❄️❄️ ⭐️⭐️
外观 facade ❄️ ⭐️⭐️
享元 flyweight ❄️❄️❄️ ⭐️
代理 proxy ❄️❄️ ⭐️

行为模式

软件包 复杂度 流行度
责任链 chainofresponsibility ❄️❄️ ⭐️
命令 command ❄️ ⭐️⭐️⭐️
迭代器 iterator ❄️❄️ ⭐️⭐️⭐️
中介者 mediator ❄️❄️ ⭐️⭐️
备忘录 memento ❄️❄️❄️ ⭐️
观察者 observer ❄️❄️ ⭐️⭐️⭐️
状态 state ❄️ ⭐️⭐️
策略 strategy ❄️ ⭐️⭐️⭐️
模版方法 templatemethod ❄️ ⭐️⭐️
访问者 visitor ❄️❄️❄️ ⭐️

Hello World

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

深入探讨5种单例模式

深入探讨5种单例模式

一、对比总览

以下是不同单例模式实现方式的特性对比表格。表格从线程安全性、延迟加载、实现复杂度、反序列化安全性、防反射攻击性等多个方面进行考量。

特性 饿汉式 饱汉式 饱汉式-双检锁 静态内部类 枚举单例(推荐)
线程安全性 ×
延迟加载 × ×
实现复杂度 ×
反序列化安全性 × × × ×
防反射攻击性 × × × ×

详细解释

  1. 线程安全性

    • 饿汉式:类加载时即创建实例(因为instance是static),线程安全。
    • 饱汉式:未使用同步机制,线程不安全。
    • 饱汉式-双检锁:使用同步块和双重检查锁,线程安全。
    • 静态内部类:通过类加载机制,线程安全。
    • 枚举单例:JVM确保线程安全。
  2. 延迟加载

    • 饿汉式:类加载时即创建实例,不具备延迟加载。
    • 饱汉式:实例在首次使用时创建,具备延迟加载。
    • 饱汉式-双检锁:实例在首次使用时创建,具备延迟加载。
    • 静态内部类:实例在首次使用时创建(因为静态内部类只有在使用时才会加载),具备延迟加载。
    • 枚举单例:类加载时即创建实例,不具备延迟加载。
  3. 实现复杂度

    • 饿汉式:实现简单。
    • 饱汉式:实现简单。
    • 饱汉式-双检锁:实现相对复杂。
    • 静态内部类:实现简单。
    • 枚举单例:实现简单。
  4. 反序列化安全性

    • 饿汉式饱汉式饱汉式-双检锁静态内部类 :需要实现 readResolve 方法以防止反序列化创建新实例。
    • 枚举单例:天然防止反序列化创建新实例,JVM保证。
  5. 防反射攻击性

    • 饿汉式饱汉式饱汉式-双检锁静态内部类:可能通过反射创建新实例。
    • 枚举单例 :防止反射攻击,创建新实例时抛出 IllegalArgumentException

二、代码

1. 饿汉式

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class OrderManager1 {

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

@Getter
private static final OrderManager1 instance = new OrderManager1();

/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

2. 饱汉式

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class OrderManager2 {

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

private static OrderManager2 instance;

public static OrderManager2 getInstance(){

if (instance == null){

instance = new OrderManager2();
}
return instance;
}

/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

3. 饱汉式-双检锁

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class OrderManager3 {

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

private static OrderManager3 instance;

public static OrderManager3 getInstance(){

if (instance == null){

synchronized (OrderManager3.class){

if (instance == null){

instance = new OrderManager3();
}
}
instance = new OrderManager3();
}
return instance;
}

/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

4. 静态内部类

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class OrderManager4 {

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

private static class SingletonHelper{

private static final OrderManager4 INSTANCE = new OrderManager4();
}

public static OrderManager4 getInstance(){

return SingletonHelper.INSTANCE;
}


/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

5. 枚举单例

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public enum OrderManager5 {

INSTANCE;

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

三、性能对比

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
package org.dragon.singleton;

import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

@Slf4j
public class SingletonPerformanceTest {

static long timeout = 20; // 超时时间,单位为秒
static int testIterations = 10_000_000; // 测试次数
static int threadCount = 1000; // 并发线程数
static Map<String, HashMap<String, Long>> result = new HashMap<>();

public static void main(String[] args) {

/*
* 多次调用,结果是最后一次调用存入。为什么多次调用,因为单次test不准确,总是靠前的OrderManager跑的快,可能是因为Java某些机制导致的
* 所以多次调用,逐渐平稳。
* */
firstCreationTest();
mulAccessTest();
mulAccessTest();
mulAccessTest();
ConcurrentAccessTest();
ConcurrentAccessTest();
printRes();
ConcurrentAccessTest();
printRes();
ConcurrentAccessTest();
printRes();
}

/**
* 打印结果
*/
private static void printRes(){

ArrayList<String> names = new ArrayList<>();
names.add(OrderManager1.class.getSimpleName());
names.add(OrderManager2.class.getSimpleName());
names.add(OrderManager3.class.getSimpleName());
names.add(OrderManager4.class.getSimpleName());
names.add(OrderManager5.class.getSimpleName());
// 表头
System.out.printf("%-20s%-20s%-25s%-25s%-20s%n", "Singleton Type", "First Creation (ms)", "Multiple Access (ms)", "Concurrent Access (ms)", "Memory Used (MB)");
System.out.println("---------------------------------------------------------------------------------------------------------------");

for (String name : names) {

// 打印结果,转换时间为毫秒
System.out.printf("%-20s%-20.3f%-25.3f%-25.3f%-20.3f%n", name, result.get(name).get("firstCreation") / 1_000_000.0, result.get(name).get("mulAccess") / 1_000_000.0, result.get(name).get("ConcurrentAccess") / 1_000_000.0, 0 / (1024.0 * 1024.0));
}
}

/**
* 首次创建测试
*/
private static void firstCreationTest(){

List<Runnable> tests = new ArrayList<>();
tests.add(()->firstCreation(OrderManager1::getInstance));
tests.add(()->firstCreation(OrderManager2::getInstance));
tests.add(()->firstCreation(OrderManager3::getInstance));
tests.add(()->firstCreation(OrderManager4::getInstance));
tests.add(()->firstCreation(() -> OrderManager5.INSTANCE));
// 随机化测试顺序
Collections.shuffle(tests);
//run
for (Runnable test : tests) {

test.run();
log.info("Complete one test");
try {

Thread.sleep(200);
} catch (InterruptedException e) {

throw new RuntimeException(e);
}
}
}

/**
* 多次访问测试
*/
private static void mulAccessTest(){

List<Runnable> tests = new ArrayList<>();
tests.add(()->mulAccess(OrderManager1::getInstance, testIterations));
tests.add(()->mulAccess(OrderManager2::getInstance, testIterations));
tests.add(()->mulAccess(OrderManager3::getInstance, testIterations));
tests.add(()->mulAccess(OrderManager4::getInstance, testIterations));
tests.add(()->mulAccess(() -> OrderManager5.INSTANCE, testIterations));
// 随机化测试顺序
Collections.shuffle(tests);
//run
for (Runnable test : tests) {

test.run();
log.info("Complete one test");
try {

Thread.sleep(200);
} catch (InterruptedException e) {

throw new RuntimeException(e);
}
}
}

/**
* 多线程访问测试
*/
private static void ConcurrentAccessTest(){

List<Runnable> tests = new ArrayList<>();
tests.add(()->ConcurrentAccess(OrderManager1::getInstance, testIterations, threadCount));
tests.add(()->ConcurrentAccess(OrderManager2::getInstance, testIterations, threadCount));
tests.add(()->ConcurrentAccess(OrderManager3::getInstance, testIterations, threadCount));
tests.add(()->ConcurrentAccess(OrderManager4::getInstance, testIterations, threadCount));
tests.add(()->ConcurrentAccess(() -> OrderManager5.INSTANCE, testIterations, threadCount));
// 随机化测试顺序
Collections.shuffle(tests);
//run
for (Runnable test : tests) {

test.run();
log.info("Complete one test");
try {

Thread.sleep(200);
} catch (InterruptedException e) {

throw new RuntimeException(e);
}
}
}

/**
* 首次创建
*
* @param singletonSupplier 单一供应商
* @return long ns
*/
private static <T> long firstCreation(Supplier<T> singletonSupplier){

// 测试首次创建时间
long startTime = System.nanoTime();
T instance = singletonSupplier.get();
long endTime = System.nanoTime();
long resTime = endTime - startTime;
//save res
String simpleName = instance.getClass().getSimpleName();
HashMap<String, Long> resMap = result.computeIfAbsent(simpleName, k->new HashMap<>());
resMap.put("firstCreation", resTime);
return resTime;
}

/**
* 多次访问
*
* @param singletonSupplier 单一供应商
* @param iterations 迭代
* @return long ns
*/
private static <T> long mulAccess(Supplier<T> singletonSupplier, int iterations){

//预热
for (int i = 0; i < 100_000; i++) {

T instance = singletonSupplier.get();
}
//计算
long startTime = System.nanoTime();
for (int i = 0; i < iterations; i++) {

T instance = singletonSupplier.get();
}
long endTime = System.nanoTime();
long resTime = endTime - startTime;
//save res
String simpleName = singletonSupplier.get().getClass().getSimpleName();
HashMap<String, Long> resMap = result.computeIfAbsent(simpleName, k->new HashMap<>());
resMap.put("mulAccess", resTime);
return resTime;
}

/**
* 并发访问
*
* @param singletonSupplier 单一供应商
* @param iterations 迭代
* @param threadCount 线程数
* @return long ns
*/
private static <T> long ConcurrentAccess(Supplier<T> singletonSupplier, int iterations, int threadCount){

ExecutorService executorService = Executors.newFixedThreadPool(100);
//预热
CountDownLatch latch1 = new CountDownLatch(100);
for (int i = 0; i < threadCount; i++) {

executorService.submit(() -> {

for (int j = 0; j < 100_000; j++) {

T instance = singletonSupplier.get();
}
latch1.countDown();
});
}
try {

boolean completed = latch1.await(timeout, TimeUnit.SECONDS);
if (!completed) {

System.out.println("Concurrent access test for 预热" + singletonSupplier.get().getClass().getSimpleName() + " timed out!");
}
} catch (InterruptedException e) {

e.printStackTrace();
}
//计算
CountDownLatch latch2 = new CountDownLatch(threadCount);
long startTime = System.nanoTime();
for (int i = 0; i < threadCount; i++) {

executorService.submit(() -> {

for (int j = 0; j < iterations; j++) {

T instance = singletonSupplier.get();
}
latch2.countDown();
});
}
try {

boolean completed = latch2.await(timeout, TimeUnit.SECONDS);
if (!completed) {

System.out.println("Concurrent access test for " + singletonSupplier.getClass().getSimpleName() + " timed out!");
}
} catch (InterruptedException e) {

e.printStackTrace();
}
long endTime = System.nanoTime();
long concurrentAccessTime = endTime - startTime;

executorService.shutdown();
//save res
String simpleName = singletonSupplier.get().getClass().getSimpleName();
HashMap<String, Long> resMap = result.computeIfAbsent(simpleName, k->new HashMap<>());
resMap.put("ConcurrentAccess", concurrentAccessTime);
return concurrentAccessTime;
}
}

结果输出如下

language-txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
[17:15:54.519] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:54.730] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:54.936] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:55.141] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:55.347] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:55.554] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:55.782] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.007] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.227] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.445] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.669] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.906] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:57.146] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:57.376] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:57.598] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:57.818] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:58.054] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:58.276] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:58.495] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:58.716] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:59.430] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:15:59.658] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:02.737] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:08.533] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:13.700] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:19.432] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:24.632] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:30.366] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:35.516] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:40.431] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
Singleton Type First Creation (ms) Multiple Access (ms) Concurrent Access (ms) Memory Used (MB)
---------------------------------------------------------------------------------------------------------------
OrderManager1 0.010 16.848 4928.236 0.000
OrderManager2 0.010 18.592 5504.781 0.000
OrderManager3 0.009 19.127 5513.309 0.000
OrderManager4 0.241 18.772 4920.940 0.000
OrderManager5 0.117 16.637 4704.835 0.000
[17:16:45.002] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:48.982] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:52.778] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:57.834] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:02.298] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
Singleton Type First Creation (ms) Multiple Access (ms) Concurrent Access (ms) Memory Used (MB)
---------------------------------------------------------------------------------------------------------------
OrderManager1 0.010 16.848 4217.333 0.000
OrderManager2 0.010 18.592 4340.869 0.000
OrderManager3 0.009 19.127 4824.581 0.000
OrderManager4 0.241 18.772 3743.032 0.000
OrderManager5 0.117 16.637 3558.995 0.000
[17:17:06.778] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:11.231] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:15.733] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:20.812] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:25.837] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
Singleton Type First Creation (ms) Multiple Access (ms) Concurrent Access (ms) Memory Used (MB)
---------------------------------------------------------------------------------------------------------------
OrderManager1 0.010 16.848 4281.649 0.000
OrderManager2 0.010 18.592 4849.279 0.000
OrderManager3 0.009 19.127 4782.224 0.000
OrderManager4 0.241 18.772 4267.228 0.000
OrderManager5 0.117 16.637 4233.907 0.000

进程已结束,退出代码为 0

可以去多跑几次,基本上最后一种枚举单例,性能属于最好的一批。并且也最安全。

基于Ruoyi-Cloud-Plus重构黑马项目-学成在线

基于Ruoyi-Cloud-Plus重构黑马项目-学成在线

一、系统介绍

毕设:基于主流微服务技术栈的在线教育系统的设计与实现

前端仓库:https://github.com/Xiamu-ssr/Dragon-Edu-Vue3
后端仓库:https://github.com/Xiamu-ssr/Dragon-Edu

感谢来自”疯狂的狮子”开源精神,RuoYi 微服务Plus版本:

文档地址: plus-doc

二、系统架构图

三、参考教程

主要以视频录制的方式展示。
包含:服务器选购,环境初始化,本地开发,部署系统,功能测试,性能测试,代码解析,架构探讨。

b站-木子dn

四、演示图例

机构端

运营端

用户端

开发端

Navicat导出表结构到Excel或Word

Navicat导出表结构到Excel或Word

sql语句

language-sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
SELECT
cols.COLUMN_NAME AS 字段,
cols.COLUMN_TYPE AS 数据类型,
IF(pks.CONSTRAINT_TYPE = 'PRIMARY KEY', 'YES', 'NO') AS 是否为主键,
IF(idxs.INDEX_NAME IS NOT NULL, 'YES', 'NO') AS 是否为索引,
cols.IS_NULLABLE AS 是否为空,
cols.COLUMN_DEFAULT AS 默认值,
cols.COLUMN_COMMENT AS 备注
FROM
INFORMATION_SCHEMA.COLUMNS AS cols
LEFT JOIN
INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kc ON kc.TABLE_SCHEMA = cols.TABLE_SCHEMA AND kc.TABLE_NAME = cols.TABLE_NAME AND kc.COLUMN_NAME = cols.COLUMN_NAME
LEFT JOIN
INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS pks ON pks.TABLE_SCHEMA = kc.TABLE_SCHEMA AND pks.TABLE_NAME = kc.TABLE_NAME AND pks.CONSTRAINT_TYPE = 'PRIMARY KEY' AND kc.CONSTRAINT_NAME = pks.CONSTRAINT_NAME
LEFT JOIN
INFORMATION_SCHEMA.STATISTICS AS idxs ON idxs.TABLE_SCHEMA = cols.TABLE_SCHEMA AND idxs.TABLE_NAME = cols.TABLE_NAME AND idxs.COLUMN_NAME = cols.COLUMN_NAME
WHERE
cols.TABLE_SCHEMA = 'db' -- 替换为您的数据库名称
AND cols.TABLE_NAME = 'table' -- 替换为您的表名称
ORDER BY
cols.ORDINAL_POSITION ASC; -- 按列在表中的顺序排列

复制到excel

在查询结果中,Ctrl+A全选,然后复制。
到Excel中,自己写好表头,然后粘贴,就复制到Excel了。

复制到Word

从Excel全选数据,就可以直接复制到Word。

Vue3 v3.4之前如何实现组件中多个值的双向绑定?

Vue3 v3.4之前如何实现组件中多个值的双向绑定?

官方给的例子是关于el-input的,如下。但是@input不是所有组件标签都有的属性啊,有没有一种通用的办法呢?

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
defineProps({

firstName: String,
lastName: String
})

defineEmits(['update:firstName', 'update:lastName'])
</script>

<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>

基础代码

以一个Dialog组件为例。我们自己写一个course-buy.vue

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<el-dialog
v-model="localValue.dialogVisible"
title="Warning"
width="500"
align-center
>
<span>Open the dialog from the center from the screen</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="localValue.dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="localValue.dialogVisible = false">
Confirm
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import {
PropType} from "vue";

//对外变量
const props = defineProps({

dialogVisible: Object as PropType<boolean>,
courseId: Object as PropType<string | number>,
})
const emit = defineEmits(['update:dialogVisible','update:courseId'])
//本地变量
const localValue = reactive({

dialogVisible: props.dialogVisible,
courseId: props.courseId
})

</script>

外部在使用时(假设为base.vue),如下使用

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<CourseBuy
v-model:dialog-visible="orderPayParams.dialogVisible"
v-model:course-id="orderPayParams.courseId"
/>
</template>
<script setup lang="ts">
const orderPayParams = reactive({

dialogVisible: false,
courseId: 1
});
</script>

上述代码,course-buy.vue中真正使用的变量是localValue本地变量,localValue的值来自base.vue
但是上述的基础代码,dialogVisiblecourseId的值只能从base.vue流向course-buy.vue
如何实现course-buy.vue本身修改localValue的值后,修改变化同步到base.vue呢?

1. watch

如果要让dialogVisible双向绑定,可以写两个watch互相监听并更新。要实现courseId双向绑定也是同理。

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script setup lang="ts">
import {
PropType} from "vue";

//对外变量
const props = defineProps({

dialogVisible: Object as PropType<boolean>,
courseId: Object as PropType<string | number>,
})
const emit = defineEmits(['update:dialogVisible','update:courseId'])
//本地变量
const localValue = reactive({

dialogVisible: props.dialogVisible,
courseId: props.courseId
})
//值双向绑定
watch(() => props.dialogVisible, (newValue) => {

localValue.dialogVisible = newValue;
});
watch(() => localValue.dialogVisible, (newValue) => {

emit('update:dialogVisible', newValue);
});
</script>

2. computed(推荐)

不过使用computed可以更简洁,性能也更好。

language-html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup lang="ts">
import {
PropType} from "vue";

//对外变量
const props = defineProps({

dialogVisible: Object as PropType<boolean>,
courseId: Object as PropType<string | number>,
})
const emit = defineEmits(['update:dialogVisible','update:courseId'])
//本地变量
const localValue = reactive({

dialogVisible: computed({

get: () => props.dialogVisible,
set: (value) => emit('update:dialogVisible', value)
}),
courseId: props.courseId
})
</script>
支付宝沙箱版模拟网站在线完整支付流程(无营业无费用)内网穿透+局域网测试

支付宝沙箱版模拟网站在线完整支付流程(无营业无费用)内网穿透+局域网测试

环境如下

Version
手机 安卓
支付平台 支付宝
SpringBoot 3.2.1
alipay-sdk-java 4.38.200.ALL

一、介绍

系统处于开发阶段时,无需营业执照,无需任何费用,沙箱模拟网站在线完整支付流程。

参考资料如下:

1. 支付

有一个在线网站,可以为商品生成支付二维码,手机支付宝扫码,支付。

支付流程大体如下:

2. 支付结果

获取支付结果有两种方法

  • 一种为主动查询。在顾客支付后再查询方可得到正确的结果,然而这个时机是无法确定的。
  • 一种为被动接收。顾客支付后,支付宝服务器向微服务发送消息通知。

二、前提准备

1. 支付宝开放平台

  1. 注册支付宝开放平台
    https://openhome.alipay.com/

  2. 来到控制台下滑找到沙箱
    https://openhome.alipay.com/develop/manage
    或者点这里进入沙箱环境
    https://openhome.alipay.com/develop/sandbox/app

  3. 下载支付宝沙箱版到手机

2. 内网穿透

  1. 下载软件
    https://hsk.oray.com/download
    本文选择的是贝锐花生壳,会赠送一个域名。

  2. 添加映射

    • 映射类型:HTTPS
    • 外网端口:貌似改不了
    • 内网ip:port:order微服务的地址端口。

这样之后,谁往https://5m34y83626.vicp.fun/orders/receivenotify发送请求,就相当于往order微服务的/orders/receivenotify这个端点发送请求。

3. 局域网

参考这篇文章
同一Wifi下允许手机访问电脑(win10)

主要目的就是要知道,手机通过什么ip可以访问到电脑。本文是192.168.0.102,所以访问192.168.0.102:63030就相当于访问到了order微服务

三、order微服务

1. 依赖、配置

language-xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 支付宝SDK -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.38.200.ALL</version>
</dependency>
<!--生成二维码-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.3</version>
</dependency>
language-yml
1
2
3
4
5
6
7
8
9
10
11
12
server:
servlet:
context-path: /orders
port: 63030

pay:
#扫描二维码得到url
qrcodeurl: http://???:63030/orders/requestpay?payNo=%s
alipay:
APP_ID: ???
APP_PRIVATE_KEY: ???
ALIPAY_PUBLIC_KEY: ???

???填充分别为

  1. 在同一局域网中手机访问电脑的ip

  2. 沙箱环境->沙箱应用->应用信息->基本信息

  3. 沙箱环境->沙箱应用->应用信息->开发信息->应用私钥

  4. 沙箱环境->沙箱应用->应用信息->开发信息->支付宝公钥

2. 工具类

1. 二维码生成

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.xuecheng.orders.config;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.xuecheng.base.utils.EncryptUtil;
import jakarta.servlet.ServletOutputStream;
import org.apache.commons.lang3.StringUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;

/**
* @author mumu
* @version 1.0
* @description 二维码生成工具
* @date 2024/02/16 14:56
*/
public class QRCodeUtil {


/**
* 生成二维码
*
* @param content 二维码对应的URL
* @param width 二维码图片宽度
* @param height 二维码图片高度
* @return
*/
public String createQRCode(String content, int width, int height) throws IOException {


String resultImage = "";
//除了尺寸,传入内容不能为空
if (!StringUtils.isEmpty(content)) {


ServletOutputStream stream = null;
ByteArrayOutputStream os = new ByteArrayOutputStream();
//二维码参数
@SuppressWarnings("rawtypes")
HashMap<EncodeHintType,<