0%

如何在JavaScript中延迟1S

在JS中,需要进行延迟的话,通常的做法是使用**setTimeout,更好的方式是使用Promise**

有很多方法可以让JavaScript等待1秒。有些比其他更好,有些只能在特定情况下使用。

1. 使用setTimeout

如:

1
2
3
4
5
6
console.log("Executed now");

// 1 second delay
setTimeout(function(){
console.log("Executed after 1 second");
}, 1000);

需要特别注意的是,setTimeout是在异步运行的,延迟并不会阻止正常程序的流程,如

1
2
3
4
5
6
7
8
9
console.log("Executed now");

// 1 second delay
setTimeout(function(){
console.log("Executed after 1 second");
}, 1000);

// Notice this!
console.log("Executed before the delay, but after the 1st console.log");

输出:

1
2
3
Executed now
Executed before the delay, but after the 1st console.log
Executed after 1 second

如果你想在同一个延迟函数中不断添加延迟,我们很容易遇到所谓的回调地狱。

2. 使用Promise

如果我们把setTimeout函数和promises函数结合起来,我们就可以创建一个更易读的代码,并把整个代码放在同一个(async)函数中。

与我们之前解释过的setTimeout方法相比,它的主要优点是我们可以重用延迟函数。使我们的代码更加干净和简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function delay(milliseconds){
return new Promise(resolve => {
setTimeout(resolve, milliseconds);
});
}

async function init(){
console.log("Executed now");

await delay(1000);

console.log("Executed after 1 second wait");

await delay(1000);

console.log("Executed after 2 seconds wait");
}

init();

console.log("Executed after the 1st log and before the 2 delays");

输出

1
2
3
4
> Executed now
> Executed after the 1st log and before the 2 delays
> Executed after 1 second wait
> Executed after 2 seconds wait

3. 使用等待循环(loop)

这是唯一完全中断用户流程并强制 JavaScript 进行同步暂停的方法。

这并不是完美的解决方案,我认为这可能是不可取的。JavaScript 是一种异步语言,我们应该利用它,而不是使用巧妙的解决方案使我们的代码变成同步的。

1
2
3
4
5
6
7
8
9
10
11
var wait = (ms) => {
const start = Date.now();
let now = start;
while (now - start < ms) {
now = Date.now();
}
}

console.log("Executed now");
wait(1000);
console.log("Executed after 1 second");

我们基本上使用 Date 函数检查当前时间,并创建一个 do ... while 循环,只有在我们开始循环后超过1000毫秒才会退出。

最大的问题就是?我们基本上是通过运行一个“无意义”的循环并比较日期来让我们的计算机/浏览器忙碌,只是为了延迟。

如果我们等待的时间不是1秒,那么我们的浏览器很可能会崩溃,或者我们的页面停止像平常那样响应。即使我们使用1秒的数值,事情也可能不会总是按我们的期望工作。DOM 渲染发生在 JavaScript 函数栈已清空并且浏览器可以接受新事件之后。

并且为了证明这一点,请看以下示例,在这个示例中, console.log 在1秒后被触发(正如我们所期望的那样),但是两个DOM写入操作同时发生:

上面代码输出

1
2
> Executed now
> Executed after 1 second

这不是一个好的解决方案,如果可能的话,尽量避免采用这个解决方案,而是选择前两种方案。

Add new disk on LVM for Ubuntu 22.04 LTS

这些说明将帮助您在Ubuntu 22.04上向现有的逻辑卷管理(LVM)中添加额外的磁盘。LVM可以帮助您轻松地扩展存储空间跨多个物理磁盘设备。

用于向当前磁盘扩展空间

  1. 找出要分配给LVM的磁盘
1
2
3
4
sudo pvs
PV VG Fmt Attr PSize PFree
/dev/vda3 ubuntu-vg lvm2 a-- <8.25g 0
/dev/vdb ubuntu-vg lvm2 a-- <50.00g 0

查看详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ sudo pvdisplay
--- Physical volume ---
PV Name /dev/vda3
VG Name ubuntu-vg
PV Size <8.25 GiB / not usable 0
Allocatable yes (but full)
PE Size 4.00 MiB
Total PE 2111
Free PE 0
Allocated PE 2111
PV UUID 96Cnvs-1rYe-xWk8-lB2V-cLSd-ALqk-4ujiPc

