Explorar o código

整合casbin添加adapter处理器

yxh %!s(int64=6) %!d(string=hai) anos
achega
5eb00597f1

+ 3 - 0
.gitattributes

@@ -0,0 +1,3 @@
+*.js linguist-language=GO
+*.css linguist-language=GO
+*.html linguist-language=GO

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+.buildpath
+.hgignore.swp
+.project
+.orig
+.swp
+.idea/
+.settings/
+.vscode/
+vender/
+log/
+composer.lock
+gitpush.sh
+pkg/
+bin/
+cbuild
+*/.DS_Store
+config/config.toml
+main
+.vscode
+go.sum
+*.exe

+ 3 - 0
README.MD

@@ -0,0 +1,3 @@
+# GoFrame Project
+
+https://goframe.org

+ 10 - 0
app/api/hello/hello.go

@@ -0,0 +1,10 @@
+package hello
+
+import (
+    "github.com/gogf/gf/net/ghttp"
+)
+
+// Hello World
+func Handler(r *ghttp.Request) {
+    r.Response.Writeln("Hello World!")
+}

+ 81 - 0
app/api/user/user.go

@@ -0,0 +1,81 @@
+package user
+
+import (
+	"gfast/app/service/user"
+	"gfast/library/response"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+//用户API管理对象
+type Controller struct {}
+
+//用户注册接口
+func (c Controller) SignUp(r *ghttp.Request) {
+	if err:=user.SignUp(r.GetPostMapStrStr());err!=nil{
+		response.Json(r, 1, err.Error())
+	}else{
+		response.Json(r, 0, "ok")
+	}
+}
+
+
+// 用户登录接口
+func (c *Controller) SignIn(r *ghttp.Request) {
+	data := r.GetPostMapStrStr()
+	rules := map[string]string{
+		"passport": "required",
+		"password": "required",
+	}
+	msgs := map[string]interface{}{
+		"passport": "账号不能为空",
+		"password": "密码不能为空",
+	}
+	if e := gvalid.CheckMap(data, rules, msgs); e != nil {
+		response.Json(r, 1, e.String())
+	}
+	if err := user.SignIn(data["passport"], data["password"], r.Session); err != nil {
+		response.Json(r, 1, err.Error())
+	} else {
+		response.Json(r, 0, "ok")
+	}
+}
+
+// 判断用户是否已经登录
+func (c *Controller) IsSignedIn(r *ghttp.Request) {
+	if user.IsSignedIn(r.Session) {
+		response.Json(r, 0, "ok")
+	} else {
+		response.Json(r, 1, "")
+	}
+}
+
+// 用户注销/退出接口
+func (c *Controller) SignOut(r *ghttp.Request) {
+	user.SignOut(r.Session)
+	response.Json(r, 0, "ok")
+}
+
+// 检测用户账号接口(唯一性校验)
+func (c *Controller) CheckPassport(r *ghttp.Request) {
+	passport := r.GetString("passport")
+	if e := gvalid.Check(passport, "required", "请输入账号"); e != nil {
+		response.Json(r, 1, e.String())
+	}
+	if user.CheckPassport(passport) {
+		response.Json(r, 0, "ok")
+	}
+	response.Json(r, 1, "账号已经存在")
+}
+
+// 检测用户昵称接口(唯一性校验)
+func (c *Controller) CheckNickName(r *ghttp.Request) {
+	nickname := r.Get("nickname")
+	if e := gvalid.Check(nickname, "required", "请输入昵称"); e != nil {
+		response.Json(r, 1, e.String())
+	}
+	if user.CheckNickName(r.GetString("nickname")) {
+		response.Json(r, 0, "ok")
+	}
+	response.Json(r, 1, "昵称已经存在")
+}

+ 0 - 0
app/model/.gitkeep


+ 0 - 0
app/service/.gitkeep


+ 91 - 0
app/service/user/user.go

@@ -0,0 +1,91 @@
+package user
+
+import (
+	"database/sql"
+	"errors"
+	"fmt"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+const USER_SESSION_MARK = "user_info"
+
+var (
+	//表对象
+	table  = g.DB().Table("user").Safe()
+)
+
+//用户注册
+func SignUp(data g.MapStrStr)error{
+	//数据校验
+	rules:=[]string{
+		"passport @required|length:6,16#账号不能为空|账号长度应在:min到:max之间",
+		"password2@required|length:6,16#请输入确认密码|密码长度应当在:min到:max之间",
+		"password @required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等",
+	}
+	if err:=gvalid.CheckMap(data,rules);err!=nil{
+		return errors.New(err.String())
+	}
+	if _, ok := data["nickname"]; !ok {
+		data["nickname"] = data["passport"]
+	}
+	// 唯一性数据检查
+	if !CheckPassport(data["passport"]) {
+		return errors.New(fmt.Sprintf("账号 %s 已经存在", data["passport"]))
+	}
+	if !CheckNickName(data["nickname"]) {
+		return errors.New(fmt.Sprintf("昵称 %s 已经存在", data["nickname"]))
+	}
+	// 记录账号创建/注册时间
+	if _, ok := data["create_time"]; !ok {
+		data["create_time"] = gtime.Now().String()
+	}
+	if _, err := table.Filter().Data(data).Save(); err != nil {
+		return err
+	}
+	return nil
+}
+
+// 判断用户是否已经登录
+func IsSignedIn(session *ghttp.Session) bool {
+	return session.Contains(USER_SESSION_MARK)
+}
+
+// 用户登录,成功返回用户信息,否则返回nil; passport应当会md5值字符串
+func SignIn(passport, password string, session *ghttp.Session) error {
+	record, err := table.Where("passport=? and password=?", passport, password).One()
+	if err != nil && err != sql.ErrNoRows {
+		return err
+	}
+	if record == nil {
+		return errors.New("账号或密码错误")
+	}
+	session.Set(USER_SESSION_MARK, record)
+	return nil
+}
+
+// 用户注销
+func SignOut(session *ghttp.Session) {
+	session.Remove(USER_SESSION_MARK)
+}
+
+
+// 检查账号是否符合规范(目前仅检查唯一性),存在返回false,否则true
+func CheckPassport(passport string) bool {
+	if i, err := table.Where("passport", passport).Count(); err != nil && err != sql.ErrNoRows {
+		return false
+	} else {
+		return i == 0
+	}
+}
+
+// 检查昵称是否符合规范(目前仅检查唯一性),存在返回false,否则true
+func CheckNickName(nickname string) bool {
+	if i, err := table.Where("nickname", nickname).Count(); err != nil && err != sql.ErrNoRows {
+		return false
+	} else {
+		return i == 0
+	}
+}

+ 0 - 0
boot/.gitkeep


+ 8 - 0
boot/boot.go

@@ -0,0 +1,8 @@
+package boot
+
+import "github.com/gogf/gf/frame/g"
+
+func init() {
+    g.Server().SetPort(8200)
+}
+

+ 0 - 0
config/.gitkeep


+ 0 - 0
docfile/.gitkeep


+ 9 - 0
go.mod

@@ -0,0 +1,9 @@
+module gfast
+
+require (
+	github.com/casbin/casbin/v2 v2.1.2
+	github.com/gogf/gf v1.10.0
+	google.golang.org/appengine v1.6.5 // indirect
+)
+
+go 1.13

+ 0 - 0
i18n/.gitkeep


+ 29 - 0
library/response/response.go

@@ -0,0 +1,29 @@
+package response
+
+import (
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+)
+
+// 标准返回结果数据结构封装。
+// 返回固定数据结构的JSON:
+// err:  错误码(0:成功, 1:失败, >1:错误码);
+// msg:  请求结果信息;
+// data: 请求结果,根据不同接口返回结果的数据结构不同;
+func Json(r *ghttp.Request, err int, msg string, data ...interface{}) {
+	responseData := interface{}(nil)
+	if len(data) > 0 {
+		responseData = data[0]
+	}
+	r.Response.WriteJson(g.Map{
+		"err":  err,
+		"msg":  msg,
+		"data": responseData,
+	})
+}
+
+// 返回JSON数据并退出当前HTTP执行函数。
+func JsonExit(r *ghttp.Request, err int, msg string, data ...interface{}) {
+	Json(r, err, msg, data...)
+	r.Exit()
+}

+ 279 - 0
library/utils/casbin_adapter.go

@@ -0,0 +1,279 @@
+package utils
+
+import (
+	"fmt"
+	"github.com/casbin/casbin/v2/model"
+	"github.com/casbin/casbin/v2/persist"
+	"github.com/gogf/gf/database/gdb"
+	"runtime"
+)
+
+type CasbinRule struct {
+	PType string `json:"ptype"`
+	V0    string `json:"v0"`
+	V1    string `json:"v1"`
+	V2    string `json:"v2"`
+	V3    string `json:"v3"`
+	V4    string `json:"v4"`
+	V5    string `json:"v5"`
+}
+
+// Adapter represents the gdb adapter for policy storage.
+type Adapter struct {
+	DriverName     string
+	DataSourceName string
+	TableName      string
+	Db             gdb.DB
+}
+
+// finalizer is the destructor for Adapter.
+func finalizer(a *Adapter) {
+	// 注意不用的时候不需要使用Close方法关闭数据库连接(并且gdb也没有提供Close方法),
+	// 数据库引擎底层采用了链接池设计,当链接不再使用时会自动关闭
+	a.Db = nil
+}
+
+// NewAdapter is the constructor for Adapter.
+func NewAdapter(driverName string, dataSourceName string) (*Adapter, error) {
+	a := &Adapter{}
+	a.DriverName = driverName
+	a.DataSourceName = dataSourceName
+	a.TableName = "casbin_rule"
+
+	// Open the DB, create it if not existed.
+	err := a.open()
+	if err != nil {
+		return nil, err
+	}
+
+	// Call the destructor when the object is released.
+	runtime.SetFinalizer(a, finalizer)
+
+	return a, nil
+}
+
+// NewAdapterFromOptions is the constructor for Adapter with existed connection
+func NewAdapterFromOptions(adapter *Adapter) (*Adapter, error) {
+
+	if adapter.TableName == "" {
+		adapter.TableName = "casbin_rule"
+	}
+	if adapter.Db == nil {
+		err := adapter.open()
+		if err != nil {
+			return nil, err
+		}
+
+		runtime.SetFinalizer(adapter, finalizer)
+	}
+	return adapter, nil
+}
+
+func (a *Adapter) open() error {
+	var err error
+	var db gdb.DB
+
+	gdb.SetConfig(gdb.Config{
+		"casbin": gdb.ConfigGroup{
+			gdb.ConfigNode{
+				Type:     a.DriverName,
+				LinkInfo: a.DataSourceName,
+				Role:     "master",
+				Weight:   100,
+			},
+		},
+	})
+	db, err = gdb.New("casbin")
+
+	if err != nil {
+		return err
+	}
+
+	a.Db = db
+
+	return a.createTable()
+}
+
+func (a *Adapter) close() error {
+	// 注意不用的时候不需要使用Close方法关闭数据库连接(并且gdb也没有提供Close方法),
+	// 数据库引擎底层采用了链接池设计,当链接不再使用时会自动关闭
+	a.Db = nil
+	return nil
+}
+
+func (a *Adapter) createTable() error {
+	_, err := a.Db.Exec(fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (ptype VARCHAR(10), v0 VARCHAR(256), v1 VARCHAR(256), v2 VARCHAR(256), v3 VARCHAR(256), v4 VARCHAR(256), v5 VARCHAR(256))", a.TableName))
+	return err
+}
+
+func (a *Adapter) dropTable() error {
+	_, err := a.Db.Exec(fmt.Sprintf("DROP TABLE %s", a.TableName))
+	return err
+}
+
+func loadPolicyLine(line CasbinRule, model model.Model) {
+	lineText := line.PType
+	if line.V0 != "" {
+		lineText += ", " + line.V0
+	}
+	if line.V1 != "" {
+		lineText += ", " + line.V1
+	}
+	if line.V2 != "" {
+		lineText += ", " + line.V2
+	}
+	if line.V3 != "" {
+		lineText += ", " + line.V3
+	}
+	if line.V4 != "" {
+		lineText += ", " + line.V4
+	}
+	if line.V5 != "" {
+		lineText += ", " + line.V5
+	}
+
+	persist.LoadPolicyLine(lineText, model)
+}
+
+// LoadPolicy loads policy from database.
+func (a *Adapter) LoadPolicy(model model.Model) error {
+	var lines []CasbinRule
+
+	if err := a.Db.Table(a.TableName).Scan(&lines); err != nil {
+		return err
+	}
+
+	for _, line := range lines {
+		loadPolicyLine(line, model)
+	}
+
+	return nil
+}
+
+func savePolicyLine(ptype string, rule []string) CasbinRule {
+	line := CasbinRule{}
+
+	line.PType = ptype
+	if len(rule) > 0 {
+		line.V0 = rule[0]
+	}
+	if len(rule) > 1 {
+		line.V1 = rule[1]
+	}
+	if len(rule) > 2 {
+		line.V2 = rule[2]
+	}
+	if len(rule) > 3 {
+		line.V3 = rule[3]
+	}
+	if len(rule) > 4 {
+		line.V4 = rule[4]
+	}
+	if len(rule) > 5 {
+		line.V5 = rule[5]
+	}
+
+	return line
+}
+
+// SavePolicy saves policy to database.
+func (a *Adapter) SavePolicy(model model.Model) error {
+	err := a.dropTable()
+	if err != nil {
+		return err
+	}
+	err = a.createTable()
+	if err != nil {
+		return err
+	}
+
+	for ptype, ast := range model["p"] {
+		for _, rule := range ast.Policy {
+			line := savePolicyLine(ptype, rule)
+			_, err := a.Db.Table(a.TableName).Data(&line).Insert()
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	for ptype, ast := range model["g"] {
+		for _, rule := range ast.Policy {
+			line := savePolicyLine(ptype, rule)
+			_, err := a.Db.Table(a.TableName).Data(&line).Insert()
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+// AddPolicy adds a policy rule to the storage.
+func (a *Adapter) AddPolicy(sec string, ptype string, rule []string) error {
+	line := savePolicyLine(ptype, rule)
+	_, err := a.Db.Table(a.TableName).Data(&line).Insert()
+	return err
+}
+
+// RemovePolicy removes a policy rule from the storage.
+func (a *Adapter) RemovePolicy(sec string, ptype string, rule []string) error {
+	line := savePolicyLine(ptype, rule)
+	err := rawDelete(a, line)
+	return err
+}
+
+// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
+func (a *Adapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
+	line := CasbinRule{}
+
+	line.PType = ptype
+	if fieldIndex <= 0 && 0 < fieldIndex+len(fieldValues) {
+		line.V0 = fieldValues[0-fieldIndex]
+	}
+	if fieldIndex <= 1 && 1 < fieldIndex+len(fieldValues) {
+		line.V1 = fieldValues[1-fieldIndex]
+	}
+	if fieldIndex <= 2 && 2 < fieldIndex+len(fieldValues) {
+		line.V2 = fieldValues[2-fieldIndex]
+	}
+	if fieldIndex <= 3 && 3 < fieldIndex+len(fieldValues) {
+		line.V3 = fieldValues[3-fieldIndex]
+	}
+	if fieldIndex <= 4 && 4 < fieldIndex+len(fieldValues) {
+		line.V4 = fieldValues[4-fieldIndex]
+	}
+	if fieldIndex <= 5 && 5 < fieldIndex+len(fieldValues) {
+		line.V5 = fieldValues[5-fieldIndex]
+	}
+	err := rawDelete(a, line)
+	return err
+}
+
+func rawDelete(a *Adapter, line CasbinRule) error {
+	db := a.Db.Table(a.TableName)
+
+	db.Where("ptype = ?", line.PType)
+	if line.V0 != "" {
+		db.Where("v0 = ?", line.V0)
+	}
+	if line.V1 != "" {
+		db.Where("v1 = ?", line.V1)
+	}
+	if line.V2 != "" {
+		db.Where("v2 = ?", line.V2)
+	}
+	if line.V3 != "" {
+		db.Where("v3 = ?", line.V3)
+	}
+	if line.V4 != "" {
+		db.Where("v4 = ?", line.V4)
+	}
+	if line.V5 != "" {
+		db.Where("v5 = ?", line.V5)
+	}
+
+	_, err := db.Delete()
+	return err
+}

+ 11 - 0
main.go

@@ -0,0 +1,11 @@
+package main
+
+import(
+	_ "gfast/boot"
+	_ "gfast/router"
+	"github.com/gogf/gf/frame/g"
+)
+
+func main() {
+	g.Server().Run()
+}

+ 24 - 0
main2.go

@@ -0,0 +1,24 @@
+package main
+
+import (
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/os/glog"
+)
+
+func main() {
+	// 基本事件回调使用
+	p := "/:name/info/{uid}"
+	s := g.Server()
+	s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
+		ghttp.HOOK_BEFORE_SERVE  : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_BEFORE_SERVE) },
+		ghttp.HOOK_AFTER_SERVE   : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_AFTER_SERVE) },
+		ghttp.HOOK_BEFORE_OUTPUT : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_BEFORE_OUTPUT) },
+		ghttp.HOOK_AFTER_OUTPUT  : func(r *ghttp.Request){ glog.Println(ghttp.HOOK_AFTER_OUTPUT) },
+	})
+	s.BindHandler(p, func(r *ghttp.Request) {
+		r.Response.Write("用户:", r.Get("name"), ", uid:", r.Get("uid"))
+	})
+	s.SetPort(8199)
+	s.Run()
+}

