Docker


Docker is software that provides containers, which allows teams to emulate development environments. Docker began as an internal project, initially developed by dotCloud engineers.


Start useing docker

Full image

1
docker full <name>

Start container

1
docker run <container id/name>

Start a stopped container

1
docker start <container id/name>

Stop an already container

1
docker stop <container id/name>

Two ways to get into the container

  1. docker attach
    Causes the container to stop if exiting the inter action.
  2. docker exec
    Exit the container without affecting the operation.

*** Start the interactive container ***

1
docker run -i -t <container id/name> /bin/bash

Export container

1
docker export <container id/name> <filename>.tar

Import container

1
cat <filename>.tar | docker import - <name>

Delete container

1
docker rm -f <container>

Remove all stopped container

1
docker container prune

View the internal process of the container

1
docker top <container id/name>

Delete image

1
docker rmi <image name>
Run parameter Explain
-i Interactive
-t Terminal
-d Program running in the background
-p Port Mapping (Host:Pc:80->Container:80)
-name Container name

ESP8266开发板之WI-PWN(Wi-Fi杀手)制作教程

一、材料

  • ESP8266 开发板一块(废话)
  • Micro-USB 数据线一根(用于电脑和 ESP8266 通讯)
  • ESP8266 CH340G 驱动(根据自己的开发板选择,开发版背面有标注)
  • WI-PWN 固件
  • 烧录软件 ESP8266Flasher

二、步骤

1、首先把 ESP8266 连接到电脑,安装好 CH340 驱动
2、选择 Operation -> COM Port -> 选择 ESP8266 的端口(此电脑 -> 设备管理器中可看)
3、选择 Config -> 点击 齿轮按钮 -> 选择 WI-PWN 固件文件 并设置为 -> 0x00000
4、选择 Advanced -> 并依次设置为 -> Baudrate(512000) -> Flash size(1MByte) -> Flash speed(80MHz) -> SPI Mode(QIO)
5、回到 Operation 点击 Flash 按钮,等待烧录完成即可
6、长按开发版上的 RST 按钮,LED 灯亮起即可

三、说明

当重启完成后,Wi-Fi列表会多出一个名为 WI-PWN 的Wi-Fi,默认没有密码,点击连接,浏览器输入 192.168.4.1 即可进入控制面板,首次使用,设置好 SSID (网络名称)以及密码等待系统重启,重新连接即可使用。

四、附件

1、WI-PWN GitHub -> WI-PWN
2、HC340 驱动 -> HC340

GORM时间格式化问题

说明

在做项目的时候,遇到 Gorm 添加进数据库的数据时间格式是没有问题的,但想要查询返回给前端的时候就会发现时间格式是这样的:“2022-07-03T22:14:02.973528+08:00”,带着时区和毫秒,但其实往往这样的格式不是我们想要的。废话不多说直接上代码,主要就是 JOSN 的序列化和反序列化的问题。

定义一个 Time 类型

在model下面新建一个 time.go 文件,定义一个 Time 类型,主要就是做 JSON 的序列化和反序列化。

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
package model

import (
"database/sql/driver"
"fmt"
"time"
)

const timeFormat = "2006-01-02 15:04:05"
const timezone = "Asia/Shanghai"

type Time time.Time

func (t Time) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(timeFormat)+2)
b = append(b, '"')
b = time.Time(t).AppendFormat(b, timeFormat)
b = append(b, '"')
return b, nil
}

func (t *Time) UnmarshalJSON(data []byte) (err error) {
now, err := time.ParseInLocation(`"`+timeFormat+`"`, string(data), time.Local)
*t = Time(now)
return
}

func (t Time) String() string {
return time.Time(t).Format(timeFormat)
}

func (t Time) local() time.Time {
loc, _ := time.LoadLocation(timezone)
return time.Time(t).In(loc)
}

func (t Time) Value() (driver.Value, error) {
var zeroTime time.Time
var ti = time.Time(t)
if ti.UnixNano() == zeroTime.UnixNano() {
return nil, nil
}
return ti, nil
}

func (t *Time) Scan(v interface{}) error {
value, ok := v.(time.Time)
if ok {
*t = Time(value)
return nil
}
return fmt.Errorf("can not convert %v to timestamp", v)
}

将数据库结构体中的类型改为 Time 类型

1
2
3
4
5
6
7
package model