--- Physical volume ---
PV Name /dev/vdb
VG Name ubuntu-vg
PV Size 50.00 GiB / not usable 4.00 MiB
Allocatable yes (but full)
PE Size 4.00 MiB
Total PE 12799
Free PE 0
Allocated PE 12799
PV UUID Hle3gv-3FNW-jkoe-dRFf-CwWd-pdJx-MWy3Ad
  1. 获取LVM的路径
1
2
3
$ sudo lvdisplay
--- Logical volume ---
LV Path /dev/ubuntu-vg/ubuntu-lv

省略了其他信息

  1. 找到要添加的新磁盘
1
2
3
4
5
6
7
8
9
10
$ sudo fdisk -l | grep '^Disk /dev/'
Disk /dev/loop0: 63.24 MiB, 66314240 bytes, 129520 sectors
Disk /dev/loop1: 63.23 MiB, 66301952 bytes, 129496 sectors
Disk /dev/loop2: 79.95 MiB, 83832832 bytes, 163736 sectors
Disk /dev/loop3: 102.98 MiB, 107986944 bytes, 210912 sectors
Disk /dev/loop4: 49.62 MiB, 52031488 bytes, 101624 sectors
Disk /dev/vda: 10 GiB, 10737418240 bytes, 20971520 sectors
Disk /dev/vdb: 50 GiB, 53687091200 bytes, 104857600 sectors
Disk /dev/vdc: 500 GiB, 536870912000 bytes, 1048576000 sectors
Disk /dev/mapper/ubuntu--vg-ubuntu--lv: 558.24 GiB, 599403790336 bytes, 1170710528 sectors

在本例中,它将是/dev/vdc,因为我向这个实例添加了一个500GB的磁盘驱动器。

  1. 在新的磁盘驱动器上创建物理卷
1
2
$ sudo pvcreate /dev/vdc
Physical volume "/dev/vdc" successfully created.
  1. 扩展现有卷组以包含这个新的磁盘驱动器
1
2
$ sudo vgextend ubuntu-vg /dev/vdc
Volume group "ubuntu-vg" successfully extended
  1. 扩展LV大小以包含100%的新磁盘
1
2
3
$ sudo lvm lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
Size of logical volume ubuntu-vg/ubuntu-lv changed from 58.24 GiB (14910 extents) to <558.24 GiB (142909 extents).
Logical volume ubuntu-vg/ubuntu-lv successfully resized.
  1. 现在需要调整文件系统的大小以匹配新的大小
1
2
3
4
5
$ sudo resize2fs -p /dev/ubuntu-vg/ubuntu-lv
resize2fs 1.46.5 (30-Dec-2021)
Filesystem at /dev/ubuntu-vg/ubuntu-lv is mounted on /; on-line resizing required
old_desc_blocks = 8, new_desc_blocks = 70
The filesystem on /dev/ubuntu-vg/ubuntu-lv is now 146338816 (4k) blocks long.
  1. 现在可以用df -kh来验证
1
2
3
4
5
6
7
8
9
$ df -kh
Filesystem Size Used Avail Use% Mounted on
tmpfs 9.9G 1.6M 9.9G 1% /run
/dev/mapper/ubuntu--vg-ubuntu--lv 550G 8.9G 519G 2% /
tmpfs 50G 0 50G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 50G 0 50G 0% /run/qemu
/dev/vda2 1.7G 247M 1.4G 16% /boot
tmpfs 9.9G 4.0K 9.9G 1% /run/user/1000

参考:

使用Fastapi实现 server-sent events (SSE)

服务器推送事件(SSE)是一种在不重新加载页面的情况下向浏览器发送数据的方式。这使得您可以使用流式数据并构建可用于各种情境的实时应用程序。

在本教程中,我们将使用FastAPI创建一个简单的SSE服务器,该服务器将每秒发送一条消息。

安装相关包

1
2
3
pip install "fastapi[all]"
pip install sse-starlette
pip install asyncio

创建一个简单的Fastapi项目

main.py

1
2
3
4
5
6
7
8
9
10
import asyncio
import uvicorn
from fastapi import FastAPI, Request

app = FastAPI()


@app.get("/")
async def root():
return {"message": "Hello World"}

使用uvicorn 运行

1
uvicorn main:app --reload

这将在8000端口上运行服务器。 –reload标志将自动重新加载服务器当你对代码进行更改时,这样你就不必每次更改时都重新启动服务器。