+ 0 - 0
public/html/.gitkeep


+ 0 - 0
public/plugin/.gitkeep


+ 0 - 0
public/resource/css/.gitkeep


+ 0 - 0
public/resource/image/.gitkeep


+ 0 - 0
public/resource/js/.gitkeep


+ 0 - 0
router/.gitkeep


+ 13 - 0
router/router.go

@@ -0,0 +1,13 @@
+package router
+
+import (
+    "gfast/app/api/hello"
+    "gfast/app/api/user"
+    "github.com/gogf/gf/frame/g"
+)
+
+// 统一路由注册.
+func init() {
+    g.Server().BindHandler("/", hello.Handler)
+    g.Server().BindObject("/user", new(user.Controller))
+}

+ 0 - 0
template/.gitkeep


+ 15 - 0
test/casbin_conf/model.conf

@@ -0,0 +1,15 @@
+# Request definition
+[request_definition]
+r = sub, obj, act
+
+# Policy definition
+[policy_definition]
+p = sub, obj, act
+
+# Policy effect
+[policy_effect]
+e = some(where (p.eft == allow))
+
+# Matchers
+[matchers]
+m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

+ 2 - 0
test/casbin_conf/policy.csv

@@ -0,0 +1,2 @@
+p, alice, data1, read
+p, bob, data2, write

