0%

说明

本次主要时在windows下,利用go 操作redis

redis

概述

  • Redis(Remote Dictionary Server ),即远程字典服务,它是一个开源的,使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis 默认端口为 6379,是一个NoSQL数据库

安装

  • 打开地址下载安装包并安装,安装成功后在环境变量中可以看到,安装目录已经加入到了PATH中

启动服务端

  • 执行命令启动服务端
1
2
D:\app\Redis>redis-server.exe redis.windows.conf
[6436] 10 Jul 17:44:10.177 # Creating Server TCP listening socket *:6379: bind: No error
  • 查看服务端口是否正常
1
2
3
D:\app\Redis>netstat -an | findstr "6379"
TCP 0.0.0.0:6379 0.0.0.0:0 LISTENING
TCP [::]:6379 [::]:0 LISTENING

启动客户端

打开redis目录中的redis.windows.conf

  • 配置Redis的设置最大占用内存,设置maxmemory参数,maxmemory是字节字节类型,如果不设置maxmemory或者设置为0,64位系统不限制内存,32位系统最多使用3GB内存。
1
2
3
# maxmemory <bytes>
# 设置最大的内存为1G
maxmemory 1000000000
  • 执行命令
1
D:\app\Redis>redis-cli.exe -h 127.0.0.1 -p 6379
  • 进行set和get操作
1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> set username hello
OK
127.0.0.1:6379> get username
"hello"
127.0.0.1:6379>SCAN 0 MATCH * // 查询所有key
127.0.0.1:6379> FLUSHDB //删除所有Key
127.0.0.1:6379> keys * // 查询所有key
1) "cache:user:id:23"
127.0.0.1:6379> del "cache:user:id:admin" // 删除指定key
(integer) 1

go

实现

  • 使用gin框架

  • 自定义Person结构体

  • 实现对Person的增删改查。

  • 根据id查询或查询所有、插入、修改、删除

代码

  • 先安装依赖文件
1
2
go get -u github.com/gin-gonic/gin
go get github.com/redis/go-redis/v9
  • 新建并初始化项目
1
2
3
4
cd E:\proj\gowork
mkdir studyGin
cd studyGin
go mod init example.com/goRedis
  • 用vscode ide编写代码
  • db\dbs.go 主要存放连接redis的代码
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
package db

import (
"context"
"fmt"

"github.com/redis/go-redis/v9"
)

var Rdbs *redis.Client

func InitRedis(ctx context.Context) {
rd := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
Rdbs = rd
//清空当前数据库中的所有key,只要加了这个每次重新启动服务器,所有数据被清空
// Rdbs.FlushDB(ctx)
_, err := Rdbs.Ping(ctx).Result() // PING, <nil>
if err != nil {
fmt.Println("connect redis failed:", err)
return
}

}
func Close() {
Rdbs.Close()
}


  • model\users.go 操作user的实体类,如曾删改查
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
package models

import (
"context"
"encoding/json"
"fmt"

"example.com/goRedis/db"
)

type User struct {
Key string `json:"key"`
Name string `json:"name"`
Password string `json:"password"`
Id int `json:"id"`
}

// 存储和读取redis时,不能直接存入结构体,必须进行序列号和反序列化

// 序列化
func (u *User) MarshalBinary() (data []byte, err error) {
return json.Marshal(u)
}

// 反序列化
func (u *User) UnmarshalBinary(data []byte) (err error) {
return json.Unmarshal(data, u)
}

// 新增数据
func (u *User) Add(ctx *context.Context) (err error) {

// HMSet 批量设置 map[string]interface{}{"name": "张", "password": "11111", "id": 123}

fmt.Println("json.=", u)
// 必须转为[]byte, redis不支持直接存结构体

// 0 表示key永不过期
_, err = db.Rdbs.Set(*ctx, u.Key, u, 0).Result()
if err != nil {
fmt.Println("db.Rdbs.Set,", err)
return
}
return
}

// 根据key 获取到数据
func (u *User) GetUser(ctx *context.Context) (user1 User, err error) {

err = db.Rdbs.Get(*ctx, u.Key).Scan(u)
if err != nil {
fmt.Println("GetUser error", err)
return
}
user1 = *u
return
}

// 查询所有
func (u *User) QueryAll(ctx *context.Context) (user1 []User, er error) {
// keys, err := db.Rdbs.Keys(*ctx, "[1-9]*")).Result()
keys, err := db.Rdbs.Keys(*ctx, "*").Result()
fmt.Println("key=", keys)
if err != nil {
fmt.Println("db.Rdbs.Keys error:", err)
return
}
for _, key := range keys {
err = db.Rdbs.Get(*ctx, key).Scan(u)
if err != nil {
fmt.Print("Rdbs.Get error", err)
fmt.Println("key=", key)
return
}
fmt.Println("query=", *u)
user1 = append(user1, *u)

}
return
}

// 删除
func (u *User) DeleteUser(ctx *context.Context) (err bool) {
// 先判断key是否存在
if db.Rdbs.Exists(*ctx, u.Key).Val() == 1 {
_, er := db.Rdbs.Del(*ctx, u.Key).Result()
if er != nil {
fmt.Println("DeleteUser error: ", err)
return false
}
return true
}
return false
}

// 修改
func (u *User) UpdateUser(ctx *context.Context) (err error) {
// 先判断key是否存在
if db.Rdbs.Exists(*ctx, u.Key).Val() == 1 {
// 必须转为[]byte, redis不支持直接存结构体
u1, _ := json.Marshal(u)
_, err = db.Rdbs.Set(*ctx, u.Key, u1, 0).Result()
if err != nil {
fmt.Println("UpdateUser error:", err)
return
}
}
return
}


  • api/users.go 调用model层代码
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
package api

import (
"context"
"fmt"
"net/http"

"example.com/goRedis/models"
"github.com/gin-gonic/gin"
)

func UserAdd(c *gin.Context, ctx *context.Context) {
fmt.Println("add=", c)
var user models.User
if c.Bind(&user) == nil { //把客户端格式传过来的数据绑定到结构体user中去
fmt.Println("data=", user)
err := user.Add(ctx) // 调用model层的对应方法
if err != nil {

c.JSON(http.StatusOK, gin.H{
"msg": "新增失败",
"code": -1,
})

} else {
c.JSON(http.StatusOK, gin.H{
"msg": "新增成功",
"code": 1,
})
}
} else {
c.JSON(400, gin.H{"JSON=== status": "binding JSON error!"})
}
}

func UserGet(c *gin.Context, ctx *context.Context, key string) {
// 接受key
users := models.User{
Key: key,
}
if c.Bind(&users) == nil {
users, err := users.GetUser(ctx)
if err != nil {

c.JSON(http.StatusOK, gin.H{
"msg": "获取失败",
"code": -1,
"user": users,
})

} else {
c.JSON(http.StatusOK, gin.H{
"msg": "获取成功",
"code": 1,
"user": users,
})
}
} else {
c.JSON(400, gin.H{"JSON=== status": "binding JSON error!"})
}
}

func UserAll(c *gin.Context, ctx *context.Context) {
var user models.User
if c.Bind(&user) == nil {
users, err := user.QueryAll(ctx)
if err != nil {

c.JSON(http.StatusOK, gin.H{
"msg": "获取失败",
"code": -1,
"user": users,
})

} else {
c.JSON(http.StatusOK, gin.H{
"msg": "获取成功",
"code": 1,
"user": users,
})
}
} else {
c.JSON(400, gin.H{"JSON=== status": "binding JSON error!"})
}
}

func UserDel(c *gin.Context, ctx *context.Context) {
var user models.User
if c.Bind(&user) == nil {
err := user.DeleteUser(ctx)
fmt.Println("del=", err)
if !err {

c.JSON(http.StatusOK, gin.H{
"msg": "删除失败1",
"code": -1,
})

} else {
c.JSON(http.StatusOK, gin.H{
"msg": "删除成功1",
"code": 1,
})
}
} else {
c.JSON(400, gin.H{"JSON=== status": "binding JSON error!"})
}
}

func UserUpdate(c *gin.Context, ctx *context.Context) {
var user models.User
if c.Bind(&user) == nil {
err := user.UpdateUser(ctx)
if err != nil {

c.JSON(http.StatusOK, gin.H{
"msg": "修改失败",
"code": -1,
})

} else {
c.JSON(http.StatusOK, gin.H{
"msg": "修改成功",
"code": 1,
})
}
} else {
c.JSON(400, gin.H{"JSON=== status": "binding JSON error!"})
}
}

  • main.go 代码
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
package main

import (
"context"

"example.com/goRedis/api"
"example.com/goRedis/db"
"github.com/gin-gonic/gin"
)