增加SSE逻辑

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
STREAM_DELAY = 1  # second
RETRY_TIMEOUT = 15000 # milisecond

@app.get('/stream')
async def message_stream(request: Request):
def new_messages():
# Add logic here to check for new messages
yield 'Hello World'
async def event_generator():
while True:
# If client closes connection, stop sending events
if await request.is_disconnected():
break

# Checks for new messages and return them to client if any
if new_messages():
yield {
"event": "new_message",
"id": "message_id",
"retry": RETRY_TIMEOUT,
"data": "message_content"
}

await asyncio.sleep(STREAM_DELAY)

return EventSourceResponse(event_generator())

依赖

  • NGINX
  • 认证文件创建工具,这里演示的是使用apache2-utils

创建认证文件

  1. 安装apache2-utils
1
sudo apt install apache2-utils
  1. 创建认证文件,-c是创建新的文件
1
2
3
4
sudo htpasswd -c /etc/apache2/.htpasswd user1
# 按提示输入user1的密码
# 也可以直接在命令中输入密码,加上-b参数
sudo htpasswd -cb /etc/apache2/.htpasswd user1 password
  1. 添加额外的用户
1
sudo htpasswd /etc/apache2/.htpasswd user2
  1. 查看/etc/apache2/.htpasswd文件,示例内容如下
1
2
3
4
$ cat /etc/apache2/.htpasswd
user1:$apr1$/woC1jnP$KAh0SsVn5qeSMjTtn0E9Q0
user2:$apr1$QdR8fNLT$vbCEEzDj7LyqCMyNpSoBh/
user3:$apr1$Mr5A0e.U$0j39Hp5FfxRkneklXaMrr/

配置NGINX

  1. 在要加认证的路径中,增加 auth_basic配置
1
2
3
4
location /api {
auth_basic "Administrator’s Area";
#...
}
  1. 配置认证文件 auth_basic_user_file
1
2
3
4
location /api {
auth_basic "Administrator’s Area";
auth_basic_user_file /etc/apache2/.htpasswd;
}

同样,也可以配置在整个server下面,如果某个路径不需要认证,增加auth_basic:off即可

1
2
3
4
5
6
7
8
9
server {
...
auth_basic "Administrator’s Area";
auth_basic_user_file conf/htpasswd;

location /public/ {
auth_basic off;
}
}

Basic Authentication与IP相结合

设想以下场景

  • 既要认证,又要要求IP白名单才可以访问
  • 认证或者IP白名单可以访问
  1. 使用 allowdeny指令
1
2
3
4
5
6
7
location /api {
#...
deny 192.168.1.2;
allow 192.168.1.1/24;
allow 127.0.0.1;
deny all;
}

拒绝来自192.168.1.2的访问,允许192.168.1.1/24网段内的访问。 deny, allow按顺序匹配。

  1. 结合satisfy 指令,如果设置为all,则需要满足所有条件才可以访问,如果设置为any,则ip认证和basic auth认证满足其中一个即可,如
1
2
3
4
5
6
7
8
9
10
11
12
location /api {
#...
satisfy all;

deny 192.168.1.2;
allow 192.168.1.1/24;
allow 127.0.0.1;
deny all;

auth_basic "Administrator’s Area";
auth_basic_user_file conf/htpasswd;
}

CURL的 –url-query的用法

背景

2022年12月21日发布的curl 7.87.0版本中,新增了–url-query参数,详见commit

在此之前,如果要使用post方法发送数据,可以使用-d,将数据包装成body发送出去。

1
curl -d name=mrsmith -d color=blue https://example.com

如果转变为GET方式,可以使用-G/–get参数,会自动将参数转变为get的query的形式,如

1
curl -G -d name=mrsmith -d color=blue https://example.com

将URL转为:

1
https://example.com/?name=mrsmith&color=blue

但是,如果使用POST的方式,既要携带body,又要使用query参数呢? 就需要手动构建带有query参数的url,使用-d携带body。

–url-query就是来解决这个问题的。

简单的例子

1
curl -d name=mrsmith -d color=blue --url-query name=mrsmith --url-query color=blue https://example.com

基本语法

–url-query [data], [data]需要符合以下用法