+ 14 - 0
test/casbin_conf/rbac_model.conf

@@ -0,0 +1,14 @@
+[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) && r.obj == p.obj && r.act == p.act

+ 5 - 0
test/casbin_conf/rbac_policy.csv

@@ -0,0 +1,5 @@
+p, alice, data1, read
+p, bob, data2, write
+p, data2_admin, data2, read
+p, data2_admin, data2, write
+g, alice, data2_admin

+ 170 - 0
test/demo_test.go

@@ -0,0 +1,170 @@
+package test
+
+import (
+	"fmt"
+	"gfast/library/utils"
+	"github.com/casbin/casbin/v2"
+	"github.com/casbin/casbin/v2/util"
+	"github.com/gogf/gf/os/glog"
+	"testing"
+)
+
+func TestDemo(t *testing.T){
+	//t.Run("demo1" ,Demo1)
+	t.Run("Adapters_test", Adapters)
+}
+
+func Demo1(t *testing.T){
+	e,err:=casbin.NewEnforcer("casbin_conf/model.conf","casbin_conf/policy.csv")
+	if err!=nil{
+		panic(err)
+	}
+	sub := "alice" // the user that wants to access a resource. 对象
+	obj := "data1" // the resource that is going to be accessed. 资源
+	act := "write" // the operation that the user performs on the resource. 操作
+	ok, err := e.Enforce(sub, obj, act)
+
+	if err != nil {
+		fmt.Println("验证失败",err)
+	}
+
+	if ok == true {
+		fmt.Println("权限通过")
+	} else {
+		fmt.Println("没有权限")
+	}
+}
+
+func Adapters(t *testing.T) {
+	a := initAdapter(t, "mysql", "root:123456@tcp(127.0.0.1:3306)/test2")
+	testAutoSave(t, a)
+	testSaveLoad(t, a)
+
+	a = initAdapterFormOptions(t, &utils.Adapter{
+		DriverName:     "mysql",
+		DataSourceName: "root:123456@tcp(127.0.0.1:3306)/test2",
+	})
+	testAutoSave(t, a)
+	testSaveLoad(t, a)
+}
+
+func initAdapterFormOptions(t *testing.T, adapter *utils.Adapter) *utils.Adapter {
+	// Create an adapter
+	a, _ := utils.NewAdapterFromOptions(adapter)
+	// Initialize some policy in DB.
+	initPolicy(t, a)
+	// Now the DB has policy, so we can provide a normal use case.
+	// Note: you don't need to look at the above code
+	// if you already have a working DB with policy inside.
+
+	return a
+}
+
+func initPolicy(t *testing.T, a *utils.Adapter) {
+	// Because the DB is empty at first,
+	// so we need to load the policy from the file adapter (.CSV) first.
+	e, err := casbin.NewEnforcer("casbin_conf/rbac_model.conf", "casbin_conf/rbac_policy.csv")
+	if err != nil {
+		panic(err)
+	}
+
+	// This is a trick to save the current policy to the DB.
+	// We can't call e.SavePolicy() because the adapter in the enforcer is still the file adapter.
+	// The current policy means the policy in the Casbin enforcer (aka in memory).
+	err = a.SavePolicy(e.GetModel())
+	if err != nil {
+		panic(err)
+	}
+
+	// Clear the current policy.
+	e.ClearPolicy()
+	testGetPolicy(t, e, [][]string{})
+
+	// Load the policy from DB.
+	err = a.LoadPolicy(e.GetModel())
+	if err != nil {
+		panic(err)
+	}
+	testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
+}
+
+func testGetPolicy(t *testing.T, e *casbin.Enforcer, res [][]string) {
+	myRes := e.GetPolicy()
+	glog.Info("Policy: ", myRes)
+
+	if !util.Array2DEquals(res, myRes) {
+		t.Error("Policy: ", myRes, ", supposed to be ", res)
+	}
+}
+
+func initAdapter(t *testing.T, driverName string, dataSourceName string) *utils.Adapter {
+	// Create an adapter
+	a, err := utils.NewAdapter(driverName, dataSourceName)
+	if err != nil {
+		panic(err)
+	}
+
+	// Initialize some policy in DB.
+	initPolicy(t, a)
+	// Now the DB has policy, so we can provide a normal use case.
+	// Note: you don't need to look at the above code
+	// if you already have a working DB with policy inside.
+
+	return a
+}
+
+func testAutoSave(t *testing.T, a *utils.Adapter) {
+
+	// NewEnforcer() will load the policy automatically.
+	e, err := casbin.NewEnforcer("casbin_conf/rbac_model.conf", a)
+	if err!=nil{
+		panic(err)
+	}
+	// AutoSave is enabled by default.
+	// Now we disable it.
+	e.EnableAutoSave(false)
+
+	// Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer,
+	// it doesn't affect the policy in the storage.
+	e.AddPolicy("alice", "data1", "write")
+	// Reload the policy from the storage to see the effect.
+	e.LoadPolicy()
+	// This is still the original policy.
+	testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
+
+	// Now we enable the AutoSave.
+	e.EnableAutoSave(true)
+
+	// Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer,
+	// but also affects the policy in the storage.
+	e.AddPolicy("alice", "data1", "write")
+	// Reload the policy from the storage to see the effect.
+	e.LoadPolicy()
+	// The policy has a new rule: {"alice", "data1", "write"}.
+	testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}, {"alice", "data1", "write"}})
+
+	// Remove the added rule.
+	e.RemovePolicy("alice", "data1", "write")
+	e.LoadPolicy()
+	testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
+
+	// Remove "data2_admin" related policy rules via a filter.
+	// Two rules: {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"} are deleted.
+	e.RemoveFilteredPolicy(0, "data2_admin")
+	e.LoadPolicy()
+	testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}})
+}
+
+func testSaveLoad(t *testing.T, a *utils.Adapter) {
+	// Initialize some policy in DB.
+	initPolicy(t, a)
+	// Note: you don't need to look at the above code
+	// if you already have a working DB with policy inside.
+
+	// Now the DB has policy, so we can provide a normal use case.
+	// Create an adapter and an enforcer.
+	// NewEnforcer() will load the policy automatically.
+
+	e, _ := casbin.NewEnforcer("casbin_conf/rbac_model.conf", a)
+	testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
+}