func main() {
ctx := context.Background()
db.InitRedis(ctx)
defer db.Close()
r := gin.Default()
r.POST("/userAdd", func(c *gin.Context) {
api.UserAdd(c, &ctx)
})
r.GET("/userAll", func(c *gin.Context) {
api.UserAll(c, &ctx)
})
r.GET("/userGet/:key", func(c *gin.Context) {
key := c.Param("key")

api.UserGet(c, &ctx, key)
})
r.POST("/userUpdate", func(c *gin.Context) {
api.UserUpdate(c, &ctx)
})
r.POST("/userDel", func(c *gin.Context) {
api.UserDel(c, &ctx)
})
r.Run(":8000")
}

客户端

  • 使用python调用服务端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data ={"name": "test11", "password": "1123456", "id": 1, "key": "t_key1"}
resp = requests.post("http://127.0.0.1:8000/userAdd", json=data)
print(resp.text)
resp = requests.get("http://127.0.0.1:8000/userGet/t_key2")
print(resp.text)

resp = requests.get("http://127.0.0.1:8000/userAll")
print(resp.text)


data ={"name": "test1131", "password": "123456811", "id": 111, "key": "t_key21"}
resp = requests.post("http://127.0.0.1:8000/userUpdate", json=data)
print(resp.text)

data ={"key": "t_key1"}
resp = requests.post("http://127.0.0.1:8000/userDel", json=data)
print(resp.text)

其他

说明

  • 重装系统后,电脑上搭建的很多东西用不了,简单记录
  • 本次系统win10

heox

  • 本地搭建了hexo的博客,其中主要用的是node,只要把node的安装目录设置到环境变量中即可
  • 然后再博客的目录,执行hexo cl && hexo g && hexo s执行成功
  • 发现使用hexo d 提交到服务器后,内容没有变化,需要如下设置
1
2
3
git config --global user.email '284772894@qq.com'
# 然后提交到服务器
hexo cl && hexo g && hexo d

mysql

  • 环境变量设置好了mysql的根目录后,直接开启服务提示无效
1
2
3
C:\Users\Administrator>net start mysqld
服务名无效。

  • 分别执行如下操作
1
2
3
4
5
6
7
8
9
# 将mysql服务注册到win服务中
C:\Users\Administrator>mysqld --initialize
# 安装服务
C:\Users\Administrator>mysqld --install
Service successfully installed.
# 启动服务
C:\Users\Administrator>net start mysql
MySQL 服务正在启动 .
MySQL 服务已经启动成功。
  • 最终用heidisql连接数据库成功,库里面的数据正常

python

  • 直接在环境变量中设置python的根目录和Script目录
  • 设置pip下载为国内镜像
1
2
3
4
5
# 临时使用
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xxx
# 永久使用,清华镜像
C:\Users\Administrator>pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
Writing to C:\Users\Administrator\AppData\Roaming\pip\pip.ini
  • 测试
1
2
3
C:\Users\Administrator>pip install pip -U
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Requirement already satisfied: pip in d:\app\python37\lib\site-packages (23.1.2)

vue

  • 需要安装node.js,安装hexo 时已经安装了node.js
  • 打开本地的Microsoft VS Code IDE 然后打开本地项目
  • 打开终端执行安装依赖
1
npm install
  • 运行代码
1
npm run serve

typora

  • 下载并解压typora1.2.4-Windows(破解包).7z

  • 安装后,不打开typora

  • 使用app.asar 文件替换typora_home/resources 中文件

  • 打开typora输入任意邮箱,序列号输入:E8Q9Y5-KXMTL5-7578SL-4S5XKS

go

  • 设置go_home\bin到环境变量中
  • 启动本地的Microsoft VS Code IDE,打开扩展安装中文
  • 打开go项目文件夹,终端中输入:go run main.go

说明

  • FISCO BCOS是由国内企业主导研发、对外开源、安全可控的企业级金融联盟链底层平台,由金链盟开源工作组协作打造,并于2017年正式对外开源
  • 因为需要使用python sdk来操作FISCO BCOS,官方只支持FISCO BCOS2.0

概念介绍

  • 智能合约:可以理解为逻辑代码,比如此次实例中我的智能合约代码就是一个sol文件中
  • 区块链网络:智能合约的代码若要运行,就要放在区块链网络上执行

环境搭建

区块链网络

  • 云服务器搭建成功后,发现无法外网访问,看这里的文档说明云主机的公网IP均为虚拟IP造成的

  • 也就是区块链的服务端,可以参考这里

  • 不启用国密

Python SDK

  • 主要用来操作FISCO BCOS搭建的区块链网络

  • 环境搭建参考这里

  • 我用的win10,使用的python版本为3.7.9,没有使用虚拟环境

  • 没有安装Microsoft Visual C++ 14.0

  • client_config.py 为配置文件:

1
2
channel_host = "XXXXXX.121.XXX"  # 填入channel通信ip
channel_port = 20200 # 节点的channel 端口
  • 把区块链网络层那边产生的证书文件放在bin目录下,一般为:
1
2
channel_ca = "bin/ca.crt"
channel_ca = "bin/ca.crt"

编写代码

  • 合约文件一般放在contracts目录中,如contracts\helloword.sol
  • 合约代码采用的solidity的语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pragma solidity ^0.4.24;

contract HelloWorld{
string name;
event onset(string newname);
constructor() public{
name = "Hello, World!";
}

function get() constant public returns(string){
return name;
}

function set(string n) public{
emit onset(n);
name = n;
}
}
  • 编写部署合约文件,调用合约函数等
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

from client.contractnote import ContractNote
from client.bcosclient import BcosClient
import os
from client.datatype_parser import DatatypeParser
from client.common.compiler import Compiler
from client_config import client_config

# 从文件加载abi定义

PATH = lambda p: os.path.abspath(
os.path.join(os.path.dirname(__file__), p)
)

demo_config = client_config
_sol = PATH("./contracts/HelloWord.sol") # 合约文件
_api = PATH("./contracts/HelloWord.abi")

if os.path.isfile(demo_config.solc_path) or os.path.isfile(demo_config.solcjs_path):
Compiler.compile_file(_sol)
# Compiler.compile_file(SimpleInfo_sol)
_bin = PATH("./contracts/HelloWord.bin")
abi_file = _api
data_parser = DatatypeParser()
data_parser.load_abi_file(abi_file)
contract_abi = data_parser.contract_abi


def deploy():
client = BcosClient()
print(client.getinfo())
# 部署合约
print("\n>>Deploy:----------------------------------------------------------")
with open(_bin, 'r') as load_f:
contract_bin = load_f.read()
load_f.close()
result = client.deploy(contract_bin)
print("deploy", result)
print("new address : ", result["contractAddress"])
contract_name = os.path.splitext(os.path.basename(abi_file))[0]
memo = "tx:" + result["transactionHash"]
# 把部署结果存入文件备查
ContractNote.save_address_to_contract_note("demo", contract_name,
result["contractAddress"])
client.finish()
def call():
client = BcosClient()
# stat = StatTool.begin()
# 调用一下call,获取数据
print("\n>>Call:------------------------------------------------------------------------")
to_address = "xxxxxxxx"

client.sendRawTransactionGetReceipt(to_address,contract_abi,"set",["hi"])

data = client.call(to_address, contract_abi, "get")
print(data)
client.finish()


if __name__ == "__main__":
deploy()
# call()

  • client.deploy 部署合约
  • sendRawTransactionGetReceipt 执行修改操作
  • call 执行查询操作

其他实例

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
// pragma solidity ^0.6.3;
pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;

contract TestStruct {

struct User {
string name; // 对应的是item_id
string user_name;
string issue_date;
string start_date;
string end_date;
}

event onadd(string newname);
event onadduser(string newname,User u);
event onaddusers(uint256 len,User[] u);

mapping (string => User) users;

constructor() public
{
// User memory u = User("alice",10001, "2012-12-01", "start");
User memory u = User("100001","lisa", "2012-12-01", "start", "end");
addUser(u);
}


function addUser (User memory _user) public {

addbyname(_user.name,_user);


}

function addbyname (string memory name,User memory _user) public {

users[name] = _user;
emit onadd(name);
emit onadduser(name,_user);

}

function addUsers (User [] memory _users) public {

for (uint i = 0; i < _users.length; i++) {
//users[_users[i].name] = _users[i];
addUser(_users[i]);
}
emit onaddusers(_users.length,_users);
}

function getUser (string memory username) public view returns (User memory) {

//bytes32 hash = keccak256(abi.encode(username));
return users[username];
}
event on_putbytes(string n,bytes32 b);
function putbytes(string memory uname,bytes32 br) public
{
emit on_putbytes(uname,br);
}
}

说明

  • 本次搭建环境为:win10, go 1.20.4

