Dockerfile

This commit is contained in:
2026-03-18 10:18:03 +08:00
parent 5c5dbc7420
commit b65f3439f3
189 changed files with 19027 additions and 0 deletions

110
dao/asset/asset_dao.go Normal file
View File

@@ -0,0 +1,110 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/asset"
entity "assets/model/entity/asset"
"context"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
var Asset = new(assetDao)
type assetDao struct {
}
// Insert 插入资产
func (d *assetDao) Insert(ctx context.Context, req *dto.CreateAssetReq) (id int64, err error) {
var result entity.Asset
if err = gconv.Struct(req, &result); err != nil {
return
}
return gfdb.DB(ctx).Model(ctx, public.AssetCollection).Ctx(ctx).Data(&result).InsertAndGetId()
}
// GetOne 获取单个资产
func (d *assetDao) GetOne(ctx context.Context, req *dto.GetAssetReq) (res *entity.Asset, err error) {
err = gfdb.DB(ctx).Model(ctx, public.AssetCollection).Ctx(ctx).Where("id", req.Id).Scan(&res)
return
}
// Update 更新资产
func (d *assetDao) Update(ctx context.Context, req *dto.UpdateAssetReq) (err error) {
data := g.Map{
"name": req.Name,
"description": req.Description,
"type": req.Type,
"category_id": req.CategoryId,
"image_url": req.ImageURL,
"images": req.Images,
"status": req.Status,
"online_time": req.OnlineTime,
"offline_time": req.OfflineTime,
"physical_asset_config": req.PhysicalAssetConfig,
"service_asset_config": req.ServiceAssetConfig,
"virtual_asset_config": req.VirtualAssetConfig,
"metadata": req.Metadata,
}
_, err = gfdb.DB(ctx).Model(ctx, public.AssetCollection).Ctx(ctx).Where("id", req.Id).Update(data)
return
}
// DeleteFake 删除资产-根据id进行假删
func (d *assetDao) DeleteFake(ctx context.Context, req *dto.DeleteAssetReq) (err error) {
_, err = gfdb.DB(ctx).Model(ctx, public.AssetCollection).Ctx(ctx).Where("id", req.Id).Update(g.Map{
"is_deleted": true,
})
return
}
// GetOneById 通过ID获取单个资产内部使用uint64
func (d *assetDao) GetOneById(ctx context.Context, id uint64) (res *entity.Asset, err error) {
err = gfdb.DB(ctx).Model(ctx, public.AssetCollection).Ctx(ctx).Where("id", id).Scan(&res)
return
}
// Count 获取资产数量
func (d *assetDao) Count(ctx context.Context, req *dto.ListAssetReq) (count int64, err error) {
m := d.buildListFilter(ctx, req)
c, err := m.Count()
return int64(c), err
}
// List 获取资产列表
func (d *assetDao) List(ctx context.Context, req *dto.ListAssetReq) (res []entity.Asset, total int, err error) {
m := d.buildListFilter(ctx, req)
if req.Page != nil {
m = m.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
err = m.ScanAndCount(&res, &total, false)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *assetDao) buildListFilter(ctx context.Context, req *dto.ListAssetReq) *gdb.Model {
m := gfdb.DB(ctx).Model(ctx, public.AssetCollection).Cache(ctx).Where("is_deleted", false)
if !g.IsEmpty(req.Name) {
m = m.Where("name", req.Name)
}
if !g.IsEmpty(req.Type) {
m = m.Where("type", req.Type)
}
if !g.IsEmpty(req.CategoryId) {
m = m.Where("category_id", req.CategoryId)
}
if !g.IsEmpty(req.Status) {
m = m.Where("status", req.Status)
}
if !g.IsEmpty(req.CategoryPath) {
m = m.WhereLike("category_path", req.CategoryPath+"%")
}
if !g.IsEmpty(req.Keyword) {
m = m.WhereLike("name", "%"+req.Keyword+"%")
}
return m
}

130
dao/asset/asset_sku_dao.go Normal file
View File

@@ -0,0 +1,130 @@
package dao
import (
"assets/consts/public"
"assets/model/dto/asset"
"assets/model/entity/asset"
"context"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var AssetSku = new(assetSku)
type assetSku struct {
}
// Insert 插入SKU
func (d *assetSku) Insert(ctx context.Context, req *dto.CreateAssetSkuReq) (ids []any, err error) {
var result *entity.AssetSku
if err = utils.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.AssetSkuCollection)
return
}
// Update 更新SKU
func (d *assetSku) Update(ctx context.Context, req *dto.UpdateAssetSkuReq) (err error) {
buildUpdateData, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": buildUpdateData}
if !g.IsEmpty(req.Stock) {
// 从$set中移除stock字段避免$set和$inc冲突
delete(buildUpdateData, "stock")
update = bson.M{
"$inc": bson.M{
"stock": req.Stock,
},
}
if len(buildUpdateData) > 0 {
update["$set"] = buildUpdateData
}
}
_, err = mongo.DB().Update(ctx, filter, update, public.AssetSkuCollection)
return
}
// DeleteFake 删除SKU-根据id进行假删
func (d *assetSku) DeleteFake(ctx context.Context, req *dto.DeleteAssetSkuReq) (err error) {
filter := bson.M{"_id": req.Id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.AssetSkuCollection)
return
}
// GetOne 获取单个SKU
func (d *assetSku) GetOne(ctx context.Context, req *dto.GetAssetSkuReq, noTenantId bool) (res *entity.AssetSku, err error) {
filter := bson.M{"_id": req.Id}
if noTenantId {
err = mongo.DB().NoTenantId().FindOne(ctx, filter, &res, public.AssetSkuCollection)
} else {
err = mongo.DB().FindOne(ctx, filter, &res, public.AssetSkuCollection)
}
return
}
// GetListByAssetIdExcludeCurrentSku 根据资产ID获取SKU列表并且排除当前SKU
func (d *assetSku) GetListByAssetIdExcludeCurrentSku(ctx context.Context, assetId *bson.ObjectID, req *dto.ListAssetSkuReq) (res []entity.AssetSku, total int64, err error) {
filter := bson.M{"assetId": assetId, "_id": bson.M{"$ne": req.Id}}
total, err = mongo.DB().Find(ctx, filter, &res, public.AssetSkuCollection, req.Page, req.OrderBy)
return
}
// List 获取SKU列表
func (d *assetSku) List(ctx context.Context, req *dto.ListAssetSkuReq, noTenantId bool) (res []entity.AssetSku, total int64, err error) {
// 构建查询过滤条件
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
// 排序处理
req.OrderBy = []beans.OrderBy{
{Field: "sort", Order: beans.Asc},
{Field: "createdAt", Order: beans.Desc},
}
if noTenantId {
total, err = mongo.DB().NoTenantId().Find(ctx, filter, &res, public.AssetSkuCollection, req.Page, req.OrderBy)
} else {
total, err = mongo.DB().Find(ctx, filter, &res, public.AssetSkuCollection, req.Page, req.OrderBy)
}
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *assetSku) buildListFilter(ctx context.Context, req *dto.ListAssetSkuReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.AssetId) {
filter["assetId"] = req.AssetId
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.CategoryPath) {
filter["categoryPath"] = bson.M{"$regex": "^" + req.CategoryPath, "$options": "i"}
}
if !g.IsEmpty(req.Keyword) {
orConditions := bson.A{
bson.M{"skuName": bson.M{"$regex": req.Keyword, "$options": "i"}},
bson.M{"assetName": bson.M{"$regex": req.Keyword, "$options": "i"}},
}
filter["$or"] = orConditions
}
if req.MinPrice > 0 {
filter["price"] = bson.M{"$gte": req.MinPrice}
}
if req.MaxPrice > 0 {
if filter["price"] == nil {
filter["price"] = bson.M{}
}
filter["price"].(bson.M)["$lte"] = req.MaxPrice
}
return
}

95
dao/asset/category_dao.go Normal file
View File

@@ -0,0 +1,95 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/asset"
entity "assets/model/entity/asset"
"context"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/guid"
"github.com/gogf/gf/v2/frame/g"
)
var Category = new(category)
type category struct {
}
// Insert 插入分类
func (d *category) Insert(ctx context.Context, req *dto.CreateCategoryReq) (res *entity.Category, err error) {
if err = gconv.Struct(req, &res); err != nil {
return
}
res.Bid = guid.S()
_, err = gfdb.DB(ctx).Model(ctx, public.CategoryCollection).Insert(&res)
return res, nil
}
// Update 更新分类
func (d *category) Update(ctx context.Context, req *dto.UpdateCategoryReq) (err error) {
model := gfdb.DB(ctx).Model(ctx, public.CategoryCollection).Data(gconv.Map(&req)).OmitEmpty()
if !g.IsEmpty(req.Bid) {
model.Where("bid", req.Bid)
}
if !g.IsEmpty(req.Id) {
model.Where("id", req.Id)
}
_, err = model.Update()
return
}
// GetOne 获取单个分类
func (d *category) GetOne(ctx context.Context, req *dto.GetCategoryReq, fields ...string) (category *entity.Category, err error) {
model := gfdb.DB(ctx).Model(ctx, public.CategoryCollection)
if !g.IsEmpty(req.Bid) {
model.Where(entity.CategoryCol.Bid, req.Bid)
}
if !g.IsEmpty(req.Id) {
model.Where(entity.CategoryCol.Id, req.Id)
}
res, err := model.Fields(fields).One()
err = res.Struct(&category)
return
}
// DeleteFake 删除分类-根据id及父id进行假删
func (d *category) DeleteFake(ctx context.Context, req *dto.DeleteCategoryReq) (err error) {
model := gfdb.DB(ctx).Model(ctx, public.CategoryCollection)
model.Where(entity.CategoryCol.Bid, req.Bid)
_, err = model.Delete()
return
}
// Count 根据条件统计分类数量
func (d *category) Count(ctx context.Context, req *dto.ListCategoryReq) (count int64, err error) {
m := d.buildListFilter(ctx, req)
c, err := m.Count()
return int64(c), err
}
// List 获取分类列表
func (d *category) List(ctx context.Context, req *dto.ListCategoryReq, fields ...string) (res []entity.Category, total int, err error) {
model := d.buildListFilter(ctx, req)
model.Fields(fields)
r, total, err := model.AllAndCount(false)
err = gconv.Structs(r.List(), &res)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *category) buildListFilter(ctx context.Context, req *dto.ListCategoryReq) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, public.CategoryCollection).Model
if !g.IsEmpty(req.Keyword) {
model.WhereLike(entity.CategoryCol.Name, "%"+req.Keyword+"%")
}
model.Where(entity.CategoryCol.ParentId, req.ParentId)
model.Where(entity.CategoryCol.Status, req.Status)
model.OrderAsc(entity.CategoryCol.Sort)
model.OrderDesc(entity.CategoryCol.CreatedAt)
model.OmitEmptyWhere()
return model
}

View File

@@ -0,0 +1,116 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/asset"
entity "assets/model/entity/asset"
"context"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"go.mongodb.org/mongo-driver/v2/bson"
)
var PrivateCategory = new(privateCategory)
type privateCategory struct{}
// Insert 插入私域分类
func (d *privateCategory) Insert(ctx context.Context, req *dto.CreatePrivateCategoryReq) (ids []interface{}, err error) {
var result *entity.PrivateCategory
if err = gconv.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.PrivateCategoryCollection)
return
}
// BatchInsert 批量插入私域分类
func (d *privateCategory) BatchInsert(ctx context.Context, req *dto.BatchCreatePrivateCategoryReq) (ids []interface{}, err error) {
items := make([]*entity.PrivateCategory, 0, len(req.Categories))
for _, item := range req.Categories {
var result *entity.PrivateCategory
if err = gconv.Struct(item, &result); err != nil {
return
}
items = append(items, result)
}
interfaces := make([]interface{}, len(items))
for i, item := range items {
interfaces[i] = item
}
ids, err = mongo.DB().Insert(ctx, interfaces, public.PrivateCategoryCollection)
return
}
// GetOne 获取单个私域分类
func (d *privateCategory) GetOne(ctx context.Context, id *bson.ObjectID) (category *entity.PrivateCategory, err error) {
filter := bson.M{"_id": id}
err = mongo.DB().FindOne(ctx, filter, &category, public.PrivateCategoryCollection)
return
}
// Update 更新私域分类
func (d *privateCategory) Update(ctx context.Context, req *dto.UpdatePrivateCategoryReq) (err error) {
buildUpdateData, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.ID}
update := bson.M{"$set": buildUpdateData}
_, err = mongo.DB().Update(ctx, filter, update, public.PrivateCategoryCollection)
return
}
// DeleteFake 删除私域分类-根据id进行假删
func (d *privateCategory) DeleteFake(ctx context.Context, id *bson.ObjectID) (err error) {
filter := bson.M{"_id": id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.PrivateCategoryCollection)
return
}
// List 获取私域分类列表
func (d *privateCategory) List(ctx context.Context, req *dto.ListPrivateCategoryReq) (res []*entity.PrivateCategory, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.PrivateCategoryCollection, nil, nil)
return
}
// GetTree 获取私域分类树
func (d *privateCategory) GetTree(ctx context.Context) (res []*entity.PrivateCategory, err error) {
filter := bson.M{}
_, err = mongo.DB().Find(ctx, filter, &res, public.PrivateCategoryCollection, nil, nil)
return
}
// GetByParentId 根据父ID获取子分类列表
func (d *privateCategory) GetByParentId(ctx context.Context, parentId string) (res []*entity.PrivateCategory, err error) {
filter := bson.M{"parentId": parentId}
_, err = mongo.DB().Find(ctx, filter, &res, public.PrivateCategoryCollection, nil, nil)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *privateCategory) buildListFilter(ctx context.Context, req *dto.ListPrivateCategoryReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.Name) {
filter["name"] = bson.M{"$regex": req.Name, "$options": "i"}
}
if !g.IsEmpty(req.ParentID) {
filter["parentId"] = req.ParentID
}
if !g.IsEmpty(req.Level) {
filter["level"] = req.Level
}
if req.IsLeafNode != nil {
filter["isLeafNode"] = *req.IsLeafNode
}
return
}

View File