type User struct {
Name string `json:"name" gorm:"type:varchar(32);not null"`
CreatedAt Time `json:"created_at" gorm:"type:timestamp"`
UpdatedAt Time `json:"updated_at" gorm:"type:timestamp"`
}

设置数据库时区

到这里还需要注意一个问题就是,需要把Gorm连接数据库的时区设置一下,也就是 loc=Asia/Shanghai

1
fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Asia/Shanghai")

至此完美解决!!!

Gin框架学习笔记

获取参数

获取 Query 参数

在 Gin 框架中,可以通过 Query 来获取 URL 中?后面所携带的参数。例如/name=admin&pwd=123456。获取方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
name := c.Query("name")
pwd := c.Query("pwd")
// fmt.Printf("name:%s ; pwd:%s",name,pwd)
c.JSON(http.StatusOK, gin.H{
"name": name,
"pwd": pwd,
})
})
r.Run()
}

获取 Form 参数

当前端请求的数据通过 form 表单提交时,例如向/user/reset发送了一个 POST 请求,获取请求数据方法如下:

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

import (
"net/http"
"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()
r.POST("/", func(c *gin.Context) {
username := c.PostForm("username") //对应h5表单中的name字段
password := c.PostForm("password")
c.HTML(http.StatusOK, gin.H{
"username": username,
"password": password,
})
})
r.Run()
}

获取 Path 参数

请求的参数通过 URL 路径传递,例如/user/admin,获取请求 URL 路径中的参数方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()
r.GET("/user/:username", func(c *gin.Context) {
username := c.Param("username")
c.JSON(http.StatusOK, gin.H{
"username": username,
})
})
r.Run()
}

路由

普通路由

1
2
3
r.GET("/get",func(c *gin.Context) {})
r.GET("/login",func(c *gin.Context) {})
r.POST("/login",func(c *gin.Context) {})

此外,还有一个可以匹配所有请求方法的 Any 方法如下:

1
r.Any("/test",func(c *gin.Context) {})

路由组

我们可以将拥有共同前缀 URL 的路由划分为一个路由组,并且 Group 支持嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
v5 := route.Group("/v5")
{
// Create
v5.POST("/classes/:table", v5a.Create)

// Delete
v5.DELETE("/classes/:table/:id", v5a.Delete)

// Update
v5.PUT("/classes/:table/:id", v5a.Update)

// Read
v5.GET("/classes/:table/:id", v5a.Read)
}

Golang获取当前时间、时间戳和时间字符串及它们之间的相互转换

1、获取当前时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
currentTime := time.Now() // 获取当前时间,类型是Go的时间类型Time

t1 := time.Now().Year() // 年

t2 := time.Now().Month() // 月

t3 := time.Now().Day() // 日

t4 := time.Now().Hour() // 小时

t5 := time.Now().Minute() // 分钟

t6 := time.Now().Second() // 秒

t7 := time.Now().Nanosecond() // 纳秒

// 如果获取UTC时间,则可以使用time.UTC
currentTimeData := time.Date(t1, t2, t3, t4, t5, t6, t7, time.Local) // 获取当前时间,返回当前时间Time

fmt.Println(currentTime) // 打印结果:2017-04-11 12:52:52.794351777 +0800 CST

fmt.Println(t1, t2, t3, t4, t5, t6) // 打印结果:2017 April 11 12 52 52

fmt.Println(currentTimeData) // 打印结果:2017-04-11 12:52:52.794411287 +0800 CST

说明:从打印结果可以看出,time.Now() 和 Date() 方法都可以获取当前时间,time.Now() 用起来比较简单,但是 Date() 可以获取不同的精确值,如time.Date(t1, t2, t3, t4, t5, t6, 0, time.Local)将毫秒省略,精确到秒,结果为:2017-04-11 12:52:52 +0800 CST

2、获取当前时间戳

1
2
3
timeUnix := time.Now().Unix() // 单位s,打印结果:1491888244

timeUnixNano := time.Now().UnixNano() // 单位纳秒,打印结果:1491888244752784461

3、获取当前时间的字符串格式

1
2
3
timeStr := time.Now().Format("2006-01-02 15:04:05") // 当前时间的字符串,2006-01-02 15:04:05 据说是golang的诞生时间,固定写法

fmt.Println(timeStr) // 打印结果:2017-04-11 13:24:04

4、它们之间的相互转化

1) 时间戳转时间字符串 (int64 —> string)