安装

  • 新建并初始化项目
1
2
3
4
cd E:\proj\gowork
mkdir studyGin
cd studyGin
go mod init example.com/myGin
  • 下载并安装 gin
1
E:\proj\gowork\studyGin> go get -u github.com/gin-gonic/gin
  • 在vscode中编写测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"net/http"

"github.com/gin-gonic/gin"
)

func main() {
// 1.创建路由
r := gin.Default()
// 2.绑定路由规则,执行的函数
// gin.Context,封装了request和response
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "hello World!")
})
// 3.监听端口,默认在8080
// Run("里面不指定端口号默认为8080")
r.Run(":8000")
}

  • 运行
1
PS E:\proj\gowork\studyGin> go run .\main.go

image-20230621153207713

项目结构

  • 分别新建文件夹,查看到E:\proj\gowork\studyGin>tree /F文件结构如下:
1
2
3
4
5
6
7
8
9
10
11
go.mod
go.sum
│ main.go

├─apis # 存放具体的业务逻辑,比如users.go中放的增删改查的逻辑
├─database # 存放数据库的相关配置
├─middleware # 存放中间件的内容
│ ├─jwt
│ └─middleware
├─models # 存放结构体的文件
└─routers # 存放路由,比如get,post等对应关系

代码结构

  • database/mysql.go数据库连接
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
package database

//安装gorm
//go get gorm.io/gorm
// 安装mysql驱动
//go get gorm.io/driver/mysql
import (
"fmt"

"gorm.io/driver/mysql"
"gorm.io/gorm"
)

var Db *gorm.DB

func InitDb() {
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
database, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println("open mysql error:", err)
}
Db = database

}
func Close() {
sqlDB, _ := Db.DB()
sqlDB.Close()
}

  • models\users.go 存放的表结构,实现登录功能(对user实体类的逻辑处理)
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
package models

import (
"fmt"

"example.com/myGin/database"
)

type User struct {
Name string `form:"name",json:"name",bingding:"required",gorm:"unique;not null"`
Password string `form:"password",json:"password",bingding:"required"gorm:"NOT NULL"`
Id int `form:"id",gorm:"PRIMARY_KEY"`
}

// 登录
func (u *User) Login() (user1 User, err error) {
obj := database.Db.Where("name=? and password=?", u.Name, u.Password).First(&user1)
if err = obj.Error; err != nil {
fmt.Printf("这是登陆错误 %v 和 %T", err, err)
return
}
fmt.Println(user1)
return

}

// 获取用户列表
func (u *User) UserList() (users []User, err error) {
if err = database.Db.Find(&users).Error; err != nil {
return
}
return

}

注意func (u *User) UserList这里的用法,外面的函数要调用时,必须用User.UserList的方式进行调用

  • api/users.go 主要对models\users.go 代码进行调用,并把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
54
55
package apis

import (
"net/http"

"example.com/myGin/models"
"github.com/gin-gonic/gin"
)


func UserLogin(c *gin.Context) {
var user models.User
// c.ShouldBindJSON
if c.Bind(&user) == nil { //把客户端格式传过来的数据绑定到结构体user中去
msg, err := user.Login() // 调用model层的对应方法
if err != nil {
if err.Error() == "record not found" {
c.JSON(http.StatusOK, gin.H{
"msg": "用户不存在",
"user": nil,
})
} else {
c.JSON(http.StatusOK, gin.H{
"msg": "登陆错误",
"user": nil,
})

}

} else {
c.JSON(http.StatusOK, gin.H{
"msg": "登陆成功",
"user": msg,
})
}
} else {
c.JSON(400, gin.H{"JSON=== status": "binding JSON error!"})
}

}

// 获取用户列表
func GetUserList(c *gin.Context) {
var user models.User
users, err := user.UserList()
if err != nil {
fmt.Println("get user list error:", err)
}
c.JSON(http.StatusOK, gin.H{
"msg": users,
})
}



  • routers/router.go 对外的路由处理,比如http,get方法的处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package routers

import (
"example.com/myGin/apis"
"github.com/gin-gonic/gin"
)

func InitRouter() *gin.Engine {

router := gin.Default()
router.POST("/login", apis.UserLogin)
router.GET("/GetUserList", apis.GetUserList)
return router
}

  • main.go 代码入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"example.com/myGin/database"
"example.com/myGin/routers"
)

func main() {
database.InitDb() // 初始化数据库
defer database.Close()
router := routers.InitRouter() //指定路由
router.Run(":8000") //在8000端口上运行

}


  • 运行main.go,启动服务器
1
2
3
4
5
6
7
PS E:\proj\gowork\studyGin> go run .\main.go

...
GIN-debug] POST /login --> example.com/myGin/apis.UserLogin (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8000

go编写客户端

  • client\client.go用go编写客户端代码,包名为main,传递参数为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
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
package main

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
)

// 1、测试post请求的结构体
func TestPostLogin() {
reqMap := map[string]interface{}{
"name": "test1",
"password": "123456",
}

body, err := json.Marshal(reqMap)
if err != nil {
fmt.Println("TestPostReq json.Marshal err:", err)
return
}

url := "http://127.0.0.1:8000/login"

req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
if err != nil {
fmt.Println("TestPostReq http.NewRequest err:", err)
return
}

req.Header.Set("Content-Type", "application/json")

client := &http.Client{Timeout: 5 * time.Second} // 设置请求超时时长5s
resp, err := client.Do(req)
if err != nil {
fmt.Println("TestPostReq http.DefaultClient.Do() err: ", err)
return
}
defer resp.Body.Close()

respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("TestPostReq ioutil.ReadAll() err: ", err)
return
}
fmt.Println("respBody: ", string(respBody))

rsp := make(map[string]interface{})
err = json.Unmarshal(respBody, &rsp)
if err != nil {
fmt.Println("TestPostReq json.Unmarshal() err: ", err)
return
}
fmt.Printf("rsp: %+v", rsp)

// 最后经过字段筛选后,再序列化成json格式即可
// result, err := json.Marshal(rsp)
// if err != nil {
// fmt.Println("TestPostReq json.Marrshal() err2: ", err)
// return
// }
// fmt.Println(string(result))
}

func TestGetUserList() {
resp, err := http.Get("http://127.0.0.1:8000/GetUserList")
if err != nil {
fmt.Println("http get error:", err)
return
}
defer resp.Body.Close()
buf := make([]byte, 1024) // 创建一个切片,用例接受服务器返回的数据
for {
m, err := resp.Body.Read(buf)
if err != nil && err != io.EOF {
fmt.Println("resp.Body.Read error:", err)
return
}
res := string(buf[:m])
fmt.Println("get server content,", res)
break

}
}

func main() {
TestPostLogin()
TestGetUserList()
}

  • 运行client.go
1
2
3
4
5
PS E:\proj\gowork\studyGin> go run .\client\client.go
respBody: {"msg":"登陆成功","user":{"Name":"test1","Password":"123456","Id":3}}
rsp: map[msg:登陆成功 user:map[Id:3 Name:test1 Password:123456]]

get server content, {"msg":[{"Name":"test1","Password":"123456","Id":3},{"Name":"test2","Password":"1234567","Id":4}]}

python 发起请求

1
2
3
4
5
6
7
8
9
import requests

data ={"name": "test1", "password": "123456"}
resp = requests.post("http://127.0.0.1:8000/login", data=data)
print(resp.text)

resp2 = requests.get("http://127.0.0.1:8000/GetUserList")
print(resp2.text)

  • 得到结果如下
1
2
{"msg":"登陆成功","user":{"Name":"test1","Password":"123456","Id":3}}
{"msg":[{"Name":"test1","Password":"123456","Id":3},{"Name":"test2","Password":"1234567","Id":4}]}

HTML 渲染

  • gin 支持使用 LoadHTMLGlob() 或者 LoadHTMLFiles() 对html进行渲染

用户列表

  • 新建一个目录和文件:template\user\list.tmpl代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
<title>用户列表</title>
</head>
<body>
<div>
<p>列表数据:</p>
<!-- 定义一个 结构体切片 -->
{{ $userList := . }}

{{ range $index,$v := $userList.users }}
<p>{{ $index }}--{{ $v.Name }}--{{ $v.Password }}</p>
{{ end }}
</div>
</body>
</html>
  • 初始化路由器代码那里需要修改,新增加入加载 HTML 模板文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// routers/router.go
package routers

import (
"example.com/myGin/apis"
"github.com/gin-gonic/gin"
)

func InitRouter() *gin.Engine {

router := gin.Default()

router.POST("/login", apis.UserLogin)
// 加载 HTML 模板文件
router.LoadHTMLGlob("template/user/*")
router.GET("/GetUserList", apis.GetUserList)
return router
}
  • apis.GetUserList 中,返回的数据修改为采用html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// apis/user.go