@@ -0,0 +1,119 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/asset"
entity "assets/model/entity/asset"
"context"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"go.mongodb.org/mongo-driver/v2/bson"
)
var PrivateSku = new(privateSku)
type privateSku struct{}
// Insert 插入私域SKU
func (d *privateSku) Insert(ctx context.Context, req *dto.CreatePrivateSkuReq) (ids []interface{}, err error) {
var result *entity.PrivateSku
if err = gconv.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.PrivateSkuCollection)
return
}
// BatchInsert 批量插入私域SKU
func (d *privateSku) BatchInsert(ctx context.Context, req *dto.BatchCreatePrivateSkuReq) (ids []interface{}, err error) {
items := make([]*entity.PrivateSku, 0, len(req.Skus))
for _, item := range req.Skus {
var result *entity.PrivateSku
if err = gconv.Struct(item, &result); err != nil {
return
}
items = append(items, result)
}
interfaces := make([]interface{}, len(items))
for i, item := range items {
interfaces[i] = item
}
ids, err = mongo.DB().Insert(ctx, interfaces, public.PrivateSkuCollection)
return
}
// GetOne 获取单个私域SKU
func (d *privateSku) GetOne(ctx context.Context, id *bson.ObjectID) (sku *entity.PrivateSku, err error) {
filter := bson.M{"_id": id}
err = mongo.DB().FindOne(ctx, filter, &sku, public.PrivateSkuCollection)
return
}
// Update 更新私域SKU
func (d *privateSku) Update(ctx context.Context, req *dto.UpdatePrivateSkuReq) (err error) {
buildUpdateData, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.ID}
update := bson.M{"$set": buildUpdateData}
_, err = mongo.DB().Update(ctx, filter, update, public.PrivateSkuCollection)
return
}
// UpdateStock 更新库存
func (d *privateSku) UpdateStock(ctx context.Context, id *bson.ObjectID, stockChange int) (err error) {
filter := bson.M{"_id": id}
update := bson.M{"$inc": bson.M{"stock": stockChange}}
_, err = mongo.DB().Update(ctx, filter, update, public.PrivateSkuCollection)
return
}
// DeleteFake 删除私域SKU-根据id进行假删
func (d *privateSku) DeleteFake(ctx context.Context, id *bson.ObjectID) (err error) {
filter := bson.M{"_id": id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.PrivateSkuCollection)
return
}
// List 获取私域SKU列表
func (d *privateSku) List(ctx context.Context, req *dto.ListPrivateSkuReq) (res []*entity.PrivateSku, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.PrivateSkuCollection, nil, nil)
return
}
// ListByCategoryPath 根据分类路径获取SKU列表
func (d *privateSku) ListByCategoryPath(ctx context.Context, categoryPath string) (res []*entity.PrivateSku, err error) {
filter := bson.M{"privateCategoryPath": bson.M{"$regex": "^" + categoryPath, "$options": "i"}}
_, err = mongo.DB().Find(ctx, filter, &res, public.PrivateSkuCollection, nil, nil)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *privateSku) buildListFilter(ctx context.Context, req *dto.ListPrivateSkuReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.SkuName) {
filter["skuName"] = bson.M{"$regex": req.SkuName, "$options": "i"}
}
if !g.IsEmpty(req.PrivateCategoryPath) {
filter["privateCategoryPath"] = bson.M{"$regex": "^" + req.PrivateCategoryPath, "$options": "i"}
}
if req.MinPrice > 0 {
filter["price"] = bson.M{"$gte": req.MinPrice}
}
if req.MaxPrice > 0 {
if filter["price"] == nil {
filter["price"] = bson.M{}
}
filter["price"].(bson.M)["$lte"] = req.MaxPrice
}
return
}

325
dao/base/catch_sql.go Normal file
View File

@@ -0,0 +1,325 @@
package base
import (
"context"
"fmt"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// ==================== CatchSQL 全局SQL条件拼接控制 ====================
// SQLConditionBuilder SQL条件构建器
type SQLConditionBuilder struct {
// 是否自动添加租户ID条件
EnableTenantId bool
// 是否自动添加创建人条件
EnableCreator bool
// 是否自动添加修改人条件
EnableUpdater bool
// 是否自动添加删除标记条件(只查询未删除数据)
EnableDeletedFilter bool
// 自定义额外条件
ExtraConditions map[string]interface{}
}
// DefaultSQLConditionBuilder 默认SQL条件构建器配置
var DefaultSQLConditionBuilder = SQLConditionBuilder{
EnableTenantId: true,
EnableCreator: false,
EnableUpdater: false,
EnableDeletedFilter: true,
ExtraConditions: make(map[string]interface{}),
}
// ctxKeyCatchSQL 上下文键
type ctxKeyCatchSQL string
const (
// ctxKeySQLBuilder SQL构建器上下文键
ctxKeySQLBuilder ctxKeyCatchSQL = "catch_sql_builder"
// ctxKeySkipCatchSQL 跳过CatchSQL的上下文键
ctxKeySkipCatchSQL ctxKeyCatchSQL = "catch_sql_skip"
)
// WithSQLBuilder 设置自定义SQL条件构建器到上下文
func WithSQLBuilder(ctx context.Context, builder *SQLConditionBuilder) context.Context {
return context.WithValue(ctx, ctxKeySQLBuilder, builder)
}
// GetSQLBuilder 从上下文获取SQL条件构建器
func GetSQLBuilder(ctx context.Context) *SQLConditionBuilder {
if ctx == nil {
return &DefaultSQLConditionBuilder
}
if builder, ok := ctx.Value(ctxKeySQLBuilder).(*SQLConditionBuilder); ok && builder != nil {
return builder
}
return &DefaultSQLConditionBuilder
}
// SkipCatchSQL 跳过CatchSQL条件拼接
func SkipCatchSQL(ctx context.Context) context.Context {
return context.WithValue(ctx, ctxKeySkipCatchSQL, true)
}
// IsSkipCatchSQL 检查是否跳过CatchSQL
func IsSkipCatchSQL(ctx context.Context) bool {
if ctx == nil {
return false
}
v, ok := ctx.Value(ctxKeySkipCatchSQL).(bool)
return ok && v
}
// ==================== CatchSQL 核心方法 ====================
// CatchSQL 全局统一控制SQL条件拼接
// 根据上下文自动添加租户ID、创建人、修改人、删除标记等条件
// 使用示例:
//
// // 基础使用(自动添加默认条件)
// m := base.CatchSQL(ctx, g.DB().Model("asset"))
// m.Where("status", 1).Scan(&result)
//
// // 自定义条件构建器
// builder := &base.SQLConditionBuilder{
// EnableTenantId: true,
// EnableCreator: true,
// }
// ctx = base.WithSQLBuilder(ctx, builder)
// m := base.CatchSQL(ctx, g.DB().Model("asset"))
//
// // 跳过CatchSQL
// ctx = base.SkipCatchSQL(ctx)
// m := base.CatchSQL(ctx, g.DB().Model("asset"))
func CatchSQL(ctx context.Context, model *gdb.Model) *gdb.Model {
if ctx == nil || model == nil {
return model
}
// 检查是否跳过
if IsSkipCatchSQL(ctx) {
return model
}
builder := GetSQLBuilder(ctx)
userInfo, _ := utils.GetUserInfo(ctx)
// 1. 自动添加租户ID条件
if builder.EnableTenantId && !g.IsEmpty(userInfo.TenantId) {
model = model.Where("tenant_id", userInfo.TenantId)
}
// 2. 自动添加创建人条件
if builder.EnableCreator && !g.IsEmpty(userInfo.UserName) {
model = model.Where("creator", userInfo.UserName)
}
// 3. 自动添加修改人条件
if builder.EnableUpdater && !g.IsEmpty(userInfo.UserName) {
model = model.Where("updater", userInfo.UserName)
}
// 4. 自动添加删除标记条件(只查询未删除数据)
if builder.EnableDeletedFilter {
model = model.Where("is_deleted", 0)
}
// 5. 添加自定义额外条件
for field, value := range builder.ExtraConditions {
if field != "" && value != nil {
model = model.Where(field, value)
}
}
return model
}
// CatchSQLWithTable 指定表名创建带CatchSQL条件的Model
// 使用示例:
//
// m := base.CatchSQLWithTable(ctx, "asset")
// m.Where("status", 1).Scan(&result)
func CatchSQLWithTable(ctx context.Context, table string) *gdb.Model {
if ctx == nil {
return g.DB().Model(table).Safe()
}
model := g.DB().Model(table).Safe().Ctx(ctx)
return CatchSQL(ctx, model)
}
// CatchSQLWithSchema 指定Schema和表名创建带CatchSQL条件的Model
// 使用示例:
//
// m := base.CatchSQLWithSchema(ctx, "public", "asset")
// m.Where("status", 1).Scan(&result)
func CatchSQLWithSchema(ctx context.Context, schema, table string) *gdb.Model {
if ctx == nil {
return g.DB().Schema(schema).Model(table).Safe()
}
model := g.DB().Schema(schema).Model(table).Safe().Ctx(ctx)
return CatchSQL(ctx, model)
}
// ==================== 快捷条件构建器 ====================
// NewSQLBuilder 创建新的SQL条件构建器
func NewSQLBuilder() *SQLConditionBuilder {
return &SQLConditionBuilder{
EnableTenantId: true,
EnableCreator: false,
EnableUpdater: false,
EnableDeletedFilter: true,
ExtraConditions: make(map[string]interface{}),
}
}
// WithTenantId 启用/禁用租户ID条件
func (b *SQLConditionBuilder) WithTenantId(enable bool) *SQLConditionBuilder {
b.EnableTenantId = enable
return b
}
// WithCreator 启用/禁用创建人条件
func (b *SQLConditionBuilder) WithCreator(enable bool) *SQLConditionBuilder {
b.EnableCreator = enable
return b
}
// WithUpdater 启用/禁用修改人条件
func (b *SQLConditionBuilder) WithUpdater(enable bool) *SQLConditionBuilder {
b.EnableUpdater = enable
return b
}
// WithDeletedFilter 启用/禁用删除标记过滤
func (b *SQLConditionBuilder) WithDeletedFilter(enable bool) *SQLConditionBuilder {
b.EnableDeletedFilter = enable
return b
}
// WithExtraCondition 添加自定义条件
func (b *SQLConditionBuilder) WithExtraCondition(field string, value interface{}) *SQLConditionBuilder {
if b.ExtraConditions == nil {
b.ExtraConditions = make(map[string]interface{})
}
b.ExtraConditions[field] = value
return b
}
// Build 构建并返回Model
func (b *SQLConditionBuilder) Build(ctx context.Context, model *gdb.Model) *gdb.Model {
ctx = WithSQLBuilder(ctx, b)
return CatchSQL(ctx, model)
}
// ==================== 常用场景快捷方法 ====================
// CatchSQLForTenant 只添加租户ID条件的快捷方法
func CatchSQLForTenant(ctx context.Context, model *gdb.Model) *gdb.Model {
builder := NewSQLBuilder().
WithTenantId(true).
WithDeletedFilter(false)
ctx = WithSQLBuilder(ctx, builder)
return CatchSQL(ctx, model)
}
// CatchSQLForCreator 只添加创建人条件的快捷方法
func CatchSQLForCreator(ctx context.Context, model *gdb.Model) *gdb.Model {
builder := NewSQLBuilder().
WithTenantId(false).
WithCreator(true).
WithDeletedFilter(false)
ctx = WithSQLBuilder(ctx, builder)
return CatchSQL(ctx, model)
}
// CatchSQLForList 列表查询的快捷方法租户ID + 删除标记)
func CatchSQLForList(ctx context.Context, model *gdb.Model) *gdb.Model {
builder := NewSQLBuilder().
WithTenantId(true).
WithDeletedFilter(true)
ctx = WithSQLBuilder(ctx, builder)
return CatchSQL(ctx, model)
}
// CatchSQLForAdmin 管理员查询的快捷方法(只过滤删除标记,不过滤租户)
func CatchSQLForAdmin(ctx context.Context, model *gdb.Model) *gdb.Model {
builder := NewSQLBuilder().
WithTenantId(false).
WithDeletedFilter(true)
ctx = WithSQLBuilder(ctx, builder)
return CatchSQL(ctx, model)
}
// ==================== DAO层无感知集成 ====================
// CtxModel 创建带 CatchSQL 条件的 ModelDAO层无感知使用
// 这是推荐的无感知使用方式,直接在 DAO 的 Ctx() 方法中调用
//
// 使用示例DAO层
//
// func (d *assetDao) Ctx(ctx context.Context) *gdb.Model {
// return base.CtxModel(ctx, entity.Asset{})
// }
//
// func (d *assetDao) GetById(ctx context.Context, id uint64) (*entity.Asset, error) {
// var result entity.Asset
// err := d.Ctx(ctx).Where("id", id).Scan(&result) // 自动带 tenant_id 和 is_deleted=0 条件
// return &result, err
// }
func CtxModel(ctx context.Context, table interface{}) *gdb.Model {
if ctx == nil {
return g.DB().Model(table).Safe()
}
model := g.DB().Model(table).Safe().Ctx(ctx)
return CatchSQL(ctx, model)
}
// CtxModelWithHook 创建带 CatchSQL 条件和 Hook 的 Model完整版
// 同时启用 CatchSQL 条件拼接和 CatchSQLHook 自动字段赋值
//
// 使用示例DAO层
//
// func (d *assetDao) Ctx(ctx context.Context) *gdb.Model {
// return base.CtxModelWithHook(ctx, entity.Asset{})
// }
//
// func (d *assetDao) Insert(ctx context.Context, data g.Map) (int64, error) {
// return d.Ctx(ctx).Data(data).InsertAndGetId() // 自动赋值 tenant_id, creator, updater
// }
func CtxModelWithHook(ctx context.Context, table interface{}) *gdb.Model {
if ctx == nil {
return g.DB().Model(table).Safe()
}
model := g.DB().Model(table).Safe().Ctx(ctx)
// 先应用 CatchSQL 条件
model = CatchSQL(ctx, model)
// 再应用 Hook用于 Insert/Update 自动字段赋值)
model = model.Hook(CatchSQLHook())
return model
}
// ==================== 调试工具 ====================
// GetCatchSQLInfo 获取当前CatchSQL的配置信息用于调试
func GetCatchSQLInfo(ctx context.Context) string {
if IsSkipCatchSQL(ctx) {
return "CatchSQL: skipped"
}
builder := GetSQLBuilder(ctx)
userInfo, _ := utils.GetUserInfo(ctx)
return fmt.Sprintf(
"CatchSQL{TenantId:%v(%v), Creator:%v(%v), Updater:%v(%v), DeletedFilter:%v, Extra:%v}",
builder.EnableTenantId, userInfo.TenantId,
builder.EnableCreator, userInfo.UserName,
builder.EnableUpdater, userInfo.UserName,
builder.EnableDeletedFilter,
len(builder.ExtraConditions),
)
}

75
dao/base/db.go Normal file
View File

@@ -0,0 +1,75 @@
package base
import (
"context"
"database/sql"
"github.com/gogf/gf/contrib/drivers/pgsql/v2"
"github.com/gogf/gf/v2/database/gdb"
)
type GDB struct {
*pgsql.Driver
}
var (
// customDriverName is my driver name, which is used for registering.
customDriverName = "MyDriver"
)
func init() {
// It here registers my custom driver in package initialization function "init".
// You can later use this type in the database configuration.
if err := gdb.Register(customDriverName, &GDB{}); err != nil {
panic(err)
}
}
// New creates and returns a database object for mysql.
// It implements the interface of gdb.Driver for extra database driver installation.
func (d *GDB) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
return &GDB{
&pgsql.Driver{
Core: core,
},
}, nil
}
// DoCommit commits current sql and arguments to underlying sql driver.
func (d *GDB) DoCommit(ctx context.Context, in gdb.DoCommitInput) (out gdb.DoCommitOutput, err error) {
out, err = d.Core.DoCommit(ctx, in)
return
}
// 其他接口方法
func (d *GDB) DoFilter(ctx context.Context, link gdb.Link, sql string, args []any) (string, []any, error) {
return d.Core.DoFilter(ctx, link, sql, args)
}
func (d *GDB) DoPrepare(ctx context.Context, link gdb.Link, sql string) (*gdb.Stmt, error) {
return d.Core.DoPrepare(ctx, link, sql)
}
func (d *GDB) ConvertValueForField(ctx context.Context, fieldType string, fieldValue any) (any, error) {
return fieldValue, nil
}
func (d *GDB) ConvertValueForLocal(ctx context.Context, fieldType string, fieldValue any) (any, error) {
return d.Core.ConvertValueForLocal(ctx, fieldType, fieldValue)
}
func (d *GDB) Exec(ctx context.Context, sql string, args ...any) (sql.Result, error) {
in := gdb.DoCommitInput{Type: gdb.SqlTypeExecContext, Sql: sql, Args: args}
out, err := d.DoCommit(ctx, in)
if err != nil {
return nil, err
}
return out.Result, nil
}
func (d *GDB) DoExec(ctx context.Context, link gdb.Link, sql string, args ...any) (sql.Result, error) {
in := gdb.DoCommitInput{Type: gdb.SqlTypeExecContext, Sql: sql, Args: args}
out, err := d.DoCommit(ctx, in)
if err != nil {
return nil, err
}
return out.Result, nil
}

