Claran's blog

怎么能成为Go学长,不行不行!(※也不是不行?!)

数据库

📚 数据库核心概念

数据库定义

数据库是用于长期保存数据、高并发访问数据、快速查询数据的系统,是后端的核心基础设施。

数据库五大特性

  • 持久化存储 - 数据写入后不会丢失
  • 并发处理能力 - 多用户同时访问,保证数据正确性
  • 高效查询 - 通过索引、优化器实现快速检索
  • 安全与权限控制 - 精确控制数据访问权限
  • 事务一致性 - 通过ACID保证数据完整性

数据库分类

关系型数据库(本课重点)

特点:

  • 表结构
  • SQL标准
  • 强一致性

代表:

  • MySQL
  • PostgreSQL
  • Oracle

适用:订单管理、用户系统、业务数据

非关系型数据库

特点:

  • 无固定结构
  • 高扩展性

代表:

  • Redis(键值)
  • MongoDB(文档)
  • ElasticSearch(搜索)

适用:缓存、日志、大数据分析

ACID 原则

ACID 是数据库事务的四个核心特性,用于确保数据的可靠性和一致性,尤其在关系型数据库(如 MySQL、PostgreSQL)中至关重要:

  • A:原子性(Atomicity)​

    事务被视为一个不可分割的最小单元,事务中的所有操作要么全部成功,要么全部失败回滚,不会停留在中间状态。例如,银行转账必须同时完成扣款和收款,否则回滚到初始状态。

  • C:一致性(Consistency)​

    事务执行前后,数据库必须保持一致性状态,即所有数据约束、规则(如唯一性、外键)都得到遵守。例如,转账前后账户总金额应保持不变。

  • I:隔离性(Isolation)​

    多个并发事务同时执行时,彼此隔离,互不干扰。数据库通过锁或并发控制机制防止脏读、不可重复读等问题。

  • D:持久性(Durability)​

    事务一旦提交,其对数据的修改就是永久性的,即使系统发生故障(如断电)也不会丢失,通常通过持久化存储(如硬盘)实现。

CAP 原则

CAP 理论是分布式系统设计的基础理论,由 Eric Brewer 提出,指出在分布式系统中,以下三个特性无法同时完全满足,最多只能实现其中两个:

  • C:一致性(Consistency)​

    在分布式系统的所有节点上,同一时刻读取的数据都是最新的相同版本。例如,用户在任何节点查询数据,都会得到最新的写入结果,否则返回错误。

  • A:可用性(Availability)​

    系统始终能够响应请求(不保证数据最新),即使部分节点故障,每个请求都能获得非错误响应。例如,即使数据未同步,系统也返回当前可用的数据。

  • P:分区容错性(Partition Tolerance)​

    系统在遇到网络分区(即节点之间因网络问题无法通信)时,仍然能够继续运行。这是分布式系统的基本要求,因为网络分区难以避免。

CAP 的权衡(常见于分布式数据库设计):

  • CP 系统:保证一致性和分区容错性,牺牲可用性。例如,发生网络分区时,系统可能拒绝写入或读取以确保数据一致。代表:MongoDB(通常配置为 CP)、HBase。

  • AP 系统:保证可用性和分区容错性,牺牲一致性。例如,发生网络分区时,系统仍可读写,但数据可能临时不一致。代表:Cassandra、DynamoDB。

  • CA 系统:保证一致性和可用性,牺牲分区容错性。这类系统通常不是真正的分布式系统,如单机关系数据库。

MySQL

数据类型

📊 MySQL数据类型速查

类型 占用空间 范围 用途 注意事项
INT 4字节 -2^31~2^31-1 主键、计数 最常用
BIGINT 8字节 -2^63~2^63-1 分布式ID 雪花ID
VARCHAR(n) 可变 0~65535字符 姓名、标题 最推荐
DECIMAL(10,2) 可变 精确小数 金额、财务 无精度误差
DATETIME 8字节 1000~9999年 创建时间 最推荐
TIMESTAMP 4字节 1970~2038年 自动时间戳 2038年问题

基础语法

SQL三大组成部分

DDL(数据定义语言)

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

-- 创建数据库

CREATE DATABASE school CHARACTER SET utf8mb4;

-- 创建表

CREATE TABLE students (

id INT PRIMARY KEY AUTO_INCREMENT,

name VARCHAR(50) NOT NULL,

age INT,

created_at DATETIME DEFAULT CURRENT_TIMESTAMP

);

-- 修改表结构

ALTER TABLE students ADD COLUMN email VARCHAR(100);

DML(数据操作语言)

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

-- 插入数据

INSERT INTO students(name, age) VALUES ('张三', 18);

-- 查询数据

SELECT * FROM students WHERE age > 17;

