go进阶Golang修养之路 (yuque.com)

项目简介

阿里巴巴Java开发手册中的DO、DTO、BO、AO、VO、POJO定义 - 知乎 (zhihu.com)

使用到的库:

  • golang.org/x/crypto/bcrypt:由于加密库的是十分敏感的,因此crypto库的代码需要审核后托管在官方仓库,bcrypt 是一种密码哈希函数结合了盐值(salt)和多轮哈希(rounds of hashing)。

前台:

redis

redis:是一个高性能的key-value数据库,将数据存储在内存中,方便高效读写

后台:

JSON Web Tokens (JWT)鉴权

通过JSON形式作为Web应用中的令牌

  • 认证鉴权
  • 信息交换

JWT与Session的区别

  • 跨域问题:Session使用Cookie,而Cookie无法跨域,JWT没有这个问题。
  • 服务器开销:Session需要在服务器存储每个用户的登录信息,而JWT自身携带用户信息,不需要额外的服务器存储空间。
  • 分布式系统:JWT更适合分布式系统,因为它是无状态的

https://golang-jwt.github.io/jwt/

https://jwt.io/introduction

Golang 一日一库之jwt-go - 始識 - 博客园 (cnblogs.com)

CASBIN

  • 访问控制框架,支持多种访问控制模型。
  • 这个项目基于角色的访问控制(RBAC, Role-Based Access Control)是一种广泛使用的访问控制机制,它通过将权限与角色关联,再将角色分配给用户,从而实现对资源的访问控制

其他

前后端分离的核心:后台提供数据,前端负责显示

RESTful 风格

RESTful API 最佳实践 - 阮一峰的网络日志 (ruanyifeng.com)

RESTful 的核心思想就是,客户端发出的数据操作指令都是”动词 + 宾语”的结构。

APIFOX

集成的测试工具:Apifox = Postman + Swagger + Mock + JMeter

前端:

  • Swagger:通常用于生成 API 文档,并为前端开发人员提供接口的说明和交互功能。
  • Mock:用于模拟后端 API 响应。

后端:

  • Postman:验证 API 的正确性、性能和响应数据。
  • JMeter:后端的性能和压力测试。

docker compose&k8s

https://docker-practice.github.io/zh-cn/

  • docker compose用于本地简单的容器编排
  • k8s适用于适合于构建分布式、高可用的生产环境。

nginx&apache

nginx

  • nginx适合处理静态内容
  • 可作反向代理服务器、负载均衡器

apache

  • apache适合处理动态内容

  • Apache 采用多线程模型,每个请求由一个独立的线程处理。

nuxt3

1
2
3
4
5
6
7
8
9
├── global         # 用于存储全局变量或整个应用程序中共享的设置的目录或包
├── handle # 用于处理请求的目录或包,管理传入的 HTTP 请求
├── helper.go # 包含辅助功能的文件,用于协助代码库中的各种任务
├── manager.go # 负责管理应用程序核心组件的文件,可能处理逻辑、协调或资源管理
├── manager_test.go# 针对 `manager.go` 中实现功能的单元测试文件
├── middleware # 包含中间件功能的目录或包,应用于 HTTP 请求,如日志记录、身份验证等
├── model # 用于数据模型的目录或包,通常表示数据库实体或数据结构
└── utils # 包含可以在整个应用程序中复用的工具/辅助函数的目录或包

2024/8/16

命令行参数解析&配置文件读取

  • flag:解析命令行参数
  • viper:用于解析配置文件
    • viper有优先使用环境变量的配置,主要是方便程序在不同开发环境间迁移

2024/8/18

数据库

设计

https://mermaid.nodejs.cn/syntax/entityRelationshipDiagram.html

user

权限控制: 7 张表(4 模型 + 3 关联)

  • UserAuth(用户认证):用户的基本信息。
  • Role(角色):定义系统中的各种角色。
  • Resource(资源):定义需要权限控制的资源,如API端点。
  • Menu(菜单):定义系统的菜单结构,支持多级菜单。
  • 用户认证:角色 1:n
  • 角色:资源 1:n
  • 角色:菜单 1:n

//TODO 关系

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