// 获取用户列表
func GetUserList(c *gin.Context) {
var user models.User
users, err := user.UserList()
if err != nil {
fmt.Println("get user list error:", err)
}
// c.JSON(http.StatusOK, gin.H{
// "msg": users,
// })
c.HTML(http.StatusOK, "list.tmpl", gin.H{
"users": users,
})
}
  • 运行服务器端
1
PS E:\proj\gowork\studyGin> go run .\main.go
  • 打开浏览器,查看到结果

image-20230626103441913

登录验证

  • 通过上面的实例,缺少一个登录验证的流程,一个典型的验证流程为:登录成功–生成token–获取用户列表(检查token存在,则获取成功,否则获取失败)

  • 中间件中middleware\jwt\jwt.go 对登录后的token进行处理和验证

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

import (
"errors"
"fmt"
"log"
"net/http"
"time"

"github.com/golang-jwt/jwt/v5" // go get -u github.com/golang-jwt/jwt/v5

"github.com/gin-gonic/gin"
)

// JWTAuth 中间件,检查token
func JWTAuth() gin.HandlerFunc {
return func(c *gin.Context) {
// //获取到请求头中的token
token := c.Request.Header.Get("token")
if token == "" {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": "请求未携带token无权限访问",
})
c.Abort()
return
}

log.Print("get token: ", token)

j := NewJWT()
// parseToken 解析token包含的信息
claims, err := j.ParseToken(token)
fmt.Println("claims", claims)
if err != nil {
if err == TokenExpired {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": "授权已过期",
})
c.Abort()
return
}
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": err.Error(),
})
c.Abort()
return
}
// 继续交由下一个路由处理,并将解析出的信息传递下去
c.Set("claims", claims)
}
}

// JWT 签名结构
type JWT struct {
SigningKey []byte
}

// 一些常量
var (
TokenExpired error = errors.New("Token is expired")
TokenNotValidYet error = errors.New("Token not active yet")
TokenMalformed error = errors.New("That's not even a token")
TokenInvalid error = errors.New("Couldn't handle this token:")
SignKey string = "newtrekWang"
)

// 载荷,可以加一些自己需要的信息
type CustomClaims struct {
ID int `json:"userId"`
Name string `json:"name"`
Password string `json:"password"`
jwt.RegisteredClaims // 设置过期时间
}

// 新建一个jwt实例
func NewJWT() *JWT {
return &JWT{
[]byte(GetSignKey()),
}
}

// 获取signKey
func GetSignKey() string {
return SignKey
}

// 这是SignKey
func SetSignKey(key string) string {
SignKey = key
return SignKey
}

// CreateToken 生成一个token
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.SigningKey)
}

// 解析Tokne
func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, TokenInvalid
}

// 更新token
func (j *JWT) RefreshToken(tokenString string) (string, error) {

token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
return "", err
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
claims.RegisteredClaims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Hour * 24))

return j.CreateToken(*claims)
}
return "", TokenInvalid
}

  • apis/user.go 加入生成token代码
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
import (
...
"example.com/myGin/middleware/jwt" // 引用中间件
"example.com/myGin/models"
"github.com/gin-gonic/gin"
// 引用jwt-v5
jwtgo "github.com/golang-jwt/jwt/v5" // go get -u github.com/golang-jwt/jwt/v5
)

// 生成token
func GengerateToken(c *gin.Context, user models.User) {
// 调用中间件的初始化秘钥
j := &jwt.JWT{
[]byte("newtrekWang"), // 秘钥
}
claims := jwt.CustomClaims{
user.Id,
user.Name,
user.Password,
jwtgo.RegisteredClaims{
ExpiresAt: jwtgo.NewNumericDate(time.Now().Add(time.Hour * 24)), //定义过期时间为24小时
Issuer: "newtrekWang", //签名的发行者
},
}

token, err := j.CreateToken(claims)

if err != nil {
c.JSON(http.StatusOK, gin.H{
"status": -1,
"msg": err.Error(),
})
return
}

log.Println(token)
// 登录成功后的结构体数据
data := LoginResult{
User: user,
Token: token,
}
c.JSON(http.StatusOK, gin.H{
"status": 0,
"msg": "登录成功!",
"data": data,
})
return

}

  • routers\router.go 对请求进行验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package routers

import (
"example.com/myGin/apis"
"example.com/myGin/middleware/jwt"
"github.com/gin-gonic/gin"
)

func InitRouter() *gin.Engine {

router := gin.Default()
// 这样使用,并不能进行token验证
// v1 := router.Group("/v1")
// v1.Use(jwt.JWTAuth())
router.POST("/login", apis.UserLogin)
// 加载 HTML 模板文件
router.LoadHTMLGlob("template/user/*")
// 对接口进行token验证
router.GET("/GetUserList", jwt.JWTAuth(), apis.GetUserList)
return router

}
  • 运行服务器端
1
PS E:\proj\gowork\studyGin> go run .\main.go

客户端发起请求

  • 采用python,不带token值,报错
1
2
3
4
5
resp2 = requests.get("http://127.0.0.1:8000/GetUserList")
print(resp2.text)

{"msg":"请求未携带token无权限访问","status":-1}

  • 登录后,读取token,填入token发送请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
import json
data ={"name": "test1", "password": "123456"}
resp = requests.post("http://127.0.0.1:8000/login", data=data)
dic = json.loads(resp.text)
token = dic["data"]["Token"]
header = {"token": token}

resp2 = requests.get("http://127.0.0.1:8000/GetUserList", headers=header)
print(resp2.text)

<p>列表数据:</p>
<p>0--test1--123456</p>
<p>1--test11--1234567</p>
  • 直接传送正确的token,获取数据正常
1
2
3
4
5
6
7
8
9
token = "yJhbGciOiJIUzI1Ni......"
header = {"token": token}

resp2 = requests.get("http://127.0.0.1:8000/GetUserList", headers=header)
print(resp2.text)

<p>列表数据:</p>
<p>0--test1--123456</p>
<p>1--test11--1234567</p>

注意

  • 代码来自gin框架实例,其中jwt的验证这里,我用的v5版本,博主用的v4,

接口

  • 在Go语言中接口(interface)是一种类型,一种抽象的类型。
  • interface(接口)是golang最重要的特性之一,实现多态。Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量
  • 本篇主要来源这里

接口定义和实现

接口定义

1
2
3
4
5
6
// hello_work\iinterface\studyInterface.go
/ 定义一个动物接口,接口中有个方法名称,但是不具体实现
type Animal interface {
Say()
GetName() string //返回内容为string
}

接口实现

  • 一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

type Dog struct {
Name string
}

type Cat struct {
Name string
}

// 对应实现了接口中的方法
func (d Dog) Say() {
fmt.Println("汪汪汪")
}
// 对应实现了接口中的方法
func (d Dog) GetName() string {
return d.Name
}

func (c Cat) Say() {
fmt.Println("喵喵喵")
}
func (c Cat) GetName() string {
return c.Name
}

完整代码如下:

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
\\ hello_work/iinterface/studyInterface.go
package iinterface

import "fmt"

// 定义一个动物接口,接口中有个方法
type Animal interface {
Say()
GetName() string
}

// 如下开始,为实现接口中的方法

type Dog struct {
Name string
}

type Cat struct {
Name string
}

// 对应实现了接口中的方法
func (d Dog) Say() {
fmt.Println("汪汪汪")
}
func (d Dog) GetName() string {
return d.Name
}

func (c Cat) Say() {
fmt.Println("喵喵喵")
}
func (c Cat) GetName() string {
return c.Name
}

接口类型变量

  • 接口类型变量能够存储所有实现了该接口的实例
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
// hello_work\main.go
package main

import (
"fmt"

"example.com/hello_work/hello_work/iinterface"
)

func main() {
// 声明一个Animal类型的变量animal
var animal iinterface.Animal
// 实例化一个dog结构体
dog := iinterface.Dog{Name: "旺财"}
// 可以把dog实例直接赋值给变量animal
animal = dog
dogName := animal.GetName()
fmt.Println(dogName)
animal.Say()

cat := iinterface.Cat{Name: "工程喵"}
animal = cat
catName := animal.GetName()
fmt.Println(catName)
animal.Say()

}

  • 运行结果
1
2
3
4
5
PS E:\proj\gowork> go run .\hello_work\main.go
旺财
汪汪汪
工程喵
喵喵喵

注意,如果实现了接口中的方法(studyInterface.go)用的是指针,如func (d *Dog),那么对应实例化时也要传入指针,dog := &iinterface.Dog{Name: “旺财”}

空接口

  • 空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。

  • 空接口类型的变量可以存储任意类型的变量。