说明
content 会对数据进行url-encode编码发送,但是需要注意的是,其中不能包含=或@符号,这两个符有特殊用途
=content 会对数据进行url-encode编码后发送
name=content 会对数据进行url-encode编码后发送
@filename 会从指定的文件总加载数据,对数据进行url-encode编码,并通过post进行发送
name@filename 将会总给定的文件中加载数据,对数据进行url-encode编码,并通过post进行发送。最终的数据结构是 name=urlencoded-file-content。
+content 不进行url-encode编码,以原始数据传送

对于多个–url-query,curl会自动添加&符号。

参考链接

将字典转变为字符串 dict to string

Python3.x

1
2
3
4
5
6
# urllib.parse.urlencode(query, doseq=False, [...])
# Convert a mapping object or a sequence of two-element tuples, which may contain str or bytes objects, to a percent-encoded ASCII text string.
# Example
from urllib.parse import urlencode
urlencode({'pram1': 'foo', 'param2': 'bar'})
# pram1=foo&param2=bar

Python2.x

1
2
3
4
from urllib import urlencode

data = {'name': 'Desmond Lua', 'age': 40}
query_string = urlencode(data)

Python2 和Python3的兼容写法

1
2
3
4
5
6
try:
#python2
from urllib import urlencode
except ImportError:
#python3
from urllib.parse import urlencode

将字符串转换为字典 string to dict

Python3

方法1 使用urllib.parse.parse_qs()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# import module
import urllib.parse

# initializing string
test_str = 'gfg=4&is=5&best=yes'

# printing original string
print("The original string is : " + str(test_str))

# parse_qs gets the Dictionary and value list
res = urllib.parse.parse_qs(test_str)

# printing result
print("The parsed URL Params : " + str(res))

# The parsed URL Params : {'gfg': ['4'], 'is': ['5'], 'best': ['yes']}

方法2 使用正则表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re

# initializing string
test_str = 'gfg=4&is=5&best=yes'

# printing original string
print("The original string is : " + str(test_str))

# getting all params
params = re.findall(r'([^=&]+)=([^=&]+)', test_str)

# assigning keys with values
res = dict()
for key, val in params:

res.setdefault(key, []).append(val)

# printing result
print("The parsed URL Params : " + str(res))

方法3 使用Split()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#python program to convert
#URL parameters to dictionary items
# initializing string
test_str = 'gfg=4&is=5&best=yes'

# printing original string
print("The original string is : " + str(test_str))

# getting all params
res = dict()
x=test_str.split("&")
for i in x:
a,b=i.split("=")
# assigning keys with values
res[a]=[b]
# printing result
print("The parsed URL Params : " + str(res))

#The original string is : gfg=4&is=5&best=yes
# The parsed URL Params : {'gfg': ['4'], 'is': ['5'], 'best': ['yes']}

背景

一个简单的API服务,由于经常改动,每次改动后还需要手动登录服务器,pull下代码,然后重启,作为多一步都嫌麻烦的人来说,绝对不能接受。

功能设想

由于代码仓库是部署在github上的,想到了github action,由于之前没有了解过github action,不知道能否实现,于是开始了学习github action的过程,发现完全没问题,亦有种相见恨晚的感觉。

主要实现的点:

  • 当代码push到master分支后,自动pull下来代码;
  • 然后重启服务;

就是这么简单的几个步骤。

实现细节

考虑到服务不复杂且较简单,依赖不多,本着怎么简单怎么来,直接使用ssh登录到服务器执行命令即可。

前期准备

准备ssh免密登录的private key,我这里直接用我自己机器的key,建议重新生成一个

1
2
3
4
# 生成key
ssh-keygen -t rsa -C "github actions"
# 复制到服务器
ssh-copy-id -i [公钥文件] user@host

在github的仓库中设置思考,处于安全性的考虑,使用GitHub的Secrets。 在项目的Settings->Secrets->Actions增加私钥,我这边将服务器的Host,Username都配置进去了,方便修改。

Actions文件

存储在项目目录的.github/workflows/deploy.yml,文件名可以随便起,路径必须对。

actions文件示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
name: deploy
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: executing remote ssh commands to develop
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SECRET }}
script: cd [path] && git pull && sudo supervisorctl restart param

我这里用的是supervisor启动的服务,如果用其他方式启动的,可替换自己的重启命令。

另外,使用到了ssh-action, 其中:

  • host:为自己主机的地址,这里设置在了项目的secrets中
  • username: 为主机ssh的用户名,同样写在了项目的secrets中
  • key:为登录主机的私钥

