DaoSingle相关的结构,整体生成一个说明开发文档
DaoSingle是基于GoWebFrame3框架实现的数据访问门面单例,采用门面模式统一封装多个DAO组件,提供简洁的数据访问入口。其核心职责包括统一访问入口、单例管理、缓存支持和简化调用。架构上位于业务层与数据访问层之间,通过DaoQuery结构体聚合多个Facade组件。文档详细介绍了其生命周期管理、公共API、使用示例和扩展指南,并提供了最佳实践建议。DaoSingle通过统一门面设计简化调用、解耦业务、支持缓存扩展,适用于GoTrain项目的数据访问层开发。
https://gitee.com/gowebframe3/webframe.git
DaoSingle 开发文档
1. 概述
1.1 定义
DaoSingle是基于GoWebFrame3框架实现的数据访问门面单例,采用门面模式(Facade Pattern)将多个数据访问层(DAO)组件统一封装,提供简洁、统一的数据访问入口。
1.2 核心职责
| 职责 | 说明 |
|---|---|
| 统一入口 | 聚合多个 DAO Facade,提供单一访问点 |
| 单例管理 | 通过依赖注入容器管理生命周期 |
| 缓存支持 | 提供用户信息等缓存查询能力 |
| 简化调用 | 屏蔽底层复杂的 DAO 调用链 |
1.3 架构定位
┌─────────────────────────────────────────────────────────────┐ │ Controller / Service │ │ (业务层) │ └───────────────────────────┬─────────────────────────────────┘ │ 调用 ▼ ┌─────────────────────────────────────────────────────────────┐ │ DaoSingle │ │ (门面层 - 单例) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ DaoQuery │ │ │ │ CreditFacade | OpcFacade | PlanFacade | ... │ │ │ └─────────────────────────────────────────────────────┘ │ └───────────────────────────┬─────────────────────────────────┘ │ 委托调用 ▼ ┌─────────────────────────────────────────────────────────────┐ │ DAO Facades (数据访问层) │ │ CreditFacade | OpcFacade | PlanFacade | StudentFacade ... │ └─────────────────────────────────────────────────────────────┘2. 核心结构
2.1 DaoSingle 结构体
go
type DaoSingle struct { basedto.BaseEntitySingle // 继承基础单例实体 *daoquery.DaoQuery // 组合 DAO 查询组件 }字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
BaseEntitySingle | basedto.BaseEntitySingle | 提供单例基础能力 |
DaoQuery | *daoquery.DaoQuery | 聚合多个 DAO Facade |
2.2 DaoQuery 结构体
go
type DaoQuery struct { basedto.BaseEntity *daofacade.CreditFacade // 积分系统 *daofacade.OpcFacade // 操作日志 *daofacade.PlanFacade // 训练计划 *daofacade.StudentFacade // 学生管理 *daofacade.UserDaoFacade // 用户管理 *daofacade.WordDaoFacade // 单词管理 *daofacade.KbaseFacade // 知识库 }包含的 Facade 组件:
| Facade | 职责 |
|---|---|
CreditFacade | 积分查询、积分变更记录 |
OpcFacade | 操作日志记录与查询 |
PlanFacade | 训练计划 CRUD 操作 |
StudentFacade | 学生信息管理 |
UserDaoFacade | 用户信息查询、缓存 |
WordDaoFacade | 单词库查询、词表管理 |
KbaseFacade | 知识库管理 |
3. 生命周期管理
3.1 单例注册流程
plainText
┌─────────────────────────────────────────────────────┐ │ init() │ │ 调用 registerBeanDaoSingle() │ └───────────────────────────┬─────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ basedi.RegisterLoadBean() │ │ 注册单例名称和加载函数 LoadDaoSingle │ └───────────────────────────┬─────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ LoadDaoSingle() │ │ 1. NewDaoSingle() 创建实例 │ │ 2. InjectDaoSingle() 依赖注入 │ │ 3. 返回单例实例 │ └─────────────────────────────────────────────────────┘3.2 初始化代码
go
// 单例名称常量 var singleNameDaoSingle = "*daosingle.DaoSingle-98055d62-ef8f-4464-a518-57ddd65c92d9" // 注册单例 func registerBeanDaoSingle() { err := basedi.RegisterLoadBean(singleNameDaoSingle, LoadDaoSingle) if err != nil { logrus.Error("register bean error!", err) } } // 加载单例 func LoadDaoSingle() baseiface.ISingleton { var inst = NewDaoSingle() InjectDaoSingle(inst) return inst }3.3 获取单例实例
go
func FindBeanDaoSingle() *DaoSingle { if bean, ok := basedi.FindBeanOk(singleNameDaoSingle); ok { return bean.(*DaoSingle) } logrus.Error("not find bean!") return nil }4. 公共 API
4.1 Facade 获取方法
| 方法 | 返回类型 | 说明 |
|---|---|---|
FindBeanPlanFacade() | *daofacade.PlanFacade | 获取训练计划 Facade |
go
func FindBeanPlanFacade() *daofacade.PlanFacade { return FindBeanDaoSingle().PlanFacade }4.2 用户缓存查询方法
| 方法 | 参数 | 返回类型 | 说明 |
|---|---|---|---|
CacheFillName() | nameField *string,userId int64 | *pagemodel.IchubResult[*userentity.Users] | 根据用户ID填充用户名 |
CacheFindUser() | userId int64 | *pagemodel.IchubResult[*userentity.Users] | 从缓存查询用户信息 |
go
// 从缓存查询用户信息 func CacheFindUser(userId int64) *pagemodel.IchubResult[*userentity.Users] { return FindBeanDaoSingle().UserDaoFacade.FindByIdAtCache(userId) } // 填充用户名称到指定字段 func CacheFillName(nameField *string, userId int64) *pagemodel.IchubResult[*userentity.Users] { return FindBeanDaoSingle().UserDaoFacade.CacheFillName(nameField, userId) }5. 使用示例
5.1 获取计划 Facade
go
// 获取训练计划 Facade planFacade := daosingle.FindBeanPlanFacade() // 使用 Facade 执行查询 plans, err := planFacade.FindByStudentId(studentId) if err != nil { // 处理错误 }5.2 查询用户缓存
go
// 从缓存查询用户 result := daosingle.CacheFindUser(userId) if result.IsSuccess() { user := result.Data fmt.Printf("用户名: %s\n", user.Name) } // 填充用户名到字段 var userName string result := daosingle.CacheFillName(&userName, userId) if result.IsSuccess() { fmt.Printf("用户名已填充: %s\n", userName) }5.3 直接使用 DaoSingle
go
// 获取 DaoSingle 实例 dao := daosingle.FindBeanDaoSingle() if dao == nil { log.Error("DaoSingle 初始化失败") return } // 调用各个 Facade // 1. 查询训练计划 plans, _ := dao.PlanFacade.FindAll() // 2. 查询用户 user, _ := dao.UserDaoFacade.FindById(123) // 3. 查询单词 words, _ := dao.WordDaoFacade.Search("apple") // 4. 记录操作日志 dao.OpcFacade.Record("train", "start", "用户开始训练")6. 扩展指南
6.1 添加新的 Facade
步骤 1:在DaoQuery中添加新字段
go
type DaoQuery struct { basedto.BaseEntity *daofacade.CreditFacade *daofacade.OpcFacade *daofacade.PlanFacade // ... 现有字段 ... *daofacade.NewFacade // 添加新 Facade }步骤 2:在Init()方法中初始化
go
func (self *DaoQuery) Init() { // ... 现有初始化 ... self.NewFacade = daofacade.FindBeanNewFacade() }步骤 3:在ShutDown()方法中添加清理
go
func (self *DaoQuery) ShutDown() { // ... 现有清理 ... self.NewFacade.Shutdown() }步骤 4(可选):在daosingle包中添加便捷方法
go
func FindBeanNewFacade() *daofacade.NewFacade { return FindBeanDaoSingle().NewFacade }7. 最佳实践
7.1 调用方式选择
| 场景 | 推荐方式 | 说明 |
|---|---|---|
| 频繁使用单一 Facade | 使用便捷方法(如FindBeanPlanFacade()) | 代码更简洁 |
| 需要多个 Facade | 获取DaoSingle实例后直接调用 | 减少多次获取开销 |
| 性能敏感场景 | 缓存DaoSingle引用 | 避免重复查找 |
7.2 错误处理
go
// 正确:检查返回结果 result := daosingle.CacheFindUser(userId) if !result.IsSuccess() { log.Warnf("查询用户失败: %v", result.Error) return nil } // 正确:检查单例是否为空 dao := daosingle.FindBeanDaoSingle() if dao == nil { log.Error("DaoSingle 未初始化") return }7.3 避免滥用
- 不要在 DAO 层调用:
DaoSingle是门面层,不应在 DAO 内部使用,避免循环依赖 - 不要存储到结构体字段:单例应通过
FindBeanDaoSingle()获取,避免持有过期引用 - 注意并发安全:虽然单例本身是线程安全的,但具体 Facade 的方法需要确认是否线程安全
8. 依赖关系
plainText
DaoSingle ├── basedto.BaseEntitySingle (基础单例能力) └── DaoQuery ├── basedto.BaseEntity (基础实体能力) ├── CreditFacade (积分系统) ├── OpcFacade (操作日志) ├── PlanFacade (训练计划) ├── StudentFacade (学生管理) ├── UserDaoFacade (用户管理) ├── WordDaoFacade (单词管理) └── KbaseFacade (知识库)9. 总结
DaoSingle作为数据访问层的统一门面,提供了以下价值:
| 价值 | 说明 |
|---|---|
| 简化调用 | 统一入口,无需关注底层 DAO 实现 |
| 解耦业务 | 业务层只依赖门面,不依赖具体 DAO |
| 单例管理 | 通过依赖注入容器统一管理生命周期 |
| 缓存支持 | 内置用户缓存查询能力 |
| 扩展性强 | 新增 Facade 只需修改DaoQuery |
文档版本:v1.0
创建时间:2026-05-20
适用范围:GoTrain 项目数据访问层开发
import ( "gitea.super-study.com/ys-study/gotrain/beapi/facade/daoquery" "gitee.com/gowebframe3/webframe.git/goconfig/base/basedto" ) type DaoSingle struct { basedto.BaseEntitySingle *daoquery.DaoQuery } func NewDaoSingle() *DaoSingle { return &DaoSingle{ DaoQuery: daoquery.FindBeanDaoQuery(), } } package daosingle import ( "gitee.com/gowebframe3/webframe.git/goconfig/base/baseiface" "gitee.com/gowebframe3/webframe.git/goconfig/basedi" "github.com/sirupsen/logrus" ) /* @Title 文件名称: dao_single_init.go @Desp 描述: 依赖自动注入 @Company 公司: www.learn.com @Author 作者: raymond@163.com 时间: 2026-05-17 01:23:23 @Update 作者: raymond@163.com 时间: 2026-05-17 01:23:23 */ var singleNameDaoSingle = "*daosingle.DaoSingle-98055d62-ef8f-4464-a518-57ddd65c92d9" // init register load func init() { registerBeanDaoSingle() } // register DaoSingle func registerBeanDaoSingle() { err := basedi.RegisterLoadBean(singleNameDaoSingle, LoadDaoSingle) if err != nil{ logrus.Error("register bean error!", err) } } // FindBeanDaoSingle func FindBeanDaoSingle() *DaoSingle { if bean, ok := basedi.FindBeanOk(singleNameDaoSingle); ok { return bean.(*DaoSingle) } logrus.Error("not find bean!") return nil } func LoadDaoSingle() baseiface.ISingleton { var inst = NewDaoSingle() InjectDaoSingle(inst) return inst } func InjectDaoSingle(s *DaoSingle) { // s.Init() }package daosingle import "gitea.super-study.com/ys-study/gotrain/beapi/facade/daofacade" func FindBeanPlanFacade() *daofacade.PlanFacade { return FindBeanDaoSingle().PlanFacade } package daosingle import ( "gitea.super-study.com/ys-study/gotrain/beapi/db/dbentity/userentity" "gitee.com/gowebframe3/webframe.git/goweb/pagemodel" ) func CacheFillName(nameField *string, userId int64) *pagemodel.IchubResult[*userentity.Users] { return FindBeanDaoSingle().UserDaoFacade.CacheFillName(nameField, userId) } func CacheFindUser(userId int64) *pagemodel.IchubResult[*userentity.Users] { return FindBeanDaoSingle().UserDaoFacade.FindByIdAtCache(userId) }package daoquery import ( "gitea.super-study.com/ys-study/gotrain/beapi/facade/daofacade" "gitee.com/gowebframe3/webframe.git/goconfig/base/basedto" ) type DaoQuery struct { basedto.BaseEntity *daofacade.CreditFacade *daofacade.OpcFacade *daofacade.PlanFacade *daofacade.StudentFacade *daofacade.UserDaoFacade *daofacade.WordDaoFacade *daofacade.KbaseFacade } func NewDaoQuery() *DaoQuery { return &DaoQuery{} } func (self *DaoQuery) Init() { self.CreditFacade = daofacade.FindBeanCreditFacade() self.OpcFacade = daofacade.FindBeanOpcFacade() self.PlanFacade = daofacade.FindBeanPlanFacade() self.StudentFacade = daofacade.FindBeanStudentFacade() self.UserDaoFacade = daofacade.FindBeanUserDaoFacade() self.WordDaoFacade = daofacade.FindBeanWordDaoFacade() self.KbaseFacade = daofacade.FindBeanKbaseFacade() } func (self *DaoQuery) AutoInit() bool { return true } func (self *DaoQuery) ShutDown() { self.CreditFacade.Shutdown() self.OpcFacade.Shutdown() self.PlanFacade.Shutdown() self.StudentFacade.Shutdown() self.UserDaoFacade.Shutdown() self.WordDaoFacade.Shutdown() self.KbaseFacade.Shutdown() }package daofacade import ( "gitea.super-study.com/ys-study/gotrain/beapi/db/dbdao/creditdao" "gitea.super-study.com/ys-study/gotrain/beapi/gotool/dbframe/dbsequence" "gitee.com/gowebframe3/webframe.git/goconfig/base/basedto" ) // 多例使用区 type CreditFacade struct { basedto.BaseEntity CreditSchemeDao *creditdao.CreditSchemeDao CreditTransactionsDao *creditdao.CreditTransactionsDao } func NewCreditFacade() *CreditFacade { return &CreditFacade{} } func (self *CreditFacade) Init() { self.CreditSchemeDao = creditdao.FindBeanCreditSchemeDao() self.CreditTransactionsDao = creditdao.FindBeanCreditTransactionsDao() } func (self *CreditFacade) AutoInit() bool { return true } func (self *CreditFacade) InitAllTransNoWhenEmptty() *basedto.IchubResult { var dao = self.CreditTransactionsDao.NewDao() dao.DbOrFieldsCurrent().DbEq("trans_no", "").DbIsNull("trans_no") var ret = dao.QueryModelMax() if ret.ExistRecord() { for _, item := range ret.Data { if item.TransNo == "" { item.TransNo, _ = dbsequence.FindBeanDbFacade().FindSeqTransNo() dao.UpdateMap2Result(item.Id, map[string]any{ "trans_no": item.TransNo, }) } } } return &ret.IchubResult }package daofacade import ( "gitea.super-study.com/ys-study/gotrain/beapi/common" "gitea.super-study.com/ys-study/gotrain/beapi/db/dbdao/userdao" "gitea.super-study.com/ys-study/gotrain/beapi/db/dbentity/userentity" "gitea.super-study.com/ys-study/gotrain/beapi/db/dbparam" "gitee.com/gowebframe3/webframe.git/goconfig/base/basedto" "gitee.com/gowebframe3/webframe.git/goweb/pagefacade" "gitee.com/gowebframe3/webframe.git/goweb/pagemodel" "github.com/gogf/gf/v2/util/gconv" ) type UserDaoFacade struct { basedto.BaseEntity common.BaseService UsersDao *userdao.UsersDao PagePtrFacade *pagefacade.PagePtrFacade[*userentity.Users, *dbparam.StudentParam] UserExtendDao *userdao.UserExtendDao } func NewUserDaoFacade() *UserDaoFacade { return &UserDaoFacade{} } func (self *UserDaoFacade) Init() { self.UsersDao = userdao.FindBeanUsersDao() self.UserExtendDao = userdao.FindBeanUserExtendDao() } func (self *UserDaoFacade) AutoInit() bool { return true } func (self *UserDaoFacade) UpdateStudent(param *dbparam.StudentParam) *basedto.IchubResult { //var p,err=self.PagePtrFacade.From(param) var ret = self.CheckPhone(param) if ret.ExistRecord() { return basedto.ResultFailMsg("手机号已存在") } return self.UsersDao.UpdateMap2Result(param.Id, param.Stru2MapFields("id")) } func (self *UserDaoFacade) CheckPhone(param *dbparam.StudentParam) *pagemodel.PageResult[*userentity.Users] { var dao = self.UsersDao.NewDao() dao.DbNe("id", param.Id) dao.DbEq("phone", param.Phone) var ret = dao.QueryModelOne() return ret } func (self *UserDaoFacade) FindByIdAtCache(userIds any) *pagemodel.IchubResult[*userentity.Users] { var userId = gconv.Int64(userIds) if userId == 0 { return pagemodel.ResultOk(&userentity.Users{}, false) } var rets, exist = self.GetCache(userentity.FindBeanUsers().ObjectKeyOfPkey(userId)) if exist { return rets.(*pagemodel.IchubResult[*userentity.Users]) } var dao = userdao.FindBeanUsersDao() var ret = dao.FindByIdResult(userId) if ret.ExistRecord() { self.SetCache(ret.Data.ObjectKey(), ret) } return ret } func (self *UserDaoFacade) CacheFillName(nameField *string, userId int64) *pagemodel.IchubResult[*userentity.Users] { if userId == 0 { return pagemodel.ResultOk(&userentity.Users{}, false) } var ret = self.FindByIdAtCache(userId) if ret.ExistRecord() { *nameField = ret.Data.Name } return ret }DBQueryPool
package daopool import ( "gitea.super-study.com/ys-study/gotrain/beapi/facade/daoquery" "gitee.com/gowebframe3/webframe.git/goconfig/base/basedto" "gitee.com/gowebframe3/webframe.git/gomini/gopool" ) type DaoQueryPool struct { basedto.BaseEntitySingle *gopool.GeneralObjectPool[*daoquery.DaoQuery] } func NewDaoQueryPool() *DaoQueryPool { return &DaoQueryPool{ GeneralObjectPool: gopool.NewGeneralObjectPool[*daoquery.DaoQuery](), } }package daopool import ( "gitee.com/gowebframe3/webframe.git/goconfig/base/baseiface" "gitee.com/gowebframe3/webframe.git/goconfig/basedi" "github.com/sirupsen/logrus" ) /* @Title 文件名称: dao_query_pool_init.go @Desp 描述: 依赖自动注入 @Company 公司: www.learn.com @Author 作者: raymond@163.com 时间: 2026-05-17 00:32:37 @Update 作者: raymond@163.com 时间: 2026-05-17 00:32:37 */ var singleNameDaoQueryPool = "*daopool.DaoQueryPool-c397125c-7d8b-42a7-a44f-23773919d71c" // init register load func init() { registerBeanDaoQueryPool() } // register DaoQueryPool func registerBeanDaoQueryPool() { err := basedi.RegisterLoadBean(singleNameDaoQueryPool, LoadDaoQueryPool) if err != nil{ logrus.Error("register bean error!", err) } } // FindBeanDaoQueryPool func FindBeanDaoQueryPool() *DaoQueryPool { if bean, ok := basedi.FindBeanOk(singleNameDaoQueryPool); ok { return bean.(*DaoQueryPool) } logrus.Error("not find bean!") return nil } func LoadDaoQueryPool() baseiface.ISingleton { var inst = NewDaoQueryPool() InjectDaoQueryPool(inst) return inst } func InjectDaoQueryPool(s *DaoQueryPool) { // s.Init() }func (suite *TestViewUiSuite) Test007_borrowDaoQuery() { var pool = daopool.FindBeanDaoQueryPool() var daoQuery, err = pool.BorrowObject(context.Background()) if err != nil { golog.Error("borrow object error!", err) return } daoQuery.WordsDao.DbEq("id", 101552) var ret = daoQuery.WordsDao.QueryModel() golog.Info(ret) pool.ReturnObject(context.Background(), daoQuery) daoQuery, err = pool.BorrowObject(context.Background()) daoQuery.WordsDao.DbEq("id", 7125) ret = daoQuery.WordsDao.QueryModel() golog.Info(ret, err) }![]()
