Files
StudyNote/语言/GO/go.md
2026-02-13 23:38:38 +08:00

910 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 路由管理
## goframe
### 如何将函数与路径绑定在一起
```go
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("Hello World!")
})
```
**BindHandler("路径",函数)**
通过`Server`对象的`BindHandler`方法*绑定路由*以及*路由函数*。在本示例中,我们绑定了`/`路由,并指定路由函数返回`Hello World`
当然 我们也可以写一个函数 来代替func
### 批量绑定控制器
> s.BindObject("路径", obj)
```go
s.BindObject("/user", user.Controller{})
```
这样就可以一次性 将user包下面的方法给绑定到对应的路径下面
- `BindObject(path, obj)` 中,基础路径 `path` 需要**显式传入**(比如 `/user`),每次绑定都要手动指定。
GoFrame 的 `Bind` 方法为了兼容 “值类型” 和 “指针类型” 的控制器实例,内部做了**自动转换处理(统一转为指针类型)**,以确保指针接收者的方法能被正确识别和绑定。这就是为什么传入 `Hello{}``NewHello()`(返回 `*Hello`)会得到相同结果的底层原因。
### 分组
`RouterGroup.Bind(obj)` 中,基础路径来自**当前路由组RouterGroup预先定义的路径**(比如 `group := s.Group("/api")` 中,`/api` 就是基础路径),后续绑定无需重复写,直接继承组的路径。
```go
s.Group("/api", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Group("/hello", func(helloGroup *ghttp.RouterGroup) {
helloGroup.Bind(hello.NewHello())
})
group.Group("/user", func(userGroup *ghttp.RouterGroup) {
userGroup.Bind(user.Controller{})
})
//分成了两个组 hello和user
//但是 他们都在 api的下面 所以是api/user api/hello
})
```
### 规范路由
GoFrame中提供了规范化的路由注册方式注册方法如下
```go
func Handler(ctx context.Context, req *Request) (res *Response, err error)
```
其中`Request``Response`为自定义的结构体。
通过如下方式指定请求方法与路径
```go
type HelloReq struct {
g.Meta `path:"/hello" method:"get"`
}
```
## 获取请求参数Req
```go
func (c *Hello) Param(ctx context.Context, req *hello.ParamReq) (res *hello.ParamRes, err error) {
r := g.RequestFromCtx(ctx)
name := r.GetQuery("name")
data := r.GetQueryMap(g.Map{"name": name, "age": 2})
r.Response.Writeln(name.String() + "2778")
return
}
```
> g.RequestFromCtx(ctx) --> 从上下文中提取当前 HTTP 请求对象 `*ghttp.Request`。
在 GoFrame 框架中,每个 HTTP 请求处理时,框架会自动将当前请求的 `*ghttp.Request` 对象存入上下文 `ctx` 中,方便在函数调用链中传递和获取(无需显式在函数参数中传递 `*ghttp.Request`)。
>ctx context.Context -->是传递请求生命周期和元数据的载体。
- 里面包含了当前请求的元信息(如请求 ID、超时控制等`g.RequestFromCtx` 通过这个上下文 “找到” 对应的请求实例。
- 示例中的意义:在 `Param` 方法中,通过 `g.RequestFromCtx(ctx)` 从上下文获取当前请求对象 `r`,后续可以通过 `r` 操作请求(如获取参数、写入响应等)。
> r.GetQuery("name") -->获取 URL 查询参数中 key 为 "name" 的值。
> data := r.GetQueryMap(g.Map{"name": name, "age": 2}) 这个name 和2 就是默认值 让这个其实获取的就是{"age":"1231","name":"啊啊啊","rwqe":"123123"} 这种格式的 当然 只要有 就写上去 即使是你不想要的
### Get方法
- **`GetQuery\*` 系列方法**(如 `GetQuery``GetQueryMap`):仅用于获取 **URL 查询参数**(对应 HTTP 的 GET 方法参数)。
- **`GetForm\*` 系列方法**(如 `GetForm``GetFormMap`):用于获取 **POST 表单参数**(如 `application/x-www-form-urlencoded``multipart/form-data` 类型的 POST 数据)。
- **`GetJson\*` 系列方法**:用于获取 **POST JSON 数据**`application/json` 类型的 POST 数据)。
- `getRouterMap` 用于获取 **动态路由** {}里面的数据
### 从req中直接取数值 goland推荐的
```go
func (c *Hello) Param(ctx context.Context, req *hello.ParamReq) (res *hello.ParamRes, err error) {
r := g.RequestFromCtx(ctx)
r.Response.Write(req)
return
}
```
- 无需关心参数的原始提交方式GET/POST/JSON 自动兼容);
- 自动过滤无关参数(只保留 `ParamReq` 中定义的字段);
- 支持通过结构体标签(如 `v:"required"`)做参数校验(框架会自动返回校验错误)。
### 自己写一个结构体 取接住
```go
func (c *Hello) Param(ctx context.Context, req *hello.ParamReq) (res *hello.ParamRes, err error) {
r := g.RequestFromCtx(ctx)
type User struct {
Name string
Age int
}
var u User
err = r.ParseForm(&u)
if err != nil {
r.Response.Write(u)
}
return
}
```
### p d
```go
type ParamReq struct{
Name string `p:"name" d:"无名"`
Age int `json:age`
}
如果 前端不是name 但是你又不想新建一个字段 那么就用 p 起一个别名 也鞥适用于name
d 默认值
josn 可以让返回值返回为json的样式
```
## 相应Res
> r.Response.Writeln() 最常用的 直接返回文字 结构体 甚至是html标签
r.Response.
- Writeln 换行
- Write 不换行
- Writef 可以有占位符
- WriteJson()
# 数据库
## 前期准备
### 配置文件
> *manifest/config/config.yaml*
```yml
server:
address: ":8000" # 服务监听端口
openapiPath: "/api.json" # OpenAPI接口文档地址
swaggerPath: "/swagger" # 内置SwaggerUI展示地址
database:
default:
link: "mysql:root:12345678@tcp(127.0.0.1:3306)/star?loc=Local"
debug: true
```
## CRUD
### 查询
> `g.Model("表名")` 返回是模型对象
`g.Model("products")` 会创建一个与 `products` 表绑定的模型对象(后续所有操作都针对该表)。
获得到 g.MOdel()以后 比如 query= g.MOdel(“一个表名”)就可以通过
**products, err := query.One()** 查询一个 返回值是 查询的返回值 和错误数量
- qu.**One**()查询一条
- qu.*All*() 查全
- qu.**Fields**("id", "vend_id").All() 查询那两个字段
- qu.**FieldsEX**("id", "vend_id").All() 查询 除了那两个字段
- qu.**Value**("id") 查id这一个字段的一个数据
- qu.**Array**("id")查id这一个字段的所有
- qu.**Count** 用于查询并返回记录数。
> 如果对于同一个g.Model() 的对象 query 使用的话 就会在原有的基础上 查询 (上一条的查询语句 会影响到吓一跳)
所以我们的解决思路就是 ---> 每次都查询的是g.Model().查询的内容
> ### Where
- qu.**Where**("id", 1).All() --> 查询id=1
- qu.**Where**("id=2").All() ---> 查询id=2
- qu.**WhereGT**("id",2).All() ---> 查询id> GTE >=
- LT -->< LTE--> < =
- Where("level=? OR money >=**?**", 1, 1000000) 使用?作为占位符
- Where("level", 1)**.WhereOr**("money >=", 1000000) 或者直接使用WhereOr来表示or
> ### Order
pro, error := qu.WhereGTE("id", 2).OrderAsc("prod_price").All()
> ### Group
pro, error := qu.WhereGTE("id", 2).Group("prod_price").All()
> ### 分页
pro, error := qu.Limit(1,3).All()
pro, error := qu.Page(3,5).All() 第三页 每页五个
#### 结构体映射
```go
type Products struct {
Id int
VendId int
ProdName string
ProdPrice float64
}
var products *Products
_ = g.Model("products").Scan(&products) 其实这个的返回值是error
req.Response.WriteJson(products)
```
> Scan 的是& 因为你是直接把这个对象给改了
然后有一点
var products *Products 和var products Products的返回结果是一样的
但是在查询查不到的地方 用指针的是Null 而用对象的是返回语句 “sqlxxx查不到”
> 把var products *Products 改成var products 【】\*Products 就是查询所有的了
### 插入
### insert
```go
result, err := qu.Data(data).Insert()
等价的两句话
result, err := qu.Insert(data)
```
返回值
```go
{
"Result": {
"Locker": {}
},
"Affected": 1 影响的数量
}
```
#### Replace
```go
result, err := qu.Replace(data)
```
根insert的区别就在于 replace是替换 就是即使是主键冲突 也可以改 当然 也可以添加
而insert就是会报错
3.save 如果写入的数据中存在主键或者唯一索引时,更新原有数据,否则写入一条新数据。
#### 批量插入
```go
type Products struct {
Id int
VendId int
ProdName string
ProdPrice float64
}
data := g.List{//一系列键值对集合
{"vend_id": 10, "prod_id": 11, "prod_name": "商品2", "prod_price": 100.0},
{"vend_id": 20, "prod_id": 11, "prod_name": "商品2", "prod_price": 100.0},
{"vend_id": 40, "prod_id": 11, "prod_name": "商品2", "prod_price": 100.0},
{"vend_id": 40, "prod_id": 11, "prod_name": "商品2", "prod_price": 100.0},
}
result, err := qu.Insert(data)
if err == nil {
req.Response.WriteJson(result)
}
```
或者是g.Array 但是里面就要存g.map或者结构体
```go
data := g.Array{//存g.map或者结构体
g.Map{
"vend_id": 10, // 正确字段名(避免拼写错误)
"prod_name": "商品A", // 必填字段
"prod_price": 99.9, // 价格字段
},
g.Map{
"vend_id": 10,
"prod_name": "商品B",
"prod_price": 199.9,
},
}
```
### Update
#### `Update`
```go
// UPDATE `user` SET `status`=1 WHERE `status`=0 ORDER BY `login_time` asc LIMIT 10
g.Model("user").Data("status", 1).Order("login_time asc").Where("status", 0).Limit(10).Update()
//或者直接使用update
g.Model("user").Update("status=1", 1)
```
用于数据的更新,往往需要结合 `Data``Where` 方法共同使用。 `Data` 方法用于指定需要更新的数据, `Where` 方法用于指定更新的条件范围。同时, `Update` 方法也支持直接给定数据和条件参数。
#### `Decremet&Increment`
```go
result, err := qu.WhereLTE("id", 5).Increment("prod_price",10)
//让那些 id《=5的 加10的price
```
### 删除
#### Delete()
```
g.Model("user").Where("uid", 10).Delete()
```
## 事务
### 普通的写法
```go
db := g.DB()//是 g 提供的数据库操作方法,用于获取一个数据库连接对象
//如果有上下文 也就那个 ctx context.Context 可以直接写成 db.Begin(ctx) 这个req.Context()==ctx
if tx, err := db.Begin(req.Context()); err == nil {
r, err := tx.Save("user", g.Map{
"id": 1,
"name": "john",
})
if err == nil {
tx.Commit()
} else {
tx.Rollback()
}
fmt.Println(r)
}
```
```go
data := new(Products)
tx, err := g.DB().Begin(req.Context())//开启事务
if err != nil {
req.Response.WritelnExit("发生错误: " + err.Error())//事务开启时失败
}
md := tx.Model("book")
result, err := md.Insert(data)
if err == nil {//插入失败
tx.Commit()
req.Response.WriteJson(result)
} else {
tx.Rollback()
req.Response.Writeln("发生错误: " + err.Error())
}
```
### 闭包操作
```go
g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error {
data := new(Products)
tx, err := g.DB().Begin(req.Context())
if err != nil {
req.Response.WritelnExit("发生错误: " + err.Error())
}
md := tx.Model("book")
result, err := md.Insert(data)
if err == nil {
req.Response.WriteJson(result)
} else {
req.Response.Writeln("发生错误: " + err.Error())
}
return err
})
```
这个方法 就是通过判断error方法 来实现对于自动提交和rollback
## 原生sql
> 你想听我原生家庭的故事吗
```go
sql := "INSERT INTO `book` (`name`, `author`, `price`) VALUES (?, ?, ?)"
db := g.DB()
result, err := db.Exec(req.Context(), sql, g.Array{3, 7})
```
## DAO
自动生成的 先去hack/config.yml中进行
然后在执行 gf gen dao
| `/internal/dao` | 数据操作对象 | 通过对象方式访问底层数据源,底层基于 `ORM` 组件实现。往往需要结合 `entity``do` 共同使用。该目录下的文件开发者可扩展修改。 |
| ------------------------ | ------------ | ------------------------------------------------------------ |
| `/internal/model/do` | 数据转换模型 | 数据转换模型用于业务模型到数据模型的转换,由工具维护,用户不能修改。工具每次生成代码文件将会覆盖该目录。关于 `do` 文件的介绍请参考: - [数据模型与业务模型](https://goframe.org/docs/design/project-models) - [DAO-工程痛点及改进](https://goframe.org/docs/design/project-dao-improvement) - [利用指针属性和do对象实现灵活的修改接口](https://goframe.org/docs/core/gdb-practice-using-pointer-and-do-for-update-api) |
| `/internal/model/entity` | 数据模型 | 数据模型由工具维护,用户不能修改。工具每次生成代码文件将会覆盖该目录。 |
do 在插入的时候不会将其他的给顶替掉
而 entity 的却会
### 连表查询
![image-20251114192410449](F:\记录\GO\image-20251114192410449.png)
在entity表里面 写入其他表的指针 然后 json表示的是 在关联查出的名字 然后orm表示的是链表的 whit"xx"="xx"
```
db := g.DB()
db.With(entity.Dept{}).Scan(&emp)
```
> db.With(entity.Dept{} 也就是entity表里面的构造器 也是要链表 ).Scan(&emp 是这个表的对象 也是)
## Service层
作用:
- 定义接口
- 定义接口变更
- 定义一个获取接口实例的函数
- 定义一个接口实现的注册方式
dcdevicegroup.go
dc_device_group.go
dc_device_group.go
# WebSocket
<img src="F:\记录\GO\image-20251207094527958.png" alt="image-20251207094527958" style="zoom:33%;" />
WebSocket 通信始于 **HTTP 握手**,客户端通过 `Upgrade` 请求头将 HTTP 连接升级为 WebSocket 连接。握手成功后,双方通过**消息帧**(文本、字节或控制帧如 ping/pong进行通信。控制帧用于维护连接例如 ping/pong 用于检测连接是否存活。
#### `type Conn`:网络连接的通用抽象
`Conn`是Go语言定义的网络连接接口涵盖了所有网络类型TCP、UDP、Unix域套接字等的公共操作
## >websocket升级器
```go
// wsUpgrader WebSocket 升级器配置
var wsUpgrader = websocket.Upgrader{
// CheckOrigin 允许任何来源(开发环境)
// 生产环境中应该实现适当的来源检查以确保安全
CheckOrigin: func(r *http.Request) bool {
return true
},
// Error 处理升级失败的错误
Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
// 在这里实现错误处理逻辑
g.Log().Errorf(r.Context(), "WebSocket upgrade error: %v", reason)
},
}
```
> ### 原生
```go
package websocket
import (
"net/http"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gorilla/websocket"
)
// WebSocketController WebSocket 控制器
type WebSocketController struct{}
// New 创建 WebSocket 控制器实例
func New() *WebSocketController {
return &WebSocketController{}
}
// wsUpgrader WebSocket 升级器配置
var wsUpgrader = websocket.Upgrader{
// CheckOrigin 允许任何来源(开发环境)
// 生产环境中应该实现适当的来源检查以确保安全
CheckOrigin: func(r *http.Request) bool {
return true
},
// Error 处理升级失败的错误
Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
// 在这里实现错误处理逻辑
g.Log().Errorf(r.Context(), "WebSocket upgrade error: %v", reason)
},
}
// Echo WebSocket Echo 服务器处理器
// 路径: /ws
func (c *WebSocketController) Echo(r *ghttp.Request) {
// 将 HTTP 连接升级为 WebSocket
ws, err := wsUpgrader.Upgrade(r.Response.Writer, r.Request, nil)
if err != nil {
r.Response.Write(err.Error())
return
}
defer ws.Close()
// 获取请求上下文用于日志记录
var ctx = r.Context()
logger := g.Log()
// 消息处理循环
for {
// 读取传入的 WebSocket 消息
msgType, msg, err := ws.ReadMessage()
if err != nil {
break // 连接关闭或发生错误
}
// 记录接收到的消息
logger.Infof(ctx, "received message: %s", msg)
// 将消息回显给客户端
if err = ws.WriteMessage(msgType, msg); err != nil {
break // 写入消息时出错
}
}
// 记录连接关闭
logger.Info(ctx, "websocket connection closed")
}
```
> ### 使用`gorilla/websocket`库
```go
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
// 定义 WebSocket 升级器
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
// 存储所有客户端连接
var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan string) // 广播消息通道
// 处理 WebSocket 连接
func handleConnections(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("upgrade error: %v", err)
return
}
defer ws.Close()
// 注册新客户端
clients[ws] = true
for {
var msg string
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("read error: %v", err)
delete(clients, ws)
break
}
// 将消息发送到广播通道
broadcast <- msg
}
}
// 广播消息给所有客户端
func handleBroadcast() {
for {
msg := <-broadcast
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("write error: %v", err)
client.Close()
delete(clients, ws)
}
}
}
}
func main() {
http.HandleFunc("/ws", handleConnections)
go handleBroadcast() // 启动广播 goroutine
log.Fatal(http.ListenAndServe(":8080", nil))
}
```
- `clients` 存储所有活跃连接,`broadcast` 通道用于消息分发。
- 每个客户端连接运行在独立 goroutine 中,处理消息读取。
- `handleBroadcast` 循环监听广播通道,将消息推送给所有客户端。
>### 并发处理
```go
package main
import (
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}
var clients = make(map[*websocket.Conn]bool)
var mutex = sync.RWMutex{} // 保护 clients 并发访问
func handleConnections(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("upgrade error: %v", err)
return
}
// 注册客户端
mutex.Lock()
clients[ws] = true
mutex.Unlock()
defer func() {
mutex.Lock()
delete(clients, ws)
mutex.Unlock()
ws.Close()
}()
for {
var msg string
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("read error: %v", err)
break
}
// 广播消息(简化示例)
mutex.RLock()
for client := range clients {
client.WriteJSON(msg)
}
mutex.RUnlock()
}
}
```
> 性能优化 通过临时的bufferPool
```go
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
func readMessage(ws *websocket.Conn) ([]byte, error) {
buf := bufferPool.Get().([]byte)
defer bufferPool.Put(buf)
_, data, err := ws.ReadMessage()
return data, err
}
```
> 心跳
```go
func handlePingPong(ws *websocket.Conn) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
log.Println("ping error:", err)
return
}
}
}
}
```
> 多房间聊天
```go
type Room struct {
clients map[*websocket.Conn]bool
broadcast chan string
}
func (r *Room) handleBroadcast() {
for msg := range r.broadcast {
for client := range r.clients {
client.WriteJSON(msg)
}
}
}
```
# GC
垃圾回收的过程 分为两个半独立的组件
> 赋值器 & 回收器
赋值器:这一名称本质上是在指代用户态的代码。因为对垃圾回收器而言,用户态的代码仅仅只是在修改对象之间的引用关系,也就是在对象图(对象之间引用关系的一个有向图)上进行操作。
回收器:执行垃圾回收的代码
> 根对象 --> 垃圾回收器在标记的过程中最先检查的对象
- 全局变量 :程序在**编译期**就能确定的那些存在于程序整个生命周期的**变量**。
- 执行栈 每个goroutine 都包含自己的执行栈 这些执行栈包含**栈上的变量**以及指向**分配的堆内存区块的指针**
- 寄存器 寄存器的值可能表示一个**指针**,参与计算的这些指针可能指向某些赋值器分配的堆内存区块
> GC方法 追踪式GC
从根对象出发 根据对象之间的引用信息 一步步推导知道扫描完整个堆并且确定要保存的对象 从而确定回收的对象
> 三色标记法
三色抽象规定了三种不同类型的对象,并用不同的颜色相称
- 白色对象**(可能死亡)**:未被回收器访问到的对象。在回收开始阶段,所有对象均为白色,当回收结束后,白色对象均不可达。
- 灰色对象**(波面)**:已被回收器访问到的对象,但回收器需要对其中的一个或多个指针进行扫描,因为他们可能还指向白色对象。
- 黑色对象**(确定存活)**:已被回收器访问到的对象,其中所有字段都已被扫描,黑色对象中任何一个指针都不可能直接指向白色对象。
<img src="F:\记录\GO\image-20251220221017875.png" alt="image-20251220221017875" style="zoom:33%;" />