其他参数可参考ssh-action的项目主页。

此时,可直接push到master分支来测试了。

可完善的点

  • 对于项目依赖,没有自动安装,可增加pip3 install -r requirements自动安装相关依赖,需要做好错误处理;
  • 部署成功或失败,可增加监控或者通知;

reference

/images/minio-logo.svg

在Docker中运行Minio

minio需要一个稳定的运行环境,测试的时候我们可以再docker中运行一个简单的实例,📢注意这个例子并没有映射任何volume,所以停止container的时候数据也会销毁

1
2
3
4
5
6
docker run \
-p 9000:9000 \
-p 9001:9001 \
-e "MINIO_ROOT_USER=<USERNAME>" \
-e "MINIO_ROOT_PASSWORD=<PASSWORD>" \
quay.io/minio/minio server /data --console-address ":9001"

集成到Spring boot中

在Spring boot中的pom.xml增加依赖

这里用的maven

1
2
3
4
5
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.0</version>
</dependency>

在application.properties中增加相关配置项,相关配置修改为自己的

1
2
3
4
5
6
7
8
server.port=8080
spring.servlet.multipart.max-file-size=2MB

# Minio
minio.bucket.name=minio-example-demo
minio.access.key=minioadmin
minio.access.secret=minioadmin
minio.url=https://play.min.io

配置Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class MinioConfiguration {

@Value("${minio.access.key}")
private String accessKey;

@Value("${minio.access.secret}")
private String secretKey;

@Value("${minio.url}")
private String minioUrl;

@Bean
@Primary
public MinioClient minioClient() {
return new MinioClient.Builder()
.credentials(accessKey, secretKey)
.endpoint(minioUrl)
.build();
}

}

DTO,用来传输数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FileDto implements Serializable {

private static final long serialVersionUID = 232836038145089522L;

private String title;

private String description;

@SuppressWarnings("java:S1948")
private MultipartFile file;

private String url;

private Long size;

private String filename;

}

获取文件列表

创建MinioService

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
@Slf4j
@Service
public class MinioService {

@Autowired
private MinioClient minioClient;

@Value("${minio.bucket.name}")
private String bucketName;

public List<FileDto> getListObjects() {
List<FileDto> objects = new ArrayList<>();
try {
Iterable<Result<Item>> result = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(bucketName)
.recursive(true)
.build());
for (Result<Item> item : result) {
objects.add(FileDto.builder()
.filename(item.get().objectName())
.size(item.get().size())
.url(getPreSignedUrl(item.get().objectName()))
.build());
}
return objects;
} catch (Exception e) {
log.error("Happened error when get list objects from minio: ", e);
}

return objects;
}

private String getPreSignedUrl(String filename) {
return "http://localhost:8080/file/".concat(filename);
}

}

创建Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@RestController
@RequestMapping(value = "/file")
public class FileController {

@Autowired
private MinioService minioService;

@GetMapping
public ResponseEntity<Object> getFiles() {
return ResponseEntity.ok(minioService.getListObjects());
}

}

上传文件到minio

下面代码是添加到MinioService中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public FileDto uploadFile(FileDto request) {
try {
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(request.getFile().getOriginalFilename())
.stream(request.getFile().getInputStream(), request.getFile().getSize(), -1)
.build());
} catch (Exception e) {
log.error("Happened error when upload file: ", e);
}
return FileDto.builder()
.title(request.getTitle())
.description(request.getDescription())
.size(request.getFile().getSize())
.url(getPreSignedUrl(request.getFile().getOriginalFilename()))
.filename(request.getFile().getOriginalFilename())
.build();
}

对应的Controller

1
2
3
4
@PostMapping(value = "/upload")
public ResponseEntity<Object> upload(@ModelAttribute FileDto request) {
return ResponseEntity.ok().body(minioService.uploadFile(request));
}

下载文件

添加到MinioService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public InputStream getObject(String filename) {
InputStream stream;
try {
stream = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(filename)
.build());
} catch (Exception e) {
log.error("Happened error when get list objects from minio: ", e);
return null;
}

return stream;
}

Controller

1
2
3
4
5
6
7
8
@GetMapping(value = "/**")
public ResponseEntity<Object> getFile(HttpServletRequest request) throws IOException {
String pattern = (String) request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE);
String filename = new AntPathMatcher().extractPathWithinPattern(pattern, request.getServletPath());
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(IOUtils.toByteArray(minioService.getObject(filename)));
}