实例

  • 可以作为字符串,数字,bool类型等
1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
// 定义一个空接口x
var x interface{}
s := "pprof.cn"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}
  • 空接口作为函数的参数
1
2
3
4
// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
  • 空接口作为map的值
1
2
3
4
5
6
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "李白"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)

分析网站

image-20230620190153496

  • 点击下载图片时,经过分析最终的下载地址的n数量减一,为正则提取或者图片查找做准备http://i.52desktop.cn:81/upimg/allimg/{n}/201912415221023477802.jpg

image-20230620190505925

单线程

  • hello_work\craw\Craw1.go
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
// hello_work\craw\Craw1.go
package crawl

import (
"bufio"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"regexp"
"strconv"
"strings"
"time"
)

var (
// 正则查找图片的地址
reImg = `http://i.52desktop.cn:81/upimg/allimg/[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`
)

// 读取链接中所有的内容
func GetPageStr(url string) (pageStr string) {

resp, err := http.Get(url)
if err != nil {
fmt.Println("http.Get error", err)
return
}
defer resp.Body.Close()
// 读取页面内容
pagebytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("ioutil.ReadAll error", err)
return
}
// 字节转换为字符串
pageStr = string(pagebytes)
return pageStr
}
// 下载图片,核心内容是获取图片的reader、write对象
func downloadImg(savePath string, url string) error {

v, err := http.Get(url)
if err != nil {
fmt.Println("http.Get error", err)
return err
}
defer v.Body.Close()
fileName := path.Base(url) // 获取文件名
// 获得get请求响应的reader对象
reader := bufio.NewReaderSize(v.Body, 32*1024)
// 创建单个文件保存下来
file, err := os.Create(savePath + fileName)
if err != nil {
fmt.Println("ioutil.WriteFile error", err)
return err
}
// // 获得文件的writer对象
writer := bufio.NewWriter(file)

written, err := io.Copy(writer, reader)
if err != nil {
fmt.Println("io.Copy error", err)
return err
}
fmt.Println(fileName, "download is success, Total length:", written)
return nil

}
// 给main.go进行调用的外部地址
func GetCrawl1Img() {
start := time.Now()
for i := 2; i <= 21; i++ {
// 根据观察规则,发现下载图片地址网站
url := "http://www.52desktop.cn/html/DLZM/KPBZ/20191205/15898_" + strconv.Itoa(i) + ".html"
pageStr := GetPageStr(url)
re := regexp.MustCompile(reImg)
results := re.FindAllStringSubmatch(pageStr, -1) // -1 表示搜索所有可能的匹配项。
for _, result := range results {
if strings.Contains(result[0], "20191204") { // 过滤条件读取图片地址
downloadImg("E:\\proj\\gowork\\img\\", result[0])

}

}

}
elapse := time.Since(start)
fmt.Println("elapsed time is : ", elapse, "s")

}

  • hello_work/main.go
1
2
3
4
5
6
7
8
9
10
11
// hello_work/main.go
package main

import (
"example.com/hello_work/hello_work/crawl"
)

func main() {
crawl.GetCrawl1Img()
}

  • 调用,共下载了20张图片,耗时20秒
1
2
3
4
5
6
7
 E:\proj\gowork> go run .\hello_work\main.go
201912415221023477802.jpg download is success, Total length: 344218
201912415221023477802.jpg download is success, Total length: 344218
201912415221026577803.jpg download is success, Total length: 342307
...
2019124152210921778021.jpg download is success, Total length: 340471
elapsed time is : 20.861619s

并发爬虫

思路

  • 初始化一个数据管道
  • 爬虫写出:创建多个协程用于添加图片
  • 任务统计协程:检查多个个任务是否都完成,完成则关闭数据管道
  • 下载协程:从管道里读取链接并下载

代码

  • hello_work/crawl/Crawl2.go
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
package crawl

import (
"fmt"
"io/ioutil"
"net/http"
"path"
"regexp"
"strconv"
"strings"
"sync"
"time"
)

var (
// 正则提取表达式
geReImg = `http://i.52desktop.cn:81/upimg/allimg/[^"]+?(\.((jpg)|(png)|(jpeg)|(gif)|(bmp)))`

// 存放图片链接的数据管道
chanImageUrls chan string
// 启动多个goroutine,sync.WaitGroup来实现goroutine的同步
waitGroup sync.WaitGroup
// 用于监控协程
chanTask chan string
// 监控协程总数
chanTaskCount = 23
// 监控当前的下载任务是否完成的总数
count int
)

// 爬取图片链接,获取当前页的所有图片链接
func getImgs(url string) (urls []string) {
pageStr := GetPageStr(url)
re := regexp.MustCompile(geReImg)
results := re.FindAllStringSubmatch(pageStr, -1) // -1 表示搜索所有可能的匹配项。
fmt.Printf("共找到%d条结果\n", len(results))
for _, result := range results {
url := result[0]
if strings.Contains(result[0], "20191204") { // 过滤条件读取图片地址
urls = append(urls, url)
// fmt.Println(url)

}
}
return urls

}

func getImgUrls(url string) {
urls := getImgs(url)
// 遍历切片里所有链接,存入数据管道
for _, url := range urls {
// 存放图片链接的数据管道,发送图片链接
chanImageUrls <- url
}
// 标识当前协程完成
// 每完成一个任务,写一条数据
// 用于监控协程知道已经完成了几个任务
chanTask <- url
waitGroup.Done()

}

// 任务统计
func CheckOk() {
for {
// 从chanTask中接收值并赋值给变量,url
url := <-chanTask
fmt.Printf("%s 完成了爬虫任务\n", url)
count++
// 任务统计个协程是否全部完成,完成了久关闭通道
// 因为的我循环起始地址的索引为2,所以这里减去2
if count == chanTaskCount-2 {
close(chanImageUrls)
break
}
}
waitGroup.Done()

}
func downloadFile(savePath string, url string) {

v, err := http.Get(url)
if err != nil {
fmt.Println("http.Get error", err)
return
}
defer v.Body.Close()
fileName := savePath + path.Base(url) // 获取文件名
bytes, err := ioutil.ReadAll(v.Body)
if err != nil {
fmt.Println("io.ReadAll error", err)
return
}
// 直接写入内容,比Crawl1.go代码更简单
err = ioutil.WriteFile(fileName, bytes, 0666)
if err != nil {
fmt.Println("ioutil.WriteFile error", err)
return
}
fmt.Println(fileName, " download is success")

}

// 下载协程,从hanImageUrls管道中读取链接下载,读取的数据来自于这里函数getImgUrls,对chanImageUrls的写入
func DownloadImg() {
for url := range chanImageUrls {
downloadFile("E:\\proj\\gowork\\img\\", url)
}
waitGroup.Done()
}

func GetCrawl2Img() {
// 初始化管道
chanImageUrls = make(chan string, 100)
chanTask = make(chan string, chanTaskCount)
start := time.Now()

for i := 2; i < chanTaskCount; i++ {
waitGroup.Add(1)
// 根据观察规则,发现下载图片地址网站
go getImgUrls("http://www.52desktop.cn/html/DLZM/KPBZ/20191205/15898_" + strconv.Itoa(i) + ".html")

}
// 任务统计个协程是否全部完成
waitGroup.Add(1)
go CheckOk()
// 创建一个新的协程来执行 Wait 后面的操作
go func() {
// 等待所有协程完成
waitGroup.Wait()

// 执行一些操作
fmt.Println("All workers have completed.")
}()
// 下载协程,从管道中读取链接下载
for i := 0; i < 5; i++ {
waitGroup.Add(1)
go DownloadImg()

}

waitGroup.Wait()
elapse := time.Since(start)
fmt.Println("elapsed time is : ", elapse, "s")

}


  • hello_work/main.go
1
2
3
4
5
6
7
8
9
10
package main

import (
"example.com/hello_work/hello_work/crawl"
)

func main() {
// crawl.GetCrawl1Img()
crawl.GetCrawl2Img()
}
  • 调用,下载了20张图片,共耗时7秒,大幅提升下载速度
1
2
3
4
5
6
PS E:\proj\gowork> go run .\hello_work\main.go
E:\proj\gowork\img\201912415221040677807.jpg download is success
E:\proj\gowork\img\201912415221040677807.jpg download is success
E:\proj\gowork\img\2019124152210546778011.jpg download is success
All workers have completed.
elapsed time is : 7.2002013s s

image-20230620203609304

总结

  • 代码主要来自这里

  • 其中CheckOk代码中的count计算不正确,无法关闭close(chanImageUrls),导致waitGroup无法关闭,引起阻塞,本次代码中已经修复