1
2
3
4
5
timeUnix := time.Now().Unix() // 已知的时间戳

formatTimeStr := time.Unix(timeUnix,0).Format("2006-01-02 15:04:05")

fmt.Println(formatTimeStr) // 打印结果:2017-04-11 13:30:39

2) 时间字符串转时间(string —> Time)

1
2
3
4
5
6
7
formatTimeStr = "2017-04-11 13:33:37"

formatTime, err := time.Parse("2006-01-02 15:04:05", formatTimeStr)

if err == nil {
fmt.Println(formatTime) // 打印结果:2017-04-11 13:33:37 +0000 UTC
}

3) 时间字符串转时间戳 (string —> int64)
比上面多一步,formatTime.Unix() 即可

JavaScript实现MQTT的订阅与发布系统

最近在写项目的时候,遇到需要使用纯前端的方式实现 MQTT 的订阅与发布功能,网上找了几小时都没有找到太有用的资料;后面在 EMQ 的官方网站上找到了 JavaScript 的 SDK,于是就到该 SDK 的 GitHub 上研究了一番,总体来说,实现起来还是比较简单的,直接上代码:

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
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>MQTT</title>
<meta charset="utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js"></script>
<script type="text/javascript">
// 使用 Paho MQTT (EMQX Public Server)
var client = new Paho.MQTT.Client('broker.emqx.io', Number(8083), 't1y');

// 设置回调处理程序
client.onConnectionLost = onConnectionLost; // 重新连接
client.onMessageArrived = onMessageArrived; // 接收消息

// 建立 MQTT 连接
client.connect({onSuccess: onConnect});

// 客户端连接时调用
function onConnect() {
// 建立连接后订阅并发送消息
console.log('连接成功');
// 订阅主题
client.subscribe('t1y/os/apikey');
// 发送消息
message = new Paho.MQTT.Message('Client test'); // 内容
message.destinationName = 't1y/os/apikey'; // 主题
client.send(message);
}

// 当客户端失去连接时调用
function onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log('连接丢失: ' + responseObject.errorMessage);
}
}

// 消息到达时调用
function onMessageArrived(message) {
console.log('接收到消息: [' + message.payloadString + ']');
}
</script>
</head>
<body>
Hello MQTT
</body>
</html>

MQTT之PubSubClient库

我在 MQTT 简介中的 ESP8266 的示例显得有点复杂,虽说 Adafruit 的库貌似很强大,但从软件角度来看却显得非常的臃肿,简洁至尚才是写代码的王道。

幸亏 MQTT 的库有非常的多,这里我会采用一款更简单好用的库PubSubClient来做一个最简单的MQTT客户端。

PubSubClient 可以在 Arduino IDE 的库管理器中找到。
然后我们来写个例子,当 ESP8266 收到来自 tone/devices/switch/主题中值为 1 的信息就点亮板载的 LED,收到 0 就熄灭 LED。

首先定义基本的变量:

1
2
3
4
5
6
7
8
9
10
11
12
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "网络SSID";
const char* password = "密码";
const char* mqtt_server = "broker.mqtt-dashboard.com"; // 使用 HIVEMQ 的信息中转服务
const char* TOPIC = "tone/devices/switch/"; // 订阅信息主题
const char* client_id = "clientId-ApjJZcy9Dh"; // 标识当前设备的客户端编号

WiFiClient espClient; // 定义 wifiClient 实例
PubSubClient client(espClient); // 定义 PubSubClient 实例
long lastMsg = 0;

然后是初始化

1
2
3
4
5
6
7
void setup() {
pinMode(BUILTIN_LED, OUTPUT); // 定义板载 LED 灯为输出方式
Serial.begin(115200);
setup_wifi(); // 执行 Wifi 初始化,下文有具体描述
client.setServer(mqtt_server, 1883); // 设定 MQTT 服务器与使用的端口,1883是默认的 MQTT 端口
client.setCallback(callback); // 设定回调方式,当 ESP8266 收到订阅消息时会调用此方法
}

接下来是初始化WIFI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void setup_wifi() {
delay(10);
// 板子通电后要启动,稍微等待一下让板子点亮
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}

Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}

连接成功后会在串口监视器中输出当前 ESP8266 的 IP地址。接下来就是编写回调的逻辑,就是当收到一特定的信息时如何让 ESP 来执行一个具体动作:

注意 void callback(char* topic, byte* payload, unsigned int length)的参数是固定的,是一个方法接口除了方法名可改,类型与参数个数都不能错。

payload 传递过来的内容是个二进制的流,所以可以任何的内容,当然上述代码中我们也可以将其转化为整数来用,另外还可以直接传递JSON格式的数据,这样能让数据更加可读。但在单片机的设备间通信还是以“小”为原则, 能传整数的时候绝不传字符串,这样可以大大减轻整体网络通信的负担同时也可以降低单片机运行的功耗。

接下来我们再写一个方法来防止 MQTT 服务下线,当断开服务时可以尝试重新连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect(client_id)) {
Serial.println("connected");
// 连接成功时订阅主题
client.subscribe(TOPIC);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}

最后就是主循环,主循环的逻辑非常简单:

  • MQTT是否连接,没有就重试
  • 每隔2秒向 tone/status/ 主题发一个设备上线的信息

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
client.publish("tone/status/", "{device: client_id,'status': 'on'}");
}
}

以下是本例的全部代码:

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
#include <ESP8266WiFi.h>
#include <PubSubClient.h>

const char* ssid = "网络SSID";
const char* password = "密码";
const char* mqtt_server = "broker.mqtt-dashboard.com"; // 使用 HIVEMQ 的信息中转服务
const char* TOPIC = "tone/devices/switch/"; // 订阅信息主题
const char* client_id = "clientId-ApjJZcy9Dh"; // 标识当前设备的客户端编号

WiFiClient espClient; // 定义 wifiClient 实例
PubSubClient client(espClient); // 定义 PubSubClient 的实例
long lastMsg = 0; // 记录上一次发送信息的时长

void setup() {
pinMode(BUILTIN_LED, OUTPUT); // 定义板载 LED 灯为输出方式
Serial.begin(115200);
setup_wifi(); // 执行 Wifi 初始化,下文有具体描述
client.setServer(mqtt_server, 1883); // 设定 MQTT 服务器与使用的端口,1883是默认的 MQTT 端口
client.setCallback(callback); // 设定回调方式,当 ESP8266 收到订阅消息时会调用此方法
}

void setup_wifi() {
delay(10);
// 板子通电后要启动,稍微等待一下让板子点亮
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic); // 打印主题信息
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]); // 打印主题内容
}
Serial.println();

if ((char)payload[0] == '1') {
digitalWrite(BUILTIN_LED, HIGH); // 亮灯
} else {
digitalWrite(BUILTIN_LED, LOW); // 熄灯
}
}

void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect(client_id)) {
Serial.println("connected");
// 连接成功时订阅主题
client.subscribe(TOPIC);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}

void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
client.publish("tone/status/", "{device: client_id,'status': 'on'}");
}
}

That’s all! 与 Adafruit 库相比 PubSubClient 是不是更加简单实用?

Linux查找/终止进程

以前一直都是使用ps -aux | grep "Str"的方式,列出进程后通过 grep 过滤得到对应的 PID 进程号,然后再通过kill结束掉进程;但经常这样使用就会觉得很繁琐,于是就又研究了一下,发现了一些更方便的方式,废话不多说,直接开始:

根据字符串查找 PID

1
pgrep -f [搜索字符串]

根据字符串终止 PID 对应进程

慎用:请注意是不是只找到一个PID

1
pkill -f [搜索字符串]

就是这么一回事,更简短的命令,很方便使用,由于我的项目名称比较复杂,所以查出来的进程基本上是唯一的,所以我就直接使用 pkill 了;但大家不清楚的情况下还是先查找 PID 看看是不是只有一个进程再结束,以免出现一些意外!另外大家做好的成熟项目在 Linux 中跑也可以在.bashrc中创建几个永久的别名保存,例如:alias xxxstop=’pkill -f “Str”‘,这样就更方便管理自己的项目了。

MongoDB用户权限管理及配置

一、MongoDB命令

理解 admin 数据库

安装 MongoDB 时,会自动创建 admin 数据库,这是一个特殊数据库,提供了普通数据库没有的功能。

有些用户角色赋予用户操作多个数据库的权限,而这些角色只能在 admin 数据库中创建,要创建有权操作所有数据库的超级用户,必须将该用户加入到 admin 数据库中。检查凭证时,MongoDB 将在指定数据库和 admin 数据库中检查用户账户。