456
dao/base/hook.go Normal file
View File

@@ -0,0 +1,456 @@
package base
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"github.com/gogf/gf/v2/text/gstr"
"time"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/database/gredis"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/util/gconv"
)
// ==================== 上下文键定义 ====================
type ctxKey string
const (
// ctxKeySkipTenant 跳过租户ID自动赋值的上下文键
ctxKeySkipTenant ctxKey = "hook_skip_tenant"
// ctxKeyCacheEnabled 缓存启用标记的上下文键
ctxKeyCacheEnabled ctxKey = "hook_cache_enabled"
// ctxKeyCachePrefix 缓存key前缀的上下文键
ctxKeyCachePrefix ctxKey = "hook_cache_prefix"
)
// ==================== 租户相关 ====================
// SkipTenantId 在上下文中标记跳过租户ID自动赋值
func SkipTenantId(ctx context.Context) context.Context {
return context.WithValue(ctx, ctxKeySkipTenant, true)
}
// isSkipTenant 检查是否跳过租户ID
func isSkipTenant(ctx context.Context) bool {
if ctx == nil {
return false
}
v, ok := ctx.Value(ctxKeySkipTenant).(bool)
return ok && v
}
// ==================== 缓存配置 ====================
// CacheConfig 缓存配置
type CacheConfig struct {
// 本地缓存过期时间默认60秒
LocalTTL int
// Redis缓存过期时间默认300秒
RedisTTL int
}
// DefaultCacheConfig 默认缓存配置
var DefaultCacheConfig = CacheConfig{
LocalTTL: 60,
RedisTTL: 300,
}
// isCacheEnabled 检查是否启用缓存
func isCacheEnabled(ctx context.Context) bool {
if ctx == nil {
return false
}
v, ok := ctx.Value(ctxKeyCacheEnabled).(bool)
return ok && v
}
// getCachePrefix 获取缓存key前缀
func getCachePrefix(ctx context.Context) string {
if ctx == nil {
return ""
}
v, ok := ctx.Value(ctxKeyCachePrefix).(string)
if !ok {
return ""
}
return v
}
// ==================== 缓存管理器(单例) ====================
var (
localCache *gcache.Cache
)
// getLocalCache 获取本地缓存实例
func getLocalCache() *gcache.Cache {
if localCache == nil {
localCache = gcache.New()
}
return localCache
}
// buildCacheKey 构建缓存key
// 根据表名和查询条件自动生成key
func buildCacheKey(prefix string, table string, where ...interface{}) string {
// 基础key: prefix:table
key := fmt.Sprintf("%s:%s", prefix, table)
// 如果有where条件追加到key中
if len(where) > 0 {
for _, w := range where {
key = fmt.Sprintf("%s:%v", key, w)
}
}
return key
}
// getFromCache 从缓存获取数据(本地缓存 -> Redis
func getFromCache(ctx context.Context, key string) ([]byte, bool) {
config := DefaultCacheConfig
// 1. 先查本地缓存
if val, err := getLocalCache().Get(ctx, key); err == nil && val != nil {
if data := val.Bytes(); len(data) > 0 {
glog.Debugf(ctx, "[Cache] Hit local cache: %s", key)
return data, true
}
}
// 2. 再查Redis缓存
if g.Redis() != nil {
result, err := g.Redis().Get(ctx, key)
if err == nil && !result.IsEmpty() {
data := result.Bytes()
// 写入本地缓存
getLocalCache().Set(ctx, key, data, time.Duration(config.LocalTTL)*time.Second)
glog.Debugf(ctx, "[Cache] Hit redis cache: %s", key)
return data, true
}
}
return nil, false
}
// setToCache 写入缓存(本地缓存 + Redis
func setToCache(ctx context.Context, key string, data []byte) {
if len(data) == 0 {
return
}
config := DefaultCacheConfig
// 1. 写入本地缓存
getLocalCache().Set(ctx, key, data, time.Duration(config.LocalTTL)*time.Second)
// 2. 写入Redis缓存
if g.Redis() != nil {
expire := int64(config.RedisTTL)
_, err := g.Redis().Set(ctx, key, data, gredis.SetOption{
TTLOption: gredis.TTLOption{
EX: &expire,
},
})
if err != nil {
glog.Warningf(ctx, "[Cache] Failed to set redis cache: %s, err: %v", key, err)
}
}
}
// deleteCache 删除缓存
func deleteCache(ctx context.Context, key string) {
// 1. 删除本地缓存
getLocalCache().Remove(ctx, key)
// 2. 删除Redis缓存
if g.Redis() != nil {
_, err := g.Redis().Del(ctx, key)
if err != nil {
glog.Warningf(ctx, "[Cache] Failed to delete redis cache: %s, err: %v", key, err)
}
}
}
// deleteCacheByPattern 根据模式删除缓存
func deleteCacheByPattern(ctx context.Context, pattern string) {
// 1. 清空本地缓存(简单实现:清空所有)
getLocalCache().Clear(ctx)
// 2. 删除Redis缓存使用SCAN+DEL
if g.Redis() != nil {
var cursor uint64 = 0
for {
result, err := g.Redis().Do(ctx, "SCAN", cursor, "MATCH", pattern, "COUNT", 100)
if err != nil {
glog.Warningf(ctx, "[Cache] Failed to scan redis keys: %s, err: %v", pattern, err)
break
}
resultMap := result.Map()
cursor = gconv.Uint64(resultMap["cursor"])
keys := gconv.Strings(resultMap["keys"])
if len(keys) > 0 {
args := make([]interface{}, len(keys))
for i, k := range keys {
args[i] = k
}
_, err = g.Redis().Do(ctx, "DEL", args...)
if err != nil {
glog.Warningf(ctx, "[Cache] Failed to delete redis keys: %v, err: %v", keys, err)
}
}
if cursor == 0 {
break
}
}
}
}
// ==================== 统一Hook入口 ====================
// CatchSQLHook 返回统一的 HookHandler包含租户自动赋值和缓存
// 使用示例:
//
// // 基础使用(自动租户赋值,无缓存)
// g.DB().Model("user").Hook(base.CatchSQLHook()).Ctx(ctx).Insert(data)
//
// // 启用缓存用户无感知自动处理缓存key
// ctx = base.WithCacheEnabled(ctx, "asset")
// Asset.CtxWithCache(ctx).Where("id", 123).Scan(&result)
func CatchSQLHook() gdb.HookHandler {
return gdb.HookHandler{
Insert: insertHook,
Update: updateHook,
Delete: deleteHook,
Select: selectHook,
}
}
// ==================== Insert钩子 ====================
func insertHook(ctx context.Context, in *gdb.HookInsertInput) (result sql.Result, err error) {
// 1. 自动赋值租户字段
userInfo, _ := utils.GetUserInfo(ctx)
if !g.IsEmpty(userInfo.TenantId) {
in.Model.Data("tenant_id", userInfo.TenantId)
}
if !g.IsEmpty(userInfo.UserName) {
in.Model.Data("creator", userInfo.UserName)
in.Model.Data("updater", userInfo.UserName)
}
//for i := range in.Data {
// if !g.IsEmpty(userInfo.TenantId) {
// if _, ok := in.Data[i]["tenant_id"]; !ok {
// in.Data[i]["tenant_id"] = userInfo.TenantId
// }
// }
// if !g.IsEmpty(userInfo.UserId) {
// if _, ok := in.Data[i]["creator"]; !ok {
// in.Data[i]["creator"] = userInfo.UserId
// }
// if _, ok := in.Data[i]["updater"]; !ok {
// in.Data[i]["updater"] = userInfo.UserId
// }
// }
//}
// 2. 执行插入
result, err = in.Next(ctx)
if err != nil {
return nil, err
}
// 3. 清除相关缓存
prefix := getCachePrefix(ctx)
if prefix != "" {
deleteCacheByPattern(ctx, prefix+":*")
glog.Debugf(ctx, "[Hook] Cache cleared after insert, prefix: %s", prefix)
}
return result, nil
}
// ==================== Update钩子 ====================
func updateHook(ctx context.Context, in *gdb.HookUpdateInput) (result sql.Result, err error) {
// 1. 自动赋值修改人
userInfo, _ := utils.GetUserInfo(ctx)
if !g.IsEmpty(userInfo.TenantId) {
in.Model.Where("tenant_id", userInfo.TenantId)
}
if !g.IsEmpty(userInfo.UserName) {
in.Model.Where("creator", userInfo.UserName)
in.Model.Where("updater", userInfo.UserName)
}
//switch data := in.Data.(type) {
//case gdb.Map:
// if !g.IsEmpty(userInfo.UserId) {
// if _, ok := data["updater"]; !ok {
// data["updater"] = userInfo.UserId
// }
// }
//case gdb.List:
// for i := range data {
// if !g.IsEmpty(userInfo.UserId) {
// if _, ok := data[i]["updater"]; !ok {
// data[i]["updater"] = userInfo.UserId
// }
// }
// }
//}
// 2. 执行更新
result, err = in.Next(ctx)
if err != nil {
return nil, err
}
// 3. 清除相关缓存
prefix := getCachePrefix(ctx)
if prefix != "" {
deleteCacheByPattern(ctx, prefix+":*")
glog.Debugf(ctx, "[Hook] Cache cleared after update, prefix: %s", prefix)
}
return result, nil
}
// ==================== Delete钩子 ====================
func deleteHook(ctx context.Context, in *gdb.HookDeleteInput) (result sql.Result, err error) {
// 1. 执行删除
result, err = in.Next(ctx)
if err != nil {
return nil, err
}
// 2. 清除相关缓存
prefix := getCachePrefix(ctx)
if prefix != "" {
deleteCacheByPattern(ctx, prefix+":*")
glog.Debugf(ctx, "[Hook] Cache cleared after delete, prefix: %s", prefix)
}
return result, nil
}
// ==================== Select钩子缓存读取 ====================
func selectHook(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
userInfo, _ := utils.GetUserInfo(ctx)
if !isSkipTenant(ctx) && !g.IsEmpty(userInfo.TenantId) {
in.Model.Where("tenant_id", userInfo.TenantId)
}
// 未启用缓存,直接执行查询
if !isCacheEnabled(ctx) {
return in.Next(ctx)
}
prefix := getCachePrefix(ctx)
if prefix == "" {
return in.Next(ctx)
}
// 从 SQL 字符串中提取 WHERE 条件部分
whereCondition := extractWhereCondition(in.Sql)
// 构建缓存keyprefix:table:where条件:args
cacheKey := buildCacheKey(prefix, in.Table, whereCondition, in.Args)
glog.Debugf(ctx, "[Hook] Cache key: %s", cacheKey)
// 1. 先查缓存
if data, ok := getFromCache(ctx, cacheKey); ok {
var records gdb.Result
if err := json.Unmarshal(data, &records); err == nil && len(records) > 0 {
glog.Debugf(ctx, "[Hook] Cache hit for key: %s", cacheKey)
return records, nil
}
}
// 2. 执行数据库查询
result, err = in.Next(ctx)
if err != nil {
return nil, err
}
// 3. 写入缓存
if len(result) > 0 {
if data, err := json.Marshal(result); err == nil {
setToCache(ctx, cacheKey, data)
glog.Debugf(ctx, "[Hook] Cache set for key: %s", cacheKey)
}
}
return result, nil
}
// extractWhereCondition 从 SQL 语句中提取 WHERE 条件部分
func extractWhereCondition(sql string) string {
// 查找 WHERE 关键字(不区分大小写)
whereIndex := gstr.PosI(sql, " WHERE ")
if whereIndex == -1 {
return ""
}
// 提取 WHERE 之后的内容
whereClause := sql[whereIndex+7:]
// 移除 ORDER BY, GROUP BY, HAVING, LIMIT 等后续子句
for _, keyword := range []string{" ORDER BY ", " GROUP BY ", " HAVING ", " LIMIT ", " FOR UPDATE"} {
if idx := gstr.PosI(whereClause, keyword); idx != -1 {
whereClause = whereClause[:idx]
}
}
return whereClause
}
// ==================== 快捷方法 ====================
type gfdb interface {
Model(tableNameOrStruct ...any) *Model
}
type cache interface {
Cache(ctx context.Context) *gdb.Model
}
type Model struct {
*gdb.Model
}
type DataBase struct {
gdb.DB
DbName string
}
func DB(dbName string) gfdb {
return &DataBase{
DB: g.DB(dbName),
DbName: dbName,
}
}
func (d *DataBase) Model(tableNameOrStruct ...any) *Model {
return &Model{
Model: d.DB.Model(tableNameOrStruct...),
}
}
func (d *Model) Cache(ctx context.Context) *gdb.Model {
ctx = context.WithValue(ctx, ctxKeyCachePrefix, true)
return d.Model
}

219
dao/base/interceptor.go Normal file
View File