mysql准备

  • 官网下载对应的zip包,我选择的(mysql-8.0.22-winx64.zip)

  • 在mysql-8.0.22-winx64文件夹下面新建一个my.ini文件和一个data文件夹

  • my.ini中的内容

    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
    my.ini内容:

    [mysqld]
    # 设置3306端口
    port=3306
    # 设置mysql的安装目录
    basedir=E:\\app\\mysql-8.0.22-winx64
    # 设置mysql数据库的数据的存放目录
    datadir=E:\\app\\mysql-8.0.22-winx64\\data
    # 允许最大连接数
    max_connections=200
    # 允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统
    max_connect_errors=10
    # 服务端使用的字符集默认为UTF8
    character-set-server=utf8
    # 创建新表时将使用的默认存储引擎
    default-storage-engine=INNODB
    [mysql]
    # 设置mysql客户端默认字符集
    default-character-set=utf8
    # 注意这里的配置,ONLY_FULL_GROUP_BY的报错问题
    sql_mode='STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'
    [client]
    # 设置mysql客户端连接服务端时默认使用的端口
    port=3306
    default-character-set=utf8

    5.7后的版本,使用了group by分组后,select后的字段必须也要是分组的,不然报错ONLY_FULL_GROUP_BY,因此需要设置sql_mode

  • 我的电脑设置环境变量

    • MYSQL_HOME=e:\app\mysql-8.0.22-winx64
    • 在path中添加%MYSQL_HOME%\bin

mysql安装

  • 已管理员身份打开cmd,输入mysqld --initialize --user=mysql --console发现弹出没有vcruntime140_1.dll文件
  • vcruntime140_1.dll文件放到C:\Windows\System32C:\Windows\SysWOW64
  • 运行下面的bat文件,进行注册
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @echo 开始注册
    copy vcruntime140_1.dll %windir%\system32\
    regsvr32 %windir%\system32\vcruntime140_1.dll /s
    @echo system32 vcruntime140_1.dll注册成功

    copy vcruntime140_1.dll %windir%\SysWOW64\
    regsvr32 %windir%\system32\vcruntime140_1.dll /s
    @echo SysWOW64 vcruntime140_1.dll注册成功
    @pause
  • 初始化数据库
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    E:\>mysqld --initialize --user=mysql --console
    2020-11-28T03:32:30.871296Z 0 [System] [MY-013169] [Server] E:\app\mysql-8.0.22-
    winx64\bin\mysqld.exe (mysqld 8.0.22) initializing of server in progress as proc
    ess 6964
    2020-11-28T03:32:30.873294Z 0 [Warning] [MY-013242] [Server] --character-set-ser
    ver: 'utf8' is currently an alias for the character set UTF8MB3, but will be an
    alias for UTF8MB4 in a future release. Please consider using UTF8MB4 in order to
    be unambiguous.
    2020-11-28T03:32:30.884295Z 1 [System] [MY-013576] [InnoDB] InnoDB initializatio
    n has started.
    2020-11-28T03:32:33.331435Z 1 [System] [MY-013577] [InnoDB] InnoDB initializatio
    n has ended.
    2020-11-28T03:32:35.760574Z 6 [Note] [MY-010454] [Server] A temporary password i
    s generated for root@localhost: JoyfjqHL5)SO

  • JoyfjqHL5)SO这个就是得到的数据库密码
  • 输入命令安装mysql
    1
    2
    E:\>mysqld install
    Service successfully installed.
  • 启动mysql服务
    1
    2
    3
    E:\>net start mysql
    MySQL 服务正在启动 .
    MySQL 服务已经启动成功
  • 登陆mysql,粘贴刚刚生成的密码
    1
    E:\>mysql -u root -p
  • 登陆成功后修改密码
    1
    ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';
  • 刷新FLUSH PRIVILEGES;

mysql连接

  • 我这里使用heidisql
  • 新建会话,输入密码,打开下面页面就说明已经成功连接数据库
    image-20230612102438254

环境搭建

库表准备

  • 创建一个数据库
1
CREATE DATABASE Educational;
  • 创建学生表
1
2
3
4
5
6
7
8
9
10
use Educational 

CREATE TABLE student
(
ID CHAR(9) PRIMARY KEY, # PRIMARY KEY表示id为主键
name VARCHAR(10),
gender CHAR(6), # 性别
birthdate date # 出生年月
)

  • 创建课程表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE course
(
# 课程号,主键
courseID CHAR(5) PRIMARY key,
# 课程名
coursename VARCHAR(36),
# 课程简介
Syllabus text,
# 课时
hours INT,
# 学分
credit TINYINT,
# 开课学期
semester text
);
  • 创建一个成绩表,将学生表和课程表进行关联
1
2
3
4
5
6
7
8
9
10
CREATE TABLE Score 
(
# 学号,关联学生表中id,作为外键
ID CHAR(9) REFERENCES student(ID),
# 课程号.关联课程表中id,作为外键
courseID CHAR(5) REFERENCES course(CourseID),
# 成绩
Results DECIMAL(4,1) CHECK(results BETWEEN 0 AND 100)

)
  • 插入数据,学生表
1
2
3
4
5
6
7
INSERT INTO student VALUES('202201001','汤姆猫','男','2000-01-01');
INSERT INTO student VALUES('202201002','风火轮','男','1998-11-03');
INSERT INTO student VALUES('202201003','闪光弹','女','2000-08-21');
INSERT INTO student VALUES('202201004','降世拳','女','2010-04-11');
INSERT INTO student VALUES('202201005','流星腿','女','2010-09-23');
INSERT INTO student VALUES('202201006','闪闪光','女','2010-02-02');
INSERT INTO student VALUES('202201007','汤汤水','女','1999-03-29');

image-20230612112157798

  • 插入数据,课程表
1
2
3
4
5
6
INSERT INTO course VALUES('10001','英语', '英语简介介绍',28,20,'2023-07-01');
INSERT INTO course VALUES('10002','数学', '英语简介介绍',32,17,'2023-07-01');
INSERT INTO course VALUES('10003','语文', '语文简介介绍',29,25,'2023-07-01');
INSERT INTO course VALUES('10004','物理', '物理简介介绍',21,20,'2023-08-05')
INSERT INTO course VALUES('10005','媒体', '摄像媒体简介介绍',13,12,'2023-08-01');
INSERT INTO course VALUES('10006','java', 'java简介介绍',17,5,'2023-08-11')
  • 插入数据,成绩表
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
INSERT INTO Score VALUES('202201001','10001',89);
INSERT INTO Score VALUES('202201002','10001',76);
INSERT INTO Score VALUES('202201003','10001',99);
INSERT INTO Score VALUES('202201004','10001',88);
INSERT INTO Score VALUES('202201005','10001',59);
INSERT INTO Score VALUES('202201006','10001',69);

INSERT INTO Score VALUES('202201001','10002',80);
INSERT INTO Score VALUES('202201002','10002',77);
INSERT INTO Score VALUES('202201003','10002',92);
INSERT INTO Score VALUES('202201004','10002',83);
INSERT INTO Score VALUES('202201005','10002',56);
INSERT INTO Score VALUES('202201006','10002',100);

INSERT INTO Score VALUES('202201001','10003',81);
INSERT INTO Score VALUES('202201002','10003',72);
INSERT INTO Score VALUES('202201003','10003',63);
INSERT INTO Score VALUES('202201004','10003',81);
INSERT INTO Score VALUES('202201005','10003',55);
INSERT INTO Score VALUES('202201006','10003',93);

INSERT INTO Score VALUES('202201001','10004',82);
INSERT INTO Score VALUES('202201002','10004',71);
INSERT INTO Score VALUES('202201003','10004',96);
INSERT INTO Score VALUES('202201004','10004',75);
INSERT INTO Score VALUES('202201005','10004',58);
INSERT INTO Score VALUES('202201006','10004',89);

INSERT INTO Score VALUES('202201001','10005',91);
INSERT INTO Score VALUES('202201002','10005',86);
INSERT INTO Score VALUES('202201003','10005',97);
INSERT INTO Score VALUES('202201004','10005',38);
INSERT INTO Score VALUES('202201005','10005',61);
INSERT INTO Score VALUES('202201006','10005',18);

查询

  • 基础语法
1
2
3
4
5
6
7
select 查询列表
from 表1 别名,表2 别名
where 表1.key=表2.key
【and 筛选条件】
【group by 分组字段】
【having 分组后的筛选】
【order by 排序字段】
  • 关于group by 分组的理解

    • 合并数据,比如张三有两条数据分别为100,20,合并后就成了120
    • 在使用聚合函数,如sum,avg等需要分组,比如张三有对应两条数据为10和20,李四对应两条数据为10和10,使用avg 求平均值时

    不分组

    1
    2
    3
    4
    5
    6
    select name,avg(num),count(1) from table 
    # 若不分组就是整张表的平均值,即:(10+20+10+10)/4=12.5
    # count(1),则是统计表中数据行,上述计算出为4
    # 而这里的name则是读取表中一条数据,张三

    张三 12.5 4

    分组

    1
    2
    3
    4
    5
    select name,avg(num),count(1) from table group name
    # 首先对张三、李四进行分组,各自的数据为10+20=30、10+10=20
    # 然后对分组后的数据进行平均计算,30/2=15、10/2=10
    张三 15 2
    李四 10 2

