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…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

> 上述API测试两个,不一一列举