SELECT * FROM students WHERE name LIKE '%张%';

-- 更新数据

UPDATE students SET age = 20 WHERE id = 1;

-- 删除数据

DELETE FROM students WHERE id = 1;

DCL(权限控制)

1
2
3
4
5
6
7
8
9
10
11
12

-- 创建用户

CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';

-- 授权

GRANT SELECT, INSERT ON school.* TO 'username'@'localhost';

-- 撤销权限

REVOKE INSERT ON school.* FROM 'username'@'localhost';

事务(Transaction)

ACID原则

  • Atomicity(原子性) - 全部成功或全部失败
  • Consistency(一致性) - 数据状态合法转移
  • Isolation(隔离性) - 事务间互不干扰
  • Durability(持久性) - 提交后数据不丢失

事务流程

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

-- 开启事务

START TRANSACTION;

-- 执行多条SQL

UPDATE account SET balance = balance - 100 WHERE id = 1;

UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 提交或回滚

COMMIT; -- 成功提交

-- ROLLBACK; -- 失败回滚

并发问题与隔离级别

隔离级别 脏读 不可重复读 幻读 MySQL默认
读未提交
读已提交
可重复读 ✅*
串行化

⚠️ 注意:MySQL默认使用**可重复读(Repeatable Read)**级别,是性能与一致性的最佳平衡。

Go操作MySQL

原生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
// 安装驱动

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

import (

"database/sql"

_ "github.com/go-sql-driver/mysql"

)

// 连接数据库
dsn := "root:123456@tcp(127.0.0.1:3306)/school?charset=utf8mb4&parseTime=True&loc=Local"
db, err := sql.Open("mysql",dsn)

// 插入数据

result, err := db.Exec("INSERT INTO students(name, age) VALUES (?, ?)", "王五", 16)

// 查询单条

var name string

var age int

err := db.QueryRow("SELECT name, age FROM students WHERE id = ?", 1).Scan(&name, &age)

// 查询多条

rows, err := db.Query("SELECT id, name FROM students WHERE age > ?", 15)

defer rows.Close()

for rows.Next() {

var id int

var name string

rows.Scan(&id, &name)

fmt.Println(id, name)

}

// 事务

tx, err := db.Begin()

tx.Exec("UPDATE students SET age = age + 1 WHERE id = 1")

tx.Exec("INSERT INTO score_log(student_id, change_amount) VALUES (1, 1)")

err = tx.Commit() // 或 tx.Rollback()

Gorm

安装与连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 安装GORM

// go get -u gorm.io/gorm

// go get -u gorm.io/driver/mysql

import (

"gorm.io/driver/mysql"

"gorm.io/gorm"

)

// 连接数据库

dsn := "root:123456@tcp(127.0.0.1:3306)/school?charset=utf8mb4&parseTime=True&loc=Local"

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

定义模型

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
type Student struct {

ID uint gorm:"primaryKey"

Name string

Age int

Grade string

CreatedAt time.Time

}

// 自定义表名

func (Student) TableName() string {

return "students"

}

// 自动建表

db.AutoMigrate(&Student{})

CRUD操作

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
// 创建(Create)

student := Student{Name: "张三", Age: 18, Grade: "高三"}

result := db.Create(&student)

fmt.Printf("插入成功,ID: %d\n", student.ID)

// 查询单条(Read)

var stu Student

db.First(&stu, 1) // 按主键查询

db.First(&stu, "name = ?", "张三") // 按条件查询

// 查询多条

var students []Student

db.Where("age > ?", 17).Find(&students)

// 更新(Update)

db.Model(&Student{}).Where("id = ?", 1).Update("age", 20)

db.Model(&Student{}).Where("id = ?", 1).Updates(Student{Age: 20, Grade: "高三"})

// 删除(Delete)

db.Delete(&Student{}, 1)

链式查询

1
2
3
4
5
6
7
8
9
10
11
12

var list []Student

db.Where("age > ?", 16).

Where("grade = ?", "高三").

Order("age desc").

Limit(10).

Find(&list)

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

// 写法1:自动事务(推荐)

err := db.Transaction(func(tx *gorm.DB) error {

if err := tx.Create(&Student{Name: "A"}).Error; err != nil {

return err // 自动回滚

}

if err := tx.Create(&Student{Name: "B"}).Error; err != nil {

return err // 自动回滚

}

return nil // 自动提交

})

// 写法2:手动事务

tx := db.Begin()

tx.Create(&Student{Name: "A"})

tx.Model(&Student{}).Where("id=1").Update("age", 20)

tx.Commit() // 或 tx.Rollback()

其他注意事项

  1. LIKE 语句支持模糊搜索,语法如下:
    • db.Where("name LIKE ?","%"+Name+"%") : 参数 :%Name只搜索关键字末尾,%Name%不限位置,Name%首关键字
  2. SQL注入:攻击者可以通过直接在数据里塞入SQL语句实现非法调用数据库,好在Gorm会自动防范SQL注入,且一般情况下使用?作为索引

项目结构规范

规范

一个项目/Demo的不同包应该按照其不同功能或实现方法/依赖位置来进行不同结构的划分

例如internal存储项目/Demo内部的所有业务逻辑

二internal内部按照功能可以划分为service(业务逻辑层)、handler(响应处理层)、util(工具层)、middleware(中间件层)、repository(数据仓库层)等

而util内部可按照不同对象的工具分为jwt_util、user_util等

repository内部可以按照不同存储方式分为memory(内存存储)、db(数据库存储)等

handler也可以分为外部处理和保护处理等层

以下为可能出现的结构(部分):

层级/类别 目录/包名 核心职责与内容 关键原则与说明
应用入口层 /cmd/<appname> 应用程序的入口点 (main 包)。每个子目录对应一个可执行文件,如 cmd/api, cmd/cli 保持精简main.go 应只包含初始化、依赖注入和启动逻辑。业务代码应置于 internalpkg
接口层 (API/HTTP) /internal/handler (或 controller) HTTP 请求处理层,负责参数绑定、校验、基本序列化/反序列化 不应包含业务逻辑,通常调用 service
业务逻辑层 /internal/service (或 usecase) 核心业务逻辑和用例流程的实现层。协调多个 repository 或领域对象完成业务操作 基于接口编程。通过依赖注入接收 repository 等依赖,便于测试
数据访问层 /internal/repository (或 dao) 负责与数据源(数据库、缓存、外部 API)交互。对上层提供统一的数据操作接口 基于接口编程service 层依赖 Repository 接口,而非具体实现,实现解耦
领域模型层 /internal/domain (或 model, entity) 定义核心业务数据结构、枚举、领域对象的行为(方法)和业务规则 应保持 高内聚、低耦合,不包含具体技术(如 DB 注解)的依赖
内部共享工具 /internal/pkg 项目内部多个模块共享的、但不想暴露给外部的辅助代码,如项目特定的数据库连接池、内部中间件 internal 目录的 Go 编译器强制保护,外部项目无法导入
公共库代码 /pkg 设计良好、希望被外部项目导入和使用的公共库代码,如 pkg/logger, pkg/errors 需慎重设计公开 API。对于是否使用此目录存在争议,中小型单体项目可酌情简化
接口定义 /api 存放 API 契约文件,如 OpenAPI (Swagger) Spec、Protocol Buffers (.proto) 文件、GraphQL Schema 接口契约与实现分离,便于前后端协作和生成客户端代码
配置模板 /configs 配置文件模板或默认配置(如 config.yaml.tmpl 切勿在此存放含密码、密钥等敏感信息的真实配置文件
部署配置 /deployments (或 /deploy) IaaS、PaaS、容器编排(如 Docker-Compose, Kubernetes/Helm, Terraform)的配置和模板 将部署逻辑与应用程序代码分离
构建与CI /build 打包和持续集成相关的配置和脚本。通常包含 /build/ci (CI 配置) 和 /build/package (系统包配置) Makefilescripts 目录协同工作
脚本库 /scripts 用于执行构建、安装、分析等操作的脚本。这些脚本可被根目录的 Makefile 调用 复杂项目可在其下建立子目录,如 scripts/make-rules, scripts/lib (Shell 库)
项目工具 /tools 存放本项目专用的支持工具,这些工具可以导入 pkginternal 中的代码 将工具代码与应用程序代码分开管理
外部工具与代码 /third_party 外部辅助工具、Fork 的第三方代码或其他第三方应用(如 Swagger UI) 方便清晰地管理自定义修改的第三方依赖
前端资源 /web (或 /assets) Web 前端静态资源,如 CSS、JavaScript 文件、服务端模板和单页应用 (SPA) 主要用于全栈 Web 项目
项目文档 /docs 设计文档、用户手册、开发指南等(非 Godoc 生成的 API 文档)可按语言细分,如 docs/guide/zh-CN 保持文档与代码版本同步
代码示例 /examples 为应用程序或公共库提供的使用示例代码,降低使用者上手门槛 示例应简洁、典型、可运行
网站数据 /website 如果不使用 GitHub Pages,可在此存放项目的网站数据 适用于有独立站点的开源项目
Git钩子 /githooks 项目相关的 Git 钩子脚本,如 commit-msg 钩子 可通过 Git 配置指向此目录来共享钩子
测试相关 /test 额外的外部测试应用和测试数据。用于集成测试、端到端测试。可包含 /test/testdata 单元测试 (_test.go 文件) 应与被测试代码放在同一包内
依赖包 /vendor 项目依赖的第三方库代码(通过 go mod vendor 生成)。用于固定依赖版本或离线构建 现代 Go Modules 下通常无需提交至仓库,但特定场景(如保障确定性构建)仍有用

RESTful API

RESTful API 是一种基于 REST架构风格设计的网络应用程序接口,它通过一系列设计原则和约束条件,让网络服务变得更加清晰、简洁且易于维护

🔑六大核心原则

RESTful API 的设计建立在以下六项架构约束之上:

  • 统一接口

    • 这是REST最核心的约束,它确保与API的交互是标准化的。主要体现在:使用URI唯一地标识每个资源;使用标准的HTTP方法(GET, POST, PUT, DELETE等)来操作资源;返回自描述的消息,通常使用JSON或XML格式
  • 无状态

    • 每个从客户端发往服务器的请求都必须包含理解该请求所需的全部信息。服务器不会存储任何与会话相关的上下文状态。这使得服务器更容易扩展,也简化了系统设计
  • 客户端-服务器分离

    • 关注点分离。客户端负责用户界面和用户体验,服务器负责数据处理和存储。两者可以独立开发和演化,只要它们之间的接口不变
  • 可缓存

    • 服务器返回的响应必须明确表明其是否可以被客户端或中间代理缓存。这可以显著提高性能,减少不必要的网络请求
  • 分层系统

    • 架构可以由多个层次组成(如:负载均衡、应用服务器、数据库)。客户端不需要知道它是在与哪一层交互,这有助于提高系统的可扩展性和安全性
  • 按需代码

    • 这是一个可选的约束。服务器可以临时将可执行代码(如JavaScript脚本)发送给客户端,以扩展客户端的功能

📐设计规范

在实际设计中,上述原则转化为一些非常具体的实践规范

URI 设计

  • 使用名词而非动词:URI应该标识资源本身,而不是对资源的操作。例如,应使用 /users,而不是 /getUsers

  • 使用复数名词:通常建议对资源集合使用复数形式,如 /products比 /product更常见

  • 使用连字符-而非下划线_:这主要是为了提升URI的可读性

  • 体现层级关系:对于有关联的资源,可以使用嵌套路径,例如 /users/123/orders表示用户ID为123的所有订单

HTTP 方法的正确使用

  • GET:检索/获取资源。不应改变资源状态

  • POST:创建新资源

  • PUT:完整更新已存在的资源(客户端提供更新后的完整资源)

  • PATCH:部分更新资源(客户端只提供需要改变的字段)

  • DELETE:删除资源

使用标准的HTTP状态码

API通过状态码告知客户端请求的结果,这构成了通信契约的重要部分

  • 200 OK:请求成功。

  • 201 Created:资源创建成功。

  • 204 No Content:请求成功,但无返回内容(如删除操作后)。

  • 400 Bad Request:客户端请求错误(如参数有误)。

  • 401 Unauthorized:未认证(身份验证失败)。

  • 403 Forbidden:无权限(认证成功,但无权访问)。

  • 404 Not Found:请求的资源不存在。

  • 500 Internal Server Error:服务器内部错误。

返回统一的响应格式

通常使用JSON作为数据交换格式,并保持响应结构的一致性。一个常见的成功响应格式如下

1
2
3
4
5
6
7
8
{
"code": 200,
"status": "success",
"data": {
"id": 1,
"name": "Example"
}
}

而错误响应则可能包含错误信息:

1
2
3
4
5
{
"code": 404,
"status": "error",
"message": "Requested resource not found"
}

流程规范

以handler为例

handler:

创建数据 -> 捕获数据 -> 调用服务层(service)-> 构建响应(model.response) -> 返回响应(response)

. . . . . .

面向接口与依赖注入

  1. 接口定义的位置

在Go中,接口由使用方定义是一种最佳实践。这意味着,操作数据的接口(如 UserRepository)应该在调用它的层中定义,而不是在实现它的层中

业务逻辑层(service)定义它需要什么样的数据存取功能(repository接口)。

数据访问层(repository)负责实现这个接口。

这样做彻底解耦了业务逻辑和具体的技术实现,使得更换数据库(如从MySQL到PostgreSQL)或为测试提供Mock实现变得非常容易

  1. 依赖注入(Dependency Injection)

依赖注入是实现控制反转、连接各层的关键技术。它通常在 main.go或专门的 wire包中完成:

在入口函数中,初始化所有具体的实现(如数据库连接、第三方SDK)。

将具体实现实例,注入到需要它们接口的业务组件中(如将 userRepository实例传递给 userService)。

实例

Demo

0%