@@ -0,0 +1,219 @@
package base
import (
"context"
"strings"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// ==================== SQL 拦截器(真正无感知) ====================
// SQLInterceptor SQL拦截器配置
type SQLInterceptor struct {
// 是否启用租户ID自动注入
EnableTenantId bool
// 是否启用删除标记过滤
EnableDeletedFilter bool
// 需要拦截的表(空表示所有表)
IncludeTables []string
// 排除的表
ExcludeTables []string
}
// DefaultSQLInterceptor 默认拦截器配置
var DefaultSQLInterceptor = &SQLInterceptor{
EnableTenantId: true,
EnableDeletedFilter: true,
IncludeTables: []string{},
ExcludeTables: []string{"sys_config", "sys_dict"}, // 排除系统表
}
// currentInterceptor 当前使用的拦截器
var currentInterceptor = DefaultSQLInterceptor
// SetSQLInterceptor 设置全局SQL拦截器
func SetSQLInterceptor(interceptor *SQLInterceptor) {
currentInterceptor = interceptor
}
// GetSQLInterceptor 获取当前SQL拦截器
func GetSQLInterceptor() *SQLInterceptor {
return currentInterceptor
}
// ==================== 无感知集成方法 ====================
// InitSQLInterceptor 初始化SQL拦截器在 main.go 中调用)
// 调用后,所有 g.DB().Model() 创建的查询都会自动注入条件
//
// func main() {
// base.InitSQLInterceptor()
// // 之后所有 g.DB().Model() 都会自动注入 tenant_id 和 is_deleted=0
// }
func InitSQLInterceptor() {
// 通过设置全局 Hook 实现拦截
// 注意:这会替换所有 DB 操作的 Hook
hook := gdb.HookHandler{
Select: selectInterceptor,
}
// 保存原始 Hook如果有
// 这里只是注册,实际需要在每个 Model 上使用
_ = hook
}
// selectInterceptor SELECT 查询拦截器
func selectInterceptor(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) {
// 获取当前配置
interceptor := GetSQLInterceptor()
if interceptor == nil || !interceptor.EnableTenantId {
return in.Next(ctx)
}
// 检查是否需要拦截
if !shouldIntercept(in.Table) {
return in.Next(ctx)
}
// 获取用户信息
userInfo, _ := utils.GetUserInfo(ctx)
if g.IsEmpty(userInfo.TenantId) {
return in.Next(ctx)
}
// 检查 SQL 是否已包含 tenant_id 条件
sql := in.Sql
if !strings.Contains(sql, "tenant_id") {
// 注入 tenant_id 条件
// 注意:这里只是示例,实际修改 SQL 需要解析和重建 SQL
// 更简单的方式是在 Model 层处理
g.Log().Debug(ctx, "SQL拦截: 需要注入 tenant_id 条件", sql)
}
return in.Next(ctx)
}
// shouldIntercept 检查表是否需要拦截
func shouldIntercept(table string) bool {
interceptor := GetSQLInterceptor()
if interceptor == nil {
return false
}
// 检查排除列表
for _, exclude := range interceptor.ExcludeTables {
if table == exclude {
return false
}
}
// 检查包含列表
if len(interceptor.IncludeTables) > 0 {
for _, include := range interceptor.IncludeTables {
if table == include {
return true
}
}
return false
}
return true
}
// ==================== 实用方法:修改现有 DAO ====================
// WrapModel 包装现有 Model添加自动条件注入
// 用于改造现有 DAO保持业务代码不变
//
// // 原 DAO 代码:
// func (d *assetDao) Ctx(ctx context.Context) *gdb.Model {
// return g.DB().Model(entity.Asset{}).Safe()
// }
//
// // 改造后(只需改 Ctx 方法):
// func (d *assetDao) Ctx(ctx context.Context) *gdb.Model {
// return base.WrapModel(ctx, g.DB().Model(entity.Asset{}).Safe())
// }
func WrapModel(ctx context.Context, model *gdb.Model) *gdb.Model {
if ctx == nil || model == nil {
return model
}
return CatchSQL(ctx, model)
}
// WrapDB 包装 g.DB(),返回带自动条件注入的查询构建器
// 这是推荐的无感知使用方式
//
// // 在 DAO 中使用:
// func (d *assetDao) Ctx(ctx context.Context) *gdb.Model {
// return base.WrapDB(ctx).Model(entity.Asset{})
// }
//
// // 业务代码保持原生写法:
// func (d *assetDao) GetById(ctx context.Context, id uint64) (*entity.Asset, error) {
// var result entity.Asset
// err := d.Ctx(ctx).Where("id", id).Scan(&result)
// return &result, err
// }
func WrapDB(ctx context.Context) *DBBuilder {
return &DBBuilder{
ctx: ctx,
db: g.DB(),
}
}
// DBBuilder 数据库查询构建器
type DBBuilder struct {
ctx context.Context
db gdb.DB
}
// Model 创建 Model自动注入条件
func (b *DBBuilder) Model(tableNameOrStruct ...interface{}) *gdb.Model {
model := b.db.Model(tableNameOrStruct...).Safe()
if b.ctx != nil {
model = CatchSQL(b.ctx, model)
}
return model
}
// Schema 指定 Schema
func (b *DBBuilder) Schema(schema string) *SchemaBuilder {
return &SchemaBuilder{
ctx: b.ctx,
schema: b.db.Schema(schema),
}
}
// SchemaBuilder Schema 查询构建器
type SchemaBuilder struct {
ctx context.Context
schema gdb.DB
}
// Model 创建 Model自动注入条件
func (s *SchemaBuilder) Model(tableNameOrStruct ...interface{}) *gdb.Model {
model := s.schema.Model(tableNameOrStruct...).Safe()
if s.ctx != nil {
model = CatchSQL(s.ctx, model)
}
return model
}
// ==================== 全局替换方案 ====================
// ReplaceGDB 替换全局 g.DB() 行为(实验性)
// 警告:这会修改全局行为,请谨慎使用
//
// func init() {
// base.ReplaceGDB()
// }
//
// // 之后所有 g.DB().Model().Ctx(ctx) 都会自动注入条件
func ReplaceGDB() {
// 注意GoFrame 不支持直接替换 g.DB()
// 建议使用 WrapDB 或修改 DAO 的 Ctx 方法
g.Log().Info(context.Background(), "ReplaceGDB: 请使用 WrapDB 或修改 DAO 的 Ctx 方法来实现无感知集成")
}

View File

@@ -0,0 +1,105 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/procurement"
entity "assets/model/entity/procurement"
"context"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"go.mongodb.org/mongo-driver/v2/bson"
)
type purchaseInbound struct{}
var PurchaseInbound = new(purchaseInbound)
// Insert 插入
func (d *purchaseInbound) Insert(ctx context.Context, req *dto.CreatePurchaseInboundReq) (ids []interface{}, err error) {
var result *entity.PurchaseInbound
if err = utils.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.PurchaseInboundCollection)
return
}
// GetOne 查询单个
func (d *purchaseInbound) GetOne(ctx context.Context, req *dto.GetPurchaseInboundReq) (res *entity.PurchaseInbound, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB(false).FindOne(ctx, filter, &res, public.PurchaseInboundCollection)
return
}
// List 查询列表
func (d *purchaseInbound) List(ctx context.Context, req *dto.ListPurchaseInboundReq) (list []entity.PurchaseInbound, total int64, err error) {
filter := d.buildListFilter(req)
var orderBy []beans.OrderBy
if req.OrderBy.Field != "" {
orderBy = []beans.OrderBy{req.OrderBy}
}
total, err = mongo.DB().Find(ctx, filter, &list, public.PurchaseInboundCollection, &req.Page, orderBy)
return
}
// UpdateDetails 更新入库记录详细信息
func (d *purchaseInbound) UpdateDetails(ctx context.Context, id *bson.ObjectID, orderId *bson.ObjectID, inboundNo, batchNo, warehouseName, zoneName, locationName, privateSkuName, privateCategoryPath string) (err error) {
filter := bson.M{"_id": id}
update := bson.M{
"$set": bson.M{
"orderId": orderId,
"inboundNo": inboundNo,
"batchNo": batchNo,
"warehouseName": warehouseName,
"zoneName": zoneName,
"locationName": locationName,
"privateSkuName": privateSkuName,
"privateCategoryPath": privateCategoryPath,
},
}
_, err = mongo.DB().Update(ctx, filter, update, public.PurchaseInboundCollection)
return
}
// UpdatePrivateStockId 更新入库记录关联的库存ID
func (d *purchaseInbound) UpdatePrivateStockId(ctx context.Context, id, privateStockId *bson.ObjectID) (err error) {
filter := bson.M{"_id": id}
update := bson.M{
"$set": bson.M{
"privateStockId": privateStockId,
},
}
_, err = mongo.DB().Update(ctx, filter, update, public.PurchaseInboundCollection)
return
}
// buildListFilter 构建列表过滤条件
func (d *purchaseInbound) buildListFilter(req *dto.ListPurchaseInboundReq) bson.M {
filter := bson.M{}
if !g.IsEmpty(req.OrderId) {
filter["orderId"] = req.OrderId
}
if !g.IsEmpty(req.OrderItemId) {
filter["orderItemId"] = req.OrderItemId
}
if !g.IsEmpty(req.InboundNo) {
filter["inboundNo"] = bson.M{"$regex": req.InboundNo, "$options": "i"}
}
if !g.IsEmpty(req.StartDate) {
startTime, _ := gtime.StrToTime(req.StartDate + " 00:00:00")
filter["inboundDate"] = bson.M{"$gte": startTime}
}
if !g.IsEmpty(req.EndDate) {
endTime, _ := gtime.StrToTime(req.EndDate + " 23:59:59")
if filter["inboundDate"] != nil {
filter["inboundDate"].(bson.M)["$lte"] = endTime
} else {
filter["inboundDate"] = bson.M{"$lte": endTime}
}
}
return filter
}

View File

@@ -0,0 +1,113 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/procurement"
entity "assets/model/entity/procurement"
"context"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var PurchaseOrder = new(purchaseOrder)
type purchaseOrder struct{}
// Insert 插入采购订单
func (d *purchaseOrder) Insert(ctx context.Context, req *dto.CreatePurchaseOrderReq) (ids []any, err error) {
var result *entity.PurchaseOrder
if err = utils.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.PurchaseOrderCollection)
return
}
// BatchInsert 批量插入采购订单
func (d *purchaseOrder) BatchInsert(ctx context.Context, req *dto.BatchCreatePurchaseOrdersReq) (ids []any, err error) {
items := make([]*entity.PurchaseOrder, 0, len(req.Orders))
for _, item := range req.Orders {
var result *entity.PurchaseOrder
if err = utils.Struct(item, &result); err != nil {
return
}
items = append(items, result)
}
// 转换为 interface{} 切片
interfaces := make([]interface{}, len(items))
for i, item := range items {
interfaces[i] = item
}
ids, err = mongo.DB().Insert(ctx, interfaces, public.PurchaseOrderCollection)
return
}
// GetOne 获取单个采购订单
func (d *purchaseOrder) GetOne(ctx context.Context, id *bson.ObjectID) (order *entity.PurchaseOrder, err error) {
filter := bson.M{"_id": id}
err = mongo.DB().FindOne(ctx, filter, &order, public.PurchaseOrderCollection)
return
}
// Update 更新采购订单
func (d *purchaseOrder) Update(ctx context.Context, req *dto.UpdatePurchaseOrderReq) (err error) {
buildUpdateData, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.ID}
update := bson.M{"$set": buildUpdateData}
_, err = mongo.DB().Update(ctx, filter, update, public.PurchaseOrderCollection)
return
}
// DeleteFake 删除采购订单-根据id进行假删
func (d *purchaseOrder) DeleteFake(ctx context.Context, id *bson.ObjectID) (err error) {
filter := bson.M{"_id": id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.PurchaseOrderCollection)
return
}
// List 获取采购订单列表
func (d *purchaseOrder) List(ctx context.Context, req *dto.ListPurchaseOrdersReq) (res []*entity.PurchaseOrder, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.PurchaseOrderCollection, nil, nil)
return
}
// ListByBuyerId 根据采购方ID获取采购订单列表
func (d *purchaseOrder) ListByBuyerId(ctx context.Context, buyerId *bson.ObjectID) (res []*entity.PurchaseOrder, err error) {
filter := bson.M{"buyerId": buyerId}
_, err = mongo.DB().Find(ctx, filter, &res, public.PurchaseOrderCollection, nil, nil)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *purchaseOrder) buildListFilter(ctx context.Context, req *dto.ListPurchaseOrdersReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.OrderNo) {
filter["orderNo"] = req.OrderNo
}
if !g.IsEmpty(req.Title) {
filter["title"] = bson.M{"$regex": req.Title, "$options": "i"}
}
if req.BuyerId != nil {
filter["buyerId"] = req.BuyerId
}
if req.OrderType != "" {
filter["orderType"] = req.OrderType
}
if req.Status != nil {
filter["status"] = *req.Status
}
return
}

View File

@@ -0,0 +1,141 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/procurement"
entity "assets/model/entity/procurement"
"context"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"go.mongodb.org/mongo-driver/v2/bson"
)
var PurchaseOrderItem = new(purchaseOrderItem)
type purchaseOrderItem struct{}
// Insert 插入采购订单明细
func (d *purchaseOrderItem) Insert(ctx context.Context, req *dto.CreatePurchaseOrderItemReq) (ids []any, err error) {
var result *entity.PurchaseOrderItem
if err = gconv.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.PurchaseOrderItemCollection)
return
}
// BatchInsert 批量插入采购订单明细
func (d *purchaseOrderItem) BatchInsert(ctx context.Context, req *dto.BatchCreatePurchaseOrderItemsReq) (ids []any, err error) {
items := make([]*entity.PurchaseOrderItem, 0, len(req.Items))
for _, item := range req.Items {
var result *entity.PurchaseOrderItem
if err = gconv.Struct(item, &result); err != nil {
return
}
items = append(items, result)
}
// 转换为 interface{} 切片
interfaces := make([]interface{}, len(items))
for i, item := range items {
interfaces[i] = item
}
ids, err = mongo.DB().Insert(ctx, interfaces, public.PurchaseOrderItemCollection)
return
}
// GetOne 获取单个采购订单明细
func (d *purchaseOrderItem) GetOne(ctx context.Context, id *bson.ObjectID) (item *entity.PurchaseOrderItem, err error) {
filter := bson.M{"_id": id}
err = mongo.DB().FindOne(ctx, filter, &item, public.PurchaseOrderItemCollection)
return
}
// Update 更新采购订单明细
func (d *purchaseOrderItem) Update(ctx context.Context, req *dto.UpdatePurchaseOrderItemReq) (err error) {
buildUpdateData, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.ID}
update := bson.M{"$set": buildUpdateData}
_, err = mongo.DB().Update(ctx, filter, update, public.PurchaseOrderItemCollection)
return
}
// IncrementInboundQty 原子增加已入库数量(并发安全)
// 使用$inc + $expr条件inboundQty + deltaQty <= passQuantity防止并发超量入库
func (d *purchaseOrderItem) IncrementInboundQty(ctx context.Context, id *bson.ObjectID, deltaQty int) (err error) {
filter := bson.M{
"_id": id,
"$expr": bson.M{
"$lte": bson.A{
bson.M{"$add": bson.A{"$inboundQty", deltaQty}},
"$passQuantity",
},
},
}
update := bson.M{
"$inc": bson.M{
"inboundQty": deltaQty,
},
}
modifiedCount, err := mongo.DB().Update(ctx, filter, update, public.PurchaseOrderItemCollection)
if err != nil {
return
}
if modifiedCount == 0 {
return gerror.Newf("入库数量超出签收数量限制,本次入库%d", deltaQty)
}
return
}
// DeleteFake 删除采购订单明细-根据id进行假删
func (d *purchaseOrderItem) DeleteFake(ctx context.Context, id *bson.ObjectID) (err error) {
filter := bson.M{"_id": id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.PurchaseOrderItemCollection)
return
}
// List 获取采购订单明细列表
func (d *purchaseOrderItem) List(ctx context.Context, req *dto.ListPurchaseOrderItemsReq) (res []*entity.PurchaseOrderItem, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.PurchaseOrderItemCollection, nil, nil)
return
}
// ListByOrderId 根据订单ID获取采购订单明细列表
func (d *purchaseOrderItem) ListByOrderId(ctx context.Context, orderId *bson.ObjectID) (res []*entity.PurchaseOrderItem, err error) {
filter := bson.M{"orderId": orderId}
_, err = mongo.DB().Find(ctx, filter, &res, public.PurchaseOrderItemCollection, nil, nil)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *purchaseOrderItem) buildListFilter(ctx context.Context, req *dto.ListPurchaseOrderItemsReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if req.OrderId != nil {
filter["orderId"] = req.OrderId
}
if req.AssetId != nil {
filter["assetId"] = req.AssetId
}
if req.AssetSkuId != nil {
filter["assetSkuId"] = req.AssetSkuId
}
if !g.IsEmpty(req.ProductName) {
filter["productName"] = bson.M{"$regex": req.ProductName, "$options": "i"}
}
if !g.IsEmpty(req.Brand) {
filter["brand"] = bson.M{"$regex": req.Brand, "$options": "i"}
}
return
}

View File