实践

  • 不及格的学生姓名,性别,科目,分数
1
SELECT student.name, student.gender,course.coursename, score.Results FROM score, student,course WHERE score.ID=student.ID AND score.courseID=course.courseID and Results <=60

image-20230614154352090

  • 查询所有学生共参加了几门考试的信息,并且分数大于80,一定要分组,不分组就是查询的共有多少人参加了考试
1
SELECT student.ID,student.name,COUNT(1) FROM student,score WHERE student.ID = score.ID AND score.Results>80 GROUP BY student.ID

image-20230614154417658

  • 计算当前学生所有成绩的总分,使用的左连接
1
SELECT student.name,sum(score.Results),COUNT(1) FROM score LEFT JOIN student on score.ID=student.ID GROUP BY student.ID

image-20230614154453362

  • 计算当前学生所有成绩的总分大于300的信息,分组后,使用having sum
1
2
SELECT student.name,sum(score.Results),COUNT(1) FROM score LEFT JOIN student on score.ID=student.ID GROUP BY student.ID HAVING SUM(score.Results)>90

image-20230614154545101

  • 查询当前分数最高的学生名字、科目。注意这里and results= 使用了子查询
1
SELECT student.name,course.coursename,results from course,score, student WHERE course.courseID=score.courseID AND student.ID=score.ID and results=(SELECT MAX(score.Results) FROM score)

image-20230614160341440

  • 查询各个科目的平均成绩
1
select course.coursename,avg(score.Results),COUNT(1) FROM course  JOIN score ON course.courseID = score.courseID GROUP BY course.courseID

image-20230614160434587

  • 查询某个学生共参加了几门考试
1
2
SELECT student.name,count(score.Results) FROM student,score WHERE student.name="汤姆猫" AND student.ID=score.ID

  • 查看学生成绩总分为前三的数据。先对学生进行分组,然后用sum计算所有分数,然后取前3条数据
1
SELECT stu.name, sum(sc.Results) FROM student stu ,score sc WHERE sc.ID=stu.ID GROUP BY stu.ID ORDER BY sum(sc.Results) DESC LIMIT 3

image-20230614160553477

  • 查询各科成绩前三的学生信息,这个自己写不出来,用chatgpt写的
1
2
3
4
5
6
7
8
9
10
11
SELECT s.name AS student_name, c.coursename AS course_name, sc.Results AS course_score
FROM score sc
JOIN student s ON sc.ID = s.ID -- 连接score表和student表,获取学生姓名
JOIN course c ON sc.courseID = c.courseID -- 连接score表和course表,获取课程名称
WHERE (
SELECT COUNT(DISTINCT Results) -- 查询比当前成绩高的不同分数的数量
FROM score sc2
WHERE sc2.courseID = sc.courseID AND sc2.Results > sc.Results
) < 3 -- 只保留比当前成绩高的不同分数小于3个的记录,即前三名
ORDER BY c.coursename, sc.Results DESC; -- 按照课程名称和成绩降序排序

image-20230614173746372

  • 查询所有课程最高分数的学生信息,用查询各科成绩前三的语句,把<3给为<1即可

数组Array

  • 是同一种数据类型的固定长度的序列。

  • 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值

  • 数组定义:var a [len]T,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变。

  • 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
全局:
var arr0 [5]int = [5]int{1, 2, 3}
var arr1 = [5]int{1, 2, 3, 4, 5}
var arr2 = [...]int{1, 2, 3, 4, 5, 6}
var str = [5]string{3: "hello world", 4: "tom"}
局部:
a := [3]int{1, 2} // 未初始化元素值为 0。
b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
d := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
}

字符串

如下代码所示:

  • 函数外定义字符串,为全局变量
  • 在函数内定义s3 := “5555”,为局部变量
  • 循环字符串时,单个字符的类型为byte,而字符有两种类型
    • 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符
    • 另一种是 rune 类型,代表一个 UTF-8 字符,如中文,rune 类型等价于 int32 类型。
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
// 全局定义字符串
var s1 = `
111
222
33
`
var s2 = "4444"

func main() {
// 局部定义
s3 := "5555"
var a = "中"
fmt.Println(s1, s2, s3, a)
s := "pprof.cn博客"
for i := 0; i < len(s); i++ { //byte
fmt.Printf("%v(%c) ", s[i], s[i])
//112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 229(å) 141() 154() 229(å) 174(®) 162(¢)
}
fmt.Println()
// 打印中文,需转换为rune类型
for _, r := range s { //rune
fmt.Printf("%v(%c) ", r, r)
//112(p) 112(p) 114(r) 111(o) 102(f) 46(.) 99(c) 110(n) 21338(博) 23458(客)
}
fmt.Println()

s1 := "hello"
// 强制类型转换
byteS1 := []byte(s1)
byteS1[0] = 'H'
fmt.Println(reflect.TypeOf(byteS1[0])) // uint8
fmt.Println(string(byteS1)) //Hello

s2 := "博客"
runeS2 := []rune(s2)
runeS2[0] = '狗'
fmt.Println(string(runeS2)) //狗客

byte

  • 在字符串中代码已经详细说明了byte的定义,就是字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符。

切片Slice

初始化

  • 看如下代码,为声明和初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//1.声明切片,int类型的数组
var s1 []int
if s1 == nil {
fmt.Println("是空")
} else {
fmt.Println("不是空")
}
// 2.:=
s2 := []int{}
// 3.make()
var s3 []int = make([]int, 0)
fmt.Println("s1,s2,s3", s1, s2, s3) // s1,s2,s3 [] [] []
// 4.初始化赋值
var s4 []int = make([]int, 0, 0)
fmt.Println("s4=", s4) // s4= []
s5 := []int{1, 2, 3}
fmt.Println(s5)
// 5.从数组切片
arr := [5]int{1, 2, 3, 4, 5}
var s6 []int
// 前包后不包
s6 = arr[1:4]
fmt.Println("s6=", s6) //s6= [2 3 4]

append

  • 切片追加
1
2
3
4
5
6
7
8
9
10
11
var a = []int{1, 2, 3}
fmt.Printf("slice a : %v\n", a) // slice a : [1 2 3]
var b = []int{4, 5, 6}
fmt.Printf("slice b : %v\n", b) // slice b : [4 5 6]
c := append(a, b...)
fmt.Printf("slice c : %v\n", c) //slice c : [1 2 3 4 5 6]
d := append(c, 7)
fmt.Printf("slice d : %v\n", d) // slice d : [1 2 3 4 5 6 7]
e := append(d, 8, 9, 10)
fmt.Printf("slice e : %v\n", e) // slice e : [1 2 3 4 5 6 7 8 9 10]

  • 向 slice 尾部添加数据,返回新的 slice 对象
1
2
3
4
5
6
7
s1 := make([]int, 0, 5)
fmt.Printf("%p\n", &s1) // 0xc000008078

s2 := append(s1, 1)
fmt.Printf("%p\n", &s2) // 0xc000008090

fmt.Println(s1, s2) // [] [1]

总结

  • 切片是指针类型,数组是值类型
  • 数组的赋值形式为值传递,切片的赋值形式为引用传递
  • 数组的长度是固定的,而切片长度可以任意调整(切片是动态的数组)
  • 数组只有长度一个属性,而切片比数组多了一个容量(cap)属性

map

  • map是一种无序的基于key-value的数据结构
  • 定义 map[KeyType]ValueType,如map[string]int
1
2
3
4
5
6
7
8
9
10
11
func main(){
// 使用make初始化一个map
scoreMap := make(map[string]int)
scoreMap["张三"] = 90
scoreMap["小明"] = 100
scoreMap["王五"] = 60
delete(scoreMap, "小明")//将小明:100从map中删除
for k,v := range scoreMap{
fmt.Println(k, v)
}
}

元素为map类型的切片

  • 对应于python中的[{"name":"王五","password": "123456", "address":"红旗大街"},{}]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var mapSlice = make([]map[string]string, 3)
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value) //index:0 value:map[]
}
fmt.Println("after init")
// 对切片中的map元素进行初始化
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "王五"
mapSlice[0]["password"] = "123456"
mapSlice[0]["address"] = "红旗大街"
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
// index:0 value:map[address:红旗大街 name:王五 password:123456]
//index:1 value:map[]
for k, v := range value {
fmt.Println("k=", k, "v=", v)
//k= name v= 王五
//k= address v= 红旗大
}
}
fmt.Println(mapSlice) // [map[address:红旗大街 name:王五 password:123456] map[] map[]]