erDiagram
USERAUTH ||--o{ ROLE : has
ROLE ||--o{ RESOURCE : has
ROLE ||--o{ MENU : has

USERAUTH {
username string
password string
loginType int
ipAddress string
ipSource string
lastLoginTime time
isDisable boolean
isSuper boolean
userInfoId int
userInfo USERINFO
}

ROLE {
name string
label string
isDisable boolean
}

RESOURCE {
name string
parentId int
url string
method string
anonymous boolean
}

MENU {
parentId int
name string
path string
component string
icon string
orderNum int8
redirect string
catalogue boolean
hidden boolean
keepAlive boolean
external boolean
externalLink string
}

Article

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
erDiagram
Article {
int id PK
string title
string desc
string content
string img
int type
int status
bool is_top
bool is_delete
string original_url
int category_id FK
int user_id FK
}
Tag {
int id PK
string name
}
Category {
int id PK
string name
}
UserAuth {
int id PK
string username
}
ArticleTag {
int article_id FK
int tag_id FK
}
BlogArticleVO {
int comment_count
int like_count
int view_count
}
ArticlePaginationVO {
int id
string img
string title
}
RecommendArticleVO {
int id
string img
string title
datetime created_at
}

Article ||--o{ ArticleTag : has
Tag ||--o{ ArticleTag : has
Article ||--o| Category : belongs_to
Article ||--o| UserAuth : authored_by
Article ||--|| BlogArticleVO : extends
BlogArticleVO ||--|{ ArticlePaginationVO : has
BlogArticleVO ||--|{ RecommendArticleVO : has

gorm

https://gorm.io/zh_CN/docs/models.html

  • ORM(Object-Relational Mapping,对象关系映射)是一种在面向对象编程语言中实现数据持久化的一种技术。它允许开发者以面向对象的方式来操作数据库,而无需编写原始的SQL语句。
  • gen (代码生成器):生成与数据库表对应的模型和基本的数据库操作方法。
  • DAO 层的主要职责是抽象和封装对底层数据源(如数据库、文件系统或其他持久化机制)的访问。
  • 客户端请求 -> Servlet层 -> Service层 -> Dao层 -> 数据库
  • 当你使用 GORM 并调用 AutoMigrate 方法时,GORM 会检查数据库中是否存在指定的表,如果不存在,GORM 会根据提供的模型创建相应的表。
  • 软删除:实际未删除,只是普通查找不返回该链接,可以通过设置标志位实现

预加载(Eager Loading)和连接(Joining)的区别:

  • N+1 查询问题:查询一个对象集合后对该集合的每个对象再进行分别的查询(1+n)

  • Preload:查询一个对象集合后对该集合的每个对象再进行一次总体的查询(1+1)

  • Preload 适合

    • 数据关系复杂,多个关联关系。
    • 需要避免数据冗余问题,返回的数据不希望有重复。
    • 查询效率和性能不是最优先考虑的场景。
  • Joins 适合

    • 表的关系相对简单,适合通过一次 JOIN 查询获取结果。

    • 数据量较大,希望减少查询次数,提高性能。

    • 需要尽量在一次查询中获取所有相关数据的场景。

slog

后端 - 万字解析 Go 官方结构化日志包 slog - 江湖十年 - SegmentFault 思否

Logger 又被称为 前端Handler 被称为 后端,而 Record 用来表示一条日志。

前端 Logger 直接面向用户侧,我们可以对其进行二次封装,来定制自己的用户 API。

后端 Handler 可以统一日志处理接口,我们也可以在自定义的 Handler 中很方便的集成如 zapzerolog 等第三方日志库。你可以在 [Go Wiki: Resources for slog

gin

重量级来了!

面向切面编程AOP(Aspect-Oriented Programming)

系统实现“高内聚、低耦合”一直是我们程序开发者的追求。

什么是面向切面编程? - 知乎 (zhihu.com)

使用路由组管理路由:方便不同路由的分组和中间件管理。

context

context in gin

  • ginContext 是处理 HTTP 请求的核心数据结构。它包含了请求和响应的所有信息,以及处理请求的中间件链。
  1. 当一个 HTTP 请求到达 Gin 服务器时,它首先被分配到一个 Context 对象。
  2. 随后进入中间件链。每个中间件可以对 Context 进行读取和修改,执行特定的逻辑。(c.next()进入下一中间件)
  3. 处理函数生成响应,并通过 Context 返回给客户端。
  4. 在响应返回给客户端之前,中间件链会逆序执行。(c.next()返回)

为什么不设置全局变量而是使用上下文(context ctx)传递数据

  • 线程安全:全局变量可能需要锁保护。
  • 中间件链:Gin 框架使用中间件链来处理请求。通过在上下文中传递数据,可以在链中的任何位置访问或修改这些数据,而不需要在中间件之间显式传递参数。
  • 数据隔离:在处理并发请求时,每个请求可能需要不同的数据副本。上下文允许每个请求拥有自己的数据副本,从而实现数据隔离。
  • …….

中间件

中间件:中间件和处理函数是按照它们被注册的顺序执行的。

Gin框架默认提供了两个中间件:Logger 和 Recovery。

  1. 全局中间件(Global Middleware)

    • 对所有请求都生效。

    • 使用 r.Use() 方法添加到 *gin.Engine 实例上。

  2. 路由组中间件(Group Middleware)

    • 针对特定路由组,只对注册在该路由组下的所有路由生效。
    • 使用 group.Use() 方法添加到 *gin.RouterGroup 实例上。
  3. 自定义中间件(Custom Middleware)

  4. 第三方中间件(Third-Party Middleware)

    • 可在 github.com/gin-gonic/contrib 寻找对应的库。

单个session和多个session的区别:单个session用于

//TODO

自定义组件,主要是增加一些功能和替换一些组件的条件检查、以及使用slog替换日志输出。

基于 cookie 的 session:

  • 当用户首次访问应用程序时,服务器会创建一个新的 Session,并生成一个唯一的 Session ID。
  • 这个 Session ID 被发送到客户端浏览器,并存储在一个 Cookie 中。Cookie 通常包含 Session ID 和一些属性,如路径、域、过期时间、安全标志等。

用户进行后续请求时,浏览器会自动在请求的 HTTP Header 中携带这个 Cookie,服务器通过解析 Cookie 中的 Session ID 来检索对应的 Session 数据。

CROS

recovery

  • recover 的实现是内置的,不在 Go 的源代码中直接可见,因为它是编译器和运行时的一部分。
  • recover 必须在 defer 语句中调用,并且只能恢复当前的 panic。
  • defer 延迟调用语句的用处是在程序执行结束,甚至是崩溃后,仍然会被调用的语句

为什么recover必须在defer语句中调用?

go

空结构体

  • 空结构体不占用空间,可用作占位映射值
  • 空结构体可以作为一个接口的唯一实现,任何具有空结构体类型的类型都自动满足了该接口

swagger

util

encrypt

context

  • context用于在不同协程中传递数据

context定义

1
2
3
4
5
6
7
8
9
10
type Context interface {
Done() <-chan struct{} // 返回一个channel,用于通知当前Context的取消,父协程通过监听管道判断是否被关闭

Err() error // 返回当前Context的取消原因

Deadline() (deadline time.Time, ok bool) // 返回当前Context的截止时间

Value(key interface{}) interface{} // 返回传递的键值对
}

context.TODO()和 context.Background()

  • context.Background() 是一个通用的构建块,用于构建整个 Context 树的起点。
  • context.TODO() 是一个临时的占位符,用于标记代码中尚未处理的部分。

传递数据

1
2
3
4
5
6
7
8
9
10
11
func f1(ctx context.Context) context.Context {

return context.WithValue(ctx, "name", "Randolfluo")
}

func main() {
// context可用于传递数据,如用户信息,请求ID等
backctx1 := context.Background() //创建一个background context,作为context的根
f1ret := f1(backctx1) //返回新context
fmt.Println(f1ret.Value("name"))
}

取消派生的context(timeout,deadline)

  • 可以设置取消条件(如网络请求超时)
  • 一旦 Context 被取消或超时,任何监听 ctx.Done() 通道的 goroutine 都会收到一个通知。
  • 通道关闭时,所有等待接收的协程都会被唤醒。因此可以在函数、协程、主函数打印状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
"context"
"fmt"
"time"
)

func f1(ctx context.Context) {

go func() {
for {
select {
case <-ctx.Done():
fmt.Println("goroutine done")
return
default:
fmt.Println("114514")
time.Sleep(time.Millisecond * 100)
}
}
}()

select {
case <-ctx.Done():
fmt.Println("timeout")
return
}
}

func main() {

backctx1 := context.Background()

ctx, cancel := context.WithTimeout(backctx1, time.Millisecond*500)
defer cancel() // 手动取消上下文,确保退出时调用,释放资源。
f1(ctx)

select {
case <-ctx.Done():
fmt.Println("f1 done")

}
fmt.Println(ctx.Err()) // context deadline exceeded
time.Sleep(time.Second) //在主Goroutine结束之后其他的所有Goroutine都会直接退出,因此我们需要等待协程执行完毕

}

参考: