Go Gin Gorm Casbin权限管理实现 - 3. 实现Gin鉴权中间件
文章目录
- 0. 背景
- 1. 准备工作
- 2. gin中间件
- 2.1 中间件代码
- 2.2 中间件使用
- 2.3 测试中间件使用结果
- 3. 添加权限管理API
- 3.1 获取所有用户
- 3.2 获取所有角色组
- 3.3 获取所有角色组的策略
- 3.4 修改角色组策略
- 3.5 删除角色组策略
- 3.6 添加用户到组
- 3.7 从组中删除用户
- 3.8 测试API
- 4. 最终目录结构和代码
- 4.1 main.go
- 4.2 casbin.go
- 4.3 middleware.go
- 5. 更进一步
0. 背景
Casbin是用于Golang项目的功能强大且高效的开源访问控制库。
强大通用也意味着概念和配置较多,具体到实际应用(以Gin Web框架开发)需要解决以下问题:
- 权限配置的存储,以及
增删改查
- Gin框架的中间件如何实现
经过一番摸索实践出经验,计划分为三个章节,循序渐进的介绍使用方法
1. Casbin概念介绍以及库使用
2. 使用Gorm存储Casbin权限配置以及增删改查
3.实现Gin鉴权中间件
代码地址 https://gitee.com/leobest2/gin-casbin-example
1. 准备工作
上一章已实现了
casbin
和gorm
权限模型设计以及增删改查操作,本章在此基础上,实现以下需求
- 集成到gin中,添加一个鉴权中间件
- 提供角色,用户增删改查API接口:
至此当前目录结构
.
├── casbin.go
├── go.mod
├── go.sum
└── test.db
casbin.go完整代码见上一章结尾:
2. gin中间件
2.1 中间件代码
添加一个
middleware.go
, 这里简便起见,假设用户从url传递 /xxxx?username=leo,实际应用中可以结合jwt等鉴权
HTTP GET /api/user?username=leo
package mainimport "github.com/gin-gonic/gin"func NewCasbinAuth(srv *CasbinService) gin.HandlerFunc {return func(ctx *gin.Context) {err := srv.enforcer.LoadPolicy()if err != nil {ctx.String(500, err.Error())ctx.Abort()return}// 简便起见,假设用户从url传递 /xxxx?username=leo,实际应用可以结合jwt等鉴权username, _ := ctx.GetQuery("username")ok, err := srv.enforcer.Enforce(username, ctx.Request.URL.Path, ctx.Request.Method)if err != nil {ctx.String(500, err.Error())ctx.Abort()return} else if !ok {ctx.String(403, "验证权限失败!")ctx.Abort()return}ctx.Next()}
}
2.2 中间件使用
main.go
package mainimport ("github.com/gin-gonic/gin""github.com/glebarez/sqlite""gorm.io/gorm"
)func main() {db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})if err != nil {panic("failed to connect database: " + err.Error())}casbinService, err := NewCasbinService(db)if err != nil {panic("failed to new casbin service: " + err.Error())}r := gin.Default()auth := r.Group("/api")auth.Use(NewCasbinAuth(casbinService))auth.GET("/api/user", func(ctx *gin.Context) {ctx.String(200, "get /api/user success")})auth.DELETE("/api/user", func(ctx *gin.Context) {ctx.String(200, "delete /api/user success")})r.Run(":8000")
}
2.3 测试中间件使用结果
测试权限数据库内容
ptype | v0 | v1 | v2 | v3 | v4 | v5 |
---|---|---|---|---|---|---|
p | admin | /api/user | GET | |||
p | admin | /api/user | DELETE | |||
p | user | /api/user | GET | |||
g | leo | admin | ||||
g | leo2 | user |
测试脚本
# 权限失败
curl -X GET 'http://localhost:8000/api/user?username=guest'
# 权限成功
curl -X GET 'http://localhost:8000/api/user?username=leo'
# 权限成功
curl -X DELETE 'http://localhost:8000/api/user?username=leo'
# 权限失败
curl -X DELETE 'http://localhost:8000/api/user?username=leo2'
测试结果
3. 添加权限管理API
以下使用上一章
casbin_service
提供的方法,示例API如下,可进一步定制
3.1 获取所有用户
// 获取所有用户auth.GET("/casbin/users", func(ctx *gin.Context) {ctx.JSON(200, casbinService.GetUsers())})
3.2 获取所有角色组
// 获取所有角色组auth.GET("/casbin/roles", func(ctx *gin.Context) {ctx.JSON(200, casbinService.GetRoles())})
3.3 获取所有角色组的策略
// 获取所有角色组的策略auth.GET("/casbin/rolepolicy", func(ctx *gin.Context) {roles, err := casbinService.GetRolePolicy()if err != nil {ctx.String(500, "获取所有角色及权限失败: "+err.Error())} else {ctx.JSON(200, roles)}})
3.4 修改角色组策略
/* 修改角色组策略type RolePolicy struct {RoleName string `gorm:"column:v0"`Url string `gorm:"column:v1"`Method string `gorm:"column:v2"`}*/auth.POST("/casbin/rolepolicy", func(ctx *gin.Context) {var p RolePolicyctx.BindJSON(&p)err := casbinService.CreateRolePolicy(p)if err != nil {ctx.String(500, "创建角色策略失败: "+err.Error())} else {ctx.JSON(200, "成功!")}})
3.5 删除角色组策略
/* 删除角色组策略type RolePolicy struct {RoleName string `gorm:"column:v0"`Url string `gorm:"column:v1"`Method string `gorm:"column:v2"`}*/auth.DELETE("/casbin/rolepolicy", func(ctx *gin.Context) {var p RolePolicyctx.BindJSON(&p)err := casbinService.DeleteRolePolicy(p)if err != nil {ctx.String(500, "删除角色策略失败: "+err.Error())} else {ctx.JSON(200, "成功!")}})
3.6 添加用户到组
// 添加用户到组, /casbin/user-role?username=leo&rolename=adminauth.POST("/casbin/user-role", func(ctx *gin.Context) {username := ctx.Query("username")rolename := ctx.Query("rolename")err := casbinService.UpdateUserRole(username, rolename)if err != nil {ctx.String(500, "添加用户到组失败: "+err.Error())} else {ctx.JSON(200, "成功!")}})
3.7 从组中删除用户
// 从组中删除用户, /casbin/user-role?username=leo&rolename=adminauth.DELETE("/casbin/user-role", func(ctx *gin.Context) {username := ctx.Query("username")rolename := ctx.Query("rolename")err := casbinService.DeleteUserRole(username, rolename)if err != nil {ctx.String(500, "从组中删除用户失败: "+err.Error())} else {ctx.JSON(200, "成功!")}})
3.8 测试API
因为这些API也用到了casbin_auth,需要自行准备下权限
> 上述API测试两个,不一一列举
4. 最终目录结构和代码
目录结构
├── casbin.go
├── go.mod
├── go.sum
├── main.go
├── middleware.go
└── test.db
4.1 main.go
package mainimport ("github.com/gin-gonic/gin""github.com/glebarez/sqlite""gorm.io/gorm"
)func main() {db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})if err != nil {panic("failed to connect database: " + err.Error())}casbinService, err := NewCasbinService(db)if err != nil {panic("failed to new casbin service: " + err.Error())}r := gin.Default()auth := r.Group("/")auth.Use(NewCasbinAuth(casbinService))auth.GET("/api/user", func(ctx *gin.Context) {ctx.String(200, "get /api/user success")})auth.DELETE("/api/user", func(ctx *gin.Context) {ctx.String(200, "delete /api/user success")})// 获取所有用户auth.GET("/casbin/users", func(ctx *gin.Context) {ctx.JSON(200, casbinService.GetUsers())})// 获取所有角色组auth.GET("/casbin/roles", func(ctx *gin.Context) {ctx.JSON(200, casbinService.GetRoles())})// 获取所有角色组的策略auth.GET("/casbin/rolepolicy", func(ctx *gin.Context) {roles, err := casbinService.GetRolePolicy()if err != nil {ctx.String(500, "获取所有角色及权限失败: "+err.Error())} else {ctx.JSON(200, roles)}})/* 修改角色组策略type RolePolicy struct {RoleName string `gorm:"column:v0"`Url string `gorm:"column:v1"`Method string `gorm:"column:v2"`}*/auth.POST("/casbin/rolepolicy", func(ctx *gin.Context) {var p RolePolicyctx.BindJSON(&p)err := casbinService.CreateRolePolicy(p)if err != nil {ctx.String(500, "创建角色策略失败: "+err.Error())} else {ctx.JSON(200, "成功!")}})/* 删除角色组策略type RolePolicy struct {RoleName string `gorm:"column:v0"`Url string `gorm:"column:v1"`Method string `gorm:"column:v2"`}*/auth.DELETE("/casbin/rolepolicy", func(ctx *gin.Context) {var p RolePolicyctx.BindJSON(&p)err := casbinService.DeleteRolePolicy(p)if err != nil {ctx.String(500, "删除角色策略失败: "+err.Error())} else {ctx.JSON(200, "成功!")}})// 添加用户到组, /casbin/user-role?username=leo&rolename=adminauth.POST("/casbin/user-role", func(ctx *gin.Context) {username := ctx.Query("username")rolename := ctx.Query("rolename")err := casbinService.UpdateUserRole(username, rolename)if err != nil {ctx.String(500, "添加用户到组失败: "+err.Error())} else {ctx.JSON(200, "成功!")}})// 从组中删除用户, /casbin/user-role?username=leo&rolename=adminauth.DELETE("/casbin/user-role", func(ctx *gin.Context) {username := ctx.Query("username")rolename := ctx.Query("rolename")err := casbinService.DeleteUserRole(username, rolename)if err != nil {ctx.String(500, "从组中删除用户失败: "+err.Error())} else {ctx.JSON(200, "成功!")}})r.Run(":8000")
}
4.2 casbin.go
package mainimport ("github.com/casbin/casbin/v2""github.com/casbin/casbin/v2/model"gormadapter "github.com/casbin/gorm-adapter/v3""gorm.io/gorm"
)/*
按如下约定:1. 所有策略只针对角色组设置2. 用户关联到组(一个用户可以有多个组)
+-------+-------+-----------+--------+----+----+----+
| ptype | v0 | v1 | v2 | v3 | v4 | v5 |
+-------+-------+-----------+--------+----+----+----+
| p | admin | /api/user | GET | | | |
+-------+-------+-----------+--------+----+----+----+
| p | admin | /api/user | DELETE | | | |
+-------+-------+-----------+--------+----+----+----+
| p | user | /api/user | GET | | | |
+-------+-------+-----------+--------+----+----+----+
| ... | ... | ... | | | | |
+-------+-------+-----------+--------+----+----+----+
| g | leo | admin | | | | |
+-------+-------+-----------+--------+----+----+----+
| g | leo2 | admin | | | | |
+-------+-------+-----------+--------+----+----+----+
| g | leo3 | user | | | | |
+-------+-------+-----------+--------+----+----+----+
*/
type CasbinService struct {enforcer *casbin.Enforceradapter *gormadapter.Adapter
}func NewCasbinService(db *gorm.DB) (*CasbinService, error) {a, err := gormadapter.NewAdapterByDB(db)if err != nil {return nil, err}m, err := model.NewModelFromString(`[request_definition]r = sub, obj, act[policy_definition]p = sub, obj, act[role_definition]g = _, _[policy_effect]e = some(where (p.eft == allow))[matchers]m = g(r.sub, p.sub) && keyMatch2(r.obj,p.obj) && r.act == p.act`)if err != nil {return nil, err}e, err := casbin.NewEnforcer(m, a)if err != nil {return nil, err}return &CasbinService{adapter: a, enforcer: e}, nil
}// (RoleName, Url, Method) 对应于 `CasbinRule` 表中的 (v0, v1, v2)
type RolePolicy struct {RoleName string `gorm:"column:v0"`Url string `gorm:"column:v1"`Method string `gorm:"column:v2"`
}// 获取所有角色组
func (c *CasbinService) GetRoles() []string {return c.enforcer.GetAllRoles()
}// 获取所有角色组权限
func (c *CasbinService) GetRolePolicy() (roles []RolePolicy, err error) {err = c.adapter.GetDb().Model(&gormadapter.CasbinRule{}).Where("ptype = 'p'").Find(&roles).Errorif err != nil {return nil, err}return
}// 创建角色组权限, 已有的会忽略
func (c *CasbinService) CreateRolePolicy(r RolePolicy) error {// 不直接操作数据库,利用enforcer简化操作err := c.enforcer.LoadPolicy()if err != nil {return err}_, err = c.enforcer.AddPolicy(r.RoleName, r.Url, r.Method)if err != nil {return err}return c.enforcer.SavePolicy()
}// 修改角色组权限
func (c *CasbinService) UpdateRolePolicy(old, new RolePolicy) error {_, err := c.enforcer.UpdatePolicy([]string{old.RoleName, old.Url, old.Method},[]string{new.RoleName, new.Url, new.Method})if err != nil {return err}return c.enforcer.SavePolicy()
}// 删除角色组权限
func (c *CasbinService) DeleteRolePolicy(r RolePolicy) error {_, err := c.enforcer.RemovePolicy(r.RoleName, r.Url, r.Method)if err != nil {return err}return c.enforcer.SavePolicy()
}type User struct {UserName stringRoleNames []string
}// 获取所有用户以及关联的角色
func (c *CasbinService) GetUsers() (users []User) {p := c.enforcer.GetGroupingPolicy()usernameUser := make(map[string]*User, 0)for _, _p := range p {username, usergroup := _p[0], _p[1]if v, ok := usernameUser[username]; ok {usernameUser[username].RoleNames = append(v.RoleNames, usergroup)} else {usernameUser[username] = &User{UserName: username, RoleNames: []string{usergroup}}}}for _, v := range usernameUser {users = append(users, *v)}return
}// 角色组中添加用户, 没有组默认创建
func (c *CasbinService) UpdateUserRole(username, rolename string) error {_, err := c.enforcer.AddGroupingPolicy(username, rolename)if err != nil {return err}return c.enforcer.SavePolicy()
}// 角色组中删除用户
func (c *CasbinService) DeleteUserRole(username, rolename string) error {_, err := c.enforcer.RemoveGroupingPolicy(username, rolename)if err != nil {return err}return c.enforcer.SavePolicy()
}// 验证用户权限
func (c *CasbinService) CanAccess(username, url, method string) (ok bool, err error) {return c.enforcer.Enforce(username, url, method)
}
4.3 middleware.go
package mainimport ("log""github.com/gin-gonic/gin"
)func NewCasbinAuth(srv *CasbinService) gin.HandlerFunc {return func(ctx *gin.Context) {err := srv.enforcer.LoadPolicy()if err != nil {ctx.String(500, err.Error())ctx.Abort()return}// 简便起见,假设用户从url传递 /xxxx?username=leo,实际应用可以结合jwt等鉴权username, _ := ctx.GetQuery("username")log.Println(username, ctx.Request.URL.Path, ctx.Request.Method)ok, err := srv.enforcer.Enforce(username, ctx.Request.URL.Path, ctx.Request.Method)if err != nil {ctx.String(500, err.Error())ctx.Abort()return} else if !ok {ctx.String(403, "验证权限失败!")ctx.Abort()return}ctx.Next()}
}
5. 更进一步
主要记录一下casbin概念和使用经验,距离业务使用还有以下等需要调整
用户身份识别
是通过URL参数username
获得,实际使用中可配合jwt
、session
等使用API
接口可根据业务规范调整重写- 用户其他信息需要关联到
casbin_rule
表- 增加前端界面操作管理权限
- …
相关文章:

Go Gin Gorm Casbin权限管理实现 - 3. 实现Gin鉴权中间件
文章目录 0. 背景1. 准备工作2. gin中间件2.1 中间件代码2.2 中间件使用2.3 测试中间件使用结果 3. 添加权限管理API3.1 获取所有用户3.2 获取所有角色组3.3 获取所有角色组的策略3.4 修改角色组策略3.5 删除角色组策略3.6 添加用户到组3.7 从组中删除用户3.8 测试API 4. 最终目…...
js 封装一个异步任务函数
// 异步任务 封装 // 1,定义函数 // 2,使用核心api(queueMicrotask,MutationObserver,setTimeout) function runAsynctask (callback){if(typeof queueMicrotask "function" ){queueMicrotask(callback)}else if( typeof MutationObserver "functio…...
目标检测YOLO实战应用案例100讲-基于无人机航拍图像的目标检测
目录 前言 国内外研究现状 目标检测研究现状 无人机航拍目标检测研究现状...

PyQt5配置踩坑
安装步骤比较简单,这里只说一下我踩的坑,以及希望一些大佬可以给点建议。 一、QtDesigner 这个配置比较简单,直接就能用,我的配置如下图: C:\Users\lenovo\AppData\Roaming\Python\Python311\site-packages\qt5_app…...

内网渗透笔记之内网基础知识
0x01 内网概述 内网也指局域网(Local Area Network,LAN)是指在某一区域内由多台计算机互联成的计算机组。一般是方圆几千米以内。局域网可以实现文件管理、应用软件共享、打印机共享、工作组内的历程安排、电子邮件和传真通信服务等功能。 内…...
vue3+elementPlus:el-select选择器里添加按钮button
vue3elementPlus:el-select选择器里添加按钮button,在el-select的option后面添加button //html <el-select class"selectIcon" value-key"id" v-model"store.state.HeaderfilterText" multiple collapse-tagscollapse-…...
Android 模拟点击
Android 模拟点击 1.通过代码的方式实现 通过模拟MotionEvent的方式实现 //----------------模拟点击--------------------- private void simulateClick(View view, float x, float y) {long downTime SystemClock.uptimeMillis();final MotionEvent downEvent MotionEve…...

css自学框架之选项卡
这一节我们学习切换选项卡,两种切换方式,一种是单击切换选项,一种是鼠标滑动切换,通过参数来控制,切换方法。 一、参数 属性默认值描述tabBar.myth-tab-header span鼠标触发区域tabCon.myth-tab-content主体区域cla…...
Element Plus组件库中的input组件如何点击查看按钮时不可编辑,点击编辑时可编辑使用setup
如果你正在使用 Vue 3 和 Composition API,你可以使用 setup 函数来实现 Element Plus 的 Input 组件在点击查看按钮时不可编辑,点击编辑按钮时可编辑的功能。 以下是一个使用 setup 的示例代码: <template><div><el-input …...

小米、华为、iPhone、OPPO、vivo如何在手机让几张图拼成一张?
现在很多手机自带的相册APP已经有这个拼图功能了。 华为手机的拼图 打开图库,选定需要拼图的几张图片后,点击底部的【创作】,然后选择【拼图】就可以将多张图片按照自己想要的位置,组合在一起。 OPPO手机的拼图 打开相册&#…...

物联网AI MicroPython传感器学习 之 WS2812 RGB点阵灯环
学物联网,来万物简单IoT物联网!! 一、产品简介 ws2812是一个集控制电路与发光电路于一体的智能外控LED光源。其外型与一个5050LED灯珠相同,每个元件即为一个像素点。像素点内部包含了智能数字接口数据锁存信号整形放大驱动电路&a…...
【GPU常见概念】GPU常见概念及分类简述
随着大模型和人工智能的爆火,大家对GPU的关注持续上升,本文简单简述下GPU经常用的概念。 GPU(图形处理器),又称显示核心、视觉处理器、显示芯片,是一种专门在个人电脑、工作站、游戏机和一些移动设备&…...

JVM篇---第九篇
系列文章目录 文章目录 系列文章目录一、什么是指针碰撞?二、什么是空闲列表三、什么是TLAB? 一、什么是指针碰撞? 一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。当类加载检查通过后࿰…...
探索 GAN 和 VAE 之外的 NLP 扩散模型
介绍 扩散模型最近引起了极大的关注,特别是在自然语言处理(NLP)领域。基于通过数据扩散噪声的概念,这些模型在各种NLP任务中表现出了卓越的能力。在本文中,我们将深入研究扩散模型,了解其基本原理,并探讨实际应用、优势、计算注意事项、扩散模型在多模态数据处理中的相…...
发现很多人分不清 jwt session token 的区别?
1. JWT(JSON Web Token) 1.1 什么是JWT? JWT,全称为JSON Web Token,是一种用于在网络上安全传输信息的开放标准。它的设计初衷是用于跨域通信,在不同域之间传递声明性信息。JWT是一种自包含的令牌&#x…...

GPT系列论文解读:GPT-3
GPT系列 GPT(Generative Pre-trained Transformer)是一系列基于Transformer架构的预训练语言模型,由OpenAI开发。以下是GPT系列的主要模型: GPT:GPT-1是于2018年发布的第一个版本,它使用了12个Transformer…...

神经网络中的知识蒸馏
多分类交叉熵损失函数:每个样本的标签已经给出,模型给出在三种动物上的预测概率。将全部样本都被正确预测的概率求得为0.70.50.1,也称为似然概率。优化的目标就是希望似然概率最大化。如果样本很多,概率不断连乘,就会造…...

jmeter利用自身代理录制脚本
在利用代理录制脚本时一定要安装java jdk,不然不能录制的。 没有安装过java jdk安装jmeter后打开时会提示安装jdk,但是mac系统中直接打开提示安装jdk页面后下载的java并不是jdk(windows中没有试验过,笔者所说的基本全部指的是在ma…...

【漏洞复现】时空智友企业流程化管控系统 session泄露
漏洞描述 时空智友企业流程化管控系统 session 泄露 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公共秩序,尊重社会公德,不得利用网络从事危害国家安全、荣誉和利益,未经授权请勿利用…...
获取泛型的类型
示例一:获取父类的泛型的类型 public class Emp<T, Q> {class Stu extends Emp<String, Integer> {}Testvoid fun() {final Type type Emp.class.getGenericSuperclass();final ParameterizedType parameterizedType (ParameterizedType) type;Syste…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...