内建的角色

数据库用户角色:read、readWrite;
数据库管理角色:dbAdmin、dbOwner、userAdmin;
集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
备份恢复角色:backup、restore;
所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
超级用户角色:root #这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase)
内部角色:__system

角色说明

read:允许用户读取指定数据库;
readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限;
readWrite:允许用户读写指定数据库;
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限;
dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile;
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限;
clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限;
userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户;
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限;
root:只在admin数据库中可用。超级账号,超级权限;

主要命令

show dbs #显示数据库列表
show collections #显示当前数据库中的集合(类似关系数据库中的表)
show users #显示用户
use #切换当前数据库,如果数据库不存在则创建数据库。
db.help() #显示数据库操作命令,里面有很多的命令
db.foo.help() #显示集合操作命令,同样有很多的命令,foo指的是当前数据库下,一个叫foo的集合,并非真正意义上的命令
db.foo.find() #对于当前数据库中的foo集合进行数据查找(由于没有条件,会列出所有数据)
db.foo.find( { a : 1 } ) #对于当前数据库中的foo集合进行查找,条件是数据中有一个属性叫a,且a的值为1
MongoDB没有创建数据库的命令,但有类似的命令。 如:如果你想创建一个“myTest”的数据库,先运行use myTest命令,之后就做一些操作(如:db.createCollection(‘user’)),这样就可以创建一个名叫“myTest”的数据库。

其他命令

db.dropDatabase() #删除当前使用数据库
db.cloneDatabase(“127.0.0.1”) #将指定机器上的数据库的数据克隆到当前数据库
db.copyDatabase(“mydb”, “temp”, “127.0.0.1”) #将本机的mydb的数据复制到temp数据库中
db.repairDatabase() #修复当前数据库
db.getName() #查看当前使用的数据库,也可以直接用db
db.stats() #显示当前db状态
db.version() #当前db版本
db.getMongo() #查看当前db的链接机器地址
db.serverStatus() #查看数据库服务器的状态

二、配置访问控制

1、介绍

MongoDB安装完成后,数据库 admin 中没有任何用户账户。在数据库 admin 中没有任何账户时,MongoDB 向从本地主机发起的连接提供全面的数据库管理权限。因此配置 MongoDB 新实例时,首先需要创建用户管理员账户和数据库管理员账户。用户管理员账户可在 admin 和其他数据库中创建用户账户。您还需要创建一个数据库管理员账户,将其作为管理数据库、集群、复制和 MongoDB 其他方面的超级用户。

用户管理员账户和数据库管理员账户都是在数据库 admn 中创建的。在 MongoDB 服务器中启用身份验证后,要以用户管理员或数据库管理员的身份连接到服务器,必须向 admin 数据库验证身份,您还需在每个数据库中创建用户账户,让这些用户能够访问该数据库。

2、创建用户管理员账户

配置访问控制的第一步是创建用户管理员账户。用户管理员应只有创建用户账户的权限,而不能管理数据库或执行其他管理任务。这确保数据库管理和用户账户管理之间有清晰的界限。

在 admin 数据库中,添加一个用户并赋予userAdminAnyDatabase角色,userAdminAnyDatabase只在admin数据库中可用,赋予用户所有数据库的userAdmin权限。
例如,下面是在 admin 数据库中创建一个名为myUserAdmin用户。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MongoDB shell version: 3.2.6
connecting to: test
> use admin
switched to db admin
> db.createUser(
... {
... user: "myUserAdmin",
... pwd: "abc123",
... roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
... }
... )
Successfully added user: {
"user" : "myUserAdmin",
"roles" : [
{
"role" : "userAdminAnyDatabase",
"db" : "admin"
}
]
}

用户管理员应只有创建用户账户的权限,而不能管理数据库或执行其他管理任务。
要创建某个库的管理用户,必须在 admin 进行认证,给哪个库创建用户就先切换到哪个库下面。

3、开启权限验证

编辑配置文件/etc/mongod.conf,修改内容如下:

1
2
security:
authorization: enabled

重启MongoDB服务

1
systemctl restart mongod

现在,客户端连接到服务器时必须提供用户名和密码。另外,从 MongoDB shell 访问 MongoDB 服务器时,如果要添加用户账户,必须执行下面的命令向数据库 admin 验证身份:

1
2
3
4
5
> use admin
switched to db admin
> db.auth("myUserAdmin","abc123")
1
>