访问:http://localhost:8080/file/{filename},页面中会展示或者下载对应的文件

参考

默认情况下,MySQL数据库只允许本地连接,当然数处于安全考虑,但是对于调试或者需要远程连接的情况下,会带来不少的麻烦

本文介绍如何允许数据库的远程连接,其方法对于MariaDB数据库也适用

配置MySQL服务

编辑配置文件

1
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf

找到 bind-address配置项,修改为0.0.0.0,允许所有地址的连接,如果有特定的服务器,建议修改为指定的IP地址

1
bind-address = 0.0.0.0

在mysql8.0版本中,可能没有bind-address选项,自己添加到[mysqld]中即可

重启服务

1
2
3
4
# unubtu or Debian
sudo systemctl restart mysql
# On RedHat based distributions like CentOS
sudo systemctl restart mysqld

Mysql 授权,允许用户远程连接

1
2
3
mysql -u root -p

mysql> GRANT ALL ON database_name.* TO user_name@'ip_address' IDENTIFIED BY 'user_password';

如授权root用户可以远程登录

1
mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'youpassword' WITH GRANT OPTION;

如果是MYSQL8.0,上述命令会报如下错误:

ERROR 1410 (42000): You are not allowed to create a user with GRANT

是由于从8.0开始,创建用户需要用 CREATE USER,如下:

1
2
3
mysql> CREATE USER 'root'@'%' IDENTIFIED BY 'PASSWORD';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
mysql> FLUSH PRIVILEGES;

配置防火墙

配置防火墙,允许3306端口远程访问

iptables

1
2
3
4
# 指定所有IP均可访问
sudo iptables -A INPUT -p tcp --destination-port 3306 -j ACCEPT
# 指定IP可访问
sudo iptables -A INPUT -s 10.8.0.5 -p tcp --destination-port 3306 -j ACCEPT

UFW

Ubuntu默认防火墙

1
2
3
4
# 所有IP可访问
sudo ufw allow 3306/tcp
# 指定IP访问
sudo ufw allow from 10.8.0.5 to any port 3306

FirewallD

一般用在centos系统上

1
2
3
4
5
6
7
8
9
10
# 所有IP可访问
sudo firewall-cmd --permanent --zone=public --add-port=3306/tcp
sudo firewall-cmd --reload

# 指定IP可访问
sudo firewall-cmd --new-zone=mysqlzone --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --permanent --zone=mysqlzone --add-source=10.8.0.5/32
sudo firewall-cmd --permanent --zone=mysqlzone --add-port=3306/tcp
sudo firewall-cmd --reload

参考:

Axios js是非常流行的HTTP请求。你可以在Vue js, node js, react js中使用Axios js来启动get, post, put等请求,但如果你需要同样的要求来从API下载文件响应,并使用Axios js来进行下载,那么你如何做到这一点?我会帮你用Axios做文件下载。

Axios HTTP Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
axios({
url: 'http://localhost:8000/api/get-file',
method: 'GET',
responseType: 'blob',
}).then((response) => {
var fileURL = window.URL.createObjectURL(new Blob([response.data]));
var fileLink = document.createElement('a');

fileLink.href = fileURL;
fileLink.setAttribute('download', 'file.pdf');
document.body.appendChild(fileLink);

fileLink.click();
});

HTML Code

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
<!DOCTYPE html>
<html>
<head>
<title>Download File using Axios Vue JS? - HackTheStuff</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js" integrity="sha256-S1J4GVHHDMiirir9qsXWc8ZWw74PHHafpsHp5PXtjTs=" crossorigin="anonymous"></script>
</head>
<body>
<div id="app">
<button @click="onClick()">DownLoad</button>
</div>
<script type="text/javascript">

var app = new Vue({
el: '#app',
methods: {
onClick() {
axios({
url: 'http://localhost:8000/my.pdf',
method: 'GET',
responseType: 'blob',
}).then((response) => {
var fileURL = window.URL.createObjectURL(new Blob([response.data]));
var fileLink = document.createElement('a');

fileLink.href = fileURL;
fileLink.setAttribute('download', 'file.pdf');
document.body.appendChild(fileLink);

fileLink.click();
});
}
}
})
</script>
</body>
</html>
参考: https://hackthestuff.com/article/how-to-download-file-in-vuejs-using-axios