值为切片类型的map

  • 对应于python中{“中国”:[“北京”, “上海”]}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap)
fmt.Println("after init")
key := "中国"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2)
}
value = append(value, "北京", "上海")
sliceMap[key] = value
fmt.Println(sliceMap) //map[中国:[北京 上海]]
fmt.Println(sliceMap["中国"]) // [北京 上海]
for _, v := range sliceMap["中国"] {
fmt.Println(v)
// 北京
// 上海
}
fmt.Println(sliceMap) //map[中国:[北京 上海]]

interface

  • interface是一种抽象的类型,可以用来表示任何类型

  • 比如如下场景,不像写结构体,就可以用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
js := `{"int":1,"string":"qwertyuiop","float":1.111}`
jsm := make(map[string]interface{})
// 反序列化
err := json.Unmarshal([]byte(js), &jsm)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(jsm) // map[float:1.111 int:1 string:qwertyuiop]
for k, v := range jsm {
fmt.Println("k=", k, "v=", v)
}


类型转换

byte 转换为string

  • data1.json中内容为:
1
2
3
1 2
2 3
中国 4
  • 代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
f, err := os.Open("hello_work\\data1.json")
if err != nil {
fmt.Println("open file eror:", err)
return
}
defer f.Close()
// 读取文件内容,类型为[]byte
byteValue, _ := ioutil.ReadAll(f)
fmt.Println(byteValue) // [49 32 50 13 10 50 32 51 13 10 228 184 173 229 155 189 32 52]
fmt.Println(reflect.TypeOf(byteValue)) // []uint8
s := string(byteValue)
for _, v := range s {
fmt.Println(string(v))
}

string 转换为map

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

import (
"encoding/json"
"fmt"
"reflect"
)

type User struct {
Code int
Msg string
}

func StrT() {
js := `{"code":1,"msg":"qwertyuiop"}`
// 定义一个map的key为string,值类型为不确定的结构体
jsm := make(map[string]interface{})
// 反序列化
err := json.Unmarshal([]byte(js), &jsm)

if err != nil {
fmt.Println(err)
return
}
fmt.Println(jsm) // map[code:1 msg:qwertyuiop]

var user User
err1 := json.Unmarshal([]byte(js), &user)
if err1 != nil {
fmt.Println(err1)
return
}
fmt.Println(user) //{1 qwertyuiop}
fmt.Println(reflect.TypeOf(user)) //conver.User
}

byte 转换为map

  • data.json内容
1
{"code": 1, "msg":"success", "data":[{"id": 1, "name":"西瓜"},{"id": 2, "name": "苹果"}]}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
f, err := os.Open("hello_work\\data.json")
if err != nil {
fmt.Println("open file eror:", err)
return
}
defer f.Close()
byteValue, _ := ioutil.ReadAll(f)

// 定义一个map的key为string,值类型为不确定的结构体
jsm := make(map[string]interface{})
// 反序列化
err1 := json.Unmarshal(byteValue, &jsm)

if err1 != nil {
fmt.Println(err)
return
}
fmt.Println(jsm) // map[code:1 data:[map[id:1 name:西瓜] map[id:2 name:苹果]] msg:success]

// map转换为string
dataType, _ := json.Marshal(jsm)
dataString := string(dataType)
fmt.Println(dataString)

map转换为string

  • 在byte 转换为map中,最后三行代码就是把map转为string
1
2
3
dataType, _ := json.Marshal(jsm)
dataString := string(dataType)
fmt.Println(dataString)

其他

说明

  • 对mysql的操作采用了两种,第一种是写原生sql,使用如下两个库
1
2
"github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
  • 第二种采用orm,使用了如下库
1
2
"gorm.io/driver/mysql"
"gorm.io/gorm"

准备工作

  • 本地搭建好mysql环境,数据库和表名如下

image-20230608170552494

原生sql

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
# hello_work/tmysql/operateMsql.go
package tmysql

import (
"fmt"

_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)

//go get github.com/go-sql-driver/mysql
//go get github.com/jmoiron/sqlx

// 对应Class表中的字段
type Class1 struct {
Id int `db:"id"` // 注意这里``的用法,对应数据库的字段
Name string `db:"name"`
Sex *string `db:"sex"` // 表中数据为空,直接取值报错,若是指针则返回nil

}

var Db *sqlx.DB

func InitDb() {
database, err := sqlx.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test")
if err != nil {
fmt.Println("connect mysql err", err)
return
}
Db = database
}

func Select() {

var class1 []Class1
err := Db.Select(&class1, "select id,name,sex from class")
if err != nil {
fmt.Println("select error", err)
return
}
fmt.Println("class succ", class1)
for _, v := range class1 {
fmt.Println(v.Id)
fmt.Println(v.Name)
if v.Sex != nil {
fmt.Println(*v.Sex)

}
}
}

func Insert() {
r, err := Db.Exec("insert into class(name) values(?)", "三年级一班")
if err != nil {
fmt.Println(err)
return
}
id, err := r.LastInsertId()
if err != nil {
fmt.Print("exec failed", err)
return
}
fmt.Println("insert success", id)
}

func Update() {
res, err := Db.Exec("update class set name=? where id=?", "三年级二班", 9)
if err != nil {
fmt.Println(err)
return
}
row, err := res.RowsAffected()
if err != nil {
fmt.Println("row failed, error", err)
}
fmt.Println("update success:", row)
}

func Delete() {
res, err := Db.Exec("delete from class where id=?", 10)
if err != nil {
fmt.Println(err)
return
}
row, err := res.RowsAffected()
if err != nil {
fmt.Println("row failed, error", err)
}
fmt.Println("detele success:", row)

}

  • 代码调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# hello_work/main.go
package main

import (
"example.com/hello_work/hello_work/tmysql"
)

func main() {
tmysql.InitDb()
tmysql.Select()
tmysql.Insert()
tmysql.Update()
tmysql.Delete()
}

gorm

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

import (
"fmt"

"gorm.io/driver/mysql"
"gorm.io/gorm"
)

// 安装gorm
//go get gorm.io/gorm
// 安装mysql驱动
//go get gorm.io/driver/mysql

var Db *gorm.DB

type User struct {
gorm.Model // 内置的结构提
Id uint // 默认为主键
Name string
}

func InitDb() {
dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
database, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// database, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil {
fmt.Println("open mysql error:", err)
}
Db = database
//// 根据User结构体,自动创建表结构,表名为users,如果表存在,不会创建
Db.AutoMigrate(&User{})
}
func Close() {
sqlDB, _ := Db.DB()
sqlDB.Close()
}

func Insert() {
u := User{
Name: "张山1",
}

if err := Db.Create(&u).Error; err != nil {
fmt.Println("create is wrong,error:", err)
return
}
fmt.Println("创建数据成功")
}

func Select() {
var users []User
// SELECT * FROM users;
if err := Db.Find(&users).Error; err != nil {
fmt.Println("find failed, error:", err)
return
}
for k, v := range users {
fmt.Println("k=", k, "v=", v)
fmt.Println(v.Name)
fmt.Println(v.Id)
}
//SELECT * FROM users WHERE name = '张山1';
Db.Where("name=?", "张山1").Find(&users)
if len(users) == 0 {
fmt.Println("find failed")
return
}

for k, v := range users {
fmt.Println("k1=", k, "v1=", v)
fmt.Println(v.Name)
fmt.Println(v.Id)
}

}

func Update() {
user := &User{Id: 1}
d := Db.Model(user).Update("Name", "lisi")
if d.Error != nil {
fmt.Println("update failed")
return
}
fmt.Println("update success")
}

func Detele() {
user := &User{Id: 2}

// 查询不到此数据,就说明无法删除
var users []User
Db.Where("id=?", 2).Find(&users)
if len(users) == 0 {
fmt.Println("del failed")
return
}
// 永久删除
d := Db.Unscoped().Delete(&user)
// 下面的语句有问题无论删除是否成功都成功
if d.Error != nil {
fmt.Println("del failed")
return
}

fmt.Println("del success")
}

  • 代码调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# hello_work/main.go
package main

import (
"example.com/hello_work/hello_work/ggorm"
"example.com/hello_work/hello_work/tmysql"
)

func main() {
tmysql.InitDb()
tmysql.Select()
tmysql.Insert()
tmysql.Update()
tmysql.Delete()

ggorm.InitDb()
ggorm.Insert()
ggorm.Update()
ggorm.Detele()
ggorm.Close()
}

来自