@@ -0,0 +1,110 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/procurement"
entity "assets/model/entity/procurement"
"context"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"go.mongodb.org/mongo-driver/v2/bson"
)
var Supplier = new(supplier)
type supplier struct{}
// Insert 插入供应商
func (d *supplier) Insert(ctx context.Context, req *dto.CreateSupplierReq) (ids []any, err error) {
var result *entity.Supplier
if err = gconv.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.SupplierCollection)
return
}
// BatchInsert 批量插入供应商
func (d *supplier) BatchInsert(ctx context.Context, req *dto.BatchCreateSuppliersReq) (ids []any, err error) {
items := make([]*entity.Supplier, 0, len(req.Suppliers))
for _, item := range req.Suppliers {
var result *entity.Supplier
if err = gconv.Struct(item, &result); err != nil {
return
}
items = append(items, result)
}
// 转换为 interface{} 切片
interfaces := make([]interface{}, len(items))
for i, item := range items {
interfaces[i] = item
}
ids, err = mongo.DB().Insert(ctx, interfaces, public.SupplierCollection)
return
}
// GetOne 获取单个供应商
func (d *supplier) GetOne(ctx context.Context, id *bson.ObjectID) (supplier *entity.Supplier, err error) {
filter := bson.M{"_id": id}
err = mongo.DB().FindOne(ctx, filter, &supplier, public.SupplierCollection)
return
}
// Update 更新供应商
func (d *supplier) Update(ctx context.Context, req *dto.UpdateSupplierReq) (err error) {
buildUpdateData, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.ID}
update := bson.M{"$set": buildUpdateData}
_, err = mongo.DB().Update(ctx, filter, update, public.SupplierCollection)
return
}
// DeleteFake 删除供应商-根据id进行假删
func (d *supplier) DeleteFake(ctx context.Context, id *bson.ObjectID) (err error) {
filter := bson.M{"_id": id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.SupplierCollection)
return
}
// List 获取供应商列表
func (d *supplier) List(ctx context.Context, req *dto.ListSuppliersReq) (res []*entity.Supplier, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.SupplierCollection, nil, nil)
return
}
// FindActiveSuppliers 获取活跃供应商列表
func (d *supplier) FindActiveSuppliers(ctx context.Context) (res []*entity.Supplier, err error) {
filter := bson.M{
"status": 1, // consts.SupplierStatusActive
"isDeleted": false,
}
_, err = mongo.DB().Find(ctx, filter, &res, public.SupplierCollection, nil, nil)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *supplier) buildListFilter(ctx context.Context, req *dto.ListSuppliersReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.Name) {
filter["name"] = bson.M{"$regex": req.Name, "$options": "i"}
}
if !g.IsEmpty(req.Code) {
filter["code"] = req.Code
}
if req.Status != nil {
filter["status"] = *req.Status
}
return
}

View File

@@ -0,0 +1,73 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var InventoryCountAdjustHistory = &inventoryCountAdjustHistory{}
type inventoryCountAdjustHistory struct{}
// Insert 插入盘点调整历史记录
func (d *inventoryCountAdjustHistory) Insert(ctx context.Context, req *dto.CreateInventoryCountAdjustHistoryReq) (ids []interface{}, err error) {
var result *entity.InventoryCountAdjustHistory
if err = utils.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.InventoryCountAdjustHistoryCollection)
return
}
// GetOne 根据ID查询单条盘点调整历史记录
func (d *inventoryCountAdjustHistory) GetOne(ctx context.Context, req *dto.GetInventoryCountAdjustHistoryReq) (res *entity.InventoryCountAdjustHistory, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.InventoryCountAdjustHistoryCollection)
return
}
// DeleteFake 软删除盘点调整历史记录
func (d *inventoryCountAdjustHistory) DeleteFake(ctx context.Context, req *dto.DeleteInventoryCountAdjustHistoryReq) (err error) {
filter := bson.M{"_id": req.Id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.InventoryCountAdjustHistoryCollection)
return
}
// List 分页查询盘点调整历史列表
func (d *inventoryCountAdjustHistory) List(ctx context.Context, req *dto.ListInventoryCountAdjustHistoryReq) (res []entity.InventoryCountAdjustHistory, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.InventoryCountAdjustHistoryCollection, req.Page, req.OrderBy)
return
}
// buildListFilter 构建列表查询过滤条件
func (d *inventoryCountAdjustHistory) buildListFilter(ctx context.Context, req *dto.ListInventoryCountAdjustHistoryReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.CountID) {
filter["countId"] = req.CountID
}
if !g.IsEmpty(req.DetailID) {
filter["detailId"] = req.DetailID
}
if !g.IsEmpty(req.AssetSkuID) {
filter["assetSkuId"] = req.AssetSkuID
}
if !g.IsEmpty(req.WarehouseID) {
filter["warehouseId"] = req.WarehouseID
}
if !g.IsEmpty(req.BatchNo) {
filter["batchNo"] = req.BatchNo
}
return
}

View File

@@ -0,0 +1,202 @@
// 盘点任务DAO层
// 职责盘点任务CRUD、检查未完成任务、更新状态/统计
// 紧密耦合service.InventoryCount、dao.InventoryCountDetail(级联删除)
// 注意HasUncompletedTask使用NoCache()跳过缓存,同一时间只允许一个盘点任务
package dao
import (
"assets/consts/public"
"assets/consts/stock"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"fmt"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var InventoryCount = new(inventoryCount)
type inventoryCount struct{}
// convertToObjectIDs 将字符串ID列表批量转换为ObjectID列表跳过空值
func convertToObjectIDs(hexIDs []string, label string) ([]*bson.ObjectID, error) {
if len(hexIDs) == 0 {
return nil, nil
}
result := make([]*bson.ObjectID, 0, len(hexIDs))
for _, hex := range hexIDs {
if g.IsEmpty(hex) {
continue
}
id, err := bson.ObjectIDFromHex(hex)
if err != nil {
return nil, fmt.Errorf("%sID格式错误(值:%s): %v", label, hex, err)
}
result = append(result, &id)
}
return result, nil
}
func (d *inventoryCount) Insert(ctx context.Context, req *dto.CreateInventoryCountReq, countNo string) (ids []interface{}, err error) {
result := &entity.InventoryCount{
CountNo: countNo,
Title: req.Title,
Description: req.Description,
CountType: req.CountType,
Scope: req.Scope,
AssigneeID: req.AssigneeID,
AssigneeName: req.AssigneeName,
Participants: req.Participants,
Remark: req.Remark,
Status: stock.InventoryCountStatusInProgress,
}
// 批量转换字符串ID为ObjectID
if result.WarehouseIDs, err = convertToObjectIDs(req.WarehouseIDs, "仓库"); err != nil {
return
}
if result.ZoneIDs, err = convertToObjectIDs(req.ZoneIDs, "库区"); err != nil {
return
}
if result.LocationIDs, err = convertToObjectIDs(req.LocationIDs, "库位"); err != nil {
return
}
if result.AssetSkuIDs, err = convertToObjectIDs(req.AssetSkuIDs, "资产SKU"); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{result}, public.InventoryCountCollection)
return
}
func (d *inventoryCount) GetOne(ctx context.Context, req *dto.GetInventoryCountReq) (res *entity.InventoryCount, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.InventoryCountCollection)
return
}
func (d *inventoryCount) Update(ctx context.Context, req *dto.UpdateInventoryCountReq) (err error) {
var updateData entity.InventoryCount
if err = utils.Struct(req, &updateData); err != nil {
return
}
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": updateData}
_, err = mongo.DB().Update(ctx, filter, update, public.InventoryCountCollection)
return
}
func (d *inventoryCount) Delete(ctx context.Context, req *dto.DeleteInventoryCountReq) (err error) {
filter := bson.M{"_id": req.Id}
_, err = mongo.DB().Delete(ctx, filter, public.InventoryCountCollection)
return
}
func (d *inventoryCount) List(ctx context.Context, req *dto.ListInventoryCountReq) (res []entity.InventoryCount, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.InventoryCountCollection, req.Page, req.OrderBy)
return
}
func (d *inventoryCount) buildListFilter(ctx context.Context, req *dto.ListInventoryCountReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
// 兼容单值和数组参数(优先使用数组)
if len(req.WarehouseIDs) > 0 {
// 将字符串数组转为 ObjectID 数组
var oids []bson.ObjectID
for _, id := range req.WarehouseIDs {
if oid, e := bson.ObjectIDFromHex(id); e == nil {
oids = append(oids, oid)
}
}
if len(oids) > 0 {
filter["warehouseIds"] = bson.M{"$in": oids}
}
} else if !g.IsEmpty(req.WarehouseID) {
if wid, e := bson.ObjectIDFromHex(req.WarehouseID); e == nil {
filter["warehouseIds"] = wid
}
}
if len(req.ZoneIDs) > 0 {
// 将字符串数组转为 ObjectID 数组
var oids []bson.ObjectID
for _, id := range req.ZoneIDs {
if oid, e := bson.ObjectIDFromHex(id); e == nil {
oids = append(oids, oid)
}
}
if len(oids) > 0 {
filter["zoneIds"] = bson.M{"$in": oids}
}
} else if !g.IsEmpty(req.ZoneID) {
if zid, e := bson.ObjectIDFromHex(req.ZoneID); e == nil {
filter["zoneIds"] = zid
}
}
if !g.IsEmpty(req.CountType) {
filter["countType"] = req.CountType
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.AssigneeID) {
filter["assigneeId"] = req.AssigneeID
}
if !g.IsEmpty(req.Keyword) {
filter["$or"] = bson.A{
bson.M{"countNo": bson.M{"$regex": req.Keyword, "$options": "i"}},
bson.M{"title": bson.M{"$regex": req.Keyword, "$options": "i"}},
}
}
return
}
// HasUncompletedTask 检查是否存在未完成的盘点任务
func (d *inventoryCount) HasUncompletedTask(ctx context.Context) (has bool, err error) {
filter := bson.M{
"status": stock.InventoryCountStatusInProgress,
}
var result entity.InventoryCount
err = mongo.DB().NoCache().FindOne(ctx, filter, &result, public.InventoryCountCollection)
if err != nil {
return false, err
}
return result.Id != nil, nil
}
// UpdateStatus 更新盘点状态
func (d *inventoryCount) UpdateStatus(ctx context.Context, id *bson.ObjectID, status stock.InventoryCountStatus) (err error) {
filter := bson.M{"_id": id}
update := bson.M{
"$set": bson.M{
"status": status,
},
}
_, err = mongo.DB().Update(ctx, filter, update, public.InventoryCountCollection)
return
}
// UpdateStats 更新盘点统计信息
func (d *inventoryCount) UpdateStats(ctx context.Context, id *bson.ObjectID, totalItems, completedItems, discrepancyItems int) (err error) {
filter := bson.M{"_id": id}
var progress float64
if totalItems > 0 {
progress = float64(completedItems) / float64(totalItems) * 100
}
update := bson.M{
"$set": bson.M{
"totalItems": totalItems,
"completedItems": completedItems,
"discrepancyItems": discrepancyItems,
"progress": progress,
},
}
_, err = mongo.DB().Update(ctx, filter, update, public.InventoryCountCollection)
return
}

View File

@@ -0,0 +1,185 @@
// 盘点明细DAO层
// 职责明细CRUD、批量插入(性能优化)、按盘点ID查询、更新调整状态
// 紧密耦合service.InventoryCountDetail、service.InventoryCount(统计更新)
// 注意InsertBatch批量插入减少网络往返ListByCountId使用mongo.DB(false)跳过缓存
package dao
import (
"assets/consts/public"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"fmt"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"go.mongodb.org/mongo-driver/v2/bson"
)
var InventoryCountDetail = new(inventoryCountDetail)
type inventoryCountDetail struct{}
func (d *inventoryCountDetail) Insert(ctx context.Context, req *dto.CreateInventoryCountDetailReq) (ids []interface{}, err error) {
result := &entity.InventoryCountDetail{
BookQuantity: req.BookQuantity,
BookBatchInfo: req.BookBatchInfo,
ActualQuantity: req.ActualQuantity,
ActualBatchInfo: req.ActualBatchInfo,
Remark: req.Remark,
}
// required string → *bson.ObjectID
for _, item := range []struct {
val string
dest **bson.ObjectID
name string
}{
{req.CountID, &result.CountID, "盘点单ID"},
{req.AssetID, &result.AssetID, "资产ID"},
{req.AssetSkuID, &result.AssetSkuID, "资产SKU ID"},
{req.WarehouseID, &result.WarehouseID, "仓库ID"},
} {
id, e := bson.ObjectIDFromHex(item.val)
if e != nil {
return nil, fmt.Errorf("%s格式错误: %v", item.name, e)
}
*item.dest = &id
}
// optional string → *bson.ObjectID
if !g.IsEmpty(req.ZoneID) {
id, e := bson.ObjectIDFromHex(req.ZoneID)
if e != nil {
return nil, fmt.Errorf("库区ID格式错误: %v", e)
}
result.ZoneID = &id
}
if !g.IsEmpty(req.LocationID) {
id, e := bson.ObjectIDFromHex(req.LocationID)
if e != nil {
return nil, fmt.Errorf("库位ID格式错误: %v", e)
}
result.LocationID = &id
}
ids, err = mongo.DB().Insert(ctx, []interface{}{result}, public.InventoryCountDetailCollection)
return
}
// InsertBatch 批量插入盘点明细性能优化100条/批,减少网络往返)
func (d *inventoryCountDetail) InsertBatch(ctx context.Context, details []*entity.InventoryCountDetail) (ids []interface{}, err error) {
if len(details) == 0 {
return
}
docs := make([]interface{}, len(details))
for i, detail := range details {
docs[i] = detail
}
ids, err = mongo.DB().Insert(ctx, docs, public.InventoryCountDetailCollection)
return
}
func (d *inventoryCountDetail) GetOne(ctx context.Context, req *dto.GetInventoryCountDetailReq) (res *entity.InventoryCountDetail, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.InventoryCountDetailCollection)
return
}
func (d *inventoryCountDetail) Update(ctx context.Context, req *dto.UpdateInventoryCountDetailReq) (err error) {
buildFilter, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": buildFilter}
_, err = mongo.DB().Update(ctx, filter, update, public.InventoryCountDetailCollection)
return
}
func (d *inventoryCountDetail) DeleteFake(ctx context.Context, req *dto.DeleteInventoryCountDetailReq) (err error) {
filter := bson.M{"_id": req.Id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.InventoryCountDetailCollection)
return
}
// DeleteByCountId 按盘点任务ID真删除所有明细
func (d *inventoryCountDetail) DeleteByCountId(ctx context.Context, countId *bson.ObjectID) (err error) {
filter := bson.M{"countId": countId}
_, err = mongo.DB().Delete(ctx, filter, public.InventoryCountDetailCollection)
return
}
func (d *inventoryCountDetail) List(ctx context.Context, req *dto.ListInventoryCountDetailReq) (res []entity.InventoryCountDetail, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.InventoryCountDetailCollection, req.Page, req.OrderBy)
return
}
func (d *inventoryCountDetail) buildListFilter(ctx context.Context, req *dto.ListInventoryCountDetailReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.CountID) {
filter["countId"] = req.CountID
}
if !g.IsEmpty(req.AssetID) {
filter["assetId"] = req.AssetID
}
if !g.IsEmpty(req.AssetSkuID) {
filter["assetSkuId"] = req.AssetSkuID
}
if !g.IsEmpty(req.WarehouseID) {
filter["warehouseId"] = req.WarehouseID
}
if !g.IsEmpty(req.ZoneID) {
filter["zoneId"] = req.ZoneID
}
if !g.IsEmpty(req.LocationID) {
filter["locationId"] = req.LocationID
}
if !g.IsEmpty(req.DiscrepancyType) {
filter["discrepancyType"] = req.DiscrepancyType
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.IsAdjusted) {
filter["isAdjusted"] = req.IsAdjusted
}
return
}
// ListByCountId 按盘点ID查询所有明细
func (d *inventoryCountDetail) ListByCountId(ctx context.Context, countId *bson.ObjectID) (res []entity.InventoryCountDetail, err error) {
filter := bson.M{"countId": countId}
_, err = mongo.DB(false).Find(ctx, filter, &res, public.InventoryCountDetailCollection, nil, nil)
return
}
// UpdateAdjusted 更新调整状态
func (d *inventoryCountDetail) UpdateAdjusted(ctx context.Context, id *bson.ObjectID, adjustedBy string) (err error) {
filter := bson.M{"_id": id}
update := bson.M{
"$set": bson.M{
"isAdjusted": true,
"adjustedBy": adjustedBy,
"adjustedAt": gtime.Now(),
},
}
_, err = mongo.DB().Update(ctx, filter, update, public.InventoryCountDetailCollection)
return
}
// UpdateDiscrepancyReason 更新差异原因
func (d *inventoryCountDetail) UpdateDiscrepancyReason(ctx context.Context, id *bson.ObjectID, reason string) (err error) {
filter := bson.M{"_id": id}
update := bson.M{
"$set": bson.M{
"discrepancyReason": reason,
"updatedAt": gtime.Now(),
},
}
_, err = mongo.DB().Update(ctx, filter, update, public.InventoryCountDetailCollection)
return
}

