feat: flow design & page query refactor

This commit is contained in:
meilin.huang
2025-05-20 21:04:47 +08:00
parent 44d379a016
commit f676ec9e7b
269 changed files with 5072 additions and 5075 deletions

View File

@@ -54,13 +54,9 @@ type App[T model.ModelI] interface {
// @param cond 可为*model.QueryCond也可以为普通查询model
ListByCond(cond any, cols ...string) ([]T, error)
// PageByCondToAny 分页查询并绑定至指定toModels
// @param cond 可为*model.QueryCond也可以为普通查询model
PageByCondToAny(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error)
// PageByCond 根据指定条件分页查询
// @param cond 可为*model.QueryCond也可以为普通查询model
PageByCond(cond any, pageParam *model.PageParam, cols ...string) (*model.PageResult[[]T], error)
PageByCond(cond any, pageParam model.PageParam, cols ...string) (*model.PageResult[T], error)
// CountByCond 根据指定条件统计model表的数量
// @param cond 可为*model.QueryCond也可以为普通查询model
@@ -147,12 +143,7 @@ func (ai *AppImpl[T, R]) ListByCond(cond any, cols ...string) ([]T, error) {
return ai.GetRepo().SelectByCond(cond, cols...)
}
// PageByCondToAny 分页查询
func (ai *AppImpl[T, R]) PageByCondToAny(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error) {
return ai.GetRepo().PageByCondToAny(cond, pageParam, toModels)
}
func (ai *AppImpl[T, R]) PageByCond(cond any, pageParam *model.PageParam, cols ...string) (*model.PageResult[[]T], error) {
func (ai *AppImpl[T, R]) PageByCond(cond any, pageParam model.PageParam, cols ...string) (*model.PageResult[T], error) {
return ai.GetRepo().PageByCond(cond, pageParam, cols...)
}

View File

@@ -1,6 +1,7 @@
package base
import (
"cmp"
"context"
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/gormx"
@@ -80,11 +81,8 @@ type Repo[T model.ModelI] interface {
// SelectByCond 根据条件查询模型实例数组
SelectByCond(cond any, cols ...string) ([]T, error)
// PageByCondToAny 根据查询条件分页查询并绑定至指定res
PageByCondToAny(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error)
// PageByCond 根据查询条件分页查询
PageByCond(cond any, pageParam *model.PageParam, cols ...string) (*model.PageResult[[]T], error)
PageByCond(cond any, pageParam model.PageParam, cols ...string) (*model.PageResult[T], error)
// SelectBySql 根据sql语句查询数据
SelectBySql(sql string, res any, params ...any) error
@@ -172,9 +170,9 @@ func (br *RepoImpl[T]) UpdateByCondWithDb(ctx context.Context, db *gorm.DB, valu
}
if db == nil {
return gormx.UpdateByCond(br.getModel(), values, toQueryCond(cond))
return gormx.UpdateByCond(br.GetModel(), values, toQueryCond(cond))
}
return gormx.UpdateByCondWithDb(db, br.getModel(), values, toQueryCond(cond))
return gormx.UpdateByCondWithDb(db, br.GetModel(), values, toQueryCond(cond))
}
func (br *RepoImpl[T]) Save(ctx context.Context, e T) error {
@@ -195,22 +193,22 @@ func (br *RepoImpl[T]) DeleteById(ctx context.Context, id ...uint64) error {
if db := GetDbFromCtx(ctx); db != nil {
return br.DeleteByIdWithDb(ctx, db, id...)
}
return gormx.DeleteById(br.getModel(), id...)
return gormx.DeleteById(br.GetModel(), id...)
}
func (br *RepoImpl[T]) DeleteByIdWithDb(ctx context.Context, db *gorm.DB, id ...uint64) error {
return gormx.DeleteByIdWithDb(db, br.getModel(), id...)
return gormx.DeleteByIdWithDb(db, br.GetModel(), id...)
}
func (br *RepoImpl[T]) DeleteByCond(ctx context.Context, cond any) error {
if db := GetDbFromCtx(ctx); db != nil {
return br.DeleteByCondWithDb(ctx, db, cond)
}
return gormx.DeleteByCond(br.getModel(), toQueryCond(cond))
return gormx.DeleteByCond(br.GetModel(), toQueryCond(cond))
}
func (br *RepoImpl[T]) DeleteByCondWithDb(ctx context.Context, db *gorm.DB, cond any) error {
return gormx.DeleteByCondWithDb(db, br.getModel(), toQueryCond(cond))
return gormx.DeleteByCondWithDb(db, br.GetModel(), toQueryCond(cond))
}
func (br *RepoImpl[T]) ExecBySql(sql string, params ...any) error {
@@ -228,11 +226,11 @@ func (br *RepoImpl[T]) GetByIds(ids []uint64, cols ...string) ([]T, error) {
}
func (br *RepoImpl[T]) GetByCond(cond any) error {
return gormx.GetByCond(br.getModel(), toQueryCond(cond))
return gormx.GetByCond(br.GetModel(), toQueryCond(cond))
}
func (br *RepoImpl[T]) SelectByCondToAny(cond any, res any) error {
return gormx.SelectByCond(br.getModel(), toQueryCond(cond).Dest(res))
return gormx.SelectByCond(br.GetModel(), toQueryCond(cond).Dest(res))
}
func (br *RepoImpl[T]) SelectByCond(cond any, cols ...string) ([]T, error) {
@@ -240,20 +238,9 @@ func (br *RepoImpl[T]) SelectByCond(cond any, cols ...string) ([]T, error) {
return models, br.SelectByCondToAny(toQueryCond(cond).Dest(models).Columns(cols...), &models)
}
func (br *RepoImpl[T]) PageByCondToAny(cond any, pageParam *model.PageParam, toModels any) (*model.PageResult[any], error) {
return gormx.PageByCond(br.getModel(), toQueryCond(cond), pageParam, toModels)
}
func (br *RepoImpl[T]) PageByCond(cond any, pageParam *model.PageParam, cols ...string) (*model.PageResult[[]T], error) {
func (br *RepoImpl[T]) PageByCond(cond any, pageParam model.PageParam, cols ...string) (*model.PageResult[T], error) {
var models []T
res, err := br.PageByCondToAny(toQueryCond(cond).Columns(cols...), pageParam, &models)
if err != nil {
return nil, err
}
return &model.PageResult[[]T]{
List: models,
Total: res.Total,
}, nil
return gormx.PageByCond(br.GetModel(), toQueryCond(cond).Columns(cols...), pageParam, models)
}
func (br *RepoImpl[T]) SelectBySql(sql string, res any, params ...any) error {
@@ -261,7 +248,7 @@ func (br *RepoImpl[T]) SelectBySql(sql string, res any, params ...any) error {
}
func (br *RepoImpl[T]) CountByCond(cond any) int64 {
return gormx.CountByCond(br.getModel(), toQueryCond(cond))
return gormx.CountByCond(br.GetModel(), toQueryCond(cond))
}
// NewModel 新建模型实例
@@ -282,7 +269,7 @@ func (br *RepoImpl[T]) NewModel() T {
// }
// getModel 获取表的模型实例
func (br *RepoImpl[T]) getModel() T {
func (br *RepoImpl[T]) GetModel() T {
if br.model != nil {
return br.model.(T)
}
@@ -311,7 +298,7 @@ func (br *RepoImpl[T]) getModelType() reflect.Type {
// 从上下文获取登录账号信息,并赋值至实体
func (br *RepoImpl[T]) fillBaseInfo(ctx context.Context, e T) T {
// 默认使用数据库id策略, 若要改变则实体结构体自行覆盖FillBaseInfo方法。可参考 sys/entity.Resource
e.FillBaseInfo(model.IdGenTypeNone, contextx.GetLoginAccount(ctx))
e.FillBaseInfo(model.IdGenTypeNone, cmp.Or(contextx.GetLoginAccount(ctx), model.SysAccount))
return e
}

View File

@@ -8,16 +8,18 @@ import (
)
// BusSubscriber defines subscription-related bus behavior
type BusSubscriber interface {
Subscribe(topic string, subId string, fn EventHandleFunc) error
SubscribeAsync(topic string, subId string, fn EventHandleFunc, transactional bool) error
SubscribeOnce(topic string, subId string, fn EventHandleFunc) error
type BusSubscriber[T any] interface {
Subscribe(topic string, subId string, fn EventHandleFunc[T]) error
SubscribeAsync(topic string, subId string, fn EventHandleFunc[T], transactional bool) error
SubscribeOnce(topic string, subId string, fn EventHandleFunc[T]) error
Unsubscribe(topic string, subId string) error
}
// BusPublisher defines publishing-related bus behavior
type BusPublisher interface {
Publish(ctx context.Context, topic string, val any)
type BusPublisher[T any] interface {
Publish(ctx context.Context, topic string, val T)
PublishSync(ctx context.Context, topic string, val T) error
}
// BusController defines bus control behavior (checking handler's presence, synchronization)
@@ -25,90 +27,90 @@ type BusController interface {
WaitAsync()
}
// Bus englobes global (subscribe, publish, control) bus behavior
type Bus interface {
BusController
BusSubscriber
BusPublisher
}
// EventBus - box for handlers and callbacks.
type EventBus struct {
subscriberManager map[string]*SubscriberManager // topic -> SubscriberManager
lock sync.Mutex // a lock for the map
wg sync.WaitGroup
}
func New() Bus {
b := &EventBus{
make(map[string]*SubscriberManager),
sync.Mutex{},
sync.WaitGroup{},
}
return Bus(b)
}
type Event struct {
type Event[T any] struct {
Topic string
Val any
Val T
}
// 订阅者们的事件处理器
type SubscriberManager struct {
type SubscriberManager[T any] struct {
// 事件处理器 subId -> handler
handlers map[string]*eventHandler
handlers map[string]*eventHandler[T]
}
func NewSubscriberManager() *SubscriberManager {
return &SubscriberManager{
handlers: make(map[string]*eventHandler),
func NewSubscriberManager[T any]() *SubscriberManager[T] {
return &SubscriberManager[T]{
handlers: make(map[string]*eventHandler[T]),
}
}
func (sm *SubscriberManager) addSubscriber(subId string, eventHandler *eventHandler) {
func (sm *SubscriberManager[T]) addSubscriber(subId string, eventHandler *eventHandler[T]) {
sm.handlers[subId] = eventHandler
}
func (sm *SubscriberManager) delSubscriber(subId string) {
func (sm *SubscriberManager[T]) delSubscriber(subId string) {
delete(sm.handlers, subId)
}
// 事件处理函数
type EventHandleFunc func(ctx context.Context, event *Event) error
type EventHandleFunc[T any] func(ctx context.Context, event *Event[T]) error
type eventHandler struct {
handlerFunc EventHandleFunc // 事件处理函数
once bool // 是否只执行一次
type eventHandler[T any] struct {
handlerFunc EventHandleFunc[T] // 事件处理函数
once bool // 是否只执行一次
async bool
transactional bool
sync.Mutex // lock for an event handler - useful for running async callbacks serially
}
func (bus *EventBus) Subscribe(topic string, subId string, fn EventHandleFunc) error {
eh := &eventHandler{}
// Bus englobes global (subscribe, publish, control) bus behavior
type Bus[T any] interface {
BusController
BusSubscriber[T]
BusPublisher[T]
}
// EventBus - box for handlers and callbacks.
type EventBus[T any] struct {
subscriberManager map[string]*SubscriberManager[T] // topic -> SubscriberManager
lock sync.Mutex // a lock for the map
wg sync.WaitGroup
}
func New[T any]() Bus[T] {
b := &EventBus[T]{
make(map[string]*SubscriberManager[T]),
sync.Mutex{},
sync.WaitGroup{},
}
return Bus[T](b)
}
func (bus *EventBus[T]) Subscribe(topic string, subId string, fn EventHandleFunc[T]) error {
eh := &eventHandler[T]{}
eh.handlerFunc = fn
return bus.doSubscribe(topic, subId, eh)
}
// SubscribeAsync subscribes to a topic with an asynchronous callback
// Transactional determines whether subsequent callbacks for a topic are
func (bus *EventBus) SubscribeAsync(topic string, subId string, fn EventHandleFunc, transactional bool) error {
eh := &eventHandler{}
func (bus *EventBus[T]) SubscribeAsync(topic string, subId string, fn EventHandleFunc[T], transactional bool) error {
eh := &eventHandler[T]{}
eh.handlerFunc = fn
eh.async = true
return bus.doSubscribe(topic, subId, eh)
}
// SubscribeOnce subscribes to a topic once. Handler will be removed after executing.
func (bus *EventBus) SubscribeOnce(topic string, subId string, fn EventHandleFunc) error {
eh := &eventHandler{}
func (bus *EventBus[T]) SubscribeOnce(topic string, subId string, fn EventHandleFunc[T]) error {
eh := &eventHandler[T]{}
eh.handlerFunc = fn
eh.once = true
return bus.doSubscribe(topic, subId, eh)
}
func (bus *EventBus) Unsubscribe(topic string, subId string) error {
func (bus *EventBus[T]) Unsubscribe(topic string, subId string) error {
bus.lock.Lock()
defer bus.lock.Unlock()
subManager := bus.subscriberManager[topic]
@@ -119,73 +121,91 @@ func (bus *EventBus) Unsubscribe(topic string, subId string) error {
return nil
}
func (bus *EventBus) Publish(ctx context.Context, topic string, val any) {
bus.lock.Lock() // will unlock if handler is not found or always after setUpPublish
func (bus *EventBus[T]) Publish(ctx context.Context, topic string, val T) {
bus.publishInternal(ctx, topic, val, false)
}
func (bus *EventBus[T]) PublishSync(ctx context.Context, topic string, val T) error {
return bus.publishInternal(ctx, topic, val, true)
}
func (bus *EventBus[T]) publishInternal(ctx context.Context, topic string, val T, syncSubHandleErrorOnStop bool) error {
bus.lock.Lock()
defer bus.lock.Unlock()
logx.Debugf("topic - [%s] - published the event", topic)
event := &Event{
Topic: topic,
Val: val,
}
subscriberManager := bus.subscriberManager[topic]
if subscriberManager == nil {
return
event := &Event[T]{Topic: topic, Val: val}
logx.Debugf("topic-[%s] - published the event", topic)
subManager := bus.subscriberManager[topic]
if subManager == nil || len(subManager.handlers) == 0 {
return nil
}
handlers := subscriberManager.handlers
if len(handlers) == 0 {
return
}
for subId, handler := range handlers {
logx.Debugf("subscriber - [%s] - starts executing events published by topic - [%s]", subId, topic)
var syncSubError error
for subId, handler := range subManager.handlers {
if handler.once {
subscriberManager.delSubscriber(subId)
subManager.delSubscriber(subId)
}
// 同步执行处理
if !handler.async {
bus.doPublish(ctx, handler, event)
} else {
bus.wg.Add(1)
if handler.transactional {
bus.lock.Unlock()
handler.Lock()
bus.lock.Lock()
// 同步订阅者其中一个执行失败则停止后续订阅者处理字段设置为true并且已经出现订阅者处理失败则跳过后续同步订阅者的处理
if syncSubHandleErrorOnStop && syncSubError != nil {
continue
}
go bus.doPublishAsync(ctx, handler, event)
logx.Debugf("subscriber-[%s] - starts executing events published by topic-[%s]", subId, topic)
err := bus.doPublish(ctx, handler, event)
if err != nil {
syncSubError = err
logx.Errorf("subscriber-[%s] failed to handle event topic-[%s]: %v", subId, topic, err)
}
continue
}
logx.Debugf("async subscriber-[%s] - starts executing events published by topic-[%s]", subId, topic)
// 异步执行
bus.wg.Add(1)
if handler.transactional {
bus.lock.Unlock()
handler.Lock()
bus.lock.Lock()
}
go bus.doPublishAsync(ctx, subId, handler, event)
}
return syncSubError
}
// WaitAsync waits for all async callbacks to complete
func (bus *EventBus) WaitAsync() {
func (bus *EventBus[T]) WaitAsync() {
bus.wg.Wait()
}
func (bus *EventBus) doSubscribe(topic string, subId string, handler *eventHandler) error {
func (bus *EventBus[T]) doSubscribe(topic string, subId string, handler *eventHandler[T]) error {
bus.lock.Lock()
defer bus.lock.Unlock()
logx.Debugf("subscribers - [%s] - subscribed to topic - [%s]", subId, topic)
logx.Debugf("subscribers-[%s] -> subscribed to topic-[%s]", subId, topic)
subManager := bus.subscriberManager[topic]
if subManager == nil {
subManager = NewSubscriberManager()
subManager = NewSubscriberManager[T]()
bus.subscriberManager[topic] = subManager
}
subManager.addSubscriber(subId, handler)
return nil
}
func (bus *EventBus) doPublish(ctx context.Context, handler *eventHandler, event *Event) error {
err := handler.handlerFunc(ctx, event)
if err != nil {
logx.Errorf("subscriber failed to execute topic [%s]: %s", event.Topic, err.Error())
}
return err
func (bus *EventBus[T]) doPublish(ctx context.Context, handler *eventHandler[T], event *Event[T]) error {
return handler.handlerFunc(ctx, event)
}
func (bus *EventBus) doPublishAsync(ctx context.Context, handler *eventHandler, event *Event) {
func (bus *EventBus[T]) doPublishAsync(ctx context.Context, subId string, handler *eventHandler[T], event *Event[T]) {
defer bus.wg.Done()
if handler.transactional {
defer handler.Unlock()
}
bus.doPublish(ctx, handler, event)
if err := bus.doPublish(ctx, handler, event); err != nil {
logx.Errorf("async subscriber-[%s] failed to execute topic-[%s]: %s", subId, event.Topic, err.Error())
}
}

View File

@@ -9,30 +9,30 @@ import (
)
func TestSubscribe(t *testing.T) {
bus := New()
bus := New[any]()
bus.SubscribeAsync("topic", "sub5", func(ctx context.Context, event *Event) error {
bus.SubscribeAsync("topic", "sub5", func(ctx context.Context, event *Event[any]) error {
time.Sleep(5 * time.Second)
fmt.Printf("%s -> %s -> %d\n", "sub5", event.Topic, event.Val)
return nil
}, true)
bus.SubscribeOnce("topic", "sub1", func(ctx context.Context, event *Event) error {
bus.SubscribeOnce("topic", "sub1", func(ctx context.Context, event *Event[any]) error {
fmt.Printf("%s -> %s -> %d\n", "sub1", event.Topic, event.Val)
return nil
})
bus.Subscribe("topic", "sub2", func(ctx context.Context, event *Event) error {
bus.Subscribe("topic", "sub2", func(ctx context.Context, event *Event[any]) error {
time.Sleep(5 * time.Second)
return errors.New("失败。。。。")
})
bus.SubscribeAsync("topic", "sub3", func(ctx context.Context, event *Event) error {
bus.SubscribeAsync("topic", "sub3", func(ctx context.Context, event *Event[any]) error {
fmt.Printf("%s -> %s -> %d\n", "sub3", event.Topic, event.Val)
return nil
}, false)
bus.SubscribeAsync("topic", "sub4", func(ctx context.Context, event *Event) error {
bus.SubscribeAsync("topic", "sub4", func(ctx context.Context, event *Event[any]) error {
time.Sleep(5 * time.Second)
fmt.Printf("%s -> %s -> %d\n", "sub4", event.Topic, event.Val)
return nil

View File

@@ -9,5 +9,5 @@ import (
var (
Db *gorm.DB // gorm
EventBus eventbus.Bus = eventbus.New()
EventBus eventbus.Bus[any] = eventbus.New[any]()
)

View File

@@ -39,7 +39,7 @@ func CountByCond(dbModel model.ModelI, cond *model.QueryCond) int64 {
// PageQuery 根据查询条件分页查询数据,若需要伪删除过滤,则自行过滤-调用q.Undeleted()
// 若未指定查询列则查询列以toModels字段为准
func PageQuery[T any](q *Query, pageParam *model.PageParam, toModels T) (*model.PageResult[T], error) {
func PageQuery[T any](q *Query, pageParam model.PageParam, toModels []T) (*model.PageResult[T], error) {
gdb := q.GenGdb()
var count int64
err := gdb.Count(&count).Error
@@ -47,12 +47,21 @@ func PageQuery[T any](q *Query, pageParam *model.PageParam, toModels T) (*model.
return nil, err
}
if count == 0 {
return model.EmptyPageResult[T](), nil
return model.NewEmptyPageResult[T](), nil
}
page := pageParam.PageNum
pageSize := pageParam.PageSize
err = gdb.Limit(pageSize).Offset((page - 1) * pageSize).Find(toModels).Error
if page == 0 {
page = 1
}
if pageSize == 0 {
pageSize = 100
}
if pageSize > 2000 {
pageSize = 2000
}
err = gdb.Limit(pageSize).Offset((page - 1) * pageSize).Find(&toModels).Error
if err != nil {
return nil, err
}
@@ -60,7 +69,7 @@ func PageQuery[T any](q *Query, pageParam *model.PageParam, toModels T) (*model.
}
// PageByCond 根据指定查询条件分页查询数据
func PageByCond[T any](dbModel model.ModelI, cond *model.QueryCond, pageParam *model.PageParam, toModels T) (*model.PageResult[T], error) {
func PageByCond[T any](dbModel model.ModelI, cond *model.QueryCond, pageParam model.PageParam, toModels []T) (*model.PageResult[T], error) {
return PageQuery(NewQuery(dbModel, cond), pageParam, toModels)
}

View File

@@ -9,3 +9,9 @@ type LoginAccount struct {
func (la *LoginAccount) GetAesKey() string {
return la.Token[:24]
}
// 系统账号
var SysAccount = &LoginAccount{
Id: 1,
Username: "system",
}

View File

@@ -1,11 +1,7 @@
package model
import (
"database/sql/driver"
"encoding/json"
"time"
"github.com/may-fly/cast"
)
type IdGenType int
@@ -200,73 +196,3 @@ func GetIdByGenType(genType IdGenType) uint64 {
}
return 0
}
type Map[K comparable, V any] map[K]V
func (m *Map[K, V]) Scan(value any) error {
if v, ok := value.([]byte); ok && len(v) > 0 {
return json.Unmarshal(v, m)
}
return nil
}
func (m Map[K, V]) Value() (driver.Value, error) {
if m == nil {
return nil, nil
}
return json.Marshal(m)
}
type Slice[T int | uint64 | string | Map[string, any]] []T
func (s *Slice[T]) Scan(value any) error {
if v, ok := value.([]byte); ok && len(v) > 0 {
return json.Unmarshal(v, s)
}
return nil
}
func (s Slice[T]) Value() (driver.Value, error) {
if s == nil {
return nil, nil
}
return json.Marshal(s)
}
// 带有额外其他信息字段的结构体
type ExtraData struct {
Extra Map[string, any] `json:"extra" gorm:"type:varchar(2000)"`
}
// SetExtraValue 设置额外信息字段值
func (m *ExtraData) SetExtraValue(key string, val any) {
if m.Extra != nil {
m.Extra[key] = val
} else {
m.Extra = Map[string, any]{key: val}
}
}
// GetExtraString 获取额外信息中的string类型字段值
func (e ExtraData) GetExtraString(key string) string {
if e.Extra == nil {
return ""
}
return cast.ToString(e.Extra[key])
}
// GetExtraInt 获取额外信息中的int类型字段值
func (e ExtraData) GetExtraInt(key string) int {
if e.Extra == nil {
return 0
}
return cast.ToInt(e.Extra[key])
}
// GetExtraBool 获取额外信息中的bool类型字段值
func (e ExtraData) GetExtraBool(key string) bool {
if e.Extra == nil {
return false
}
return cast.ToBool(e.Extra[key])
}

View File

@@ -1,18 +1,39 @@
package model
import (
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/structx"
)
// 分页参数
type PageParam struct {
PageNum int `json:"pageNum"`
PageSize int `json:"pageSize"`
PageNum int `json:"pageNum" form:"pageNum" gorm:"-"`
PageSize int `json:"pageSize" form:"pageSize" gorm:"-"`
}
// 分页结果
type PageResult[T any] struct {
Total int64 `json:"total"`
List T `json:"list"`
List []T `json:"list"`
}
// 空分页结果
func EmptyPageResult[T any]() *PageResult[T] {
func NewEmptyPageResult[T any]() *PageResult[T] {
return &PageResult[T]{Total: 0}
}
// PageResultConv pageResult转换
func PageResultConv[F any, T any](pageResult *PageResult[F]) *PageResult[T] {
if pageResult == nil {
return NewEmptyPageResult[T]()
}
return &PageResult[T]{
Total: pageResult.Total,
List: collx.ArrayMap(pageResult.List, func(item F) T {
t := structx.NewInstance[T]()
structx.Copy(t, item)
return t
}),
}
}

89
server/pkg/model/type.go Normal file
View File

@@ -0,0 +1,89 @@
package model
import (
"database/sql/driver"
"encoding/json"
"mayfly-go/pkg/utils/collx"
)
type Map[K comparable, V any] map[K]V
func (m *Map[K, V]) Scan(value any) error {
if v, ok := value.([]byte); ok && len(v) > 0 {
return json.Unmarshal(v, m)
}
return nil
}
func (m Map[K, V]) Value() (driver.Value, error) {
if m == nil {
return nil, nil
}
return json.Marshal(m)
}
type Slice[T int | uint64 | string | Map[string, any]] []T
func (s *Slice[T]) Scan(value any) error {
if v, ok := value.([]byte); ok && len(v) > 0 {
return json.Unmarshal(v, s)
}
return nil
}
func (s Slice[T]) Value() (driver.Value, error) {
if s == nil {
return nil, nil
}
return json.Marshal(s)
}
// ExtraData 带有额外其他信息字段的结构体
type ExtraData struct {
Extra collx.M `json:"extra" gorm:"type:varchar(2000)"`
}
// SetExtraValue 设置额外信息字段值
func (m *ExtraData) SetExtraValue(key string, val any) {
if m.Extra != nil {
m.Extra[key] = val
} else {
m.Extra = collx.M{key: val}
}
}
// GetExtraVal 获取额外信息字段值
func (e ExtraData) GetExtraVal(key string) any {
if e.Extra == nil {
return nil
}
return e.Extra[key]
}
// GetExtraString 获取额外信息中的string类型字段值
func (e ExtraData) GetExtraString(key string) string {
return e.Extra.GetStr(key)
}
func (e ExtraData) GetExtraStringSlice(key string) []string {
return e.Extra.GetStrSlice(key)
}
// GetExtraInt 获取额外信息中的int类型字段值
func (e ExtraData) GetExtraInt(key string) int {
return e.Extra.GetInt(key)
}
// GetExtraInt64 获取额外信息中的int64类型字段值
func (e ExtraData) GetExtraInt64(key string) int64 {
return e.Extra.GetInt64(key)
}
func (e ExtraData) GetExtraFloat32(key string) float32 {
return e.Extra.GetFloat32(key)
}
// GetExtraBool 获取额外信息中的bool类型字段值
func (e ExtraData) GetExtraBool(key string) bool {
return e.Extra.GetBool(key)
}

View File

@@ -87,8 +87,8 @@ func (wf *wrapperF) QueryIntDefault(qm string, defaultInt int) int {
}
// 获取分页参数
func (wf *wrapperF) GetPageParam() *model.PageParam {
return &model.PageParam{PageNum: wf.QueryIntDefault("pageNum", 1), PageSize: wf.QueryIntDefault("pageSize", 100)}
func (wf *wrapperF) GetPageParam() model.PageParam {
return model.PageParam{PageNum: wf.QueryIntDefault("pageNum", 1), PageSize: wf.QueryIntDefault("pageSize", 100)}
}
// 获取路径参数

View File

@@ -35,7 +35,7 @@ func BindQuery[T any](rc *Ctx, data T) T {
}
// 绑定查询字符串到指定结构体,并将分页信息也返回
func BindQueryAndPage[T any](rc *Ctx, data T) (T, *model.PageParam) {
func BindQueryAndPage[T any](rc *Ctx, data T) (T, model.PageParam) {
if err := rc.BindQuery(data); err != nil {
panic(ConvBindValidationError(data, err))
} else {

View File

@@ -1,10 +1,72 @@
package collx
import "mayfly-go/pkg/utils/anyx"
import (
"database/sql/driver"
"encoding/json"
"mayfly-go/pkg/utils/anyx"
"github.com/may-fly/cast"
)
// M is a shortcut for map[string]any
type M map[string]any
// Set 设置key对应的值
func (m *M) Set(key string, val any) {
if *m == nil {
*m = M{}
}
(*m)[key] = val
}
// GetStr 获取key对应的string类型值
func (m M) GetStr(key string) string {
return cast.ToString(m[key])
}
func (m M) GetStrSlice(key string) []string {
return cast.ToStringSlice(m[key])
}
// GetInt 获取key对应的int类型值
func (m M) GetInt(key string) int {
return cast.ToInt(m[key])
}
// GetInt64 获取key对应的int64类型值
func (m M) GetInt64(key string) int64 {
return cast.ToInt64(m[key])
}
func (m M) GetFloat32(key string) float32 {
return cast.ToFloat32(m[key])
}
func (m M) GetFloat64(key string) float64 {
return cast.ToFloat64(m[key])
}
func (m M) GetBool(key string) bool {
return cast.ToBool(m[key])
}
/******************* M db driver *******************/
func (j *M) Scan(value any) error {
if v, ok := value.([]byte); ok && len(v) > 0 {
return json.Unmarshal(v, j)
}
return nil
}
func (m M) Value() (driver.Value, error) {
if m == nil {
return nil, nil
}
return json.Marshal(m)
}
/******************* map utils *******************/
// 将偶数个元素转为对应的M (map[string]any)
//
// 偶数索引为key奇数为value

View File

@@ -3,12 +3,13 @@ package jsonx
import (
"encoding/json"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/collx"
"github.com/tidwall/gjson"
)
// json字符串转map
func ToMap(jsonStr string) (map[string]any, error) {
func ToMap(jsonStr string) (collx.M, error) {
if jsonStr == "" {
return map[string]any{}, nil
}
@@ -21,7 +22,7 @@ func To[T any](jsonStr string, res T) (T, error) {
}
// json字节数组转map
func ToMapByBytes(bytes []byte) (map[string]any, error) {
func ToMapByBytes(bytes []byte) (collx.M, error) {
var res map[string]any
err := json.Unmarshal(bytes, &res)
return res, err

View File

@@ -5,6 +5,8 @@ import (
"mayfly-go/pkg/utils/collx"
"strings"
"testing"
"github.com/may-fly/cast"
)
func TestTemplateParse(t *testing.T) {
@@ -20,6 +22,14 @@ func TestTemplateParse(t *testing.T) {
},
}
num1 := 12
num2 := 12
num3 := 2
templ2 := "{{ eq .num1 121 }}"
re, err := TemplateParse(templ2, collx.M{"num1": num1, "num2": num2, "num3": num3})
fmt.Println(err, cast.ToBool(re))
res, _ := TemplateParse(tmpl, vars)
res2 := strings.TrimSpace(res)
fmt.Println(res2)

View File

@@ -0,0 +1,21 @@
package structx
import "reflect"
// NewInstance 创建泛型 T 的实例。如果 T 是指针类型,则创建其指向类型的实例并返回指针。
func NewInstance[T any]() T {
var t T
// 反射判断是否是指针类型,并且是否为 nil
if reflect.ValueOf(t).Kind() == reflect.Ptr {
// 创建 T 对应的非指针类型的实例,并取其地址作为新的 T
t = reflect.New(reflect.TypeOf(t).Elem()).Interface().(T)
} else if kind := reflect.TypeOf(t).Kind(); kind == reflect.Array || kind == reflect.Slice {
// 如果是数组或切片类型,创建一个新的切片(数组)
elemType := reflect.TypeOf(t).Elem()
newSlice := reflect.MakeSlice(reflect.SliceOf(elemType), 0, 0)
t = newSlice.Interface().(T)
}
return t
}

View File

@@ -130,10 +130,10 @@ func (manager *ClientManager) WriteMessage() {
cli := manager.GetByUidAndCid(uid, cid)
if cli != nil {
if err := cli.WriteMsg(msg); err != nil {
logx.Warnf("ws消息发送失败[uid=%d, cid=%s]: %s", uid, cid, err.Error())
logx.Warnf("ws send message failed - [uid=%d, cid=%s]: %s", uid, cid, err.Error())
}
} else {
logx.Warnf("[uid=%v, cid=%s]ws连接不存在", uid, cid)
logx.Warnf("[uid=%v, cid=%s] ws conn not exist", uid, cid)
}
continue
}
@@ -141,7 +141,7 @@ func (manager *ClientManager) WriteMessage() {
// cid为空则向该用户所有客户端发送该消息
for _, cli := range manager.GetByUid(uid) {
if err := cli.WriteMsg(msg); err != nil {
logx.Warnf("ws消息发送失败[uid=%d, cid=%s]: %s", uid, cli.ClientId, err.Error())
logx.Warnf("ws send message failed - [uid=%d, cid=%s]: %s", uid, cli.ClientId, err.Error())
}
}
}
@@ -181,7 +181,7 @@ func (manager *ClientManager) doConnect(client *Client) {
manager.doDisconnect(cli)
}
manager.addUserClient2Map(client)
logx.Debugf("WS客户端已连接: uid=%d, cid=%s, usercount=%d", client.UserId, client.ClientId, manager.Count())
logx.Debugf("WS client connected: uid=%d, cid=%s, usercount=%d", client.UserId, client.ClientId, manager.Count())
}
// 处理断开连接
@@ -192,7 +192,7 @@ func (manager *ClientManager) doDisconnect(client *Client) {
client.WsConn = nil
}
manager.delUserClient4Map(client)
logx.Debugf("WS客户端已断开: uid=%d, cid=%s, usercount=%d", client.UserId, client.ClientId, Manager.Count())
logx.Debugf("WS client disconnected: uid=%d, cid=%s, usercount=%d", client.UserId, client.ClientId, Manager.Count())
}
func (manager *ClientManager) addUserClient2Map(client *Client) {