当前位置:网站首页>Authorization in Golang ProjectUseing Casbin
Authorization in Golang ProjectUseing Casbin
2022-06-12 17:38:00 【Ang Jun Li】
One 、Casbin Introduce
Privilege management is a necessary module in almost every system . If the project development needs to realize the rights management once every time , It's a waste of development time , Increase development costs .
therefore ,casbin
The library appears .casbin Is a powerful 、 Efficient access control library . Support a variety of commonly used access control models , Such as ACL/RBAC/ABAC
etc. . It can realize flexible access control . meanwhile ,casbin Support for multiple programming languages ,Go/Java/Node/PHP/Python/.NET/Rust
. We only need to learn once , Use it in many ways .
Casbin Sure
- Support custom request format , The default request format is
{subject, object, action}
. - With access control model model And strategy policy Two core concepts .
- Support RBAC Multi level role inheritance in , It's not just the subject that can have a role , Resources can also have roles .
- Support for built-in super users for example :
root
oradministrator
. Superuser can perform any operation without explicit permission declaration . - Supports a variety of built-in operators , Such as
keyMatch
, It is convenient to manage the path type resources , Such as/foo/bar
Can be mapped to/foo*
Casbin You can't
- Identity Authentication authentication( That is, verify the user's user name and password ),Casbin Only responsible for access control . There should be other specialized components responsible for authentication , Then from Casbin Access control , The two are complementary .
- Manage user list or role list . Casbin The project itself is supposed to manage users 、 The role list is more appropriate , Users usually have their passwords , however Casbin It's not designed to be a container for storing passwords . It's storage RBAC Mapping relationship between users and roles in the scheme .
Two 、 Quick to use
We still use Go Module Write code , Initialize first :
$ mkdir learning-node && cd learning-node
$ go mod init learning-node
Then install casbin
, At present, it is v2
edition :
go get -u github.com/casbin/casbin/v2
Authority is actually control who Be able to What resources What to do .casbin
Abstract the access control model to a model based on PERM(Policy,Effect,Request,Matchers) Metamodel configuration file ( Model file ) in . Therefore, switching or updating the authorization mechanism only needs to modify the configuration file .
policy
, It's the definition of strategy or rule . It defines specific rules , For example, the following example :
p = {sub, obj, act, eft}
Policy rule description :subject(sub Access entity ),object(obj Resources accessed ) and action(act Access method )eft( The policy result is generally blank, and is specified by default allow), Generally, policies are stored in relational data tables , Because there will be a lot of .
request
, Is the abstraction of access requests , It is associated withe.Enforce()
The parameters of a function are one-to-one .matcher
, The matcher will match the request with each of the definedpolicy
One by one , Generate multiple matching results .effect
, Summarize all results from applying matchers to requests , To decide that the request is allow (allow) still Refuse (deny) visit .
Here's a good picture of the process :
ACL Model (Access Control List, Access control list )
First, in the learning-node
A model is defined under the folder model.conf
file :
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
[policy_effect]
e = some(where (p.eft == allow))
The above model file specifies that permissions are set by sub,obj,act
Three elements make up , Only if there is an identical policy in the policy list , The request can only be passed . The result of the matcher can be passed through p.eft
obtain ,some(where (p.eft == allow))
As long as a policy allows , See... For other strategies :( Point me to jump ).
And then we were in learning-node
Define a policy under the folder policy.csv
file ( That is, who can operate what resources ):
p, zhangsan, data1, read
p, lisi, data2, write
above policy.csv
The two lines of the document indicate zhangsan
Data pair data1
Yes read
jurisdiction ,lisi
Data pair data2
Yes write
jurisdiction ;
And then in learning-node
New under folder main.go
file
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
// ACL Permission access check
func check(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func main() {
e, err := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "zhangsan", "data1", "read")
check(e, "lisi", "data2", "write")
check(e, "zhangsan", "data1", "write")
check(e, "zhangsan", "data2", "read")
}
The code is not complicated . First create a casbin.Enforcer
object , Load model file model.conf
And policy documents policy.csv
, Call its Enforce
Method to check permissions . Run the program :
$ go run main.go
// echo result
zhangsan CAN read data1
lisi CAN write data2
zhangsan CANNOT write data1
zhangsan CANNOT read data2
The request must match exactly policy.csv
A certain strategy can be passed .
The first 1 Because ("zhangsan", "data1", "read")
matching p, zhangsan, data1, read
.
The first 2 Because ("lisi", "data2", "write")
matching p, lisi, data2, write
.
The first 3 Because "zhangsan"
No, right data1
Of write
jurisdiction .
The first 4 Because zhangsan
Yes data2
No, read
jurisdiction , So the tests can't pass .
All the above output results are in line with expectations .
sub/obj/act
Pass it to Enforce
Method . Actually... Here sub/obj/act
and read/write/data1/data2
I picked it up myself , You can use any other name , As long as it is consistent .
The implementation in the above example is ACL
(access-control-list, Access control list ).ACL
Shows the defined permissions of each principal to each resource , What is undefined has no authority . We can also add super Administrators , Super administrators can do anything . Suppose the super administrator is root
, We just need to modify it model.conf
File matcher :
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"
explain : As long as the access subject is root
Let's go .
verification :
func main() {
e, err := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "root", "data1", "read")
check(e, "root", "data2", "write")
check(e, "root", "data1", "execute")
check(e, "root", "data3", "rwx")
}
because sub = "root"
when , The matcher must be able to pass , Running results :
$ go run main.go
root CAN read data1
root CAN write data2
root CAN execute data1
root CAN rwx data3
RBAC Model (Role Based Access Control)
ACL
The model has no problem with fewer users and resources , But there are a lot of users and resources ,ACL
It's going to be incredibly cumbersome . Imagine , Add one user at a time , How painful it is to have to reset the permissions he needs .RBAC
(role-based-access-control) The model introduces roles (role
) This middle layer is used to solve this problem . Each user belongs to a role , For example, developers 、 Administrators 、 Operations etc. , Each role has its own specific permissions , Permissions are added and deleted through roles . When a user is added in this way , We just need to assign him a role , He can have all the rights of the role . When modifying the permissions of a role , User permissions belonging to this role will be modified accordingly .
stay casbin
Use in RBAC
The model needs to be in the model model.conf
Add... To the file role_definition
modular :
[role_definition]
g = _, _
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
g = _,_
Defines the user —— role , role —— The mapping of roles , The former is a member of the latter , Have the authority of the latter . And then in the matcher , We don't need to judge r.sub
And p.sub
Completely equal , Just use g(r.sub, p.sub)
To determine the subject of the request r.sub
Whether to belong to p.sub
This role can . Finally, we modify the policy file and add users —— Role definition :
p, admin, data, read
p, admin, data, write
p, developer, data, read
g, zhangsan, admin
g, lisi, developer
above policy.csv
The document states that ,zhangsan
Belong to admin
Administrators ,lisi
Belong to developer
developer , Use g
To define this relationship . in addition admin
Data pair data
Have read
and write
jurisdiction , and developer
Data pair data
Only read
jurisdiction .
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
// RBAC Permission access check
func check(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func main() {
e, err := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "zhangsan", "data", "read")
check(e, "zhangsan", "data", "write")
check(e, "lisi", "data", "read")
check(e, "lisi", "data", "write")
}
Obviously lisi
The character doesn't have write
jurisdiction :
zhangsan CAN read data
zhangsan CAN write data
lisi CAN read data
lisi CANNOT write data
Multiple RBAC
Model (Multiple Rbac)
casbin
There are more than one support at the same time RBAC
System , That is, both users and resources have roles :
[role_definition]
g=_,_
g2=_,_
[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act
The model file above defines two RBAC
System g
and g2
, We use... In matchers g(r.sub, p.sub)
Determine that the subject of the request belongs to a specific group ,g2(r.obj, p.obj)
Determine that the request resource belongs to a specific group , And the operation is consistent to release .
Policy file :
p, admin, prod, read
p, admin, prod, write
p, admin, dev, read
p, admin, dev, write
p, developer, dev, read
p, developer, dev, write
p, developer, prod, read
g, zhangsan, admin
g, lisi, developer
g2, prod.data, prod
g2, dev.data, dev
Let's look at the role relationship first , In the end 4 That's ok ,zhangsan
Belong to admin
role ,lisi
Belong to developer
role ,prod.data
It belongs to production resources prod
role ,dev.data
It belongs to development resources dev
role .admin
The role has the right to prod
and dev
Read and write permissions for class resources ,developer
Can only have the right to dev
Read and write permissions and prod
Read permission of .
check(e, "zhangsan", "prod.data", "read")
check(e, "zhangsan", "prod.data", "write")
check(e, "lisi", "dev.data", "read")
check(e, "lisi", "dev.data", "write")
check(e, "lisi", "prod.data", "write")
In the first function e.Enforce()
Method gets first when it is actually executed zhangsan
The role admin
, Get more prod.data
The role prod
, According to the first line of the file p, admin, prod, read
Allow requests . In the last function lisi
Belongs to the character developer
, and prod.data
Belongs to the character prod
, All strategies don't allow , So the request was rejected :
zhangsan CAN read prod.data
zhangsan CAN write prod.data
lisi CAN read dev.data
lisi CAN write dev.data
lisi CANNOT write prod.data
Multiple roles (Multi Layered Roles)
casbin
You can also define the role you belong to , So as to realize multi-layer role relationship , This permission relationship can be passed on . for example zhangsan
Belongs to senior developers senior
,seinor
It belongs to the developer , that zhangsan
It's also for developers , Have all the rights of the developer . We can define the common rights of developers , And then extra for senior
Define some special permissions .
Model files need not be modified , The policy document has been changed as follows :
p, senior, data, write
p, developer, data, read
g, zhangsan, senior
g, senior, developer
g, lisi, developer
above policy.csv
The file defines the advanced Developer senior
Data pair data
Yes write
jurisdiction , Ordinary developers developer
For data only read
jurisdiction . meanwhile senior
It's also developer
, therefore senior
And inherit it read
jurisdiction .zhangsan
Belong to senior
, therefore zhangsan
Yes data
Yes read
and write
jurisdiction , and lisi
Only belong to developer
, Data pair data
Only read
jurisdiction .
check(e, "zhangsan", "data", "read")
check(e, "zhangsan", "data", "write")
check(e, "lisi", "data", "read")
check(e, "lisi", "data", "write")
It turned out not to be expected :
zhangsan CAN read data
zhangsan CAN write data
lisi CAN read data
lisi CANNOT write data
RBAC
domain Model
stay casbin
in , The role can be global , It can also be specific domain
( field ) or tenant
( Tenant ), It can be simply understood as Group . for example zhangsan
In group tenant1
It's the administrator , Have higher authority , stay tenant2
Maybe it's just a little brother .
Use RBAC domain
The following changes need to be made to the model file :
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _,_,_
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.obj
g=_,_,_
Indicates that the former has an intermediate defined role in the latter , Use in matchers g
Take it with you dom
.
p, admin, tenant1, data1, read
p, admin, tenant2, data2, read
g, zhangsan, admin, tenant1
g, zhangsan, developer, tenant2
stay tenant1
in , Only admin
Can read data data1
. stay tenant2
in , Only admin
Can read data data2
.zhangsan
stay tenant1
Medium is admin
, But in tenant2
Middle is not .
func check(e *casbin.Enforcer, sub, domain, obj, act string) {
ok, _ := e.Enforce(sub, domain, obj, act)
if ok {
fmt.Printf("%s CAN %s %s in %s\n", sub, act, obj, domain)
} else {
fmt.Printf("%s CANNOT %s %s in %s\n", sub, act, obj, domain)
}
}
func main() {
e, err := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "zhangsan", "tenant1", "data1", "read")
check(e, "zhangsan", "tenant2", "data2", "read")
}
It turned out not to be expected :
zhangsan CAN read data1 in tenant1
zhangsan CANNOT read data2 in tenant2
ABAC Model (Attribute Base Access List)
RBAC
The model can be used to implement the regular 、 Relatively static permission management is very useful . But for the special 、 Dynamic demand ,RBAC
It's a little bit out of my ability . for example , We're looking at data at different times data
Realize different authority control . Normal working hours 9:00-18:00
Everyone can read and write data
, At other times, only the data owner can read and write . We can use this kind of demand conveniently ABAC
(attribute base access list) The model is completed .
Define a model model.conf
file :
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[matchers]
m = r.sub.Hour >= 9 && r.sub.Hour < 18 || r.sub.Name == r.obj.Owner
[policy_effect]
e = some(where (p.eft == allow))
The rule does not require a policy file :
type Object struct {
Name string
Owner string
}
type Subject struct {
Name string
Hour int
}
func check(e *casbin.Enforcer, sub Subject, obj Object, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s at %d:00\n", sub.Name, act, obj.Name, sub.Hour)
} else {
fmt.Printf("%s CANNOT %s %s at %d:00\n", sub.Name, act, obj.Name, sub.Hour)
}
}
func main() {
e, err := casbin.NewEnforcer("path/to/model.conf")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
o := Object{"data", "zhangsan"}
s1 := Subject{"zhangsan", 10}
check(e, s1, o, "read")
s2 := Subject{"lisi", 10}
check(e, s2, o, "read")
s3 := Subject{"zhangsan", 20}
check(e, s3, o, "read")
s4 := Subject{"lisi", 20}
check(e, s4, o, "read")
}
It turned out not to be expected :
zhangsan CAN read data at 10:00
lisi CAN read data at 10:00
zhangsan CAN read data at 20:00
lisi CANNOT read data at 20:00
We know , stay model.conf
In the document, you can use r.sub
and r.obj
,r.act
Come to visit and pass on to Enforce
Method parameters . actually sub/obj
It can be a structure object , Thanks to the govaluate
Library's powerful function , We can do it in model.conf
Get the field values of these structures in the file . As above r.sub.Name
、r.Obj.Owner
etc. .govaluate
The contents of the library can be found in my previous article 《Go One storehouse a day govaluate》.
Use ABAC
The model can be very flexible in authority control , But in general RBAC
That's enough .
Model storage
In the above scenario Introduction , We always store models in files .casbin
You can also initialize the model dynamically in the code , for example get-started
The example can be rewritten as :
func main() {
m := model.NewModel()
m.AddDef("r", "r", "sub, obj, act")
m.AddDef("p", "p", "sub, obj, act")
m.AddDef("e", "e", "some(where (p.eft == allow))")
m.AddDef("m", "m", "r.sub == g.sub && r.obj == p.obj && r.act == p.act")
a := fileadapter.NewAdapter("./policy.csv")
e, err := casbin.NewEnforcer(m, a)
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "zhangsan", "data1", "read")
check(e, "lisi", "data2", "write")
check(e, "zhangsan", "data1", "write")
check(e, "zhangsan", "data2", "read")
}
similarly , We can also load models from strings :
func main() {
text := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
`
m, _ := model.NewModelFromString(text)
a := fileadapter.NewAdapter("./policy.csv")
e, _ := casbin.NewEnforcer(m, a)
check(e, "zhangsan", "data1", "read")
check(e, "lisi", "data2", "write")
check(e, "zhangsan", "data1", "write")
check(e, "zhangsan", "data2", "read")
}
But these two methods are not recommended ( Unable to persist management ).
Policy storage
In the previous example , We all store policies in policy.csv
In file . Generally in practical application , File storage is rarely used .casbin
Supports a variety of storage methods in the form of a third-party adapter, including MySQL/MongoDB/Redis/Etcd
etc. , You can also implement your own storage . Here's a complete list https://casbin.org/docs/en/adapters. Let's introduce the use of Gorm Adapter
. First connect to the database , Execute the following mysql
Code :
docker compose Rapid deployment mysql ( Point me to jump )
CREATE DATABASE IF NOT EXISTS casbin;
USE casbin;
create table casbin_rule
(
id bigint unsigned auto_increment primary key,
ptype varchar(100) null,
v0 varchar(100) null,
v1 varchar(100) null,
v2 varchar(100) null,
v3 varchar(100) null,
v4 varchar(100) null,
v5 varchar(100) null,
v6 varchar(25) null,
v7 varchar(25) null,
constraint idx_casbin_rule unique (ptype, v0, v1, v2, v3, v4, v5, v6, v7)
);
INSERT INTO casbin_rule (ptype, v0, v1, v2)
VALUES ('p', 'zhangsan', 'data1', 'read'),
('p', 'lisi', 'data2', 'write');
And then use Gorm Adapter
load policy
,Gorm Adapter
By default casbin
In the library casbin_rule
surface :
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
_ "github.com/go-sql-driver/mysql"
"gorm.io/gorm/logger"
"log"
)
func check(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func main() {
// adopt Xorm Realization , Use the adapter to initialize a MySQL (true: It will automatically manage and create tables / to update )
a, _ := gormadapter.NewAdapter("mysql", "root:[email protected](127.0.0.1:13306)/casbin", true)
// echo sql log
a.AddLogger(logger.Default.LogMode(logger.Info))
e, _ := casbin.NewEnforcer("path/to/model.conf", a)
check(e, "zhangsan", "data1", "read")
check(e, "lisi", "data2", "write")
check(e, "zhangsan", "data1", "write")
check(e, "zhangsan", "data2", "read")
}
It turned out not to be expected :
zhangsan CAN read data1
lisi CAN write data2
zhangsan CANNOT write data1
zhangsan CANNOT read data2
Using functions
We can use functions in matchers .casbin
Built in functions keyMatch/keyMatch2/keyMatch3/keyMatch4
It's all matching URL The path of ,regexMatch
Use regular matching ,ipMatch
matching IP Address . See https://casbin.org/docs/en/function. With built-in functions, we can easily divide the permissions of routes :
Defining models model.conf
file :
[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && r.act == p.act
Define strategy policy.csv
file ( That is, who can operate what resources ):
p, zhangsan, user/zhangsan/*, read
p, lisi, user/lisi/*, read
Different users can only access under their corresponding routes URL:
func main() {
e, err := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "zhangsan", "user/zhangsan/1", "read")
check(e, "lisi", "user/lisi/2", "read")
check(e, "zhangsan", "user/lisi/1", "read")
}
Output :
zhangsan CAN read user/zhangsan/1
lisi CAN read user/lisi/2
zhangsan CANNOT read user/lisi/1
We can also define our own functions . Define a function first , return bool:
func KeyMatch(key1, key2 string) bool {
i := strings.Index(key2, "*")
if i == -1 {
return key1 == key2
}
if len(key1) > i {
return key1[:i] == key2[:i]
}
return key1 == key2[:i]
}
Here we implement a simple regular match , Only deal with *
.
And then use this function with interface{}
Type packing one layer :
func KeyMatchFunc(args ...interface{}) (interface{}, error) {
name1 := args[0].(string)
name2 := args[1].(string)
return (bool)(KeyMatch(name1, name2)), nil
}
Then add it to the authority authenticator :
e.AddFunction("my_func", KeyMatchFunc)
In this way, we can use this function in the matcher to implement regular matching :
[matchers]
m = r.sub == p.sub && my_func(r.obj, p.obj) && r.act == p.act
Next, in the strategy file, let's say zhangsan
To give permission :
p, zhangsan, data/*, read
zhangsan
For matching patterns data/*
We have all the documents read
jurisdiction .
verification :
check(e, "zhangsan", "data/1", "read")
check(e, "zhangsan", "data/2", "read")
check(e, "zhangsan", "data/1", "write")
check(e, "zhangsan", "mydata", "read")
zhangsan
Yes zhangsan/1
No, write
jurisdiction ,mydata
Do not conform to the data/*
Pattern , either read
jurisdiction :
zhangsan CAN read data/1
zhangsan CAN read data/2
zhangsan CANNOT write data/1
zhangsan CANNOT read mydata
3、 ... and 、 summary
casbin
It greatly reduces the amount of business code for permission verification , Just learn once , It can be used in many places , Its multi applicability is worth learning and applying to practical projects .
Four 、 Note address
gitlab Address :http://gitlab.lilogs.com/liang/learning-note/tree/master/casbin
5、 ... and 、 Reference resources
- Go One storehouse a day casbin:https://darjun.github.io/2020/06/12/godailylib/casbin/
- Casbin Official documents :https://casbin.org/docs/zh-CN/overview
- Casbin GitHub:https://github.com/casbin
- A meta model based access control policy description language :http://www.jos.org.cn/html/2020/2/5624.htm
边栏推荐
- Interesting LD_ PRELOAD
- 全局锁、表锁、行锁
- 山东大学软件学院项目实训-创新实训-山大软院网络攻防靶场实验平台(二十五)-项目个人总结
- channel原创
- Introduction of one object one code tracing system
- Second week of electric control learning
- String的split方法的使用
- JDBC several pits
- Understanding of binary search
- Cicada mother talks to rainbow couple: 1.3 billion goods a year, from e-commerce beginners to super goods anchor
猜你喜欢
Dongfeng Yueda Kia, Tencent advertising and hero League mobile game professional league cooperate to build a new E-sports ecology
Volcano engine held a video cloud technology force summit and released a new experience oriented video cloud product matrix
Cesium parabolic equation
写技术博客的意义
文章名字
Cesium抛物线方程
Saturated! Can't future programmers work anymore?
JDBC several pits
Learn the mitmproxy packet capturing tool from scratch
ShardingJDBC 分库分表详解
随机推荐
The significance of writing technology blog
Codeforces Round #398 (Div. 2) D. Cartons of milk
Second week of electric control learning
(5) Outputs and outputs
Implementation of asynchronous query of Flink dimension table and troubleshooting
Introduction to several common functions of fiddler packet capturing (stop packet capturing, clear session window contents, filter requests, decode, set breakpoints...)
Lambda - 1
Arm64栈回溯
406. 根据身高重建队列
The R language uses the aggregate The plot function visualizes the summary statistical information of each subset (visualization is based on the probability value and its 95% confidence interval of th
布局管理中的sizePolicy的策略问题
Fiddler抓包几种常用功能介绍(停止抓包、清空会话窗内容、过滤请求、解码、设置断点......)
Microsoft Office MSDT Code Execution Vulnerability (cve-2022-30190) vulnerability recurrence
文章名字
406. reconstruct the queue based on height
Figma from getting started to giving up
5、Embedding
Goframe gredis configuration management | comparison of configuration files and configuration methods
Kali2022安装armitage
Two ways of tensorflow2 training data sets