From 503af23ca0b888695d767eb09d606e9b0a845790 Mon Sep 17 00:00:00 2001 From: xugo Date: Fri, 10 Apr 2026 22:20:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(metadata):=20=E9=85=8D=E7=BD=AE=E6=8C=81?= =?UTF-8?q?=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/app/app.go | 5 - internal/app/wire_gen.go | 4 + internal/core/metadata/core.go | 17 +++ internal/core/metadata/doc.go | 4 + internal/core/metadata/metadata.go | 116 ++++++++++++++++++ internal/core/metadata/metadata.model.go | 26 ++++ internal/core/metadata/metadata.param.go | 31 +++++ .../core/metadata/metadataapi/metadata.go | 61 +++++++++ internal/core/metadata/model.go | 2 + internal/core/metadata/store/metadatadb/db.go | 37 ++++++ .../metadata/store/metadatadb/metadata.go | 67 ++++++++++ internal/web/api/api.go | 2 + internal/web/api/provider.go | 3 + main.go | 5 + 14 files changed, 375 insertions(+), 5 deletions(-) create mode 100755 internal/core/metadata/core.go create mode 100644 internal/core/metadata/doc.go create mode 100755 internal/core/metadata/metadata.go create mode 100755 internal/core/metadata/metadata.model.go create mode 100755 internal/core/metadata/metadata.param.go create mode 100755 internal/core/metadata/metadataapi/metadata.go create mode 100755 internal/core/metadata/model.go create mode 100755 internal/core/metadata/store/metadatadb/db.go create mode 100755 internal/core/metadata/store/metadatadb/metadata.go diff --git a/internal/app/app.go b/internal/app/app.go index 57f26db..2b765bd 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -14,7 +14,6 @@ import ( "time" "github.com/gowvp/owl/internal/conf" - "github.com/ixugo/goddd/domain/version/versionapi" "github.com/ixugo/goddd/pkg/logger" "github.com/ixugo/goddd/pkg/server" "github.com/ixugo/goddd/pkg/system" @@ -51,10 +50,6 @@ func Run(bc *conf.Bootstrap) { go setupAIClient(ctx, "http://127.0.0.1:15123/ai", bc.Debug) } - // 如果需要执行表迁移,递增此版本号和表更新说明 - versionapi.DBVersion = "0.0.23" - versionapi.DBRemark = "onvif device support" - handler, cleanUp, err := wireApp(bc, log) if err != nil { slog.Error("程序构建失败", "err", err) diff --git a/internal/app/wire_gen.go b/internal/app/wire_gen.go index 30a5f95..d4732c2 100644 --- a/internal/app/wire_gen.go +++ b/internal/app/wire_gen.go @@ -8,6 +8,7 @@ package app import ( "github.com/gowvp/owl/internal/conf" + "github.com/gowvp/owl/internal/core/metadata/metadataapi" "github.com/gowvp/owl/internal/data" "github.com/gowvp/owl/internal/web/api" "github.com/gowvp/owl/pkg/gbs" @@ -43,6 +44,8 @@ func wireApp(bc *conf.Bootstrap, log *slog.Logger) (http.Handler, func(), error) aiWebhookAPI := api.NewAIWebhookAPIWithDeps(bc, eventCore, ipcBundle) eventAPI := api.NewEventAPI(eventCore, bc) recordingAPI := api.NewRecordingAPI(recordingCore, bc) + metadataCore := metadataapi.NewMetadataCore(db) + metadataAPI := metadataapi.NewMetadataAPI(metadataCore) usecase := &api.Usecase{ Conf: bc, DB: db, @@ -57,6 +60,7 @@ func wireApp(bc *conf.Bootstrap, log *slog.Logger) (http.Handler, func(), error) AIWebhookAPI: aiWebhookAPI, EventAPI: eventAPI, RecordingAPI: recordingAPI, + MetadataAPI: metadataAPI, } handler := api.NewHTTPHandler(usecase) return handler, func() { diff --git a/internal/core/metadata/core.go b/internal/core/metadata/core.go new file mode 100755 index 0000000..627a81b --- /dev/null +++ b/internal/core/metadata/core.go @@ -0,0 +1,17 @@ +// Code generated by godddx, DO AVOID EDIT. +package metadata + +// Storer data persistence +type Storer interface { + Metadata() MetadataStorer +} + +// Core business domain +type Core struct { + store Storer +} + +// NewCore create business domain +func NewCore(store Storer) Core { + return Core{store: store} +} diff --git a/internal/core/metadata/doc.go b/internal/core/metadata/doc.go new file mode 100644 index 0000000..73e8bbf --- /dev/null +++ b/internal/core/metadata/doc.go @@ -0,0 +1,4 @@ +// Package metadata 通用数据持久化领域 +// 提供 JSON 数据的存储能力,ID 由调用方指定, +// 仅支持创建、修改、按 ID 查询,不提供列表和删除接口 +package metadata diff --git a/internal/core/metadata/metadata.go b/internal/core/metadata/metadata.go new file mode 100755 index 0000000..bc1e9b4 --- /dev/null +++ b/internal/core/metadata/metadata.go @@ -0,0 +1,116 @@ +// Code generated by godddx, DO AVOID EDIT. +package metadata + +import ( + "context" + "log/slog" + + "github.com/ixugo/goddd/pkg/orm" + "github.com/ixugo/goddd/pkg/reason" + "github.com/jinzhu/copier" + "gorm.io/gorm" +) + +// MetadataStorer Instantiation interface +type MetadataStorer interface { + Find(context.Context, *[]*Metadata, orm.Pager, ...orm.QueryOption) (int64, error) + Get(context.Context, *Metadata, ...orm.QueryOption) error + Add(context.Context, *Metadata) error + Edit(context.Context, *Metadata, func(*Metadata), ...orm.QueryOption) error + Del(context.Context, *Metadata, ...orm.QueryOption) error + Count(context.Context, ...orm.QueryOption) (int64, error) + + Session(context.Context, ...func(*gorm.DB) error) error + EditWithSession(*gorm.DB, *Metadata, func(b *Metadata) error, ...orm.QueryOption) error +} + +// FindMetadatas 分页查询(保留代码,当前不提供列表接口) +// func (c Core) FindMetadatas(ctx context.Context, in *FindMetadataInput) ([]*Metadata, int64, error) { +// query := orm.NewQuery(1).OrderBy("created_at DESC") +// items := make([]*Metadata, 0, in.Limit()) +// total, err := c.store.Metadata().Find(ctx, &items, in, query.Encode()...) +// if err != nil { +// return nil, 0, reason.ErrDB.Withf(`Find in[%+v] err[%s]`, in, err.Error()) +// } +// return items, total, nil +// } + +// GetMetadata 按 ID 查询单条数据 +func (c Core) GetMetadata(ctx context.Context, id string) (*Metadata, error) { + out := Metadata{ID: id} + if err := c.store.Metadata().Get(ctx, &out, orm.Where("id=?", id)); err != nil { + if orm.IsErrRecordNotFound(err) { + return nil, reason.ErrNotFound.Withf(`Get id[%v] err[%s]`, id, err.Error()) + } + return nil, reason.ErrDB.Withf(`Get id[%v] err[%s]`, id, err.Error()) + } + return &out, nil +} + +// AddMetadata 创建数据记录 +func (c Core) AddMetadata(ctx context.Context, in *AddMetadataInput) (*Metadata, error) { + var out Metadata + if err := copier.Copy(&out, in); err != nil { + slog.ErrorContext(ctx, "Copy", "err", err) + } + + if err := c.store.Metadata().Add(ctx, &out); err != nil { + if orm.IsDuplicatedKey(err) { + return nil, reason.ErrBadRequest.SetMsg("数据重复").Withf("key[%s]", in.ID) + } + return nil, reason.ErrDB.Withf(`Add err[%s]`, err.Error()) + } + return &out, nil +} + +// EditMetadata 更新数据记录 +func (c Core) EditMetadata(ctx context.Context, in *EditMetadataInput, id string) (*Metadata, error) { + var out Metadata + if err := c.store.Metadata().Edit(ctx, &out, func(b *Metadata) { + if err := copier.Copy(b, in); err != nil { + slog.ErrorContext(ctx, "Copy", "err", err) + } + b.LastUpdatedBy = in.LastUpdatedBy + }, orm.Where("id=?", id)); err != nil { + return nil, reason.ErrDB.Withf(`Edit id[%v] err[%s]`, id, err.Error()) + } + return &out, nil +} + +// SaveMetadata 幂等保存:先尝试更新已有记录,不存在则创建 +func (c Core) SaveMetadata(ctx context.Context, in *SaveMetadataInput, id string) (*Metadata, error) { + var out Metadata + err := c.store.Metadata().Edit(ctx, &out, func(b *Metadata) { + b.Ext = in.Ext + b.LastUpdatedBy = in.LastUpdatedBy + }, orm.Where("id=?", id)) + if err == nil { + return &out, nil + } + if !orm.IsErrRecordNotFound(err) { + return nil, reason.ErrDB.Withf(`Edit id[%v] err[%s]`, id, err.Error()) + } + + out = Metadata{ + ID: id, + Ext: in.Ext, + CreatedBy: in.CreatedBy, + LastUpdatedBy: in.LastUpdatedBy, + } + if err := c.store.Metadata().Add(ctx, &out); err != nil { + if orm.IsDuplicatedKey(err) { + return nil, reason.ErrBadRequest.SetMsg("数据重复").Withf("key[%s]", id) + } + return nil, reason.ErrDB.Withf(`Add err[%s]`, err.Error()) + } + return &out, nil +} + +// DelMetadata 删除数据(保留代码,当前不提供删除接口) +// func (c Core) DelMetadata(ctx context.Context, id string) (*Metadata, error) { +// var out Metadata +// if err := c.store.Metadata().Del(ctx, &out, orm.Where("id=?", id)); err != nil { +// return nil, reason.ErrDB.Withf(`Del id[%v] err[%s]`, id, err.Error()) +// } +// return &out, nil +// } diff --git a/internal/core/metadata/metadata.model.go b/internal/core/metadata/metadata.model.go new file mode 100755 index 0000000..09abc82 --- /dev/null +++ b/internal/core/metadata/metadata.model.go @@ -0,0 +1,26 @@ +// Code generated by godddx, DO AVOID EDIT. +package metadata + +import "github.com/ixugo/goddd/pkg/orm" + +// Metadata domain model +type Metadata struct { + ID string `gorm:"primaryKey" json:"id"` + CreatedAt orm.Time `gorm:"column:created_at;notNull;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 + UpdatedAt orm.Time `gorm:"column:updated_at;notNull;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 + CreatedBy string `gorm:"column:created_by;notNull;default:'';comment:创建者" json:"created_by"` // 创建者 + LastUpdatedBy string `gorm:"column:last_updated_by;notNull;default:'';comment:最后更新者" json:"last_updated_by"` // 最后更新者 + Ext string `gorm:"column:ext;type:text;notNull;default:'';comment:序列化的 JSON 字符串" json:"ext"` // 序列化的 JSON 字符串 +} + +// TableName database table name +func (*Metadata) TableName() string { + return "metadatas" +} + +// CacheKey 缓存主键,必须唯一 +// godddx 生成缓存代码时,依赖的主键 +// 默认应该是 ID 字段,但也可以自定义 +func (m *Metadata) CacheKey() string { + return m.ID +} diff --git a/internal/core/metadata/metadata.param.go b/internal/core/metadata/metadata.param.go new file mode 100755 index 0000000..3c42a02 --- /dev/null +++ b/internal/core/metadata/metadata.param.go @@ -0,0 +1,31 @@ +// Code generated by godddx, DO AVOID EDIT. +package metadata + +// FindMetadataInput 查询数据参数(保留未使用) +// type FindMetadataInput struct { +// web.PagerFilter +// } + +// AddMetadataInput 新增数据参数 +type AddMetadataInput struct { + ID string `json:"id" binding:"required,max=64"` // 调用方指定的唯一标识 + Ext string `json:"ext" binding:"required,max=131072"` // 序列化的 JSON 字符串 + + CreatedBy string `json:"-"` // 创建者(API层填充) + LastUpdatedBy string `json:"-"` // 最后更新者(API层填充) +} + +// EditMetadataInput 编辑数据参数 +type EditMetadataInput struct { + Ext string `json:"ext" binding:"required,max=131072"` // 序列化的 JSON 字符串 + + LastUpdatedBy string `json:"-"` // 最后更新者(API层填充) +} + +// SaveMetadataInput 保存数据参数,合并创建与更新为一个幂等操作 +type SaveMetadataInput struct { + Ext string `json:"ext" binding:"required,max=131072"` // 序列化的 JSON 字符串 + + CreatedBy string `json:"-"` // 创建者(API层填充) + LastUpdatedBy string `json:"-"` // 最后更新者(API层填充) +} diff --git a/internal/core/metadata/metadataapi/metadata.go b/internal/core/metadata/metadataapi/metadata.go new file mode 100755 index 0000000..c859fa0 --- /dev/null +++ b/internal/core/metadata/metadataapi/metadata.go @@ -0,0 +1,61 @@ +// Code generated by godddx, DO AVOID EDIT. +package metadataapi + +import ( + "github.com/gin-gonic/gin" + "github.com/gowvp/owl/internal/core/metadata" + "github.com/gowvp/owl/internal/core/metadata/store/metadatadb" + "github.com/ixugo/goddd/pkg/orm" + "github.com/ixugo/goddd/pkg/web" + "gorm.io/gorm" +) + +// MetadataAPI 为 http 提供通用数据持久化的业务方法 +type MetadataAPI struct { + metadataCore metadata.Core +} + +func NewMetadataCore(db *gorm.DB) metadata.Core { + var store metadata.Storer + store = metadatadb.NewDB(db).AutoMigrate(orm.GetEnabledAutoMigrate()) + return metadata.NewCore(store) +} + +func NewMetadataAPI(core metadata.Core) MetadataAPI { + return MetadataAPI{metadataCore: core} +} + +// RegisterMetadata 注册通用数据持久化路由 +func RegisterMetadata(g gin.IRouter, api MetadataAPI, handler ...gin.HandlerFunc) { + group := g.Group("/metadatas", handler...) + group.GET("/:id", web.WrapH(api.getMetadata)) + group.POST("/:id", web.WrapH(api.saveMetadata)) +} + +// >>> metadata >>>>>>>>>>>>>>>>>>>> + +// findMetadatas 分页查询(保留代码,当前不提供) +// func (a MetadataAPI) findMetadatas(c *gin.Context, in *metadata.FindMetadataInput) (any, error) { +// items, total, err := a.metadataCore.FindMetadatas(c.Request.Context(), in) +// return gin.H{"items": items, "total": total}, err +// } + +// getMetadata 按 ID 查询数据 +func (a MetadataAPI) getMetadata(c *gin.Context, _ *struct{}) (*metadata.Metadata, error) { + metadataID := c.Param("id") + return a.metadataCore.GetMetadata(c.Request.Context(), metadataID) +} + +// saveMetadata 幂等保存:已存在则更新,不存在则创建 +func (a MetadataAPI) saveMetadata(c *gin.Context, in *metadata.SaveMetadataInput) (*metadata.Metadata, error) { + metadataID := c.Param("id") + in.CreatedBy = web.GetUsername(c) + in.LastUpdatedBy = in.CreatedBy + return a.metadataCore.SaveMetadata(c.Request.Context(), in, metadataID) +} + +// delMetadata 删除数据(保留代码,当前不提供) +// func (a MetadataAPI) delMetadata(c *gin.Context, _ *struct{}) (*metadata.Metadata, error) { +// metadataID := c.Param("id") +// return a.metadataCore.DelMetadata(c.Request.Context(), metadataID) +// } diff --git a/internal/core/metadata/model.go b/internal/core/metadata/model.go new file mode 100755 index 0000000..6d4c02d --- /dev/null +++ b/internal/core/metadata/model.go @@ -0,0 +1,2 @@ +// Code generated by godddx, DO AVOID EDIT. +package metadata diff --git a/internal/core/metadata/store/metadatadb/db.go b/internal/core/metadata/store/metadatadb/db.go new file mode 100755 index 0000000..5f14511 --- /dev/null +++ b/internal/core/metadata/store/metadatadb/db.go @@ -0,0 +1,37 @@ +// Code generated by godddx, DO AVOID EDIT. +package metadatadb + +import ( + "github.com/gowvp/owl/internal/core/metadata" + "gorm.io/gorm" +) + +var _ metadata.Storer = DB{} + +// DB Related business namespaces +type DB struct { + db *gorm.DB +} + +// NewDB instance object +func NewDB(db *gorm.DB) DB { + return DB{db: db} +} + +// Metadata Get business instance +func (d DB) Metadata() metadata.MetadataStorer { + return Metadata(d) +} + +// AutoMigrate sync database +func (d DB) AutoMigrate(ok bool) DB { + if !ok { + return d + } + if err := d.db.AutoMigrate( + new(metadata.Metadata), + ); err != nil { + panic(err) + } + return d +} diff --git a/internal/core/metadata/store/metadatadb/metadata.go b/internal/core/metadata/store/metadatadb/metadata.go new file mode 100755 index 0000000..99f057d --- /dev/null +++ b/internal/core/metadata/store/metadatadb/metadata.go @@ -0,0 +1,67 @@ +// Code generated by godddx, DO AVOID EDIT. +package metadatadb + +import ( + "context" + + "github.com/gowvp/owl/internal/core/metadata" + "github.com/ixugo/goddd/pkg/orm" + "gorm.io/gorm" +) + +var _ metadata.MetadataStorer = Metadata{} + +// Metadata Related business namespaces +type Metadata DB + +// NewMetadata instance object +func NewMetadata(db *gorm.DB) Metadata { + return Metadata{db: db} +} + +// Find implements metadata.MetadataStorer. +func (d Metadata) Find(ctx context.Context, bs *[]*metadata.Metadata, page orm.Pager, opts ...orm.QueryOption) (int64, error) { + return orm.FindWithContext(ctx, d.db, bs, page, opts...) +} + +// Get implements metadata.MetadataStorer. +func (d Metadata) Get(ctx context.Context, model *metadata.Metadata, opts ...orm.QueryOption) error { + return orm.FirstWithContext(ctx, d.db, model, opts...) +} + +// Add implements metadata.MetadataStorer. +func (d Metadata) Add(ctx context.Context, model *metadata.Metadata) error { + return d.db.WithContext(ctx).Create(model).Error +} + +// Edit implements metadata.MetadataStorer. +func (d Metadata) Edit(ctx context.Context, model *metadata.Metadata, changeFn func(*metadata.Metadata), opts ...orm.QueryOption) error { + return orm.UpdateWithContext(ctx, d.db, model, changeFn, opts...) +} + +// Del implements metadata.MetadataStorer. +func (d Metadata) Del(ctx context.Context, model *metadata.Metadata, opts ...orm.QueryOption) error { + return orm.DeleteWithContext(ctx, d.db, model, opts...) +} + +// Count implements metadata.MetadataStorer. +func (d Metadata) Count(ctx context.Context, opts ...orm.QueryOption) (int64, error) { + return orm.CountWithContext[metadata.Metadata](ctx, d.db, opts...) +} + +// Session 事务组合 +func (d Metadata) Session(ctx context.Context, changeFns ...func(*gorm.DB) error) error { + return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + for _, fn := range changeFns { + if err := fn(tx); err != nil { + return err + } + } + return nil + }) +} + +// EditWithSession 修改事务 +func (d Metadata) EditWithSession(tx *gorm.DB, model *metadata.Metadata, changeFn func(b *metadata.Metadata) error, opts ...orm.QueryOption) error { + return orm.UpdateWithSession(tx, model, changeFn, opts...) +} diff --git a/internal/web/api/api.go b/internal/web/api/api.go index 6d2deef..5eb3bdf 100644 --- a/internal/web/api/api.go +++ b/internal/web/api/api.go @@ -18,6 +18,7 @@ import ( "github.com/gin-contrib/cors" "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" + "github.com/gowvp/owl/internal/core/metadata/metadataapi" "github.com/gowvp/owl/internal/core/sms" "github.com/gowvp/owl/pkg/ota" "github.com/gowvp/owl/plugin/stat" @@ -122,6 +123,7 @@ func setupRouter(r *gin.Engine, uc *Usecase) { uc.AIWebhookAPI.StartAISyncLoop(context.Background(), uc.SMSAPI.smsCore) RegisterEvent(r, uc.EventAPI, auth) RegisterRecording(r, uc.RecordingAPI, auth) + metadataapi.RegisterMetadata(r, uc.MetadataAPI, auth) } type playOutput struct { diff --git a/internal/web/api/provider.go b/internal/web/api/provider.go index cca7d3a..5995f94 100644 --- a/internal/web/api/provider.go +++ b/internal/web/api/provider.go @@ -15,6 +15,7 @@ import ( "github.com/gowvp/owl/internal/core/ipc" "github.com/gowvp/owl/internal/core/ipc/store/ipccache" "github.com/gowvp/owl/internal/core/ipc/store/ipcdb" + "github.com/gowvp/owl/internal/core/metadata/metadataapi" "github.com/gowvp/owl/internal/core/recording" "github.com/gowvp/owl/internal/core/recording/adapter" "github.com/gowvp/owl/internal/core/sms" @@ -49,6 +50,7 @@ var ( NewEventCore, NewEventAPI, // Recording: Store -> SMSProvider(adapter) -> Core -> API NewRecordingStore, NewSMSProviderAdapter, NewRecordingCore, NewRecordingAPI, + metadataapi.NewMetadataCore, metadataapi.NewMetadataAPI, ) ) @@ -69,6 +71,7 @@ type Usecase struct { EventAPI EventAPI RecordingAPI RecordingAPI + MetadataAPI metadataapi.MetadataAPI } // NewHTTPHandler 生成Gin框架路由内容 diff --git a/main.go b/main.go index 865cea5..7e72418 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "github.com/gowvp/owl/internal/app" "github.com/gowvp/owl/internal/conf" + "github.com/ixugo/goddd/domain/version/versionapi" "github.com/ixugo/goddd/pkg/system" ) @@ -57,6 +58,10 @@ func main() { })) } + // 如果需要执行表迁移,递增此版本号和表更新说明 + versionapi.DBVersion = "0.0.25" + versionapi.DBRemark = "onvif device support" + app.Run(&bc) }