View File

@@ -0,0 +1,71 @@
// 库存预警DAO层
// 职责:预警查询(无Insert/Update/Delete由系统自动生成)
// 紧密耦合service.InventoryWarning
// 注意:预警记录由定时任务或库存变动触发,非用户手动创建
package dao
import (
"assets/consts/public"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var InventoryWarning = new(inventoryWarning)
type inventoryWarning struct{}
func (d *inventoryWarning) GetOne(ctx context.Context, req *dto.GetInventoryWarningReq) (res *entity.InventoryWarning, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.InventoryWarningCollection)
return
}
func (d *inventoryWarning) List(ctx context.Context, req *dto.ListInventoryWarningReq) (res []entity.InventoryWarning, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.InventoryWarningCollection, req.Page, req.OrderBy)
return
}
func (d *inventoryWarning) buildListFilter(ctx context.Context, req *dto.ListInventoryWarningReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.WarningType) {
filter["warningType"] = req.WarningType
}
// 兼容单值和数组参数(优先使用数组)
if len(req.BatchIDs) > 0 {
filter["batchId"] = bson.M{"$in": req.BatchIDs}
} else if !g.IsEmpty(req.BatchID) {
filter["batchId"] = req.BatchID
}
if len(req.AssetIDs) > 0 {
filter["assetId"] = bson.M{"$in": req.AssetIDs}
} else if !g.IsEmpty(req.AssetID) {
filter["assetId"] = req.AssetID
}
if len(req.AssetSkuIDs) > 0 {
filter["assetSkuId"] = bson.M{"$in": req.AssetSkuIDs}
} else if !g.IsEmpty(req.AssetSkuID) {
filter["assetSkuId"] = req.AssetSkuID
}
if len(req.SupplierIDs) > 0 {
filter["supplierId"] = bson.M{"$in": req.SupplierIDs}
} else if !g.IsEmpty(req.SupplierID) {
filter["supplierId"] = req.SupplierID
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.Keyword) {
filter["batchNo"] = bson.M{"$regex": req.Keyword, "$options": "i"}
}
return
}

View File

@@ -0,0 +1,80 @@
// 库存预警历史DAO层
// 职责:预警历史查询、删除(无Insert/Update由预警状态变更时自动归档)
// 紧密耦合service.InventoryWarningHistory
// 注意:历史记录由系统自动归档,非用户手动创建
package dao
import (
"assets/consts/public"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var InventoryWarningHistory = new(inventoryWarningHistory)
type inventoryWarningHistory struct{}
func (d *inventoryWarningHistory) GetOne(ctx context.Context, req *dto.GetInventoryWarningHistoryReq) (res *entity.InventoryWarningHistory, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.InventoryWarningHistoryCollection)
return
}
func (d *inventoryWarningHistory) List(ctx context.Context, req *dto.ListInventoryWarningHistoryReq) (res []entity.InventoryWarningHistory, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.InventoryWarningHistoryCollection, req.Page, req.OrderBy)
return
}
func (d *inventoryWarningHistory) DeleteFake(ctx context.Context, req *dto.DeleteInventoryWarningHistoryReq) (err error) {
filter := bson.M{"_id": req.Id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.InventoryWarningHistoryCollection)
return
}
func (d *inventoryWarningHistory) buildListFilter(ctx context.Context, req *dto.ListInventoryWarningHistoryReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.WarningType) {
filter["warningType"] = req.WarningType
}
// 兼容单值和数组参数(优先使用数组)
if len(req.BatchIDs) > 0 {
filter["batchId"] = bson.M{"$in": req.BatchIDs}
} else if !g.IsEmpty(req.BatchID) {
filter["batchId"] = req.BatchID
}
if len(req.AssetIDs) > 0 {
filter["assetId"] = bson.M{"$in": req.AssetIDs}
} else if !g.IsEmpty(req.AssetID) {
filter["assetId"] = req.AssetID
}
if len(req.AssetSkuIDs) > 0 {
filter["assetSkuId"] = bson.M{"$in": req.AssetSkuIDs}
} else if !g.IsEmpty(req.AssetSkuID) {
filter["assetSkuId"] = req.AssetSkuID
}
if len(req.SupplierIDs) > 0 {
filter["supplierId"] = bson.M{"$in": req.SupplierIDs}
} else if !g.IsEmpty(req.SupplierID) {
filter["supplierId"] = req.SupplierID
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.ProcessMethod) {
filter["processMethod"] = req.ProcessMethod
}
if !g.IsEmpty(req.Keyword) {
filter["batchNo"] = bson.M{"$regex": req.Keyword, "$options": "i"}
}
return
}

191
dao/stock/location_dao.go Normal file
View File

@@ -0,0 +1,191 @@
// 库位DAO层
// 职责库位CRUD、状态更新、批量状态更新、删除前检查(3个库存集合)、更新容量
// 紧密耦合service.Location(删除检查)、service.Capacity(容量计算入口)
// 注意DeleteFake前需检查StockDetails/StockBatch/PrivateStock三个集合
package dao
import (
"assets/consts/public"
"assets/consts/stock"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var Location = new(location)
type location struct{}
func (d *location) Insert(ctx context.Context, req *dto.CreateLocationReq) (ids []interface{}, err error) {
var result *entity.Location
if err = utils.Struct(req, &result); err != nil {
return
}
// 如果未传入状态,设置默认值为空闲
if result.Status == "" {
result.Status = stock.LocationStatusIdle
}
// 初始容量默认为0
//if result.CurrentCapacity == 0 {
// result.CurrentCapacity = 0
//}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.LocationCollection)
return
}
func (d *location) GetOne(ctx context.Context, req *dto.GetLocationReq) (res *entity.Location, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.LocationCollection)
return
}
func (d *location) Update(ctx context.Context, req *dto.UpdateLocationReq) (err error) {
buildFilter, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": buildFilter}
_, err = mongo.DB().Update(ctx, filter, update, public.LocationCollection)
return
}
func (d *location) DeleteFake(ctx context.Context, req *dto.DeleteLocationReq) (err error) {
filter := bson.M{"_id": req.Id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.LocationCollection)
return
}
// UpdateStatus 更新库位状态
func (d *location) UpdateStatus(ctx context.Context, req *dto.UpdateLocationStatusReq) (err error) {
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": bson.M{"status": req.Status}}
_, err = mongo.DB().Update(ctx, filter, update, public.LocationCollection)
return
}
func (d *location) List(ctx context.Context, req *dto.ListLocationReq) (res []entity.Location, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.LocationCollection, req.Page, req.OrderBy)
return
}
func (d *location) buildListFilter(ctx context.Context, req *dto.ListLocationReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
// 兼容单值和数组参数(优先使用数组)
if len(req.WarehouseIds) > 0 {
filter["warehouseId"] = bson.M{"$in": req.WarehouseIds}
} else if !g.IsEmpty(req.WarehouseId) {
filter["warehouseId"] = req.WarehouseId
}
if len(req.ZoneIds) > 0 {
filter["zoneId"] = bson.M{"$in": req.ZoneIds}
} else if !g.IsEmpty(req.ZoneId) {
filter["zoneId"] = req.ZoneId
}
if !g.IsEmpty(req.LocationType) {
filter["locationType"] = req.LocationType
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.Keyword) {
filter["$or"] = bson.A{
bson.M{"locationCode": bson.M{"$regex": req.Keyword, "$options": "i"}},
bson.M{"locationName": bson.M{"$regex": req.Keyword, "$options": "i"}},
}
}
return
}
// CountByZoneId 统计库区下的库位数量(用于删除前检查)
func (d *location) CountByZoneId(ctx context.Context, zoneId string) (count int64, err error) {
filter := bson.M{"zoneId": zoneId}
count, err = mongo.DB().Count(ctx, filter, public.LocationCollection)
return
}
// CountByWarehouseId 统计仓库下的库位数量(用于删除前检查)
func (d *location) CountByWarehouseId(ctx context.Context, warehouseId string) (count int64, err error) {
filter := bson.M{"warehouseId": warehouseId}
count, err = mongo.DB().Count(ctx, filter, public.LocationCollection)
return
}
// BatchUpdateStatusByZoneId 批量更新库区下所有库位状态(用于状态联动)
func (d *location) BatchUpdateStatusByZoneId(ctx context.Context, zoneId string, status stock.LocationStatus) (modifiedCount int64, err error) {
filter := bson.M{"zoneId": zoneId}
update := bson.M{"$set": bson.M{"status": status}}
modifiedCount, err = mongo.DB().Update(ctx, filter, update, public.LocationCollection)
return
}
// BatchUpdateStatusByWarehouseId 批量更新仓库下所有库位状态(用于状态联动)
func (d *location) BatchUpdateStatusByWarehouseId(ctx context.Context, warehouseId string, status stock.LocationStatus) (modifiedCount int64, err error) {
filter := bson.M{"warehouseId": warehouseId}
update := bson.M{"$set": bson.M{"status": status}}
modifiedCount, err = mongo.DB().Update(ctx, filter, update, public.LocationCollection)
return
}
// DeleteByZoneId 批量删除库区下所有库位(用于级联删除)
func (d *location) DeleteByZoneId(ctx context.Context, zoneId string) (modifiedCount int64, err error) {
filter := bson.M{"zoneId": zoneId}
modifiedCount, err = mongo.DB().DeleteSoft(ctx, filter, public.LocationCollection)
return
}
// DeleteByWarehouseId 批量删除仓库下所有库位(用于级联删除)
func (d *location) DeleteByWarehouseId(ctx context.Context, warehouseId string) (modifiedCount int64, err error) {
filter := bson.M{"warehouseId": warehouseId}
modifiedCount, err = mongo.DB().DeleteSoft(ctx, filter, public.LocationCollection)
return
}
// CountStockDetailsByLocationId 统计库位上的库存明细数量(用于删除前检查)
func (d *location) CountStockDetailsByLocationId(ctx context.Context, locationId string) (count int64, err error) {
filter := bson.M{"locationId": locationId}
count, err = mongo.DB().Count(ctx, filter, public.StockDetailsCollection)
return
}
// CountStockBatchByLocationId 统计库位上的批次库存数量(用于删除前检查)
func (d *location) CountStockBatchByLocationId(ctx context.Context, locationId string) (count int64, err error) {
filter := bson.M{"locationId": locationId}
count, err = mongo.DB().Count(ctx, filter, public.StockBatchCollection)
return
}
// CountPrivateStockByLocationId 统计库位上的私域库存数量(用于删除前检查)
func (d *location) CountPrivateStockByLocationId(ctx context.Context, locationId string) (count int64, err error) {
filter := bson.M{"locationId": locationId}
count, err = mongo.DB().Count(ctx, filter, public.PrivateStockCollection)
return
}
// UpdateCapacity 更新库位容量
func (d *location) UpdateCapacity(ctx context.Context, locationId *bson.ObjectID, currentCapacity int) (err error) {
filter := bson.M{"_id": locationId}
update := bson.M{"$set": bson.M{"capacity.currentCapacity": currentCapacity}}
_, err = mongo.DB().Update(ctx, filter, update, public.LocationCollection)
return
}
// ListByZoneAndUnitType 按库区和容量单位类型查询库位列表
func (d *location) ListByZoneAndUnitType(ctx context.Context, zoneId *bson.ObjectID, unitType stock.CapacityUnitType) (res []entity.Location, err error) {
filter := bson.M{
"zoneId": zoneId,
"capacityUnitType": unitType,
}
_, err = mongo.DB().Find(ctx, filter, &res, public.LocationCollection, nil, nil)
return
}

View File

