feat: 升级 samber/do v1 → v2

This commit is contained in:
2026-05-15 09:04:12 +08:00
parent 7741f7584d
commit 9ad5d25836
47 changed files with 1456 additions and 684 deletions
+8 -1
View File
@@ -1,6 +1,6 @@
package contracts
type Service interface {
type App interface {
LoadGoPlugin(file string) error
RegControllers(items map[string]Controller) error
RegListens(items map[string]ResListener) error
@@ -8,10 +8,17 @@ type Service interface {
RegJobs(items map[string]Job) error
RegCrons(items map[string]Cron) error
RegMqtts(items map[string]Mqtt) error
RegFlowDecisions(items map[string]FlowDecision) error
RegGoBridges(items map[string]GoBridge) error
RegResVirtualTables(items map[string]ResVirtualTable) error
GetResListener(code string) (ResListener, bool)
GetAppController(code string) (Controller, bool)
GetAppGRpc(code string) (GRpc, bool)
GetAppJob(code string) (Job, bool)
GetAppCron(code string) (Cron, bool)
GetAppMqtt(code string) (Mqtt, bool)
GetAppFlowDecision(code string) (FlowDecision, bool)
GetAppGoBridge(code string) (GoBridge, bool)
GetAppResVirtualTable(code string) (ResVirtualTable, bool)
}
+37
View File
@@ -0,0 +1,37 @@
package base
// DataType 定义参数数据类型
type DataType string
const (
DataTypeString DataType = "string" // 字符串类型
DataTypeInteger DataType = "integer" // 整数类型
DataTypeFloat DataType = "float" // 浮点数类型
DataTypeBoolean DataType = "boolean" // 布尔类型
DataTypeJSON DataType = "json" // JSON类型
)
// SchemaParam 工作流参数
type SchemaParam struct {
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
DataType DataType `db:"data_type" json:"data_type"`
Required bool `db:"required" json:"required"`
Comment string `db:"comment" json:"comment"`
Fields SchemaParams `db:"fields" json:"fields,omitempty"`
}
type SchemaParams []SchemaParam
// Condflow 工作流
type Condflow struct {
Uuid string `db:"uuid" json:"uuid"`
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
Comment string `db:"comment" json:"comment"`
Params SchemaParams `db:"params" json:"params"`
CreatedAt string `db:"created_at" json:"created_at"`
UpdatedAt string `db:"updated_at" json:"updated_at"`
}
type GetCondflow func(code string) (Condflow, bool)
+23
View File
@@ -0,0 +1,23 @@
package base
import "git.fsdpf.net/go/reflux/fieldx"
// CondflowCase 工作流条件分支
type CondflowCase struct {
Uuid string `db:"uuid" json:"uuid"`
CondflowUuid string `db:"condflow_uuid" json:"condflow_uuid"`
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
Priority int `db:"priority" json:"priority"`
Action string `db:"action" json:"action"`
ActionParams fieldx.Schema `db:"action_params" json:"action_params"` // 动作参数值, 对应 params_schema
ActionConfig fieldx.Schema `db:"action_config" json:"action_config"` // 动作配置值, 对应 config_schema
NextCaseOnSuccess bool `db:"next_case_on_success" json:"next_case_on_success"`
NextFlowUuid string `db:"next_flow_uuid" json:"next_flow_uuid"`
NextFlowParams fieldx.Schema `db:"next_flow_params" json:"next_flow_params"` // 流程间参数映射
Comment string `db:"comment" json:"comment"`
CreatedAt string `db:"created_at" json:"created_at"`
UpdatedAt string `db:"updated_at" json:"updated_at"`
}
type GetCondflowCases func(uuid string) []CondflowCase
+25
View File
@@ -0,0 +1,25 @@
package base
// CondflowDecisionAction 执行器动作定义
type CondflowDecisionAction struct {
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
ConfigSchema SchemaParams `db:"config_schema" json:"config_schema"` // 配置项Schema定义
ParamsSchema SchemaParams `db:"params_schema" json:"params_schema"` // 参数Schema定义
ResourceUuid string `db:"resource_uuid" json:"resource_uuid"`
Comment string `db:"comment" json:"comment"`
}
// CondflowDecision 执行器注册表
type CondflowDecision struct {
Uuid string `db:"uuid" json:"uuid"`
ResourceUuid string `db:"resource_uuid" json:"resource_uuid"`
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
Comment string `db:"comment" json:"comment"`
Actions []CondflowDecisionAction `db:"actions" json:"actions"`
CreatedAt string `db:"created_at" json:"created_at"`
UpdatedAt string `db:"updated_at" json:"updated_at"`
}
type GetCondflowDecision func(code string) (CondflowDecision, bool)
+1
View File
@@ -51,6 +51,7 @@ type DataList struct {
ThemeConfig map[string]any `db:"themeConfig"` // 列表布局配置
Roles []string `db:"roles"` // 列表权限
IsAuthDB int `db:"is_auth_db"` // 启用权限过滤
SelectionType string `db:"selectionType"` // 行选择类型
Platform string `db:"platform"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
+1
View File
@@ -25,6 +25,7 @@ type DataListField struct {
FilterByJsSetting map[string]any `db:"filterByJsSetting"` // 字段筛选组件设置参数
FilterOperator string `db:"filterOperator"` // 字段筛选符号
EditableRoles []string `db:"editableRoles"` // 字段编辑权限
Middlewares [][2]any `db:"middlewares"` // 字段数据转换
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
}
+19 -16
View File
@@ -1,22 +1,25 @@
package base
type GridLayout struct {
Uuid string `db:"uuid"`
ResourceUuid string `db:"resource_uuid"` // 所属资源UUID
Code string `db:"code"` // 布局标识
Name string `db:"name"` // 布局名称
Type int `db:"type"` // 布局类型 [0=普通布局, 1=辅助布局]
PrimaryKey string `db:"primaryKey"` // 主键
MarginX int `db:"marginX"` // 栅格 x 间距
MarginY int `db:"marginY"` // 栅格 y 间距
Cols int `db:"cols"` // 网格列基数
RowHeight float32 `db:"rowHeight"` // 栅格行高
Css map[string]any `db:"css"` // 布局CSS
Props []any `db:"props"` // 布局PROPS
Platform string `db:"platform"`
Roles []string `db:"roles"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
Uuid string `db:"uuid"`
ResourceUuid string `db:"resource_uuid"` // 所属资源UUID
Code string `db:"code"` // 布局标识
Name string `db:"name"` // 布局名称
Type int `db:"type"` // 布局类型 [0=普通布局, 1=辅助布局]
PrimaryKey string `db:"primaryKey"` // 主键
MarginX int `db:"marginX"` // 栅格 x 间距
MarginY int `db:"marginY"` // 栅格 y 间距
Cols int `db:"cols"` // 网格列基数
RowHeight float32 `db:"rowHeight"` // 栅格行高
Css map[string]any `db:"css"` // 布局CSS
Props []any `db:"props"` // 布局PROPS
Groups []map[string]any `db:"groups"` // 布局分组
Theme string `db:"theme"` // 布局样式
ThemeConfig map[string]any `db:"themeConfig"` // 布局样式配置
Platform string `db:"platform"`
Roles []string `db:"roles"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
}
type GetGridLayout func(code string) (GridLayout, bool)
+1
View File
@@ -3,6 +3,7 @@ package base
type GridLayoutField struct {
Uuid string `db:"uuid"`
GridLayoutUuid string `db:"grid_layout_uuid"` // 所属布局 UUID
Group string `db:"group"`
Type string `db:"type"`
X int `db:"x"`
Y int `db:"y"`
+3 -1
View File
@@ -14,7 +14,9 @@ type GridLayoutForm struct {
MarginY int `db:"marginY"` // 栅格 y 间距
Cols int `db:"cols"` // 网格列基数
FormProps []map[string]string `db:"formProps"` // 表单PROPS
Groups []map[string]any `db:"groups"` // 表单分组
Groups []map[string]any `db:"groups"` // 布局分组
Theme string `db:"theme"` // 布局样式
ThemeConfig map[string]any `db:"themeConfig"` // 布局样式配置
ListenChangeFields []string `db:"listenChangeFields"` // 监听字段
ListenChangeFieldsFunc string `db:"listenChangeFieldsFunc"` // 监听字段回调方法
Platform string `db:"platform"`
+15 -9
View File
@@ -6,7 +6,9 @@ import (
"strings"
"git.fsdpf.net/go/contracts"
"github.com/samber/do"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
)
type PkgVersion struct {
@@ -39,7 +41,7 @@ func (this PkgVersion) GetSourceMapPath(prefix ...string) string {
)
}
func (this PkgVersion) GetFile(app *do.Injector) (file string, err error) {
func (this PkgVersion) GetFile(app do.Injector, u req.User) (file string, err error) {
if this.Id == 0 {
return file, contracts.ErrPkgUnknown
}
@@ -50,23 +52,27 @@ func (this PkgVersion) GetFile(app *do.Injector) (file string, err error) {
return file, contracts.ErrResNotFound
}
_, err = res.GetDBTable().Where("id", this.Id).Value(&file, "file")
ok, err = res.GetDBTable(u).Where(db.C("id").Eq(this.Id)).Select("file").ScanVal(&file)
if !ok {
return file, contracts.ErrResNotFound
}
return file, err
}
func (this PkgVersion) GetSourceMap(app *do.Injector) (file string, err error) {
func (this PkgVersion) GetSourceMap(app do.Injector) (file string, err error) {
if this.Id == 0 {
return file, contracts.ErrPkgUnknown
}
res, ok := do.MustInvoke[contracts.GetResource](app)("PkgVersion")
// res, ok := do.MustInvoke[contracts.GetResource](app)("PkgVersion")
if !ok {
return file, contracts.ErrResNotFound
}
// if !ok {
// return file, contracts.ErrResNotFound
// }
_, err = res.GetDBTable().Where("id", this.Id).Value(&file, "sourcemap")
// _, err = res.GetDBTable().Where("id", this.Id).Value(&file, "sourcemap")
return file, err
}
+7 -8
View File
@@ -5,7 +5,6 @@ import (
"strings"
"unicode"
"git.fsdpf.net/go/contracts/res_type"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/req"
)
@@ -67,13 +66,13 @@ func (this *QueryField) ToSql() db.Expression {
if this.Alias() != "" {
if this.IsExpr() {
if this.GetCode() == "" {
return db.Raw("null as `" + this.Alias() + "`")
return db.V(nil).As(this.Alias())
}
return db.Raw("(" + this.GetCode() + ") as `" + this.Alias() + "`")
return db.L(this.GetCode()).As(this.Alias())
}
return db.Raw("`" + this.GetCodeResource() + "`.`" + this.GetCode() + "` as `" + this.Alias() + "`")
return db.T(this.GetCodeResource()).Col(this.GetCode()).As(this.Alias())
}
return db.Raw("`" + this.GetCodeResource() + "`.`" + this.GetCode() + "`")
return db.T(this.GetCodeResource()).Col(this.GetCode())
}
func (this *QueryField) ToStructField(tags ...string) reflect.StructField {
@@ -104,11 +103,11 @@ func (this *QueryField) ToStructField(tags ...string) reflect.StructField {
case req.ReqString:
typ = reflect.TypeOf(string(""))
case req.ReqNumber:
typ = reflect.TypeOf(res_type.ResFieldByNumber(0))
typ = reflect.TypeOf(float64(0))
case req.ReqInteger:
typ = reflect.TypeOf(res_type.ResFieldByInteger(0))
typ = reflect.TypeOf(int64(0))
case req.ReqFloat:
typ = reflect.TypeOf(res_type.ResFieldByFloat(0))
typ = reflect.TypeOf(float64(0))
case req.ReqBool:
typ = reflect.TypeOf(true)
case req.ReqArray:
+6 -6
View File
@@ -1,11 +1,11 @@
package base
type ResApiParam struct {
Code string `db:"code"`
Name string `db:"name"`
Category string `db:"category"`
DataType string `db:"type"`
IsRequired bool `db:"isRequired"`
Code string `db:"code" json:"code"`
Name string `db:"name" json:"name"`
Category string `db:"category" json:"category"`
DataType string `db:"type" json:"type"`
IsRequired bool `db:"isRequired" json:"isRequired"`
}
type ResApi struct {
@@ -16,7 +16,7 @@ type ResApi struct {
PrimaryKey string `db:"primaryKey"`
ResourceUuid string `db:"resource_uuid"`
Method string `db:"method"`
Category string `db:"action"`
Action string `db:"action"`
Params []ResApiParam `db:"params"`
Roles []string `db:"roles"`
IsAuthDB int `db:"is_auth_db"` // 启用权限过滤
+4 -2
View File
@@ -1,6 +1,8 @@
package base
import "git.fsdpf.net/go/condition"
import (
"git.fsdpf.net/go/condition"
)
type ResCondition struct {
Id int `db:"id"`
@@ -53,7 +55,7 @@ func NewConditionByRes(items []ResCondition, opts ...condition.Option) (root *co
conditions[item.Id] = condition.NewExpr(
item.ColumnResource, item.Column,
condition.Token(item.Value, condition.TokenType(item.ValueType)),
condition.Operator(condition.ConditionOperator(item.Operator)),
condition.Operator(condition.ConditionOperator(condition.GetDBOperator(item.Operator))),
condition.FieldSqlFn(item.ColumnSqlFunc, item.ColumnSqlFuncParam),
condition.IgnoreEmptyParma(item.IgnoreEmptyParma),
)
+43 -11
View File
@@ -1,16 +1,48 @@
package base
import (
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/reflux/valuex"
)
// role_uuid => value
type ResRoleValue map[string]string
type ResConfigure struct {
Uuid string `db:"uuid"`
ResourceUuid string `db:"resource_uuid"`
Key string `db:"key"`
Value string `db:"value"`
Label string `db:"label"`
Type string `db:"type"`
IsPrivate bool `db:"isPrivate"`
Desc string `db:"desc"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
Uuid string `db:"uuid"`
ResourceUuid string `db:"resource_uuid"`
Key string `db:"key"`
Value string `db:"value"`
Label string `db:"label"`
Type string `db:"type"`
IsPrivate bool `db:"isPrivate"`
Desc string `db:"desc"`
RoleValues []ResRoleValue `db:"role_values"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
}
type GetResConfigures func(uuid string) []ResConfigure
// GetValueByRoles 根据角色优先级获取配置值
//
// - roles: 角色UUIDs
//
// 返回: valuex.Accessor 对象和是否找到角色特定值的标志
func (rc *ResConfigure) GetValueByRoles(roles ...string) (valuex.Accessor, bool) {
// 如果没有角色值配置或没有传入角色,返回默认值
if len(rc.RoleValues) == 0 || len(roles) == 0 {
return reflux.New(rc.Value), false
}
for _, rv := range rc.RoleValues {
for _, role := range roles {
if value, exists := rv[role]; exists {
return reflux.New(value), true
}
}
}
// 没有找到匹配的角色值,返回默认值
return reflux.New(rc.Value), false
}
type GetResConfigure func(key string, roles ...string) (valuex.Accessor, bool)
+62 -54
View File
@@ -7,24 +7,24 @@ import (
"strconv"
"strings"
"git.fsdpf.net/go/contracts/res_type"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/schema"
"git.fsdpf.net/go/req"
"github.com/google/uuid"
"github.com/spf13/cast"
)
// 资源字段
// ResField 资源字段
type ResField struct {
Uuid string `db:"uuid"`
Name string `db:"name"`
Code string `db:"code"`
CodeResource string `db:"codeResource"`
DataType req.ResDataType `db:"table_type"`
Length string `db:"length"`
Comment string `db:"comment"`
Default string `db:"default"`
Uuid string `db:"uuid" json:"uuid"`
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
CodeResource string `db:"codeResource" json:"codeResource"`
DataType req.ResDataType `db:"data_type" json:"data_type"`
Length string `db:"length" json:"length"`
Comment string `db:"comment" json:"comment"`
Default string `db:"default" json:"default"`
Virtual bool `db:"virtual" json:"virtual"`
VirtualExpr string `db:"virtual_expr" json:"virtual_expr"`
}
func (this ResField) ToStructField(tags ...string) reflect.StructField {
@@ -42,7 +42,7 @@ func (this ResField) ToStructField(tags ...string) reflect.StructField {
req.ResTimestamp, req.ResDate, req.ResDatetime:
typ = reflect.TypeOf(string(""))
case req.ResInteger, req.ResSmallInteger:
typ = reflect.TypeOf(res_type.ResFieldByInteger(0))
typ = reflect.TypeOf(int64(0))
case req.ResDecimal:
typ = reflect.TypeOf(float64(0))
case req.ResBoolean:
@@ -62,6 +62,10 @@ func (this ResField) ToStructField(tags ...string) reflect.StructField {
}
}
func (this ResField) IsVirtual() bool {
return this.Virtual
}
func (this ResField) GetCode() string {
return this.Code
}
@@ -101,30 +105,16 @@ func (this ResField) GetQueryDataType() req.RouteParamType {
}
func (this ResField) ToValue(v any) any {
switch this.DataType {
case req.ResString, req.ResText, req.ResEnum,
req.ResTimestamp, req.ResDate, req.ResDatetime:
return strings.Trim(cast.ToString(v), " ")
case req.ResInteger, req.ResSmallInteger:
return cast.ToInt(v)
case req.ResDecimal:
return strings.Trim(cast.ToString(v), " ")
case req.ResBoolean:
if v, _ := strconv.ParseBool(fmt.Sprintf("%v", v)); v {
return 1
}
return 0
case req.ResJson:
if this.DataType == req.ResJson {
if v == nil {
if this.Default != "" && this.Default[0:1] == "[" {
return db.Raw("'[]'")
return db.V("[]")
} else if this.Default != "" && this.Default[0:1] == "{" {
return db.Raw("'{}'")
return db.V("{}")
} else if this.Default == "" {
return db.Raw("'{}'")
return db.V("{}")
}
return this.Default
return this.GetRawDefault()
}
if str, ok := v.(string); ok {
@@ -137,52 +127,61 @@ func (this ResField) ToValue(v any) any {
panic(fmt.Sprintf("%s, 类型转换错误, %s", this.Code, err))
}
}
return strings.Trim(cast.ToString(v), " ")
return v
}
func (this ResField) GetRawDefault(driver string) db.Expression {
func (this ResField) GetRawDefault() db.Expression {
if this.DataType == req.ResJson {
if this.Default != "" && this.Default[0:1] == "[" {
return db.Raw("'[]'")
return db.V("[]")
} else if this.Default != "" && this.Default[0:1] == "{" {
return db.Raw("'{}'")
return db.V("{}")
} else if this.Default == "" {
return db.Raw("'{}'")
return db.V("{}")
}
} else if this.DataType == req.ResBoolean {
if v, _ := strconv.ParseBool(this.Default); v {
return db.Raw("'1'")
return db.V(true)
}
return db.Raw("'0'")
return db.V(false)
}
if len(this.Default) > 4 && strings.ToLower(this.Default[0:4]) == "sql:" {
sql := strings.ToLower(this.Default[4:])
if sql == "uuid()" {
if driver == "sqlite" {
return db.Raw("'" + uuid.NewString() + "'")
}
return db.Raw("uuid()")
}
return db.Raw(sql)
return db.L(this.Default[4:])
}
if this.Default == "" {
if this.GetDataType() == req.ResDate || this.GetDataType() == req.ResDatetime {
return db.Raw("NULL")
return db.V(nil)
}
return db.Raw("''")
return db.V("")
}
if strings.ToUpper(this.Default) == "CURRENT_TIMESTAMP" {
return db.Raw(this.Default)
return db.L(this.Default)
}
return db.Raw("'" + this.Default + "'")
return db.V(this.Default)
}
func (this ResField) ToBlueprint(table *schema.Blueprint) (temp *schema.ColumnDefinition) {
switch this.Code {
case "id":
return table.BigIncrements("id").AutoIncrement().Comment("ID")
case "enabled":
return table.Boolean("enabled").Default("1").Comment("是否有效")
case "created_user":
return table.Char("created_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("创建者")
case "owned_user":
return table.Char("owned_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("拥有者")
case "created_at":
return table.Timestamp("created_at").UseCurrent().Comment("创建时间")
case "updated_at":
return table.Timestamp("updated_at").UseCurrent().Default(db.L("ON UPDATE CURRENT_TIMESTAMP")).Comment("更新时间")
case "deleted_at":
return table.DateTime("deleted_at").Nullable().Comment("删除时间")
}
isNull := false
comment := this.Name
def := any(this.Default)
@@ -207,15 +206,15 @@ func (this ResField) ToBlueprint(table *schema.Blueprint) (temp *schema.ColumnDe
case "integer":
// integer 默认长度 11
temp = table.Integer(this.Code)
case "date", "dateTime":
case "date", "dateTime", "timestamp":
if this.DataType == "date" {
temp = table.Date(this.Code)
} else {
temp = table.DateTime(this.Code)
}
if strings.ToUpper(this.Default) == "CURRENT_TIMESTAMP" {
def = db.Raw(this.Default)
if strings.ToUpper(this.Default) == "SQL:CURRENT_TIMESTAMP" {
def = db.L("CURRENT_TIMESTAMP")
} else if def == "" {
isNull = true
}
@@ -242,6 +241,15 @@ func (this ResField) ToBlueprint(table *schema.Blueprint) (temp *schema.ColumnDe
case "text":
temp = table.Text(this.Code)
isNull = true
case "vector":
temp = table.Vector(this.Code, cast.ToInt(this.Length))
isNull = false
default:
panic(fmt.Sprintf("Unknown type: %s", this.DataType))
}
if this.IsVirtual() {
temp.VirtualAs(this.VirtualExpr)
}
if isNull {
+30
View File
@@ -0,0 +1,30 @@
package base
import (
"testing"
"git.fsdpf.net/go/db/schema"
"git.fsdpf.net/go/req"
_ "git.fsdpf.net/go/db/schema/dialect/mysql"
_ "git.fsdpf.net/go/db/schema/dialect/sqlite3"
)
func TestResFieldToBlueprint(t *testing.T) {
t.Run("id", func(t *testing.T) {
table := schema.NewBlueprint("users")
id := ResField{
Uuid: "00000000-0000-0000-0000-000000000000",
Name: "ID",
Code: "id",
CodeResource: "User",
DataType: req.ResInteger,
Length: "20",
Comment: "",
Default: "sql:AUTO_INCREMENT",
}
id.ToBlueprint(table)
})
}
-26
View File
@@ -1,26 +0,0 @@
package base
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
type ResFields []ResField
func (c *ResFields) Scan(value any) error {
if value == nil {
return nil
}
if err := json.Unmarshal(value.([]byte), c); err != nil {
return fmt.Errorf("ResFields json.Unmarshal Error, %s", err)
}
return nil
}
func (c ResFields) Value() (driver.Value, error) {
b, err := json.Marshal(c)
return string(b), err
}
+222 -249
View File
@@ -5,21 +5,26 @@ import (
"fmt"
"log"
"reflect"
"sync"
"unicode"
"github.com/samber/do"
"github.com/samber/do/v2"
"github.com/samber/lo"
"git.fsdpf.net/go/condition"
"git.fsdpf.net/go/contracts"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/engine"
"git.fsdpf.net/go/db/exec"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/db/schema"
"git.fsdpf.net/go/req"
)
// 资源变更事件
// ResChangeEventTopic 资源变更事件
const ResChangeEventTopic = "res-change-event-topic"
// 资源变更数据
// ResChangeRecordTopic 资源变更数据
const ResChangeRecordTopic = "res-change-record-topic"
type ResChangeEventTopicPayload struct {
@@ -37,61 +42,72 @@ type ResChangeRecordTopicPayload struct {
New []map[string]any // 新数据
}
// 资源
// ResVirtualTableSetup 由 framework-v2 在 sqlite_vtable build tag 下注册到容器。
// resource.DB() 通过 do.Invoke 取出后调用,完成 vtab 模块注册和 CREATE VIRTUAL TABLE。
// res 提供 Code / Table / Fields 信息,vt 是 ResVirtualTable 的具体实现。
type ResVirtualTableSetup func(res Resource) error
// Resource 资源
type Resource struct {
container *do.Injector
container do.Injector
initOnce *sync.Once
Uuid string `db:"uuid"`
PUuid string `db:"pUuid"`
Code string `db:"code"`
Name string `db:"name"`
IsResSystem bool `db:"isSystem"`
IsResVirtual bool `db:"isVirtual"`
Table string `db:"table"`
Primarykey string `db:"primaryKey"`
IsHistoryRecord bool `db:"isHistoryRecord"`
HistoryCacheMax int `db:"historyCacheMax"`
Fields ResFields `db:"fields"`
Roles map[string]any `db:"roles"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
Uuid string `db:"uuid"`
PUuid string `db:"pUuid"`
Code string `db:"code"`
Name string `db:"name"`
Conn string `db:"conn"`
IsResVirtual bool `db:"isVirtual"`
Table string `db:"table"`
Primarykey string `db:"primaryKey"`
HistoryRoles []string `db:"historyRoles"`
Fields []ResField `db:"fields"`
Roles map[string]any `db:"roles"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
}
func (this *Resource) InitContainer(container *do.Injector) {
func (this *Resource) InitContainer(container do.Injector) {
this.container = container
this.initOnce = &sync.Once{}
}
// 资源UUID
// GetUuid 资源UUID
func (this Resource) GetUuid() string {
return this.Uuid
}
// 资源CODE
// GetCode 资源CODE
func (this Resource) GetCode() string {
return this.Code
}
// 资源名
// GetName 资源名
func (this Resource) GetName() string {
return this.Name
}
// 主键
// GetConn 数据库连接
func (this Resource) GetConn() string {
return this.Conn
}
// GetPrimarykey 主键
func (this Resource) GetPrimarykey() string {
return this.Primarykey
}
// 是否虚拟资源
// IsVirtual 是否虚拟资源
func (this Resource) IsVirtual() bool {
return this.IsResVirtual
}
// 是否系统资源
// IsSystem 是否系统资源
func (this Resource) IsSystem() bool {
return this.IsResSystem
return this.Conn == "service-support"
}
// 资源字段
// GetFields 资源字段
func (this Resource) GetFields() (result []req.ResField) {
for _, item := range this.Fields {
result = append(result, item)
@@ -99,137 +115,145 @@ func (this Resource) GetFields() (result []req.ResField) {
return result
}
// 资源字段
// GetField 资源字段
func (this Resource) GetField(code string) (req.ResField, bool) {
return lo.Find(this.GetFields(), func(v req.ResField) bool {
return v.GetCode() == code
})
}
// 判断资源字段
// HasField 判断资源字段
func (this Resource) HasField(code string) bool {
return lo.SomeBy(this.GetFields(), func(v req.ResField) bool {
return v.GetCode() == code
})
}
// 开启事物
func (this Resource) BeginTransaction() (*db.Transaction, error) {
return this.GetDBConn().BeginTransaction()
// BeginTransaction 开启事物
func (this Resource) BeginTransaction() (*db.TxDatabase, error) {
return this.DB().Begin()
}
// 获取资源链接
func (this Resource) GetDBConn() *db.Connection {
db := do.MustInvoke[db.DB](this.container)
// DB 获取资源链接
func (this Resource) DB() *db.Database {
dbEngine := do.MustInvoke[engine.Engine](this.container)
conn := dbEngine.Connection(this.Conn)
if this.IsSystem() {
return db.Connection("service-support")
}
return db.Connection("default")
}
// 获取资源对应的数据库连接
func (this Resource) GetDBBuilder() *db.Builder {
return this.GetDBConn().Query()
}
// 获取资源对应的数据表
func (this Resource) GetTable() db.Expression {
if this.IsVirtual() {
return db.Raw("(" + this.Table + ")")
}
return db.Raw(this.Table)
}
func (this Resource) GetDBDriver() string {
return this.GetDBConn().GetConfig().Driver
}
func (this Resource) GetAuthDBTable(u req.User, params ...any) *db.Builder {
builder := this.GetDBTable(append(params, u)...)
// 数据权限过滤
builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
if t == db.TYPE_SELECT || t == db.TYPE_UPDATE || t == db.TYPE_DELETE {
this.WithRolesCondition(b, t, u)
}
})
return builder
}
// GetDBTable("Test", contracts.User)
func (this Resource) GetDBTable(params ...any) *db.Builder {
builder := this.GetDBBuilder()
var user req.User
alias := this.Code
for _, param := range params {
switch v := param.(type) {
case *db.Transaction:
builder.Tx = v
case string:
alias = v
case req.User:
user = v
}
}
// 格式化数据库存储数据
builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
if t == db.TYPE_UPDATE {
// 格式化保存数据
this.formatSaveValue(data[0])
}
if t == db.TYPE_INSERT {
// 移除 table alias
b.Table(string(this.GetTable()))
for i := 0; i < len(data); i++ {
// 格式化保存数据
this.formatSaveValue(data[i])
// 填充保存数据
this.fillSaveValue(data[i], user, db.TYPE_INSERT)
if isLocalDB(conn.Dialect()) && !this.IsResVirtual && this.Table != "" && this.initOnce != nil {
this.initOnce.Do(func() {
if err := this.autoCreateTable(conn); err != nil {
panic(err)
}
}
})
// 资源事件
this.onResEvent(builder)
// 用户事件
if this.IsHistoryRecord {
this.onUserEvent(builder, user)
})
}
// 虚拟资源暂时不考虑鉴权
if !this.IsVirtual() {
// 返回鉴权后的 DB Builder
// return
// vtable 资源:查询走 engine 中的 :memory: 连接("__"+Conn),
// 原文件连接仅用于 _vtab_cache 持久化,两者互不阻塞。
if conn.Dialect() == "vtable" && this.Table != "" && this.initOnce != nil {
memConn := dbEngine.Connection("__" + this.Conn)
this.initOnce.Do(func() {
if err := this.autoSetupVtab(memConn); err != nil {
panic(err)
}
})
return memConn
}
return builder.Table(string(this.GetTable()), alias)
return conn
}
func (this Resource) WithRolesCondition(b *db.Builder, t string, u req.User) error {
// autoSetupVtab 通过容器获取 ResVirtualTableSetup(由 framework-v2 在 sqlite_vtable tag 下注册),
// 再从 App 取出当前资源对应的 ResVirtualTable 实现,完成 vtab 注册和 CREATE VIRTUAL TABLE。
func (this Resource) autoSetupVtab(conn *db.Database) error {
_, err := conn.Exec(fmt.Sprintf(`CREATE VIRTUAL TABLE IF NOT EXISTS %s USING %s_mod()`, this.Table, this.Table))
return err
}
// autoCreateTable 自动创建数据表
func (this Resource) autoCreateTable(conn *db.Database) error {
sb := schema.New(conn)
return sb.Create(this.Table, func(bp *schema.Blueprint) {
bp.Comment = this.Name
for _, field := range this.Fields {
field.ToBlueprint(bp)
}
})
}
// GetTable 获取资源对应的数据表
func (this Resource) GetTable() exp.LiteralExpression {
if this.IsResVirtual {
return db.L("(" + this.Table + ")")
}
return db.V(db.T(this.Table))
}
// GetDBTable 获取资源对应的数据库连接
func (this Resource) GetDBTable(u req.User, opts ...req.ResOption) (sd *db.SelectDataset) {
if u == nil {
panic("GetDBTable: user cannot be nil")
}
op := &req.ResOptions{}
for _, cb := range opts {
cb(op)
}
alias := this.GetCode()
if op.Alias != "" {
alias = op.Alias
}
sd = this.DB().From(this.GetTable().As(alias))
if op.Tx != nil {
sd = op.Tx.From(this.GetTable().As(alias))
}
sd.WithHook(&ResourceHooks{res: this, u: u, tx: op.Tx})
return sd
}
func (this Resource) GetRolesCondition(u req.User) (*db.SelectDataset, db.Expression) {
isFullRight := false
isFullNot := false
if u == nil {
u = GetAnonymous()
}
// 如果是系统用户, 则不做校验
if u.Uuid() == "ffffffff-ffff-ffff-ffff-ffffffffffff" {
return nil, nil
}
// 这里不能放在 u.Uuid() == "ffffffff-ffff-ffff-ffff-ffffffffffff" 前面
// 会导致初始化数据时循环依赖
GetResRoles, err := do.Invoke[GetResRoles](this.container)
if err != nil {
return nil, nil
}
items := GetResRoles(this.GetUuid(), u.Roles()...)
if len(items) == 0 {
return nil, nil
}
GetResource := do.MustInvoke[contracts.GetResource](this.container)
NewOrm := do.MustInvoke[contracts.NewOrm](this.container)
NewOrmModel := do.MustInvoke[contracts.NewOrmModel](this.container)
NewOrmJoin := do.MustInvoke[contracts.NewOrmJoin](this.container)
GetResRelationResource := do.MustInvoke[GetResRelationResource](this.container)
GetResRelations := do.MustInvoke[GetResRelations](this.container)
GetResource := do.MustInvoke[contracts.GetResource](this.container)
GetOrmConditions := do.MustInvoke[contracts.GetOrmConditions](this.container)
items := do.MustInvoke[GetResRoles](this.container)(this.GetUuid(), u.Roles()...)
subTables := lo.Reduce(items, func(carry string, item ResRole, _ int) string {
db := this.GetDBBuilder().Table(string(this.GetTable()), this.GetCode()).Select(db.Raw("distinct `" + this.GetCode() + "`.*"))
sub := lo.Reduce(items, func(carry *db.SelectDataset, item ResRole, _ int) *db.SelectDataset {
sd := this.DB().From(this.GetTable().As(this.GetCode())).Select(db.T(this.GetCode()).All())
joins := lo.Filter(GetResRelations(item.Uuid), func(item ResRelation, _ int) bool {
return item.Type == "inner" || item.Type == "left" || item.Type == "right"
@@ -249,106 +273,52 @@ func (this Resource) WithRolesCondition(b *db.Builder, t string, u req.User) err
// 关联扩展条件
join.SetCondition(GetOrmConditions(joins[i].Uuid, condition.Describe("关联扩展条件")))
join.Inject(db, NewOrmModel(rResource, rResource.GetCode(), rResource.GetName()))
join.Inject(sd, NewOrmModel(rResource, rResource.GetCode(), rResource.GetName()))
}
conditions := GetOrmConditions(item.Uuid, condition.Describe("关联扩展条件"))
if len(joins) == 0 && conditions.IsEmpty() {
// 无权限, 直接跳过这个 unoin 语句
if carry != "" {
if carry != nil {
return carry
}
// 第一个无权限除外, 避免所有用户所属角色都是无权限
db.WhereRaw("false")
sd = sd.Where(db.V(false))
isFullNot = true
} else if len(joins) == 0 && conditions.IsNotEmpty() && conditions.IsAlwaysRight() /* 1=1 的这种条件*/ {
// 只要有1个满权限, 直接返回单条语句
isFullRight = true
return db.ToSql()
return sd
} else if conditions.IsNotEmpty() {
oOrm := NewOrm(this, nil)
oOrm.SetGlobalParams(req.NewGlobalParam("{}", u))
oOrm.SetGlobalParams(req.NewGlobalParams("{}", u))
db.Where(conditions.ToSql(oOrm.GetModel()))
sd = sd.Where(conditions.ToSql(oOrm.GetModel()))
// 如果前面是无权限的sql查看, 这直接返回本次查询
if isFullNot {
isFullNot = false
return db.ToSql()
return sd
}
}
if carry != "" {
carry += " UNION "
if carry != nil {
return carry.Union(sd)
}
return fmt.Sprintf("%s(%s)", carry, db.ToSql())
}, "")
return sd
}, nil)
if isFullRight {
return nil
return nil, nil
}
if isFullNot {
b.WhereRaw("false")
} else if subTables != "" {
if t == db.TYPE_SELECT {
b.FromSub(subTables, b.TableAlias)
} else {
b.WhereRaw(fmt.Sprintf(
"`%s`.`id` in (SELECT `temp`.`id` FROM (%s) as `temp`)",
lo.Ternary(b.TableAlias != "", b.TableAlias, this.GetCode()),
subTables,
))
}
return nil, db.V(false)
}
return nil
}
// 格式化保存数据
func (this Resource) formatSaveValue(data map[string]any) {
//
for k, v := range data {
if k == "id" || k == "created_user" || k == "created_at" || k == "deleted_at" || k == "updated_at" {
delete(data, k)
} else if val, ok := v.(db.Expression); ok {
data[k] = val
} else if field, ok := this.GetField(k); ok {
data[k] = field.ToValue(v)
}
}
}
// 填充保存数据
func (this Resource) fillSaveValue(data map[string]any, u req.User, t string) {
for _, field := range this.GetFields() {
fCode := field.GetCode()
if fCode == "id" || fCode == "created_user" || fCode == "created_at" || fCode == "deleted_at" || fCode == "updated_at" || fCode == "owned_user" {
continue
}
// 只有新增默认字段
if _, ok := data[fCode]; !ok {
data[fCode] = field.GetRawDefault(this.GetDBDriver())
}
}
// 拥有者
if _, ok := data["owned_user"]; !ok {
data["owned_user"] = u.Uuid()
}
// 创建者
data["created_user"] = u.Uuid()
if this.GetDBDriver() == "sqlite" {
// 更新时间
// sqlite 不能自动更新时间, "DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
data["updated_at"] = db.Raw("CURRENT_TIMESTAMP")
} else {
}
return sub, nil
}
func (this Resource) GetStruct(extends ...reflect.StructField) any {
@@ -379,67 +349,66 @@ func (this Resource) GetSliceStruct(extends ...reflect.StructField) any {
return reflect.New(st).Interface()
}
// 资源事件
func (this Resource) onResEvent(builder *db.Builder) {
builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) {
if err != nil || t == db.TYPE_SELECT {
return
} else if num, err := result.RowsAffected(); num == 0 || err != nil {
return
}
// onResEvent 资源事件
func (this Resource) onResEvent(t string, result sql.Result) {
if num, err := result.RowsAffected(); num == 0 || err != nil {
return
}
// 有些地方可能没有注册 contracts.Queue
if qu, err := do.Invoke[contracts.Queue](this.container); err == nil {
// 全局触发器
// 1. 清除系统缓存
if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeEventTopic, ResChangeEventTopicPayload{
if err := qu.Publish(ResChangeEventTopic, ResChangeEventTopicPayload{
Type: t,
Res: this,
Result: result,
}); err != nil {
log.Println("Queue Publish Err:", ResChangeEventTopic, err)
}
})
}
}
// 用户事件
func (this Resource) onUserEvent(builder *db.Builder, user req.User) {
old := []map[string]any{}
// onUserEvent 用户事件
func (this Resource) onUserEvent(builder exec.QueryExecutor, user req.User) {
// old := []map[string]any{}
builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
if t != db.TYPE_UPDATE && t != db.TYPE_DELETE {
return
}
// 查询保存之前的数据
if _, err := b.Get(&old); err != nil {
panic(err)
}
})
// builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
// if t != db.TYPE_UPDATE && t != db.TYPE_DELETE {
// return
// }
// // 查询保存之前的数据
// if _, err := b.Get(&old); err != nil {
// panic(err)
// }
// })
builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) {
if err != nil || t == db.TYPE_SELECT {
return
} else if num, err := result.RowsAffected(); num == 0 || err != nil {
return
}
// builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) {
// if err != nil || t == db.TYPE_SELECT {
// return
// } else if num, err := result.RowsAffected(); num == 0 || err != nil {
// return
// }
if user == nil {
user = GetAnonymous()
}
// if user == nil {
// user = GetAnonymous()
// }
// 触发消息队列
if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeRecordTopic, ResChangeRecordTopicPayload{
Type: t,
User: user,
Res: this,
Old: old,
New: data,
Result: result,
}); err != nil {
log.Println("Queue Publish Err:", ResChangeRecordTopic, err)
}
})
// // 触发消息队列
// if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeRecordTopic, ResChangeRecordTopicPayload{
// Type: t,
// User: user,
// Res: this,
// Old: old,
// New: data,
// Result: result,
// }); err != nil {
// log.Println("Queue Publish Err:", ResChangeRecordTopic, err)
// }
// })
}
func NewVirtualResource(pRes req.Resource, code, name, sql string, fields []ResField) req.Resource {
func NewVirtualResource(pRes req.Resource, code, name string, sd *db.SelectDataset, fields []ResField) req.Resource {
fieldsCopy := make([]ResField, len(fields))
copy(fieldsCopy, fields)
@@ -448,17 +417,21 @@ func NewVirtualResource(pRes req.Resource, code, name, sql string, fields []ResF
fieldsCopy[i].CodeResource = code
}
sql, _, err := sd.WithDialect(pRes.DB().Dialect()).ToSQL()
if err != nil {
panic(err)
}
return &Resource{
Uuid: code,
PUuid: pRes.GetUuid(),
Code: code,
Name: name,
IsResVirtual: true,
IsResSystem: pRes.IsSystem(),
Table: sql,
IsHistoryRecord: false,
HistoryCacheMax: 0,
Fields: fieldsCopy,
Roles: nil,
Uuid: code,
PUuid: pRes.GetUuid(),
Code: code,
Name: name,
IsResVirtual: true,
Conn: pRes.GetConn(),
Table: sql,
Fields: fieldsCopy,
Roles: nil,
}
}
+426
View File
@@ -0,0 +1,426 @@
package base
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"reflect"
"strconv"
"strings"
"sync"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/db/schema"
"git.fsdpf.net/go/req"
"github.com/samber/lo"
)
var changeLogOnce sync.Map // key: Conn,每个数据库连接只建一次 _change_log 表
// isLocalDB 判断是否为本地文件型数据库(LastInsertId 返回最后一条而非第一条)
func isLocalDB(dialect string) bool {
return dialect == "sqlite3" || dialect == "duckdb"
}
type ResourceHooks struct {
res Resource
u req.User
tx *db.TxDatabase
snapshots []map[string]any // UPDATE/DELETE 前预查的行数据
}
func (rh *ResourceHooks) isTracked() bool {
if len(rh.res.HistoryRoles) == 0 || rh.res.IsVirtual() {
return false
}
if lo.Contains(rh.res.HistoryRoles, "00000000-0000-0000-0000-000000000000") {
return true
}
return lo.Some(rh.res.HistoryRoles, rh.u.Roles())
}
// autoCreateChangeLog 在当前连接上建 _change_log 表,每个连接只执行一次
func (rh *ResourceHooks) autoCreateChangeLog() error {
conn := rh.res.DB()
v, _ := changeLogOnce.LoadOrStore(rh.res.Conn, &sync.Once{})
var err error
v.(*sync.Once).Do(func() {
sb := schema.New(conn)
err = sb.Create("_change_log", func(bp *schema.Blueprint) {
bp.Comment = "资源数据变更记录"
bp.BigIncrements("id").AutoIncrement().Comment("ID")
bp.Boolean("enabled").Default("1").Comment("是否有效")
bp.Char("created_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("操作用户")
bp.Char("owned_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("拥有者")
bp.Timestamp("created_at").UseCurrent().Comment("操作时间")
bp.Timestamp("updated_at").UseCurrent().Default(db.L("ON UPDATE CURRENT_TIMESTAMP")).Comment("更新时间")
bp.DateTime("deleted_at").Nullable().Comment("删除时间")
bp.Char("resource_uuid", 36).Default("").Comment("资源UUID")
bp.String("category", 10).Default("").Comment("操作类型 INSERT/UPDATE/DELETE")
bp.Char("trace_id", 36).Default("").Comment("请求追踪ID")
bp.Integer("row_id").Default("0").Comment("受影响行ID")
bp.Json("snapshot").Nullable().Comment("操作前数据快照")
})
})
return err
}
// captureSnapshot 在写操作执行前,查出受影响的行存入 snapshots
func (rh *ResourceHooks) captureSnapshot(where exp.ExpressionList) {
sd := rh.res.DB().From(rh.res.GetTable())
if where != nil && len(where.Expressions()) > 0 {
sd = sd.Where(where.Expressions()...)
}
rows, err := sd.Executor().GetRecords()
if err != nil {
log.Printf("_change_log captureSnapshot err: %v", err)
return
}
rh.snapshots = rows
}
// writeChangeLogs 将变更写入 _change_log
func (rh *ResourceHooks) writeChangeLogs(category string, result sql.Result) {
if err := rh.autoCreateChangeLog(); err != nil {
log.Printf("_change_log init err: %v", err)
return
}
traceId := rh.u.Runtime().TraceId()
opUser := rh.u.Uuid()
var rows []any
switch category {
case "INSERT":
lastId, _ := result.LastInsertId()
count, _ := result.RowsAffected()
for i := int64(0); i < count; i++ {
rowId := lastId + i // MySQL: lastId 是第一条
if isLocalDB(rh.res.DB().Dialect()) {
rowId = lastId - count + 1 + i // SQLite: lastId 是最后一条
}
rows = append(rows, map[string]any{
"enabled": true,
"created_user": opUser,
"owned_user": opUser,
"resource_uuid": rh.res.Uuid,
"category": category,
"trace_id": traceId,
"row_id": rowId,
"snapshot": nil,
})
}
case "UPDATE", "DELETE":
for _, snap := range rh.snapshots {
rowId := int64(0)
if id, ok := snap["id"]; ok {
rowId = toChangeLogInt64(id)
}
snapJSON, _ := json.Marshal(snap)
rows = append(rows, map[string]any{
"enabled": true,
"created_user": opUser,
"owned_user": opUser,
"resource_uuid": rh.res.Uuid,
"category": category,
"trace_id": traceId,
"row_id": rowId,
"snapshot": string(snapJSON),
})
}
}
if len(rows) == 0 {
return
}
inserter := rh.res.DB().Insert("_change_log")
if rh.tx != nil {
inserter = rh.tx.Insert("_change_log")
}
if _, err := inserter.Rows(rows...).Executor().Exec(); err != nil {
log.Printf("_change_log write err: %v", err)
}
}
func toChangeLogInt64(v any) int64 {
switch val := v.(type) {
case int64:
return val
case int:
return int64(val)
case int32:
return int64(val)
case float64:
return int64(val)
case string:
n, _ := strconv.ParseInt(val, 10, 64)
return n
}
return 0
}
func (rh *ResourceHooks) Before(dataset interface{}) error {
switch d := dataset.(type) {
case *db.SelectDataset:
return rh.beforeSelectDataset(d)
case *db.InsertDataset:
return rh.beforeInsertDataset(d)
case *db.UpdateDataset:
return rh.beforeUpdateDataset(d)
case *db.DeleteDataset:
return rh.beforeDeleteDataset(d)
}
return nil
}
func (rh *ResourceHooks) After(dataset interface{}, result interface{}) error {
if rh.res.IsVirtual() {
// 返回鉴权后的 DB Builder
return nil
}
// 用户事件
// if rh.res.IsHistoryRecord {
// rh.res.onUserEvent(builder, user)
// }
switch dataset.(type) {
case *db.SelectDataset:
case *db.InsertDataset:
r := result.(sql.Result)
rh.res.onResEvent("INSERT", r)
if rh.isTracked() {
rh.writeChangeLogs("INSERT", r)
}
case *db.UpdateDataset:
r := result.(sql.Result)
rh.res.onResEvent("UPDATE", r)
if rh.isTracked() {
rh.writeChangeLogs("UPDATE", r)
}
case *db.DeleteDataset:
r := result.(sql.Result)
rh.res.onResEvent("DELETE", r)
if rh.isTracked() {
rh.writeChangeLogs("DELETE", r)
}
}
return nil
}
func (rh *ResourceHooks) beforeInsertDataset(id *db.InsertDataset) error {
switch true {
case id.GetClauses().HasRows():
return rh.beforeInsertRows(id)
case id.GetClauses().HasVals():
return rh.beforeInsertColsVals(id)
case id.GetClauses().HasFrom():
return rh.beforeInsertFromQuery(id)
}
return nil
}
func (rh *ResourceHooks) beforeInsertRows(id *db.InsertDataset) error {
rows := id.GetClauses().Rows()
for i := range rows {
rowValue := reflect.ValueOf(rows[i])
if rowValue.Kind() == reflect.Ptr {
rowValue = rowValue.Elem()
}
if rowValue.Kind() == reflect.Struct {
if row, err := exp.NewRecordFromStruct(rowValue.Interface(), true, false); err != nil {
return err
} else {
rows[i] = row
}
}
}
for i := range rows {
row := rows[i].(map[string]any)
// 格式化保存数据
if err := rh.normalizeSaveValue(row); err != nil {
return err
}
// 填充默认数据
if err := rh.applyDefaultValue(row, true, false); err != nil {
return err
}
}
return nil
}
func (rh *ResourceHooks) beforeInsertColsVals(id *db.InsertDataset) error {
cols := id.GetClauses().Cols()
vals := id.GetClauses().Vals()
colsName := []string{}
for _, col := range cols.Columns() {
colsName = append(colsName, col.(exp.IdentifierExpression).GetCol().(string))
}
colsVal := []any{}
for k, v := range lo.OmitByKeys(rh.getFieldsDefaultValue(true, false), colsName) {
cols = cols.Append(db.C(k))
colsVal = append(colsVal, v)
}
for i := 0; i < len(vals); i++ {
for j := 0; j < len(vals[i]); j++ {
if filed, ok := rh.res.GetField(colsName[j]); ok {
vals[i][j] = filed.ToValue(vals[i][j])
}
}
vals[i] = append(vals[i], colsVal...)
}
*id = *id.Cols(cols)
return nil
}
func (rh *ResourceHooks) beforeInsertFromQuery(id *db.InsertDataset) error {
return nil
}
// 规范化保存数据
// 1. 移除系统缺省字段, 如果是 insert 需要调用 applyDefaultValue
// 2. 移除系统中没有的字段
func (rh *ResourceHooks) normalizeSaveValue(row db.Record) error {
for k, v := range row {
if k == "id" || k == "created_user" || k == "created_at" || k == "deleted_at" || k == "updated_at" {
delete(row, k)
} else if val, ok := v.(db.Expression); ok {
row[k] = val
} else if field, ok := rh.res.GetField(k); ok {
row[k] = field.ToValue(v)
} else {
delete(row, k)
}
}
return nil
}
// 填充默认数据
func (rh *ResourceHooks) applyDefaultValue(row db.Record, forInsert, forUpdate bool) error {
// 填充默认数据
for k, v := range rh.getFieldsDefaultValue(forInsert, forUpdate) {
if _, ok := row[k]; !ok {
row[k] = v
}
}
return nil
}
func (rh *ResourceHooks) getFieldsDefaultValue(forInsert, forUpdate bool) map[string]db.Expression {
defs := map[string]db.Expression{}
if forInsert {
for _, item := range rh.res.Fields {
if item.GetCode() == "updated_at" || item.GetCode() == "created_user" || item.GetCode() == "owned_user" {
continue
}
if len(item.Default) > 4 && strings.ToLower(item.Default[0:4]) == "sql:" {
defs[item.GetCode()] = item.GetRawDefault()
} else if item.DataType == req.ResJson {
defs[item.GetCode()] = item.GetRawDefault()
}
}
if _, ok := defs["owned_user"]; !ok {
defs["owned_user"] = db.V(rh.u.Uuid())
}
defs["created_user"] = db.V(rh.u.Uuid())
}
if forUpdate && isLocalDB(rh.res.DB().Dialect()) {
defs["updated_at"] = db.L("CURRENT_TIMESTAMP")
}
return defs
}
func (rh *ResourceHooks) beforeSelectDataset(sd *db.SelectDataset) error {
sub, ex := rh.res.GetRolesCondition(rh.u)
if ex != nil {
*sd = *sd.Where(ex)
} else if sub != nil {
table := sd.GetClauses().From().Columns()[0]
if alias, ok := table.(exp.AliasedExpression); ok {
*sd = *sd.From(db.V(sub.Expression()).As(alias.GetAs()))
} else {
*sd = *sd.From(db.V(sub.Expression()).As(table))
}
}
return nil
}
func (rh *ResourceHooks) beforeUpdateDataset(ud *db.UpdateDataset) error {
// 虚拟资源暂时不考虑鉴权
if rh.res.IsVirtual() {
return nil
}
sub, ex := rh.res.GetRolesCondition(rh.u)
if ex != nil {
*ud = *ud.Where(ex)
} else if sub != nil {
table := ud.GetClauses().Table()
if alias, ok := table.(exp.AliasedExpression); ok {
*ud = *ud.Where(alias.GetAs().Col("id").In(sub.Select(db.T(rh.res.GetCode()).Col("id"))))
} else {
*ud = *ud.Where(db.L("?.id", table).In(sub.Select(db.T(rh.res.GetCode()).Col("id"))))
}
}
// 记录变更前快照(role 条件已应用,WHERE 与实际 UPDATE 一致)
if rh.isTracked() {
rh.captureSnapshot(ud.GetClauses().Where())
}
// 格式化保存数据
if ud.GetClauses().HasSetValues() {
udv := ud.GetClauses().SetValues()
switch data := udv.(type) {
case map[string]any:
return rh.normalizeSaveValue(data)
case db.Record:
return rh.normalizeSaveValue(data)
default:
return fmt.Errorf("unsupported type: %T", udv)
}
}
return nil
}
func (rh *ResourceHooks) beforeDeleteDataset(dd *db.DeleteDataset) error {
// 虚拟资源暂时不考虑鉴权
if rh.res.IsVirtual() {
return nil
}
sub, ex := rh.res.GetRolesCondition(rh.u)
if ex != nil {
*dd = *dd.Where(ex)
} else if sub != nil {
table := dd.GetClauses().From().Columns()[0]
if alias, ok := table.(exp.AliasedExpression); ok {
*dd = *dd.Where(alias.GetAs().Col("id").In(sub.Select(db.T(rh.res.GetCode()).Col("id"))))
} else {
*dd = *dd.Where(db.L("?.id", table).In(sub.Select(db.T(rh.res.GetCode()).Col("id"))))
}
}
// 记录变更前快照(role 条件已应用,WHERE 与实际 DELETE 一致)
if rh.isTracked() {
rh.captureSnapshot(dd.GetClauses().Where())
}
return nil
}
+100
View File
@@ -0,0 +1,100 @@
package base_test
import (
"os"
"testing"
"git.fsdpf.net/go/contracts/base"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/engine"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
"github.com/stretchr/testify/suite"
_ "git.fsdpf.net/go/db/schema/dialect/mysql"
_ "git.fsdpf.net/go/db/schema/dialect/sqlite3"
)
type resourceTest struct {
suite.Suite
res *base.Resource
}
func TestResource(t *testing.T) {
suite.Run(t, new(resourceTest))
}
func (t *resourceTest) SetupSuite() {
app := do.New()
do.Provide(app, func(container do.Injector) (engine.Engine, error) {
return engine.Open(map[string]engine.DBConfig{
"default": engine.NewDBConfig("mysql",
engine.WithHost(os.Getenv("MYSQL_HOST")),
engine.WithPort(os.Getenv("MYSQL_PORT")),
engine.WithDatabase(os.Getenv("MYSQL_DB")),
engine.WithUsername(os.Getenv("MYSQL_USER")),
engine.WithPassword(os.Getenv("MYSQL_PASSWD")),
),
}), nil
})
fields := []base.ResField{
{Name: "字段", Code: "fields", DataType: req.ResJson, Default: "{}"},
}
res := &base.Resource{
Uuid: "4bf3a311-cbe1-4236-bdda-c64ab04ae9b1",
Code: "User",
Name: "用户",
Table: "users",
Primarykey: "id",
Fields: fields,
}
res.InitContainer(app)
t.res = res
}
func (t *resourceTest) TestInsertRows() {
type Temp struct {
Name string `db:"name"`
Fields map[string]any `db:"fields"`
}
result := "INSERT INTO `users` AS `User` (`created_user`, `fields`, `name`, `owned_user`) " +
"VALUES ('00000000-0000-0000-0000-000000000000', '{\\\"aa\\\":1}', '张三', '00000000-0000-0000-0000-000000000000')"
sql, _, _ := t.res.GetDBTable(base.GetAnonymous()).Insert().Rows(
db.Record{"name": "张三", "id": 1, "fields": map[string]any{"aa": 1}},
).Executor().ToSQL()
t.Equal(result, sql)
sql, _, _ = t.res.GetDBTable(base.GetAnonymous()).Insert().Rows(
&Temp{Name: "张三", Fields: map[string]any{"aa": 1}},
).Executor().ToSQL()
t.Equal(result, sql)
sql, _, _ = t.res.GetDBTable(base.GetAnonymous()).Insert().Rows(
Temp{Name: "张三", Fields: map[string]any{"aa": 1}},
).Executor().ToSQL()
t.Equal(result, sql)
}
func (t *resourceTest) TestInsertColsVals() {
result := "INSERT INTO `users` AS `User` (`name`, `fields`, `created_user`, `owned_user`) VALUES " +
"('李四', '{\\\"aa\\\":1}', '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000'), " +
"('王五', '{\\\"aa\\\":3}', '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000')"
sql, _, _ := t.res.GetDBTable(base.GetAnonymous()).Insert().Cols("name", "fields").Vals(
db.Vals{"李四", map[string]any{"aa": 1}},
db.Vals{"王五", map[string]any{"aa": 3}},
).Executor().ToSQL()
_ = result
_ = sql
// t.Equal(result, sql)
}
func (*resourceTest) TestInsertColsFromQuery() {}
+1
View File
@@ -15,6 +15,7 @@ type Route struct {
Exact bool `db:"exact"`
Platform string `db:"platform"`
Extra map[string]any `db:"extra"`
Roles []string `db:"roles"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
}
+2 -2
View File
@@ -51,7 +51,7 @@ func (this user) Runtime() req.UserRuntime {
return this.runtime
}
// 获取匿名用户
// GetAnonymous 获取匿名用户
func GetAnonymous(opts ...req.UserRuntimeOption) req.User {
u := user{
id: 0,
@@ -69,7 +69,7 @@ func GetAnonymous(opts ...req.UserRuntimeOption) req.User {
return u
}
// 系统用户
// GetSystemUser 系统用户
func GetSystemUser(opts ...req.UserRuntimeOption) req.User {
u := user{
id: -1,
+23
View File
@@ -0,0 +1,23 @@
package base
import (
"git.fsdpf.net/go/req"
)
type UserAsset struct {
ID int64 `db:"id"`
Name string `db:"name"`
FileName string `db:"filename"`
File string `db:"file"`
Mime string `db:"mime"`
Size int64 `db:"size"`
Ext string `db:"ext"`
Hit int64 `db:"hit"`
IsPrivate bool `db:"isPrivate"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
}
type GetUserAsset func(user req.User, code string) (UserAsset, bool)
type UploadUserAsset func(user req.User, file string, name string) (UserAsset, error)
+32
View File
@@ -0,0 +1,32 @@
package contracts
import (
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
)
type CondflowService interface {
Run(name string, input reflux.R, u req.User) error
}
type FlowDecision interface {
Controller
Res() req.Resource
}
type BaseFlowDecision struct {
Controller
res string
}
func (be BaseFlowDecision) Res() req.Resource {
return do.MustInvoke[MustResource](be.Container())(be.res)
}
func NewBaseFlowDecision(container do.Injector, res string) *BaseFlowDecision {
return &BaseFlowDecision{
Controller: &BaseController{container},
res: res,
}
}
+5 -5
View File
@@ -2,23 +2,23 @@ package contracts
import (
"git.fsdpf.net/go/req"
"github.com/samber/do"
"github.com/samber/do/v2"
)
type Controller interface {
// 初始化
Init() error
// 获取 Container
Container() *do.Injector
Container() do.Injector
// 派遣一个任务
Dispatch(job string, payload any, u req.User) error
}
type BaseController struct {
container *do.Injector
container do.Injector
}
func (this BaseController) Container() *do.Injector {
func (this BaseController) Container() do.Injector {
return this.container
}
@@ -30,6 +30,6 @@ func (this BaseController) Dispatch(job string, payload any, u req.User) error {
return do.MustInvoke[JobService](this.Container()).Dispatch(job, payload, u)
}
func NewBaseController(container *do.Injector) Controller {
func NewBaseController(container do.Injector) Controller {
return &BaseController{container}
}
+67
View File
@@ -0,0 +1,67 @@
package contracts
import (
"io"
"net/http"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
)
type EventStream interface {
Controller
// 获取请求信息
Request() *http.Request
// 请求处理
Execute(req.GlobalParams) error
// 获取用户信息
User() req.User
// 路由信息
Route() req.Route
// 发送数据
Sent(data, event string) error
}
type EsHandleController struct {
Controller
w http.ResponseWriter
flusher http.Flusher
request *http.Request
}
func (this EsHandleController) Request() *http.Request {
return this.request
}
func (this EsHandleController) Route() req.Route {
return this.Request().Context().Value(req.RouteCtx{Name: "Route"}).(req.Route)
}
func (this EsHandleController) User() req.User {
return this.Request().Context().Value(req.RouteCtx{Name: "User"}).(req.User)
}
func (this EsHandleController) Execute(req.GlobalParams) error {
return ErrFuncNotImplemented
}
func (this EsHandleController) Sent(data, event string) error {
if _, err := io.WriteString(this.w, "event: "+event+"\ndata: "+data+"\n\n"); err != nil {
return err
}
this.flusher.Flush()
return nil
}
func NewEventStreamController(container do.Injector, request *http.Request, w http.ResponseWriter) EventStream {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
return &EsHandleController{
Controller: &BaseController{container},
request: request,
w: w,
flusher: w.(http.Flusher),
}
}
+27
View File
@@ -0,0 +1,27 @@
package contracts
import (
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
)
type Executor interface {
Controller
Res() req.Resource
}
type BaseExecutor struct {
Controller
res string
}
func (be BaseExecutor) Res() req.Resource {
return do.MustInvoke[MustResource](be.Container())(be.res)
}
func NewBaseExecutor(container do.Injector, res string) *BaseExecutor {
return &BaseExecutor{
Controller: &BaseController{container},
res: res,
}
}
+41 -26
View File
@@ -1,56 +1,71 @@
module git.fsdpf.net/go/contracts
go 1.21
go 1.24
toolchain go1.24.10
replace (
git.fsdpf.net/go/condition => ../condition-v2
git.fsdpf.net/go/db => ../db-v2
git.fsdpf.net/go/reflux => ../reflux
git.fsdpf.net/go/req => ../req-v2
)
require (
git.fsdpf.net/go/condition v0.0.0-20241126093638-50e1b3630e6b
git.fsdpf.net/go/db v0.0.0-20241125095839-f186c88c5c2a
github.com/go-chi/chi/v5 v5.0.12
github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/jhump/protoreflect v1.15.1
github.com/lestrrat-go/jwx v1.2.25
github.com/samber/do v1.6.0
github.com/samber/lo v1.39.0
github.com/spf13/cast v1.6.0
github.com/spf13/viper v1.15.0
github.com/tidwall/gjson v1.17.1
github.com/tidwall/sjson v1.2.5
google.golang.org/grpc v1.52.0
git.fsdpf.net/go/condition v0.0.0-00010101000000-000000000000
git.fsdpf.net/go/db v0.0.0-00010101000000-000000000000
git.fsdpf.net/go/reflux v0.0.0-00010101000000-000000000000
git.fsdpf.net/go/req v0.0.0-00010101000000-000000000000
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff
github.com/samber/do/v2 v2.0.0
github.com/stretchr/testify v1.11.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/mattn/go-sqlite3 v1.14.42 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/samber/go-type-to-string v1.8.0 // indirect
)
require (
git.fsdpf.net/go/req v0.0.0-20241126093511-f1774e5ca2f0 // indirect
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff // indirect
github.com/bufbuild/protocompile v0.4.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/go-chi/chi/v5 v5.0.12 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang/protobuf v1.5.2
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jhump/protoreflect v1.15.1
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.1 // indirect
github.com/lestrrat-go/jwx v1.2.25
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/samber/lo v1.49.1
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.10.0
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.15.0
github.com/subosito/gotenv v1.4.2 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
google.golang.org/grpc v1.52.0
google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+44 -77
View File
@@ -36,50 +36,12 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.fsdpf.net/go/condition v0.0.0-20240509022115-37018837d1b0 h1:MHjkN+zrSCqb7Qswn9fFrHT4XaoDnGbXz0ssSKnWnQk=
git.fsdpf.net/go/condition v0.0.0-20240509022115-37018837d1b0/go.mod h1:Zj9yrMa/A3WICTc9zn3W55ociuoBompEsVPo6EUXW5Q=
git.fsdpf.net/go/condition v0.0.0-20240509023514-a871ccfa75ac h1:pJQ0sFjPtv/RCy8k0mC/Kpn/j6As5bSqXyG0MWaylhc=
git.fsdpf.net/go/condition v0.0.0-20240509023514-a871ccfa75ac/go.mod h1:Zj9yrMa/A3WICTc9zn3W55ociuoBompEsVPo6EUXW5Q=
git.fsdpf.net/go/condition v0.0.0-20240509024751-9694d9033266 h1:s5On5PKF1ZiIGgq6R2Nk/2GcURuZxDAVK1Sru54Ybn0=
git.fsdpf.net/go/condition v0.0.0-20240509024751-9694d9033266/go.mod h1:Zj9yrMa/A3WICTc9zn3W55ociuoBompEsVPo6EUXW5Q=
git.fsdpf.net/go/condition v0.0.0-20240509030058-b0e337133e44 h1:XHJxWJuofEL7TLMDBsywi48nciaon8bEFOJLMYkOrNk=
git.fsdpf.net/go/condition v0.0.0-20240509030058-b0e337133e44/go.mod h1:xgX0fc3LEgrPk9tihGQqoQND4EWlNgYbLCi70TuCvvM=
git.fsdpf.net/go/condition v0.0.0-20240509034445-afc243ea916e h1:ZfwjqZS5URF4Y0JfQeytSdmXimJ4l74ooWRppza+3Tk=
git.fsdpf.net/go/condition v0.0.0-20240509034445-afc243ea916e/go.mod h1:K5ocyKVxgC5mI1k/crgrEozK0r51qAavWg0hweXo6PU=
git.fsdpf.net/go/condition v0.0.0-20241126084857-bb134581146e h1:GR7OmszSIcutwYfYkL0wGJXKlLzOHCszjN7Oy2jAcRU=
git.fsdpf.net/go/condition v0.0.0-20241126084857-bb134581146e/go.mod h1:cQhj0R7Q1GP4qxOvqRAwpGPSdYCX/c6dN3WSHhjmMvw=
git.fsdpf.net/go/condition v0.0.0-20241126090116-c3471706f4d4 h1:BMO+OujDukHDKD0RLUEu7w1ZZyHvtz8k69pHUiP7yPE=
git.fsdpf.net/go/condition v0.0.0-20241126090116-c3471706f4d4/go.mod h1:uKIEYgBdHFFnNQs6y7cvwsi4b/0Jk1QDneDgemo3Oa0=
git.fsdpf.net/go/condition v0.0.0-20241126093638-50e1b3630e6b h1:0Ez7ny8HNlZTMhYdlsGbSDcc9vkWt/V/OWlPi+sDw/s=
git.fsdpf.net/go/condition v0.0.0-20241126093638-50e1b3630e6b/go.mod h1:5N2xtaawQLquSsF8jJYPvOXhYNplBK+Iib783hTRg8c=
git.fsdpf.net/go/db v0.0.0-20230621051209-5740d112407f h1:H+/H6j+hyFMgSy+tUNmymRy7/PWQRjjp8qY/DrhwrsY=
git.fsdpf.net/go/db v0.0.0-20230621051209-5740d112407f/go.mod h1:397Sdx1cJS0OlHtTX1bVl//9k3Xn0Klnc6jC4MAkb6w=
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b h1:fRgWNOQ9dAYuUZHQ24oi1XqRbJIcJvZGbnQDaAKI7IY=
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b/go.mod h1:397Sdx1cJS0OlHtTX1bVl//9k3Xn0Klnc6jC4MAkb6w=
git.fsdpf.net/go/db v0.0.0-20241125095839-f186c88c5c2a h1:upXLpdDk7Oz7rNoMLx4uDRx50P3tLvwwlslywSkdygk=
git.fsdpf.net/go/db v0.0.0-20241125095839-f186c88c5c2a/go.mod h1:397Sdx1cJS0OlHtTX1bVl//9k3Xn0Klnc6jC4MAkb6w=
git.fsdpf.net/go/req v0.0.0-20240508133526-672fc634ef20 h1:PhjRtac2r0Vn1WBBzhRhz4//bJhHgkf/SHZe3mQYPto=
git.fsdpf.net/go/req v0.0.0-20240508133526-672fc634ef20/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509031332-f00b993f9c7d h1:TKNhQWDKyL50eVC/ptur1YuRkjRnKOlMsE1Y9tFCVbk=
git.fsdpf.net/go/req v0.0.0-20240509031332-f00b993f9c7d/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509031626-6501b5ed0f2b h1:KlVBAuWnFt1lcRTCcBU165MIldJqleO/LahWrd3FvUA=
git.fsdpf.net/go/req v0.0.0-20240509031626-6501b5ed0f2b/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509033523-c8cda2d8db56 h1:7ElgXUzt75brzOr44mItipTQTk++kZNoGOyTWHLxkTU=
git.fsdpf.net/go/req v0.0.0-20240509033523-c8cda2d8db56/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509033936-f8a11598db60 h1:hHrEcUSbtajC2LAo2fvEo7yrSSDeM/s5FAuQluP7yZQ=
git.fsdpf.net/go/req v0.0.0-20240509033936-f8a11598db60/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509034400-4ef8c130a758 h1:nHys8Bwb29b/FvnE100KVH62aD2j5CEPxTQ/RRHo27k=
git.fsdpf.net/go/req v0.0.0-20240509034400-4ef8c130a758/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20241126084545-fd3625802b40 h1:Ik3m4G4fY0kEHGX7qg01L2Q7OXjXAvRRqg8ErwQd2Hs=
git.fsdpf.net/go/req v0.0.0-20241126084545-fd3625802b40/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20241126090024-bccb0382d886 h1:LOKgXrnJT4xmxf9KkvZBOZDQYEhNz3h0TZl13R2GCtk=
git.fsdpf.net/go/req v0.0.0-20241126090024-bccb0382d886/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20241126093511-f1774e5ca2f0 h1:XevOuJLH63dzRiE6Sni/M0xOUw+bmrqH5Rcum+Aayjo=
git.fsdpf.net/go/req v0.0.0-20241126093511-f1774e5ca2f0/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff h1:1hokQGKGIstoI7WDBir2N24OGgnussZ3GmucK4TQRuI=
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff/go.mod h1:NUoyQtFr905YT+pi850mvSf4YX0WghQIcMQkTvize5o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -102,20 +64,20 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -157,6 +119,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -172,8 +135,6 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
@@ -192,10 +153,12 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
@@ -208,12 +171,16 @@ github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0ot
github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo=
github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -223,19 +190,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/samber/do v1.6.0 h1:Jy/N++BXINDB6lAx5wBlbpHlUdl0FKpLWgGEV9YWqaU=
github.com/samber/do v1.6.0/go.mod h1:DWqBvumy8dyb2vEnYZE7D7zaVEB64J45B0NjTlY/M4k=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/samber/do/v2 v2.0.0 h1:tnunwWaoqSfJ9hxVIaJawIo7JXHQlqT9d9YBXlE9Keg=
github.com/samber/do/v2 v2.0.0/go.mod h1:ZSBCE7Xr6nTNIOVo4DBrkl2+ydUbIOzJjjdV8En5XO4=
github.com/samber/go-type-to-string v1.8.0 h1:5z6tDTjtXxkIAoAuHAZYMYR8mkBZjVgeSH7jcSLqc8w=
github.com/samber/go-type-to-string v1.8.0/go.mod h1:jpU77vIDoIxkahknKDoEx9C8bQ1ADnh2sotZ8I4QqBU=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -245,6 +211,8 @@ github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jH
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -252,13 +220,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@@ -285,8 +252,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -297,8 +264,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -354,8 +319,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -375,7 +340,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -412,8 +378,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -422,8 +388,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -575,8 +541,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743 h1:yqElulDvOF26oZ2O+2/aoX7mQ8DY/6+p39neytrycd8=
google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"net/http"
"git.fsdpf.net/go/req"
"github.com/samber/do"
"github.com/samber/do/v2"
)
var defaultWsClientGroup WsClientGroup = "__DEFAULT__"
@@ -95,7 +95,7 @@ func (HttpHandleController) AuthDB() req.ResAuthDB {
return req.ResAuthOn
}
func NewHttpController(container *do.Injector, request *http.Request) HttpController {
func NewHttpController(container do.Injector, request *http.Request) HttpController {
return &HttpHandleController{
Controller: &BaseController{container},
request: request,
+2 -2
View File
@@ -4,11 +4,11 @@ import (
"net/http"
"github.com/lestrrat-go/jwx/jwt"
"github.com/samber/do"
"github.com/samber/do/v2"
)
type JWTAuth interface {
HttpMiddleware(container *do.Injector) func(next http.Handler) http.Handler
HttpMiddleware(container do.Injector) func(next http.Handler) http.Handler
Encode(claims map[string]interface{}) (token string, err error)
Decode(token string) (jwt.Token, error)
}
+7
View File
@@ -0,0 +1,7 @@
package contracts
import "net/http"
type MCPService interface {
ServeHTTP(w http.ResponseWriter, r *http.Request)
}
+2 -2
View File
@@ -2,7 +2,7 @@ package contracts
import (
"git.fsdpf.net/go/req"
"github.com/samber/do"
"github.com/samber/do/v2"
)
type MqttService interface {
@@ -88,7 +88,7 @@ func (this MqttHandle) OnPublish(g req.GlobalParams, topic string, retain bool,
return nil, ErrFuncNotImplemented
}
func NewMqttController(container *do.Injector) Mqtt {
func NewMqttController(container do.Injector) Mqtt {
return &MqttHandle{
Controller: &BaseController{container},
}
+47 -41
View File
@@ -1,10 +1,13 @@
package contracts
import (
"context"
"reflect"
"git.fsdpf.net/go/condition"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/req"
)
@@ -12,39 +15,35 @@ type OrmExecute int
type RelationType string
type OrderByDirection string
const (
OrmExecute_Query OrmExecute = iota
OrmExecute_Show
OrmExecute_Store
OrmExecute_Destroy
OrmQuery OrmExecute = iota
OrmShow
OrmStore
OrmDestroy
)
const (
RelationType_HasOne RelationType = "hasOne"
RelationType_HasMany RelationType = "hasMany"
RelationType_Inner RelationType = "inner"
RelationType_Left RelationType = "left"
RelationType_Right RelationType = "right"
OrmHasOne RelationType = "hasOne"
OrmHasMany RelationType = "hasMany"
OrmInnerJoin RelationType = "inner"
OrmLeftJoin RelationType = "left"
OrmRightJoin RelationType = "right"
)
const (
OrderByDirection_ASC OrderByDirection = db.ORDER_ASC
OrderByDirection_DESC OrderByDirection = db.ORDER_DESC
)
type ModelDecorator func(Model) Model
type Orm interface {
SetGlobalParams(g req.GlobalParams) Orm
SetGlobalParams(g req.UserAccessor) Orm
GetModel(params ...string) Model
SetRelationModel(m Model) Model
SetQueryField(qf req.QueryField) error
SetOrderBy(params []OrderBy) error
SetCondition(cond *condition.Condition) error
SetController(ctr OrmController) error
Execute(OrmExecute) (any, error)
Clone(tx *db.Transaction) Orm
Count() int
Controller() reflect.Value
Execute(OrmExecute) (reflux.R, error)
Clone(tx *db.TxDatabase) Orm
Count() int64
// Exists() bool
}
@@ -54,7 +53,6 @@ type Relation interface {
Type() RelationType
SetType(RelationType)
SetCondition(*condition.Condition) *condition.Condition
SetPrependCondition(*condition.Condition) *condition.Condition
GetCondition() *condition.Condition
GetRelationResource() string
GetRelationField() string
@@ -66,8 +64,9 @@ type Join interface {
GetCode() string
GetResource() req.Resource
// GetDependencies(items []ResRelation, dependencies ...string) []string
Inject(dbBuilder *db.Builder, m Model)
Inject(dbBuilder *db.SelectDataset, m Model)
}
type Model interface {
Relation
condition.TokenValue
@@ -76,28 +75,30 @@ type Model interface {
GetPrimaryKey() string
GetQueryFieldsStruct(extends ...reflect.StructField) any
GetQueryFieldsSliceStruct(fields ...reflect.StructField) any
GetAttribute() map[string]any
GetResult() reflect.Value
GetAttribute() reflux.R
SetRelationAttribute(string, reflux.R) error
GetResult() reflux.R
GetResource() req.Resource
QueryBuilder() *db.Builder
GetTransaction() *db.Transaction
SetQueryField(req.QueryField)
SetRelationResult(code string, vItems reflect.Value)
GetTransaction() *db.TxDatabase
SetQueryField(...req.QueryField)
// SetRelationResult(code string, items []reflux.R)
GetJoinsCode() []string
SetAuthDB(req.ResAuthDB)
SetOrderBy(params ...OrderBy) Model
SetGroupBy(params ...GroupBy) Model
SetJoin(Join) Model
SetLimit(int) Model
SetOffset(int) Model
SetAttribute(data any) Model
SetLimit(uint) Model
SetOffset(uint) Model
// SetAttribute(data any) Model
SetPrimaryKey(string) Model
SetWithRecursive(QueryWithRecursive) Model
DelQueryField(string)
SelectDataset() *db.SelectDataset
SelectDatasetWithUser(u req.User) *db.SelectDataset
Query() error
Store() error
Destroy() error
Count() (int, error)
Count() (int64, error)
// Exists() (bool, error)
}
@@ -106,18 +107,18 @@ type OrmController interface {
}
type OrderBy interface {
ToSql() db.Expression
Inject(dbBuilder *db.Builder, m Model)
ToSql() exp.OrderedExpression
Inject(sd *db.SelectDataset, m Model)
}
type GroupBy interface {
ToSql() db.Expression
Inject(dbBuilder *db.Builder, m Model)
ToSql() exp.ColumnListExpression
Inject(sd *db.SelectDataset, m Model)
}
type QueryWithRecursive interface {
ToQueryBuilder(*db.Builder, Model) (*db.Builder, any)
ToTreeData(any) any
ToQueryBuilder(*db.SelectDataset, Model) (*db.SelectDataset, any)
ToTreeData([]reflux.R) reflux.R
}
// @title 创建关联模型
@@ -130,18 +131,23 @@ type NewOrmRelation func(t RelationType, m Model, rResource, rField, rForeignKey
type NewOrmJoin func(t RelationType, res req.Resource, alias, rResource, rField, rForeignKey string) Join
type NewOrmQueryWithRecursive func(pField, cField string, root any, isWithoutCondition bool, depth int) QueryWithRecursive
type NewOrmQueryWithRecursive func(pField, cField exp.IdentifierExpression, root any, isWithoutCondition bool, depth uint) QueryWithRecursive
type NewOrmOrderBy func(sql string, direction OrderByDirection) OrderBy
type NewOrmOrderBy func(sql db.Expression, direction exp.SortDirection) OrderBy
type NewOrmGroupBy func(sql string) GroupBy
type NewOrmGroupBy func(db.Expression) GroupBy
type NewOrmModel func(res req.Resource, code, name string) Model
type NewOrm func(res req.Resource, tx *db.Transaction) Orm
type NewOrm func(res req.Resource, opts ...OrmOption) Orm
type GetOrmConditions func(categoryUuid string, opts ...condition.Option) *condition.Condition
type GetOrmOrderBy func(categoryUuid string) []OrderBy
type GetOrmGroupBy func(categoryUuid string) []GroupBy
type OrmOption func(Orm)
type WithTx func(tx *db.TxDatabase) OrmOption
type WithContext func(ctx context.Context) OrmOption
type WithUserAccessor func(ua req.UserAccessor) OrmOption
+3 -3
View File
@@ -2,7 +2,7 @@ package contracts
import (
"git.fsdpf.net/go/req"
"github.com/samber/do"
"github.com/samber/do/v2"
"github.com/samber/lo"
)
@@ -21,7 +21,7 @@ type ResListener interface {
}
type BaseListener struct {
Container *do.Injector
Container do.Injector
res req.Resource // 监听资源
code string // code
events []string // 监听动作
@@ -51,6 +51,6 @@ func (BaseListener) Delete(old map[string]any, u req.User) error {
return nil
}
func NewBaseListener(code string, res req.Resource, container *do.Injector) *BaseListener {
func NewBaseListener(code string, res req.Resource, container do.Injector) *BaseListener {
return &BaseListener{code: code, res: res, Container: container}
}
-27
View File
@@ -1,27 +0,0 @@
package res_type
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
// 解析map ----------------------------------
type ResFieldByMap map[string]any
func (c *ResFieldByMap) Scan(value any) error {
if value == nil {
return nil
}
if err := json.Unmarshal(value.([]byte), c); err != nil {
return fmt.Errorf("ResFieldByMap json.Unmarshal Error, %s", err)
}
return nil
}
func (c ResFieldByMap) Value() (driver.Value, error) {
b, err := json.Marshal(c)
return string(b), err
}
-3
View File
@@ -1,3 +0,0 @@
package res_type
type ResFieldByFloat float64
-3
View File
@@ -1,3 +0,0 @@
package res_type
type ResFieldByInteger int64
-38
View File
@@ -1,38 +0,0 @@
package res_type
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
// 切片 ------------------------------------
type ResFieldByAnys []any
func (c *ResFieldByAnys) Scan(value any) error {
if value == nil {
return nil
}
switch v := value.(type) {
case []byte:
if len(v) == 0 {
return nil
}
if rune('[') != rune(v[0]) {
return nil
}
default:
}
if err := json.Unmarshal(value.([]byte), c); err != nil {
return fmt.Errorf("ResFieldByAnys json.Unmarshal Error, %s, %s", value, err)
}
return nil
}
func (c ResFieldByAnys) Value() (driver.Value, error) {
b, err := json.Marshal(c)
return string(b), err
}
-29
View File
@@ -1,29 +0,0 @@
package res_type
import (
"database/sql/driver"
"github.com/spf13/cast"
)
// Number 类型 --------------------------------
type ResFieldByNumber float64
func (this *ResFieldByNumber) Scan(value any) error {
if value == nil {
return nil
}
switch s := value.(type) {
case []byte:
*this = ResFieldByNumber(cast.ToFloat64(string(s)))
default:
*this = ResFieldByNumber(cast.ToFloat64(s))
}
return nil
}
func (this ResFieldByNumber) Value() (driver.Value, error) {
return this, nil
}
-28
View File
@@ -1,28 +0,0 @@
package res_type
import (
"database/sql/driver"
"fmt"
)
// 字符串 -------------------------------------
type ResFieldByString string
func (this *ResFieldByString) Scan(value any) error {
if value == nil {
return nil
}
switch s := value.(type) {
case []byte:
*this = ResFieldByString(s)
default:
*this = ResFieldByString(fmt.Sprintf("%v", s))
}
return nil
}
func (c ResFieldByString) Value() (driver.Value, error) {
return c, nil
}
+99
View File
@@ -0,0 +1,99 @@
package contracts
import (
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
)
// ResVirtualTable 虚拟表接口,由业务层实现,框架通过 SQLite vtab 机制调用。
// Select 提供列表查询,Fetch 提供单条完整查询(含 Detail 补充字段),
// Insert/Update/Delete 支持写操作并自动同步缓存。
type ResVirtualTable interface {
Controller
GetResource() req.Resource
// Fetch 按主键获取单条完整数据,内部负责调用远端接口并补充 Detail 专属字段。
// 框架在 item cache miss 时调用,结果写入 item cache 供后续查询复用。
Fetch(pk any) (map[string]any, error)
// Detail 对已有的 item 数据进行补充,填充列表接口不返回的详情字段。
// changed=true 时框架会将修改后的数据回写 item cache。
Detail(item reflux.R) (changed bool, err error)
// Select 查询列表数据,filter 为过滤条件,pagesize/page 控制分页。
// 返回的 items 每项为 map[string]anytotal 为总记录数。
Select(filter reflux.R, pagesize, page int) (items []any, total int64, err error)
// Insert 新增一条记录,返回新记录的 rowid。
Insert(item reflux.R) (rowid int64, err error)
// Update 按 rowid 更新记录。
Update(rowid any, item reflux.R) error
// Delete 按 rowid 删除记录。
Delete(rowid any) error
}
type BaseResVirtualTable struct {
Controller
res req.Resource
cache *db.Database
}
func (b *BaseResVirtualTable) Cache() *db.Database {
// CREATE TABLE IF NOT EXISTS ` + res.Table + ` (
// id INTEGER PRIMARY KEY AUTOINCREMENT,
// kind VARCHAR(8) NOT NULL DEFAULT 'list',
// key VARCHAR(255) NOT NULL,
// lmt INTEGER NOT NULL DEFAULT 0,
// page INTEGER NOT NULL DEFAULT 0,
// data TEXT NOT NULL,
// total INTEGER NOT NULL DEFAULT 0,
// expires_at BIGINT NOT NULL,
// UNIQUE(kind, key, lmt, page)
// )
return b.cache
}
func (b *BaseResVirtualTable) GetResource() req.Resource {
return b.res
}
func (b *BaseResVirtualTable) GetExtraFields() []req.ResField {
return nil
}
func (b *BaseResVirtualTable) Fetch(pk any) (map[string]any, error) {
return nil, nil
}
func (b *BaseResVirtualTable) Detail(detail reflux.R) (changed bool, err error) {
return false, nil
}
func (b *BaseResVirtualTable) Select(filter reflux.R, pagesize, page int) (items []any, total int64, err error) {
return nil, 0, ErrFuncNotImplemented
}
func (b *BaseResVirtualTable) Insert(item reflux.R) (rowid int64, err error) {
return 0, ErrFuncNotImplemented
}
func (b *BaseResVirtualTable) Update(rowid any, item reflux.R) error {
return ErrFuncNotImplemented
}
func (b *BaseResVirtualTable) Delete(rowid any) error {
return ErrFuncNotImplemented
}
func NewBaseResVirtualTable(container do.Injector, res req.Resource, conn *db.Database) ResVirtualTable {
return &BaseResVirtualTable{
Controller: &BaseController{container},
res: res,
cache: conn,
}
}
+8 -3
View File
@@ -1,9 +1,14 @@
package contracts
import "git.fsdpf.net/go/req"
import (
"git.fsdpf.net/go/req"
)
// 资源
// GetResource 资源
type GetResource func(code string) (req.Resource, bool)
// 获取用户
// MustResource 资源, 没有则抛出错误
type MustResource func(code string) req.Resource
// GetUser 获取用户
type GetUser func(uuid string, options ...req.UserRuntimeOption) (req.User, error)
+5 -4
View File
@@ -8,8 +8,9 @@ import (
"git.fsdpf.net/go/condition"
"git.fsdpf.net/go/contracts"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/utils"
"github.com/samber/do"
"github.com/samber/do/v2"
"github.com/samber/lo"
"github.com/spf13/cast"
)
@@ -37,7 +38,7 @@ type OperationAccess struct {
data map[string][]any
}
func (o Operation) ToDBColumn(model contracts.Model, container *do.Injector) (column db.Expression, field reflect.StructField) {
func (o Operation) ToDBColumn(model contracts.Model, container do.Injector) (column db.Expression, field reflect.StructField) {
GetOrmConditions := do.MustInvoke[contracts.GetOrmConditions](container)
uuid := strings.ReplaceAll(o.Uuid, "-", "_")
@@ -52,9 +53,9 @@ func (o Operation) ToDBColumn(model contracts.Model, container *do.Injector) (co
}
if cond.IsEmpty() {
column = db.Raw(pk + " as `u_" + uuid + "`")
column = db.I(pk).As("u_" + uuid)
} else {
column = db.Raw("if(" + string(cond.ToSql(model)) + ", " + pk + ", 0) as `u_" + uuid + "`")
column = exp.NewSQLFunctionExpression("IF", cond.ToSql(model), db.I(pk), 0).As("u_" + uuid)
}
// 主键类型
+3 -3
View File
@@ -37,7 +37,7 @@ var wsUpgrader = websocket.Upgrader{
func (this HttpStatusResponse) Get(path ...string) req.GlobalParams {
if this.body == nil {
return req.NewGlobalParam("", nil)
return req.NewGlobalParams("", nil)
}
return this.body.Get(path...)
}
@@ -50,7 +50,7 @@ func (this HttpStatusResponse) Send(w http.ResponseWriter, r *http.Request) {
}
func (this RawResponse) Get(path ...string) req.GlobalParams {
return lo.Ternary(len(path) == 0, req.NewGlobalParam(string(this.raw), nil), req.NewGlobalParam(string(this.raw), nil).Get(strings.Join(path, ".")))
return lo.Ternary(len(path) == 0, req.NewGlobalParams(string(this.raw), nil), req.NewGlobalParams(string(this.raw), nil).Get(strings.Join(path, ".")))
}
func (this RawResponse) Send(w http.ResponseWriter, r *http.Request) {
@@ -72,7 +72,7 @@ func (this FileResponse) Get(path ...string) req.GlobalParams {
"name": this.name,
"disposition": this.disposition,
}); err == nil {
return lo.Ternary(len(path) == 0, req.NewGlobalParam(string(resp), nil), req.NewGlobalParam(string(resp), nil).Get(strings.Join(path, ".")))
return lo.Ternary(len(path) == 0, req.NewGlobalParams(string(resp), nil), req.NewGlobalParams(string(resp), nil).Get(strings.Join(path, ".")))
}
return nil
+1 -1
View File
@@ -56,7 +56,7 @@ type UserApproval interface {
// 判断发起审批请求权限
HasUserRoles(req.User) bool
// 获取审批列表标记字段值
Execute(id int64, status UserApprovalExecute, r *http.Request, tx *db.Transaction) (current, next UserApprovalFlow, err error)
Execute(id int64, status UserApprovalExecute, r *http.Request, tx *db.TxDatabase) (current, next UserApprovalFlow, err error)
}
type UserApprovalFlow interface {
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"git.fsdpf.net/go/req"
"github.com/gorilla/websocket"
"github.com/samber/do"
"github.com/samber/do/v2"
)
type WsClientGroup string
@@ -90,7 +90,7 @@ func (WsHandleController) ClientGroup(req.GlobalParams) WsClientGroup {
return defaultWsClientGroup
}
func NewWsController(container *do.Injector, request *http.Request, ws WsClient) WsController {
func NewWsController(container do.Injector, request *http.Request, ws WsClient) WsController {
return &WsHandleController{
Controller: &BaseController{container},
request: request,