也可以在启动 MongoDB shell 时使用选项-u和-p向数据库 admin 验证身份:

mongo -u “myUserAdmin” -p “abc123” –authenticationDatabase admin

4、创建数据库管理员账户

要创建数据库管理员,可在 MongoDB shell 中切换到数据库 admin,再使用方法createUser添加角色为readWriteAnyDatabase、dbAdminAnyDatabase和clusterAdmin的用户。这让这名用户能够访问系统中的所有数据库、创建新的数据库以及管理 MongoDB 集群和副本集。

创建一个名为 dbadmin 的数据库管理员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> use admin
switched to db admin
> db.createUser(
... {
... user: "dbadmin",
... pwd: "abc123",
... roles: [ "readWriteAnyDatabase", "dbAdminAnyDatabase","clusterAdmin" ]
... }
... )
Successfully added user: {
"user" : "dbadmin",
"roles" : [
"readWriteAnyDatabase",
"dbAdminAnyDatabase",
"clusterAdmin"
]
}

数据库管理员能够访问系统中的所有数据库、创建新的数据库以及管理 MongoDB 集群和副本集。
如果要求管理其他数据库,首先要去 admin 库里面去认证。

四、创建普通用户

一旦经过认证的用户管理员,可以使用db.createUser()去创建额外的用户。
你可以分配mongodb内置的角色或用户自定义的角色给用户。

这个 myUserAdmin 用户仅仅只有特权去管理用户和角色,如果你试图执行其他任何操作,例如在 test 数据库中的foo集合中去读数据,mongodb将返回错误。

你创建用户的数据库(这里就是test数据库)是该用户认证数据库。尽管用户认证是这个数据库,用户依然可以有其他数据库的角色。即用户认证数据库不限制用户权限。

创建一个角色为readWrite的用户 test1 来管理数据库test。

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
[root@mbasic ~]# mongo
MongoDB shell version: 3.2.6
connecting to: test
> use admin
switched to db admin
> db.auth('myUserAdmin','abc123')
1
> use test
switched to db test
> db.createUser(
... {
... user:"test1",
... pwd: "test1",
... roles: [{ role: "readWrite", db: "test"}]
... }
... )
Successfully added user: {
"user" : "test1",
"roles" : [
{
"role" : "readWrite",
"db" : "test"
}
]
}
>

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@mbasic ~]# mongo
MongoDB shell version: 3.2.6
connecting to: test
> use test
switched to db test
> db.auth('test1','test1')
1
>
创建一个dmp用户,对dmp数据库只读权限。
> use admin
switched to db admin
> db.auth('myUserAdmin','abc123')
1
> use dmp
switched to db dmp
>db.createUser(
{
user:"dmp1",
pwd: "dmp1pass",
roles: [{ role: "read", db: "dmp"}]
}
)

Vue3+Vite4进行Axios请求封装及接口API的统一管理

本文使用Vue3+Vite4+TypeScript进行展示

一、准备

src目录下新建api文件夹及文件:

  • src/api/api.ts 进行接口API的统一管理
  • src/api/axios.ts 封装请求配置拦截器
  • src/api/status.ts 管理接口返回状态码

/根目录下的vite.config.ts配置如下,主要配置为envserver,实现代理解决CORS同源策略问题:

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
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
// 获取`.env`环境配置文件
const env = loadEnv(mode, process.cwd())
return {
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
server: {
// host: 'localhost', // 只能本地访问
host: '0.0.0.0', // 局域网别人也可访问
port: Number(env.VITE_APP_PORT),
// 运行时自动打开浏览器
// open: true,
proxy: {
[env.VITE_APP_BASE_API]: {
target: env.VITE_APP_SERVICE_API,
changeOrigin: true,
rewrite: (path) =>
path.replace(
new RegExp('^' + env.VITE_APP_BASE_API),
'',
),
},
},
},
resolve: {
// 配置路径别名
alias: {
'@': '/src',
},
},
}
})

/目录下新建.env.dev文件,配置开发环境下的环境变量,内容如下:

1
2
3
4
5
6
7
8
9
10
# 开发环境
# NODE_ENV='dev'

# 为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。
# js中通过`import.meta.env.VITE_APP_BASE_API`取值
VITE_APP_PORT = 5173
VITE_APP_BASE_API = '/dev-api'

