UiSimpleQR 是 uipageframe 的三泛型版本,位于 uiframe 包中(注意包名是 uiframe 而非 uipageframe)。以下是深度解析:
---
UiSimpleQR 框架核心解析
UiSimpleQR 是 uipageframe 框架的进阶版,核心差异在于它采用了三泛型参数,支持查询参数 → 实体 → DTO 的完整转换链路,专门用于复杂业务场景下的联表查询和 DTO 映射。
---
一、与 UiSimpleR 的对比
特性 UiSimpleR[E, P] UiSimpleQR[Q, E, R]
泛型参数 2个:实体 E + 参数 P 3个:查询 Q + 实体 E + 返回 DTO R
适用场景 单表 CRUD 联表查询、DTO 映射、复杂报表
查询对象 self.Param self.Query
返回类型 PageResult[E] PageResult[R](DTO 类型)
核心方法 QueryModel() QueryModelR()
---
二、三泛型参数定义
type UiSimpleQR[Q any, E any, R any] struct {
basedto.BaseEntity
Query Q // 查询参数(前端传入)
Param E // 实体参数(中间转换)
PageDbRequest *pagedb.PageDbWebRequest
// 继承自 UiSimpleR 的能力
*UiSimpleR[E, E] // 内嵌双泛型版本
// DTO 转换器
FillRows interface{}
FillRow interface{}
}
泛型 含义 示例
Q Query 查询条件参数 TrainFlowQuery
E Entity 数据库实体 *StudentTrainFlow
R Result/DTO 返回给前端的 DTO *TransDto
---
三、完整使用案例:训练流转查询
1. 定义三层数据结构
// ========== ① Query 层:前端查询条件 ==========
package trainflow
type TrainFlowQuery struct {
basedto.BaseEntitySingle
StudentId int64 `json:"studentId,string"` // 学生ID
TrialTrainType apitype.TrainType `json:"trialTrainType"` // 试训类型
User *model.User `json:"-"` // 当前用户(内部使用)
}
// ========== ② Entity 层:数据库实体 ==========
package uitraintype
type StudentTrainFlow struct {
// ... 数据库表字段
StudentId int64 `json:"studentId,string"`
TrialTrainType int32 `json:"trialTrainType"`
FlowType int32 `json:"flowType"`
TransStatus int32 `json:"transStatus"`
TrialCoachFrom int64 `json:"trialCoachFrom,string"`
TrialCoachAccept int64 `json:"trialCoachAccept,string"`
// ...
}
// ========== ③ DTO 层:返回给前端的数据 ==========
package trainflow
type TransDto struct {
basedto.BaseEntity
TrialStatus int `json:"trialStatus"` // 试训状态
CommitAt time.Time `json:"commitAt"` // 提交时间
ReviewAt time.Time `json:"reviewAt"` // 审核时间
TrialCoachFrom int64 `json:"fromId,string"` // 转出教练ID
TrialCoachAccept int64 `json:"toId,string"` // 转收教练ID
TrialTrainType int `json:"trialTrainType"` // 试训类型
FromName string `json:"fromName"` // 转出教练姓名
ToName string `json:"toName"` // 转收教练姓名
CommitOpinion string `json:"commitOpinion"` // 提交意见
ReviewOpinion string `json:"reviewOpinion"` // 审核意见
}
2. 创建 UiSimpleQR Request
package traintype
import (
"gitea.super-study.com/ys-study/gotrain/beapi/db/apiconst"
"gitea.super-study.com/ys-study/gotrain/beapi/db/dbentity/stuentity/uitraintype"
"gitea.super-study.com/ys-study/gotrain/beapi/dbopc/opcdto/trainflow"
"gitea.super-study.com/ys-study/gotrain/beapi/gotool/dbframe/uiframe"
"gitee.com/gowebframe3/webframe.git/goconfig/base/basedto"
"gitee.com/gowebframe3/webframe.git/goweb/pagemodel"
)
// 三泛型:[Query, Entity, DTO]
type UiStudentTrainFlow struct {
basedto.BaseEntity `json:"-"`
uiframe.UiSimpleQR[trainflow.TrainFlowQuery, *uitraintype.StudentTrainFlow, *trainflow.TransDto]
}
func NewUiStudentTrainFlow() *UiStudentTrainFlow {
var req = &UiStudentTrainFlow{}
req.InitDao() // 初始化 DAO
req.initQuery() // 配置查询条件
return req
}
3. 配置动态查询条件
func (self *UiStudentTrainFlow) initQuery() *UiStudentTrainFlow {
self.SetBeforeQuery(func() {
// 构建通用参数(分页、排序等)
self.BuildGeneralParams(self.PageDbRequest)
// 条件1:按学生ID筛选
if self.Query.StudentId > 0 {
self.DbEq("student_id", self.Query.StudentId)
}
// 条件2:按试训类型筛选
if self.Query.TrialTrainType > 0 {
self.DbEq("trial_train_type", self.Query.TrialTrainType)
}
// 条件3:固定条件 - 只查转训类型
self.DbEq("flow_type", apiconst.TransTransFlowType)
})
return self
}
4. 核心查询方法
// 对外暴露的业务方法
func (self *UiStudentTrainFlow) UiQueryTransFlow() *pagemodel.PageResult[*trainflow.TransDto] {
var uiret = self.List()
// DTO 后处理:填充教练姓名(联表查询的替代方案)
if uiret.ExistRecord() {
for _, item := range uiret.Data {
userservice.FindBeanUserService().CacheFillName(&item.FromName, item.TrialCoachFrom)
userservice.FindBeanUserService().CacheFillName(&item.ToName, item.TrialCoachAccept)
}
}
return uiret
}
// 底层 List 方法
func (self *UiStudentTrainFlow) List() *pagemodel.PageResult[*trainflow.TransDto] {
self.BuildRequest().BeforeQuery()() // 构建请求 + 执行前置条件
if self.OrderBys == "" {
self.OrderByDesc("created_at") // 默认排序
}
return self.QueryModelR() // ⭐ 关键:调用三泛型查询
}
---
四、核心方法:QueryModelR()
func (self *UiSimpleR[E, R]) QueryModelR(pagesize ...int) *pagemodel.PageResult[R] {
// ① 先执行基础泛型查询(返回 Entity 类型)
var ret = self.QueryModel(pagesize...)
// ② 将 Entity 分页结果转换为 DTO 分页结果
return self.PageTo(ret)
}
执行流程:
前端请求 JSON
│
▼
┌─────────────────┐
│ BindQuery() │ ← 解析到 Query 泛型 (TrainFlowQuery)
│ (self.Query) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ BuildRequest() │ ← 构建分页请求
└────────┬────────┘
│
▼
┌─────────────────┐
│ BeforeQuery()() │ ← 动态条件(DbEq/DbLike 等)
└────────┬────────┘
│
▼
┌─────────────────┐
│ QueryModel() │ ← 查询数据库,返回 PageResult[Entity]
│ │ ← StudentTrainFlow 列表
└────────┬────────┘
│
▼
┌─────────────────┐
│ PageTo() │ ← Entity → DTO 转换
│ │ ← 字段映射 + 数据填充
└────────┬────────┘
│
▼
PageResult[DTO] ← 返回前端 (TransDto)
---
五、另一个案例:取消流水查询
type UiStudentTrainFlowcancel struct {
basedto.BaseEntity `json:"-"`
// 三泛型:相同 Query,相同 Entity,不同 DTO (CancelDto)
uiframe.UiSimpleQR[trainflow.TrainFlowQuery, *uitraintype.StudentTrainFlow, *trainflow.CancelDto]
}
func (self *UiStudentTrainFlowcancel) initQuery() *UiStudentTrainFlowcancel {
self.SetBeforeQuery(func() {
self.BuildGeneralParams(self.PageDbRequest)
if self.Query.StudentId > 0 {
self.DbEq("student_id", self.Query.StudentId)
}
if self.Query.TrialTrainType > 0 {
self.DbEq("trial_train_type", self.Query.TrialTrainType)
}
// 区别:查询多种 flow_type
self.DbIn("flow_type", apiconst.TransCancelFlowType, apiconst.TransCancelPlanType)
})
return self
}
func (self *UiStudentTrainFlowcancel) UiQueryCancelFlow() *pagemodel.PageResult[*trainflow.CancelDto] {
return self.List() // 直接返回,无需额外处理
}
func (self *UiStudentTrainFlowcancel) List() *pagemodel.PageResult[*trainflow.CancelDto] {
self.BuildRequest().BeforeQuery()()
if self.OrderBys == "" {
self.OrderByDesc("created_at")
}
return self.QueryModelR()
}
关键洞察: 相同的 Query 和 Entity,通过更换第三个泛型 R(TransDto vs CancelDto),实现了同一数据源的不同视图返回。
---
六、与 UiSimpleR 的架构关系
┌─────────────────────────────────────────────────────────────┐
│ uiframe.UiSimpleQR[Q, E, R] │
│ (三泛型 - 复杂场景) │
│ ↓ 内嵌 │
├─────────────────────────────────────────────────────────────┤
│ uipageframe.UiSimpleR[E, P] │
│ (双泛型 - 基础场景) │
│ ↓ 调用 │
├─────────────────────────────────────────────────────────────┤
│ goweb/generaldb/generaldao │
│ GeneralDao[E] (泛型 DAO) │
└─────────────────────────────────────────────────────────────┘
---
七、适用场景总结
场景 推荐框架 原因
单表 CRUD,返回实体 UiSimpleR[E, P] 简单直接
联表查询,需要 DTO 映射 UiSimpleQR[Q, E, R] 三泛型分离
同一实体,多种返回视图 UiSimpleQR 更换 R 泛型即可
需要后处理填充关联数据 UiSimpleQR DTO 层独立处理
复杂报表,字段来自多表 UiSimpleQR Query 条件 + DTO 结果解耦
---
八、核心设计哲学
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Query │────→│ Entity │────→│ DTO │
│ (前端条件) │ │ (数据库表) │ │ (返回前端) │
│ │ │ │ │ │
│ StudentId │ │ student_id │ │ FromName │
│ TrainType │ │ flow_type │ │ ToName │
│ │ │ trans_status│ │ CommitAt │
└─────────────┘ └─────────────┘ └─────────────┘
Q E R
↑ ↑ ↑
动态条件构建 数据库查询映射 Entity→DTO转换
(SetBeforeQuery) (QueryModel) (PageTo)
UiSimpleQR 的精髓在于通过泛型在编译期固化三层数据结构,避免了运行时的类型断言和反射,同时保持了代码的整洁和可维护性。