@@ -0,0 +1,268 @@
// 实物库存批次DAO层
// 职责CRUD、IncrementAvailableQty原子操作(防并发超卖)、SumAvailableQtyByLocation聚合汇总
// 紧密耦合service.PrivateStock、service.Capacity(容量计算入口)
// 注意IncrementAvailableQty使用$inc+$gte条件防止并发导致库存变负
package dao
import (
"assets/consts/public"
"assets/consts/stock"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var PrivateStock = new(privateStock)
type privateStock struct {
}
// Insert 插入私域库存
func (d *privateStock) Insert(ctx context.Context, req *dto.CreatePrivateStockReq) (ids []interface{}, err error) {
var result *entity.PrivateStock
if err = utils.Struct(req, &result); err != nil {
return
}
// 设置StockType如果未指定默认为PrivateStock类型
if !g.IsEmpty(req.StockType) {
result.StockType = req.StockType
} else {
result.StockType = stock.StockLocationTypePrivateStock
}
// 获取仓库信息(非必填)
if req.WarehouseId != nil && !req.WarehouseId.IsZero() {
warehouse, err := Warehouse.GetOne(ctx, &dto.GetWarehouseReq{Id: req.WarehouseId})
if err != nil {
return nil, err
}
result.WarehouseID = req.WarehouseId
result.WarehouseCode = warehouse.WarehouseCode
result.WarehouseName = warehouse.WarehouseName
}
// 如果有库区信息
if req.ZoneId != nil && !req.ZoneId.IsZero() {
zone, err := Zone.GetOne(ctx, &dto.GetZoneReq{Id: req.ZoneId})
if err != nil {
return nil, err
}
result.ZoneID = req.ZoneId
result.ZoneCode = zone.ZoneCode
result.ZoneName = zone.ZoneName
result.ZoneType = zone.ZoneType
}
// 如果有库位信息
if req.LocationId != nil && !req.LocationId.IsZero() {
location, err := Location.GetOne(ctx, &dto.GetLocationReq{Id: req.LocationId})
if err != nil {
return nil, err
}
result.LocationID = req.LocationId
result.LocationCode = location.LocationCode
result.LocationName = location.LocationName
result.LocationType = location.LocationType
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.PrivateStockCollection)
return
}
// Update 更新私域库存
func (d *privateStock) Update(ctx context.Context, req *dto.UpdatePrivateStockReq) (err error) {
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": bson.M{}}
if req.WarehouseId != nil && !req.WarehouseId.IsZero() {
warehouse, err := Warehouse.GetOne(ctx, &dto.GetWarehouseReq{Id: req.WarehouseId})
if err != nil {
return err
}
update["$set"].(bson.M)["warehouseId"] = req.WarehouseId
update["$set"].(bson.M)["warehouseCode"] = warehouse.WarehouseCode
update["$set"].(bson.M)["warehouseName"] = warehouse.WarehouseName
}
if req.ZoneId != nil && !req.ZoneId.IsZero() {
zone, err := Zone.GetOne(ctx, &dto.GetZoneReq{Id: req.ZoneId})
if err != nil {
return err
}
update["$set"].(bson.M)["zoneId"] = req.ZoneId
update["$set"].(bson.M)["zoneCode"] = zone.ZoneCode
update["$set"].(bson.M)["zoneName"] = zone.ZoneName
update["$set"].(bson.M)["zoneType"] = zone.ZoneType
}
if req.LocationId != nil && !req.LocationId.IsZero() {
location, err := Location.GetOne(ctx, &dto.GetLocationReq{Id: req.LocationId})
if err != nil {
return err
}
update["$set"].(bson.M)["locationId"] = req.LocationId
update["$set"].(bson.M)["locationCode"] = location.LocationCode
update["$set"].(bson.M)["locationName"] = location.LocationName
update["$set"].(bson.M)["locationType"] = location.LocationType
}
if req.PrivateSkuID != nil {
update["$set"].(bson.M)["privateSkuId"] = req.PrivateSkuID
}
if !g.IsEmpty(req.BatchNo) {
update["$set"].(bson.M)["batchNo"] = req.BatchNo
}
if req.BatchQty > 0 {
update["$set"].(bson.M)["batchQty"] = req.BatchQty
}
// AvailableQty已移除全量覆盖请使用IncrementAvailableQty方法进行增量更新
if req.BatchStatus != nil {
update["$set"].(bson.M)["batchStatus"] = req.BatchStatus
}
if req.StockStatus != nil {
update["$set"].(bson.M)["stockStatus"] = req.StockStatus
}
if req.SupplierID != nil {
update["$set"].(bson.M)["supplierId"] = req.SupplierID
}
if req.SupportsRecycle != nil {
update["$set"].(bson.M)["supportsRecycle"] = *req.SupportsRecycle
}
if req.ProductionDate != nil {
update["$set"].(bson.M)["productionDate"] = req.ProductionDate
}
if req.ExpiryDate != nil {
update["$set"].(bson.M)["expiryDate"] = req.ExpiryDate
}
if req.ExpiryWarningDate != nil {
update["$set"].(bson.M)["expiryWarningDate"] = req.ExpiryWarningDate
}
if !g.IsEmpty(req.PrivateCategoryPath) {
update["$set"].(bson.M)["privateCategoryPath"] = req.PrivateCategoryPath
}
if !g.IsEmpty(req.StockType) {
update["$set"].(bson.M)["stockType"] = req.StockType
}
_, err = mongo.DB().Update(ctx, filter, update, public.PrivateStockCollection)
return
}
// DeleteFake 软删除私域库存
func (d *privateStock) DeleteFake(ctx context.Context, req *dto.DeletePrivateStockReq) error {
filter := bson.M{"_id": req.Id}
_, err := mongo.DB().DeleteSoft(ctx, filter, public.PrivateStockCollection)
return err
}
// GetOne 根据ID查询私域库存
func (d *privateStock) GetOne(ctx context.Context, req *dto.GetPrivateStockReq) (res *entity.PrivateStock, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.PrivateStockCollection)
return
}
// IncrementAvailableQty 增量更新可用数量(并发安全)
// deltaQty正数表示增加入库负数表示减少出库
// 减少时自动添加 availableQty >= |deltaQty| 条件,防止并发导致库存变负
func (d *privateStock) IncrementAvailableQty(ctx context.Context, id *bson.ObjectID, deltaQty int) (err error) {
filter := bson.M{"_id": id}
if deltaQty < 0 {
filter["availableQty"] = bson.M{"$gte": -deltaQty}
}
update := bson.M{
"$inc": bson.M{
"availableQty": deltaQty,
},
}
modifiedCount, err := mongo.DB().Update(ctx, filter, update, public.PrivateStockCollection)
if err != nil {
return
}
if deltaQty < 0 && modifiedCount == 0 {
err = gerror.Newf("库存不足,无法减少%d", -deltaQty)
}
return
}
// List 查询私域库存列表
func (d *privateStock) List(ctx context.Context, req *dto.ListPrivateStockReq) (res []entity.PrivateStock, total int64, err error) {
filter := bson.M{}
if req.WarehouseId != nil && !req.WarehouseId.IsZero() {
filter["warehouseId"] = req.WarehouseId
}
if req.ZoneId != nil && !req.ZoneId.IsZero() {
filter["zoneId"] = req.ZoneId
}
if req.LocationId != nil && !req.LocationId.IsZero() {
filter["locationId"] = req.LocationId
}
if req.PrivateSkuID != nil && !req.PrivateSkuID.IsZero() {
filter["privateSkuId"] = req.PrivateSkuID
}
if req.BatchStatus != nil {
filter["batchStatus"] = req.BatchStatus
}
if req.StockStatus != nil {
filter["stockStatus"] = req.StockStatus
}
if req.SupplierID != nil && !req.SupplierID.IsZero() {
filter["supplierId"] = req.SupplierID
}
if !g.IsEmpty(req.PrivateCategoryPath) {
filter["privateCategoryPath"] = req.PrivateCategoryPath
}
if !g.IsEmpty(req.StockType) {
filter["stockType"] = req.StockType
}
// 默认排序
if len(req.OrderBy) == 0 {
req.OrderBy = []beans.OrderBy{
{Field: "createdAt", Order: beans.Desc},
}
}
total, err = mongo.DB().Find(ctx, filter, &res, public.PrivateStockCollection, req.Page, req.OrderBy)
return
}
// SumAvailableQtyByLocation 按库位ID汇总所有库存的可用数量使用MongoDB聚合管道
func (d *privateStock) SumAvailableQtyByLocation(ctx context.Context, locationId *bson.ObjectID) (totalQty int, err error) {
pipeline := bson.A{
bson.M{"$match": bson.M{
"locationId": locationId,
"isDeleted": false,
}},
bson.M{"$group": bson.M{
"_id": nil,
"totalQty": bson.M{"$sum": "$availableQty"},
}},
}
coll := mongo.GetDB().Collection(public.PrivateStockCollection)
cursor, err := coll.Aggregate(ctx, pipeline)
if err != nil {
return
}
defer cursor.Close(ctx)
var results []struct {
TotalQty int `bson:"totalQty"`
}
if err = cursor.All(ctx, &results); err != nil {
return
}
if len(results) > 0 {
totalQty = results[0].TotalQty
}
return
}

View File

@@ -0,0 +1,78 @@
// 批次库存DAO层(逻辑库存)
// 职责批次CRUD、使用$inc原子操作更新数量
// 紧密耦合service.StockBatch、service.StockManage(入库出库)
// 注意Update使用$inc原子操作GetOne使用NoCache()跳过缓存
package dao
import (
"assets/consts/public"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"go.mongodb.org/mongo-driver/v2/bson"
)
var StockBatch = new(stockBatch)
type stockBatch struct {
}
// Insert 插入
func (d *stockBatch) Insert(ctx context.Context, req *dto.CreateBatchReq) (ids []interface{}, err error) {
var result *entity.StockBatch
if err = utils.Struct(req, &result); err != nil {
return
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.StockBatchCollection)
return
}
// Update 更新批次数量(使用$inc原子操作并发安全
func (d *stockBatch) Update(ctx context.Context, req *dto.UpdateBatchReq) (err error) {
filter := bson.M{"_id": req.Id}
update := bson.M{
"$inc": bson.M{
"batchQty": req.BatchQty,
"availableQty": req.AvailableQty,
},
}
_, err = mongo.DB().Update(ctx, filter, update, public.StockBatchCollection)
return
}
// GetOne 根据批次号查询使用NoCache跳过缓存确保获取最新数据
func (d *stockBatch) GetOne(ctx context.Context, batchNo string) (res *entity.StockBatch, err error) {
filter := bson.M{"batchNo": batchNo}
err = mongo.DB().NoCache().FindOne(ctx, filter, &res, public.StockBatchCollection)
return
}
// GetOneById 根据ID查询批次
func (d *stockBatch) GetOneById(ctx context.Context, req *dto.GetBatchReq) (res *entity.StockBatch, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.StockBatchCollection)
return
}
// DeleteFake 软删除批次
func (d *stockBatch) DeleteFake(ctx context.Context, req *dto.DeleteBatchReq) error {
filter := bson.M{"_id": req.Id}
_, err := mongo.DB().DeleteSoft(ctx, filter, public.StockBatchCollection)
return err
}
// List 查询批次列表
func (d *stockBatch) List(ctx context.Context, req *dto.ListBatchReq) (res []entity.StockBatch, total int64, err error) {
filter := bson.M{}
if req.AssetId != nil {
filter["assetId"] = req.AssetId
}
if req.AssetSkuId != nil {
filter["assetSkuId"] = req.AssetSkuId
}
total, err = mongo.DB().Find(ctx, filter, &res, public.StockBatchCollection, req.Page, req.OrderBy)
return
}

View File

@@ -0,0 +1,80 @@
// 库存明细DAO层(逻辑库存)
// 职责:批量插入/删除、按SKU统计数量、查询列表
// 紧密耦合service.StockDetails、service.StockManage(入库出库)
// 注意GetStockCountBySkuId使用NoCache()跳过缓存BatchInsert用于批量入库
package dao
import (
"assets/consts/public"
"assets/consts/stock"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var StockDetails = new(stockDetails)
type stockDetails struct {
}
// BatchInsert 批量插入库存
func (d *stockDetails) BatchInsert(ctx context.Context, stockInterfaces []interface{}) (ids []interface{}, err error) {
ids, err = mongo.DB().Insert(ctx, stockInterfaces, public.StockDetailsCollection)
return
}
// DeleteManyByIds 根据ID批量删除库存
func (d *stockDetails) DeleteManyByIds(ctx context.Context, allStockIds []*bson.ObjectID) (count int64, err error) {
if len(allStockIds) == 0 {
return 0, nil
}
filter := bson.M{"_id": bson.M{"$in": allStockIds}}
count, err = mongo.DB().Delete(ctx, filter, public.StockDetailsCollection)
return
}
// GetStockCountBySkuId 获取库存数根据SKU ID
func (d *stockDetails) GetStockCountBySkuId(ctx context.Context, assetSkuId *bson.ObjectID) (total int64, err error) {
// 构建查询过滤条件
filter := bson.M{}
filter["assetSkuId"] = assetSkuId
filter["status"] = stock.StockStatusAvailable
// 检查总数
total, err = mongo.DB().NoCache().Count(ctx, filter, public.StockDetailsCollection)
return total, err
}
// GetOneById 根据ID查询库存明细
func (d *stockDetails) GetOneById(ctx context.Context, req *dto.GetStockDetailsReq) (res *entity.StockDetails, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.StockDetailsCollection)
return
}
// List 获取SKU列表
func (d *stockDetails) List(ctx context.Context, req *dto.ListStockDetailsReq) (res []entity.StockDetails, total int64, err error) {
// 构建查询过滤条件
filter := bson.M{}
if !g.IsEmpty(req.AssetId) {
filter["assetId"] = req.AssetId
}
if !g.IsEmpty(req.AssetSkuId) {
filter["assetSkuId"] = req.AssetSkuId
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
// 排序处理
req.OrderBy = []beans.OrderBy{
{Field: "sort", Order: beans.Asc},
{Field: "createdAt", Order: beans.Desc},
}
total, err = mongo.DB().Find(ctx, filter, &res, public.StockDetailsCollection, req.Page, req.OrderBy)
return
}

View File

@@ -0,0 +1,69 @@
// 单位换算DAO层
// 职责换算规则CRUD、按单位类型和源目标单位查询
// 紧密耦合service.UnitConversion、service.Capacity(容量计算换算)
// 注意GetByUnits用于容量计算时获取换算系数
package dao
import (
"assets/consts/public"
"assets/consts/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/db/mongo"
"go.mongodb.org/mongo-driver/v2/bson"
)
var UnitConversion = new(unitConversion)
type unitConversion struct{}
// GetByUnits 根据单位类型和源目标单位查询换算规则
func (d *unitConversion) GetByUnits(ctx context.Context, unitType stock.CapacityUnitType, fromUnit, toUnit string) (res *entity.UnitConversion, err error) {
filter := bson.M{
"unitType": unitType,
"fromUnit": fromUnit,
"toUnit": toUnit,
}
err = mongo.DB().FindOne(ctx, filter, &res, public.UnitConversionCollection)
return
}
// Insert 插入换算规则
func (d *unitConversion) Insert(ctx context.Context, conversion *entity.UnitConversion) (ids []interface{}, err error) {
ids, err = mongo.DB().Insert(ctx, []interface{}{conversion}, public.UnitConversionCollection)
return
}
// Update 更新换算规则
func (d *unitConversion) Update(ctx context.Context, id *bson.ObjectID, update bson.M) (err error) {
filter := bson.M{"_id": id}
updateDoc := bson.M{"$set": update}
_, err = mongo.DB().Update(ctx, filter, updateDoc, public.UnitConversionCollection)
return
}
// DeleteFake 软删除换算规则
func (d *unitConversion) DeleteFake(ctx context.Context, id *bson.ObjectID) (err error) {
filter := bson.M{"_id": id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.UnitConversionCollection)
return
}
// List 查询换算规则列表
func (d *unitConversion) List(ctx context.Context, unitType *stock.CapacityUnitType, fromUnit, toUnit string) (res []entity.UnitConversion, err error) {
filter := bson.M{}
if unitType != nil {
filter["unitType"] = *unitType
}
if fromUnit != "" {
filter["fromUnit"] = fromUnit
}
if toUnit != "" {
filter["toUnit"] = toUnit
}
_, err = mongo.DB().Find(ctx, filter, &res, public.UnitConversionCollection, nil, nil)
return
}

141
dao/stock/warehouse_dao.go Normal file
View File

@@ -0,0 +1,141 @@
// 仓库DAO层
// 职责仓库CRUD、状态更新、批量更新库区/库位状态(状态联动)、更新容量
// 紧密耦合service.Warehouse(状态联动)、service.Capacity(容量汇总)
// 注意UpdateCapacityByUnitType使用嵌套路径更新map字段
package dao
import (
"assets/consts/public"
"assets/consts/stock"
"assets/model/config"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var Warehouse = new(warehouse)
type warehouse struct{}
func (d *warehouse) Insert(ctx context.Context, req *dto.CreateWarehouseReq) (ids []interface{}, err error) {
var result *entity.Warehouse
if err = utils.Struct(req, &result); err != nil {
return
}
// 如果未传入状态,设置默认值为启用
if result.Status == "" {
result.Status = stock.WarehouseStatusEnabled
}
// 初始化Capacity为空map避免后续UpdateCapacityByUnitType panic
if result.Capacity == nil {
emptyMap := make(map[stock.CapacityUnitType]config.Capacity)
result.Capacity = &emptyMap
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.WarehouseCollection)
return
}
func (d *warehouse) GetOne(ctx context.Context, req *dto.GetWarehouseReq) (res *entity.Warehouse, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.WarehouseCollection)
return
}
func (d *warehouse) Update(ctx context.Context, req *dto.UpdateWarehouseReq) (err error) {
buildFilter, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": buildFilter}
_, err = mongo.DB().Update(ctx, filter, update, public.WarehouseCollection)
return
}
func (d *warehouse) DeleteFake(ctx context.Context, req *dto.DeleteWarehouseReq) (err error) {
filter := bson.M{"_id": req.Id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.WarehouseCollection)
return
}
// UpdateStatus 更新仓库状态
func (d *warehouse) UpdateStatus(ctx context.Context, req *dto.UpdateWarehouseStatusReq) (err error) {
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": bson.M{"status": req.Status}}
_, err = mongo.DB().Update(ctx, filter, update, public.WarehouseCollection)
return
}
func (d *warehouse) List(ctx context.Context, req *dto.ListWarehouseReq) (res []entity.Warehouse, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.WarehouseCollection, req.Page, req.OrderBy)
return
}
func (d *warehouse) buildListFilter(ctx context.Context, req *dto.ListWarehouseReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.Keyword) {
filter["$or"] = bson.A{
bson.M{"warehouseCode": bson.M{"$regex": req.Keyword, "$options": "i"}},
bson.M{"warehouseName": bson.M{"$regex": req.Keyword, "$options": "i"}},
}
}
return
}
// CountZonesByWarehouseId 统计仓库下的库区数量(用于删除前检查)
func (d *warehouse) CountZonesByWarehouseId(ctx context.Context, warehouseId string) (count int64, err error) {
filter := bson.M{"warehouseId": warehouseId}
count, err = mongo.DB().Count(ctx, filter, public.ZoneCollection)
return
}
// BatchUpdateZoneStatus 批量更新仓库下所有库区状态(用于状态联动)
// fromStatus 可选:指定时只更新当前状态为 fromStatus 的记录,避免覆盖其他状态
func (d *warehouse) BatchUpdateZoneStatus(ctx context.Context, warehouseId string, status stock.ZoneStatus, fromStatus ...stock.ZoneStatus) (modifiedCount int64, err error) {
filter := bson.M{"warehouseId": warehouseId}
if len(fromStatus) > 0 {
filter["status"] = fromStatus[0]
}
update := bson.M{"$set": bson.M{"status": status}}
modifiedCount, err = mongo.DB().Update(ctx, filter, update, public.ZoneCollection)
return
}
// UpdateCapacityByUnitType 更新仓库指定容量单位类型的容量
func (d *warehouse) UpdateCapacityByUnitType(ctx context.Context, warehouseId *bson.ObjectID, unitType stock.CapacityUnitType, currentCapacity int, maxCapacity int, capacityUnit string) (err error) {
filter := bson.M{"_id": warehouseId}
update := bson.M{
"$set": bson.M{
"capacity." + string(unitType) + ".currentCapacity": currentCapacity,
"capacity." + string(unitType) + ".maxCapacity": maxCapacity,
"capacity." + string(unitType) + ".capacityUnit": capacityUnit,
},
}
_, err = mongo.DB().Update(ctx, filter, update, public.WarehouseCollection)
return
}
// BatchUpdateLocationStatus 批量更新仓库下所有库位状态(用于状态联动)
// fromStatus 可选:指定时只更新当前状态为 fromStatus 的记录,避免覆盖其他状态
func (d *warehouse) BatchUpdateLocationStatus(ctx context.Context, warehouseId string, status stock.LocationStatus, fromStatus ...stock.LocationStatus) (modifiedCount int64, err error) {
filter := bson.M{"warehouseId": warehouseId}
if len(fromStatus) > 0 {
filter["status"] = fromStatus[0]
}
update := bson.M{"$set": bson.M{"status": status}}
modifiedCount, err = mongo.DB().Update(ctx, filter, update, public.LocationCollection)
return
}