# 后端服务地址
VITE_APP_SERVICE_API = 'http://localhost:8080'

/目录下新建.env.prod文件,配置生产环境下的环境变量,内容如下:

1
2
3
4
5
6
7
8
9
10
# 生产环境
# NODE_ENV='prod'

# 为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。
# js中通过`import.meta.env.VITE_APP_BASE_API`取值
VITE_APP_PORT = 5173
VITE_APP_BASE_API = '/prod-api'

# 后端服务地址
VITE_APP_SERVICE_API = 'http://192.168.10.127:8080'

/目录下编辑package.json文件,配置开发以及生产环境下使用不同的环境变量:

1
2
3
4
5
6
"scripts": {
"dev": "vite --mode dev",
"prod": "vite --mode prod",
"build:prod": "vite build --mode prod",
"preview": "vite preview --port 8080 --mode prod"
},

二、axios.ts

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
import axios from 'axios'
import { getErrorMessage } from './status'
import { ElMessage } from 'element-plus'

axios.defaults.timeout = 60000 // 设置超时时间
axios.defaults.baseURL = import.meta.env.VITE_APP_BASE_API // 设置请求地址
axios.defaults.headers.common['Content-Type'] = 'application/json;charset=UTF-8' // 设置传参方式

// 请求拦截器
axios.interceptors.request.use(
(config) => {
// 设置Authorization头,可以从localStorage或cookie中获取令牌
const token = localStorage.getItem('token') // 替换为实际的令牌获取方式
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
},
)

// 响应拦截器
axios.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
const { response } = error
if (response) {
// 请求已发出,但是不在2xx的范围内
ElMessage.warning(getErrorMessage(response.status))
} else {
ElMessage.error('网络连接异常,请稍后再试!')
}
return Promise.reject(error)
},
)

// 封装请求并导出
export function request(url = '', params = {}, type = 'post') {
// 设置 url params type 的默认值
return new Promise((resolve, reject) => {
const config = {
url,
method: type,
data: type === 'get' ? undefined : params,
params: type === 'get' ? params : undefined,
}

axios(config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}

三、status.ts

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
export const getErrorMessage = (status: number | string): string => {
let message: string = ''
switch (status) {
case 400:
message = '请求错误(400)'
break
case 401:
message = '未授权,请重新登录(401)'
break
case 403:
message = '拒绝访问(403)'
break
case 404:
message = '请求出错(404)'
break
case 408:
message = '请求超时(408)'
break
case 500:
message = '服务器错误(500)'
break
case 501:
message = '服务未实现(501)'
break
case 502:
message = '网络错误(502)'
break
case 503:
message = '服务不可用(503)'
break
case 504:
message = '网络超时(504)'
break
case 505:
message = 'HTTP版本不受支持(505)'
break
default:
message = `连接出错(${status})!`
}
return `${message},请检查网络或联系管理员!`
}

四、api.ts

引入axios导出的request,按功能模块进行接口的管理:

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
import { request } from './axios'

/**
* @description - 封装User类型的接口方法
*/
export class UserService {
/**
* @description 登录
* @param {username, password} 用户名, 密码
* @return {HttpResponse} 结果
*/
static async login(params: any) {
// 登录
return request('/login', params, 'post')
}
}

/**
* @description - 封装Other类型的接口方法
*/
export class OtherService {
/**
* @description 其它
* @param {} 参数
* @return {HttpResponse} 结果
*/
static async other(params: any) {
// 其它1
return request('/other', params, 'get')
}

/**
* @description 其它2
* @param {} 参数
* @return {HttpResponse} 结果
*/
static async other2(params: any) {
// 其它1
return request('/other2', params, 'get')
}
}

五、使用

无需在main.ts内引入,需要什么模块的接口在相应页面引入改模块即可。

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
<script setup>
import { onMounted } from 'vue'
import { UserService, OtherService } from '/src/api/api.ts'

onMounted(() => {
login()
other()
})

const login = async () => {
const loginParams = {
username: 'test',
password: 'test',
}
const res = await UserService.login(loginParams)
console.log(res)
}

const other = () => {
const otherParams = {
a: 'a',
b: 'b',
}
OtherService.other(otherParams).then((res) => {
console.log(res)
})
}
</script>

六、结束语

今天在配置项目时在网上搜寻了众多Vue3 -Axios的相关文章,发现没有完全符合本人需求的内容,所以最后只能自己写了。