├── .gitignore ├── README.md ├── xentity.go ├── xerror.go ├── xhandler.go └── xormt.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xormt 2 | 基于xorm和gin多租户实现 3 | 4 | ## 实例仓库 5 | https://github.com/hrf304/ukid.git 6 | 7 | ## 使用步骤 8 | 1、在main函数中调用Init进行初始化,并传入相关TenantDBProvider和TenantIdResolver的具体实现 9 | 10 | ``` 11 | func main() { 12 | ginEngine := gin.Default() 13 | xormt.Init(xormtext.GetTenants, xormtext.GetTenantId) 14 | router.Register(ginEngine) 15 | ginEngine.Run(":8080") 16 | } 17 | ``` 18 | 2、需要同步实体类中调用AddModel 19 | 20 | ``` 21 | func init(){ 22 | xormt.AddModel(new (User)) 23 | } 24 | ``` 25 | 3、映射router,此时需要使用xormt.HandlerGin返回gin.HandlerFunc 26 | 27 | ``` 28 | v1.GET("/users/:id", xormt.HandlerGin(ctrl.Get)) 29 | ``` 30 | 4、实现controller处理函数,参数为*xormt.MultiTenantContext 31 | 32 | ``` 33 | func (c *UserController) Get(ctx *xormt.MultiTenantContext) { 34 | i++ 35 | 36 | user := &entity.User{} 37 | user.Id = util.UUID() 38 | user.Name = fmt.Sprintf("huangrf%d", i) 39 | user.LoginId = fmt.Sprintf("huangrf%d", i) 40 | user.Major = fmt.Sprintf("major%d", i) 41 | 42 | _, err := ctx.DB.InsertOne(user) 43 | if err != nil{ 44 | ctx.JSON(500, &entity.Resp{500, err.Error(), nil}) 45 | }else{ 46 | ctx.JSON(http.StatusOK, &entity.Resp{http.StatusOK, "", user.Id}) 47 | } 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /xentity.go: -------------------------------------------------------------------------------- 1 | package xormt 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/go-xorm/xorm" 6 | ) 7 | 8 | /** 9 | * @brief 租户数据库信息 10 | * @field1 Name: 数据库连接名称,用于显示,可以为空 11 | * @field2 Tid: 租户id 12 | * @field3 ConnStr: 数据库连接字符串 13 | * @field4 DriverName: 驱动名称 14 | * @field5 db: 实际数据库连接对象,内部对象 15 | */ 16 | type TenantDBInfo struct { 17 | Name string // 数据库连接名称 18 | Tid string // 租户id 19 | ConnStr string // 连接串 20 | DriverName string // 驱动名称 21 | db *xorm.Engine // 数据库连接对象 22 | } 23 | 24 | /** 25 | * @brief: 多租户上下文 26 | * 集成gin.Context 27 | */ 28 | type MultiTenantContext struct { 29 | *gin.Context 30 | DB *xorm.Engine 31 | } 32 | 33 | /** 34 | * @brief: 多租户处理函数 35 | */ 36 | type MultiTenantHandlerFunc func(*MultiTenantContext) 37 | 38 | /** 39 | * @brief: 租户数据库提供者 40 | */ 41 | type TenantDBProvider func()[]*TenantDBInfo 42 | 43 | /** 44 | * @brief: 住户id解析器 45 | */ 46 | type TenantIdResolver func(*gin.Context)string 47 | -------------------------------------------------------------------------------- /xerror.go: -------------------------------------------------------------------------------- 1 | package xormt 2 | 3 | import ( 4 | "strings" 5 | "fmt" 6 | ) 7 | 8 | type ErrParamEmpty struct { 9 | name string 10 | } 11 | 12 | func (epe *ErrParamEmpty)Error()string{ 13 | if strings.TrimSpace(epe.name) == ""{ 14 | return "the param is empty or nil" 15 | }else{ 16 | return fmt.Sprintf("the param %s is empty or nil", epe.name) 17 | } 18 | } 19 | 20 | type ErrFieldEmpty struct { 21 | field string 22 | } 23 | 24 | func (efe* ErrFieldEmpty)Error()string{ 25 | if strings.TrimSpace(efe.field) == ""{ 26 | return "the field is empty or nil" 27 | }else{ 28 | return fmt.Sprintf("the field %s is empty or nil", efe.field) 29 | } 30 | } 31 | 32 | type ErrDeaultTendarMissing struct{ 33 | 34 | } 35 | 36 | func (efe* ErrDeaultTendarMissing)Error()string { 37 | return "the default tendar is missing" 38 | } 39 | -------------------------------------------------------------------------------- /xhandler.go: -------------------------------------------------------------------------------- 1 | package xormt 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-gonic/gin" 6 | "strings" 7 | ) 8 | 9 | func HandlerGin(handler MultiTenantHandlerFunc) gin.HandlerFunc{ 10 | return func(c *gin.Context){ 11 | mc := new(MultiTenantContext) 12 | mc.Context = c 13 | tid := xTenantIdResolver(c) 14 | if tdb, exist := xdbMaps[tid]; exist{ 15 | mc.DB = tdb.db 16 | }else{ 17 | fmt.Println("can not get tdb by tid", tid) 18 | } 19 | handler(mc) 20 | } 21 | } 22 | 23 | func defaultIdResolver(ctx *gin.Context)string{ 24 | id, _ := ctx.GetQuery("tenant") 25 | if strings.TrimSpace(id) == ""{ 26 | id = ctx.GetString("tenant") 27 | } 28 | 29 | return id 30 | } 31 | -------------------------------------------------------------------------------- /xormt.go: -------------------------------------------------------------------------------- 1 | package xormt 2 | 3 | import ( 4 | "github.com/go-xorm/xorm" 5 | "strings" 6 | "sync" 7 | ) 8 | 9 | var( 10 | xdbMaps map[string]*TenantDBInfo = nil 11 | xdbMutex sync.Mutex 12 | 13 | xSyncModels []interface{} = nil 14 | xSyncModelsMutex sync.Mutex 15 | 16 | xTenantIdResolver TenantIdResolver = nil 17 | ) 18 | 19 | func init(){ 20 | xdbMaps = make(map[string]*TenantDBInfo) 21 | xSyncModels = make([]interface{}, 0) 22 | } 23 | 24 | /** 25 | * @brief: 初始化 26 | * @param1 provider: 租户数据库提供者 27 | * @return: 错误信息 28 | */ 29 | func Init(provider TenantDBProvider, idResolver TenantIdResolver)error { 30 | if provider == nil { 31 | return &ErrParamEmpty{"provider"} 32 | } 33 | if idResolver == nil{ 34 | xTenantIdResolver = defaultIdResolver 35 | }else{ 36 | xTenantIdResolver = idResolver 37 | } 38 | 39 | tdbs := provider() 40 | hasDefault := false 41 | for i := range tdbs { 42 | Add(tdbs[i]) 43 | if tdbs[i].Tid == "default" { 44 | hasDefault = true 45 | } 46 | } 47 | if !hasDefault { 48 | return &ErrDeaultTendarMissing{} 49 | } 50 | return nil 51 | } 52 | 53 | /** 54 | * @brief: 添加db 55 | * @param1 tdb: 租户数据库信息 56 | * @return1 错误信息 57 | */ 58 | func Add(tdb *TenantDBInfo)error{ 59 | if strings.TrimSpace(tdb.Tid) == ""{ 60 | return &ErrFieldEmpty{"Tid",} 61 | } else if strings.TrimSpace(tdb.ConnStr) == ""{ 62 | return &ErrFieldEmpty{"ConnStr"} 63 | } else if strings.TrimSpace(tdb.DriverName) == ""{ 64 | return &ErrFieldEmpty{"DriverName"} 65 | } 66 | 67 | var err error 68 | tdb.db, err = xorm.NewEngine(tdb.DriverName, tdb.ConnStr) 69 | if err != nil{ 70 | return err 71 | } 72 | 73 | xdbMutex.Lock() 74 | defer xdbMutex.Unlock() 75 | if _, exist := xdbMaps[tdb.Tid]; !exist{ 76 | xdbMaps[tdb.Tid] = tdb 77 | 78 | xSyncModelsMutex.Lock() 79 | defer xSyncModelsMutex.Unlock() 80 | for i := range xSyncModels{ 81 | syncModel(tdb, xSyncModels[i]) 82 | } 83 | } 84 | 85 | return nil 86 | } 87 | 88 | /** 89 | * @brief: 添加同步的实体 90 | * @param1 model: 实体对象(例如:new(User)) 91 | */ 92 | func AddModel(model interface{})error{ 93 | if model == nil{ 94 | return &ErrParamEmpty{"model"} 95 | } 96 | xSyncModelsMutex.Lock() 97 | defer xSyncModelsMutex.Unlock() 98 | 99 | xSyncModels = append(xSyncModels, model) 100 | 101 | xdbMutex.Lock() 102 | defer xdbMutex.Unlock() 103 | for _, v := range xdbMaps{ 104 | syncModel(v, model) 105 | } 106 | 107 | return nil 108 | } 109 | 110 | /** 111 | * @brief: sync model 112 | * @param1 tenant: 租户链接 113 | * @param2 model: 实体对象 114 | * @return: 错误信息 115 | */ 116 | func syncModel(tenant *TenantDBInfo, model interface{})error{ 117 | if tenant == nil{ 118 | return &ErrParamEmpty{"tenant"} 119 | } 120 | if tenant.db == nil{ 121 | return &ErrFieldEmpty{"tenant.db"} 122 | } 123 | if model == nil{ 124 | return &ErrParamEmpty{"model"} 125 | } 126 | tenant.db.Sync2(model) 127 | 128 | return nil 129 | } 130 | --------------------------------------------------------------------------------