162
dao/stock/zone_dao.go Normal file
View File

@@ -0,0 +1,162 @@
// 库区DAO层
// 职责库区CRUD、状态更新、批量更新库位状态(状态联动)、更新容量
// 紧密耦合service.Zone(状态联动)、service.Capacity(容量汇总)
// 注意UpdateCapacityByUnitType使用嵌套路径更新map字段
package dao
import (
"assets/consts/public"
"assets/consts/stock"
"assets/model/config"
dto "assets/model/dto/stock"
entity "assets/model/entity/stock"
"context"
"gitea.com/red-future/common/db/mongo"
"gitea.com/red-future/common/utils"
"github.com/gogf/gf/v2/frame/g"
"go.mongodb.org/mongo-driver/v2/bson"
)
var Zone = new(zone)
type zone struct{}
func (d *zone) Insert(ctx context.Context, req *dto.CreateZoneReq) (ids []interface{}, err error) {
var result *entity.Zone
if err = utils.Struct(req, &result); err != nil {
return
}
// 如果未传入状态,设置默认值为启用
if result.Status == "" {
result.Status = stock.ZoneStatusEnabled
}
// 初始化Capacity为空map避免后续UpdateCapacityByUnitType panic
if result.Capacity == nil {
emptyMap := make(map[stock.CapacityUnitType]config.Capacity)
result.Capacity = &emptyMap
}
ids, err = mongo.DB().Insert(ctx, []interface{}{&result}, public.ZoneCollection)
return
}
func (d *zone) GetOne(ctx context.Context, req *dto.GetZoneReq) (res *entity.Zone, err error) {
filter := bson.M{"_id": req.Id}
err = mongo.DB().FindOne(ctx, filter, &res, public.ZoneCollection)
return
}
func (d *zone) Update(ctx context.Context, req *dto.UpdateZoneReq) (err error) {
buildFilter, err := mongo.BuildUpdateData(ctx, req)
if err != nil {
return
}
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": buildFilter}
_, err = mongo.DB().Update(ctx, filter, update, public.ZoneCollection)
return
}
func (d *zone) DeleteFake(ctx context.Context, req *dto.DeleteZoneReq) (err error) {
filter := bson.M{"_id": req.Id}
_, err = mongo.DB().DeleteSoft(ctx, filter, public.ZoneCollection)
return
}
// UpdateStatus 更新库区状态
func (d *zone) UpdateStatus(ctx context.Context, req *dto.UpdateZoneStatusReq) (err error) {
filter := bson.M{"_id": req.Id}
update := bson.M{"$set": bson.M{"status": req.Status}}
_, err = mongo.DB().Update(ctx, filter, update, public.ZoneCollection)
return
}
func (d *zone) List(ctx context.Context, req *dto.ListZoneReq) (res []entity.Zone, total int64, err error) {
filter, err := d.buildListFilter(ctx, req)
if err != nil {
return
}
total, err = mongo.DB().Find(ctx, filter, &res, public.ZoneCollection, req.Page, req.OrderBy)
return
}
func (d *zone) buildListFilter(ctx context.Context, req *dto.ListZoneReq) (filter bson.M, err error) {
_ = ctx
filter = bson.M{}
// 兼容单值和数组参数(优先使用数组)
if len(req.WarehouseIds) > 0 {
filter["warehouseId"] = bson.M{"$in": req.WarehouseIds}
} else if !g.IsEmpty(req.WarehouseId) {
filter["warehouseId"] = req.WarehouseId
}
if !g.IsEmpty(req.ZoneType) {
filter["zoneType"] = req.ZoneType
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if !g.IsEmpty(req.Keyword) {
filter["$or"] = bson.A{
bson.M{"zoneCode": bson.M{"$regex": req.Keyword, "$options": "i"}},
bson.M{"zoneName": bson.M{"$regex": req.Keyword, "$options": "i"}},
}
}
return
}
// CountLocationsByZoneId 统计库区下的库位数量(用于删除前检查)
func (d *zone) CountLocationsByZoneId(ctx context.Context, zoneId string) (count int64, err error) {
filter := bson.M{"zoneId": zoneId}
count, err = mongo.DB().Count(ctx, filter, public.LocationCollection)
return
}
// CountByWarehouseId 统计仓库下的库区数量(用于删除前检查)
func (d *zone) CountByWarehouseId(ctx context.Context, warehouseId string) (count int64, err error) {
filter := bson.M{"warehouseId": warehouseId}
count, err = mongo.DB().Count(ctx, filter, public.ZoneCollection)
return
}
// BatchUpdateStatusByWarehouseId 批量更新仓库下所有库区状态(用于状态联动)
func (d *zone) BatchUpdateStatusByWarehouseId(ctx context.Context, warehouseId string, status stock.ZoneStatus) (modifiedCount int64, err error) {
filter := bson.M{"warehouseId": warehouseId}
update := bson.M{"$set": bson.M{"status": status}}
modifiedCount, err = mongo.DB().Update(ctx, filter, update, public.ZoneCollection)
return
}
// BatchUpdateLocationStatus 批量更新库区下所有库位状态(用于状态联动)
// fromStatus 可选:指定时只更新当前状态为 fromStatus 的记录,避免覆盖其他状态
func (d *zone) BatchUpdateLocationStatus(ctx context.Context, zoneId string, status stock.LocationStatus, fromStatus ...stock.LocationStatus) (modifiedCount int64, err error) {
filter := bson.M{"zoneId": zoneId}
if len(fromStatus) > 0 {
filter["status"] = fromStatus[0]
}
update := bson.M{"$set": bson.M{"status": status}}
modifiedCount, err = mongo.DB().Update(ctx, filter, update, public.LocationCollection)
return
}
// UpdateCapacityByUnitType 更新库区指定容量单位类型的容量
func (d *zone) UpdateCapacityByUnitType(ctx context.Context, zoneId *bson.ObjectID, unitType stock.CapacityUnitType, currentCapacity int, maxCapacity int, capacityUnit string) (err error) {
filter := bson.M{"_id": zoneId}
update := bson.M{
"$set": bson.M{
"capacity." + string(unitType) + ".currentCapacity": currentCapacity,
"capacity." + string(unitType) + ".maxCapacity": maxCapacity,
"capacity." + string(unitType) + ".capacityUnit": capacityUnit,
},
}
_, err = mongo.DB().Update(ctx, filter, update, public.ZoneCollection)
return
}
// ListByWarehouseAndUnitType 按仓库ID查询所有库区用于汇总仓库容量
func (d *zone) ListByWarehouseAndUnitType(ctx context.Context, warehouseId string) (res []entity.Zone, err error) {
filter := bson.M{
"warehouseId": warehouseId,
}
_, err = mongo.DB().Find(ctx, filter, &res, public.ZoneCollection, nil, nil)
return
}

169
dao/sync/sync_dao.go Normal file
View File

@@ -0,0 +1,169 @@
package dao
import (
"assets/consts/public"
dto "assets/model/dto/sync"
entity "assets/model/entity/sync"
"context"
"gitea.com/red-future/common/db/mongo"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"go.mongodb.org/mongo-driver/v2/bson"
)
var SyncTask = new(syncTask)
type syncTask struct{}
// Insert 插入同步任务
func (d *syncTask) Insert(ctx context.Context, task *entity.SyncTask) (err error) {
_, err = mongo.DB().Insert(ctx, []interface{}{task}, task.CollectionName())
return
}
// GetOne 获取单个同步任务
func (d *syncTask) GetOne(ctx context.Context, id *bson.ObjectID) (task *entity.SyncTask, err error) {
filter := bson.M{"_id": id}
task = &entity.SyncTask{}
err = mongo.DB().FindOne(ctx, filter, task, task.CollectionName())
return task, err
}
// List 获取同步任务列表
func (d *syncTask) List(ctx context.Context, req *dto.ListSyncTaskReq) (list []*entity.SyncTask, total int64, err error) {
// 构建查询过滤条件
filter := d.buildListFilter(req)
// 调用 common/db/mongo 的 Find 方法,不使用排序
total, err = mongo.DB().Find(ctx, filter, &list, "sync_task", &req.Page, nil)
return
}
// Update 更新同步任务
func (d *syncTask) Update(ctx context.Context, id string, updateData *entity.SyncTask) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return err
}
filter := bson.M{"_id": objectId}
if !g.IsEmpty(updateData) {
// 直接使用 struct 转 map不需要额外的转换
update := bson.M{"$set": gconv.Map(updateData)}
_, err = mongo.DB().Update(ctx, filter, update, "sync_task")
}
return err
}
// UpdateStatus 更新同步任务状态
func (d *syncTask) UpdateStatus(ctx context.Context, id *bson.ObjectID, status public.SyncStatus, errorMessage string) (err error) {
filter := bson.M{"_id": id}
updateData := bson.M{
"status": status,
"errorMessage": errorMessage,
}
if status == public.SyncStatusSyncing {
updateData["startedAt"] = gtime.Now()
} else if status == public.SyncStatusSuccess || status == public.SyncStatusFailed {
updateData["finishedAt"] = gtime.Now()
}
update := bson.M{"$set": updateData}
_, err = mongo.DB().Update(ctx, filter, update, "sync_task")
return err
}
// UpdateErrorCount 更新错误计数
func (d *syncTask) UpdateErrorCount(ctx context.Context, id string, increment int) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return err
}
filter := bson.M{"_id": objectId}
update := bson.M{"$inc": bson.M{"errorCount": increment}}
_, err = mongo.DB().Update(ctx, filter, update, "sync_task")
return err
}
// Delete 删除同步任务
func (d *syncTask) Delete(ctx context.Context, id string) (err error) {
objectId, err := bson.ObjectIDFromHex(id)
if err != nil {
return err
}
filter := bson.M{"_id": objectId}
_, err = mongo.DB().Delete(ctx, filter, "sync_task")
return err
}
// GetPendingTasks 获取待处理的同步任务
func (d *syncTask) GetPendingTasks(ctx context.Context, limit int) (tasks []*entity.SyncTask, err error) {
filter := bson.M{"status": public.SyncStatusPending}
// 调用 common/db/mongo 的 Find 方法,不使用排序
_, err = mongo.DB().Find(ctx, filter, &tasks, "sync_task", nil, nil)
return tasks, err
}
// buildListFilter 构建列表查询的过滤条件
func (d *syncTask) buildListFilter(req *dto.ListSyncTaskReq) bson.M {
filter := bson.M{}
if !g.IsEmpty(req.Platform) {
filter["platform"] = req.Platform
}
if !g.IsEmpty(req.Status) {
filter["status"] = req.Status
}
if req.StartTime != nil {
filter["createdAt"] = bson.M{"$gte": req.StartTime}
}
if req.EndTime != nil {
if existingFilter, exists := filter["createdAt"]; exists {
if existingTimeRange, ok := existingFilter.(bson.M); ok {
existingTimeRange["$lte"] = req.EndTime
filter["createdAt"] = existingTimeRange
}
} else {
filter["createdAt"] = bson.M{"$lte": req.EndTime}
}
}
return filter
}
// SyncConfigDao 同步配置DAO
var SyncConfig = new(syncConfig)
type syncConfig struct{}
// GetByPlatform 根据平台获取同步配置
func (d *syncConfig) GetByPlatform(ctx context.Context, platform public.SyncPlatform) (config *entity.ChannelConfig, err error) {
filter := bson.M{"platform": platform}
config = &entity.ChannelConfig{}
err = mongo.DB().FindOne(ctx, filter, config, "sync_config")
return config, err
}
// List 获取同步配置列表
func (d *syncConfig) List(ctx context.Context) (configs []*entity.ChannelConfig, err error) {
_, err = mongo.DB().Find(ctx, bson.M{}, &configs, "sync_config", nil, nil)
return configs, err
}
// Update 更新同步配置
func (d *syncConfig) Update(ctx context.Context, platform public.SyncPlatform, updateData *entity.ChannelConfig) (err error) {
filter := bson.M{"platform": platform}
if !g.IsEmpty(updateData) {
// 直接使用 struct 转 map
update := bson.M{"$set": gconv.Map(updateData)}
_, err = mongo.DB().Update(ctx, filter, update, "sync_config")
}
return err
}