package repo
import (
"box-cost/db"
"box-cost/db/model"
dm "box-cost/db/model"
"box-cost/log"
"context"
"encoding/json"
"fmt"
"html"
"regexp"
"time"
"github.com/sergi/go-diff/diffmatchpatch"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type RepoSession struct {
Ctx context.Context
Client *db.MongoDB
}
const (
CollectionMaterial = "material"
CollectionCraft = "craft"
CollectionProduct = "product"
CollectionSupplier = "supplier"
CollectionSupplierPrice = "supplier-price"
CollectionPack = "pack"
CollectionProductPlan = "product-plan"
CollectionBillPurchase = "bill-purchase"
CollectionBillProduce = "bill-produce"
CollectionBillProduct = "bill-product" // 成品采购
CollectionSupplierMatprice = "supplier-mats"
CollectionSupplierCraftprice = "supplier-crafts"
CollectionSupplierProductprice = "supplier-product" // 成品采购
CollectionIncrement = "increment"
CollectionSignature = "signature"
CollectionUsers = "users"
// 更改日志记录
CollectionLogs = "logs"
CollectionRequestLogs = "request-logs"
CollectionPlanTrack = "plan-track"
)
type Map map[string]interface{}
type PageResult struct {
List []map[string]interface{} `json:"list"`
Total int64 `json:"total"`
Page int64 `json:"page"`
Size int64 `json:"size"`
}
type PageSearchOptions struct {
Db string
CollectName string
Page int64
Size int64
Query map[string]interface{}
Project []string
Sort interface{}
}
type DocSearchOptions struct {
Db string
CollectName string
Query Map
Project []string
Sort bson.M
}
type DocFilterOptions struct {
Db string
CollectName string
Query Map
}
func NewDocSearchOptions(filter Map, project []string) *DocSearchOptions {
return &DocSearchOptions{
Query: filter,
Project: project,
}
}
func RepoAddDoc(ctx *RepoSession, collectName string, doc interface{}) (string, error) {
users := ctx.Client.GetCollection(collectName)
result, err := users.InsertOne(ctx.Ctx, doc)
if err != nil {
return "", err
}
return result.InsertedID.(primitive.ObjectID).Hex(), nil
}
func RepoDbAddDoc(ctx *RepoSession, dbName string, collectName string, doc interface{}) (string, error) {
users := ctx.Client.GetDbCollection(dbName, collectName)
result, err := users.InsertOne(ctx.Ctx, doc)
if err != nil {
return "", err
}
return result.InsertedID.(primitive.ObjectID).Hex(), nil
}
func RepoDeleteDoc(ctx *RepoSession, collectName string, id string) (interface{}, error) {
uid, _ := primitive.ObjectIDFromHex(id)
colls := ctx.Client.GetCollection(collectName)
return colls.DeleteOne(ctx.Ctx, &bson.M{"_id": uid})
}
func RepoDeleteDbDoc(ctx *RepoSession, dbName string, collectName string, id string) (interface{}, error) {
uid, _ := primitive.ObjectIDFromHex(id)
colls := ctx.Client.GetDbCollection(dbName, collectName)
return colls.DeleteOne(ctx.Ctx, &bson.M{"_id": uid})
}
func RepoDeleteDocs(ctx *RepoSession, collectName string, query interface{}) (interface{}, error) {
colls := ctx.Client.GetCollection(collectName)
return colls.DeleteMany(ctx.Ctx, query)
}
func RepoUpdateSetDoc(ctx *RepoSession, collectName string, idstr string, model interface{}) (*mongo.UpdateResult, error) {
colls := ctx.Client.GetCollection(collectName)
update := bson.M{"$set": model}
uid, _ := primitive.ObjectIDFromHex(idstr)
return colls.UpdateByID(ctx.Ctx, uid, update)
}
type RecordLogReq struct {
Path string
UserId string
TargetId string
}
func RepoUpdateSetDoc1(ctx *RepoSession, collectName string, idstr string, model interface{}, recordLogReq *RecordLogReq) (*mongo.UpdateResult, error) {
colls := ctx.Client.GetCollection(collectName)
update := bson.M{"$set": model}
// 获取模型对应的数据表
collection := getModel2Collection(model)
uid, _ := primitive.ObjectIDFromHex(idstr)
// ========================记录更新日志 查询更新前数据============================
var oldData Map
err := colls.FindOne(ctx.Ctx, bson.M{"_id": uid}).Decode(&oldData)
fmt.Println(err)
// ============================================================================
result, err1 := colls.UpdateByID(ctx.Ctx, uid, update)
// ========================记录更新日志 查询更新后数据============================
var newData Map
colls.FindOne(ctx.Ctx, bson.M{"_id": uid}).Decode(&newData)
// ============================================================================
// ============================记录更新前后差异================================
diff, err := diffUpdateData(oldData, newData)
if err != nil {
fmt.Println(err)
}
fmt.Println(diff.Diff)
userInfo, err := getUserById(ctx, recordLogReq.UserId)
if err != nil {
fmt.Println(err)
}
// 记录到数据库中
changeLogs := &dm.Logs{
Path: recordLogReq.Path,
UserId: recordLogReq.UserId,
TargetId: recordLogReq.TargetId,
Collection: collection,
UserInfo: userInfo,
Diff: diff.Diff,
Changes: diff.Changes,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
RepoAddDoc(ctx, CollectionLogs, changeLogs)
// ==========================================================================
return result, err1
}
func getUserById(ctx *RepoSession, id string) (*model.UserSmaple, error) {
user := &model.UserSmaple{}
_, err := RepoSeachDoc(ctx, &DocSearchOptions{
Db: "box-user",
CollectName: CollectionUsers,
Query: Map{"_id": id},
Project: []string{"name", "avatar", "city", "loginName", "roles", "phone"},
}, user)
return user, err
}
func getModel2Collection(omodel interface{}) string {
// 订单
if _, ok := omodel.(*dm.PurchaseBill); ok {
return CollectionBillPurchase
}
// if _, ok := omodel.(*dm.ProduceBill); ok {
// return CollectionBillProduce
// }
// test
if _, ok := omodel.(*dm.ProduceBill); ok {
return "bill-produce_copy1"
}
if _, ok := omodel.(*dm.ProductBill); ok {
return CollectionBillProduct
}
// 计划
// if _, ok := omodel.(*dm.ProductPlan); ok {
// return CollectionProductPlan
// }
// test
if _, ok := omodel.(*dm.ProductPlan); ok {
return "product-plan_copy1"
}
if _, ok := omodel.(*dm.Product); ok {
return CollectionProduct
}
// craft
if _, ok := omodel.(*dm.Craft); ok {
return CollectionCraft
}
// material
if _, ok := omodel.(*dm.Material); ok {
return CollectionMaterial
}
// pack
if _, ok := omodel.(*dm.Pack); ok {
return CollectionPack
}
if _, ok := omodel.(*dm.PlanTrack); ok {
return CollectionPlanTrack
}
if _, ok := omodel.(*dm.Signature); ok {
return CollectionSignature
}
if _, ok := omodel.(*dm.Supplier); ok {
return CollectionSupplier
}
if _, ok := omodel.(*dm.Setting); ok {
return "infos"
}
if _, ok := omodel.(*dm.Unit); ok {
return "units"
}
if _, ok := omodel.(*dm.Category); ok {
return "cates"
}
return ""
}
type DiffUpdateData struct {
Diff string
Changes []string
}
// 比较差异字符
func diffUpdateData(oldObj any, newObj any) (*DiffUpdateData, error) {
oldObjByte, err := json.Marshal(oldObj)
if err != nil {
return &DiffUpdateData{}, err
}
newObjByte, err := json.Marshal(newObj)
if err != nil {
return &DiffUpdateData{}, err
}
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(oldObjByte), string(newObjByte), false)
// 返回差异数据用于日志记录
diff := &DiffUpdateData{}
if len(diffs) > 0 {
// return dmp.DiffPrettyText(diffs), nil
htmlString := html.UnescapeString(dmp.DiffPrettyHtml(diffs))
delInsRegex := regexp.MustCompile(`"([^"]+)":\s*"([^"]*(]*>.*?|]*>.*?)+[^"]*)",`)
delInsMatches := delInsRegex.FindAllString(htmlString, -1)
diff.Diff = htmlString
diff.Changes = delInsMatches
return diff, nil
}
return &DiffUpdateData{}, err
}
func RepoUpdateSeDbDoc(ctx *RepoSession, db string, collectName string, idstr string, model interface{}) (*mongo.UpdateResult, error) {
colls := ctx.Client.GetDbCollection(db, collectName)
update := bson.M{"$set": model}
uid, _ := primitive.ObjectIDFromHex(idstr)
return colls.UpdateByID(ctx.Ctx, uid, update)
}
func RepoUpdateSetDocProps(ctx *RepoSession, collectName string, idstr string, update interface{}) (*mongo.UpdateResult, error) {
colls := ctx.Client.GetCollection(collectName)
// update := bson.M{"$set": model}
uid, _ := primitive.ObjectIDFromHex(idstr)
return colls.UpdateByID(ctx.Ctx, uid, update)
}
func RepoUpdateSetDocsProps(ctx *RepoSession, filter *DocFilterOptions, model interface{}) (*mongo.UpdateResult, error) {
colls := ctx.Client.GetCollection(filter.CollectName)
if len(filter.Db) > 0 {
colls = ctx.Client.GetDbCollection(filter.Db, filter.CollectName)
}
update := bson.M{"$set": model}
filterParams := bson.M{}
if len(filter.Query) > 0 {
for k, v := range filter.Query {
if k == "_id" {
if uid, ok := v.(string); ok {
docId, _ := primitive.ObjectIDFromHex(uid)
filterParams["_id"] = docId
continue
}
}
filterParams[k] = v
}
}
return colls.UpdateMany(ctx.Ctx, filterParams, update)
}
func RepoUpdateSetDbDocProps(ctx *RepoSession, db string, collectName string, idstr string, update interface{}) (*mongo.UpdateResult, error) {
colls := ctx.Client.GetDbCollection(db, collectName)
// update := bson.M{"$set": model}
uid, _ := primitive.ObjectIDFromHex(idstr)
return colls.UpdateByID(ctx.Ctx, uid, update)
}
func RepoSeachDoc(ctx *RepoSession, param *DocSearchOptions, v interface{}) (bool, error) {
colls := ctx.Client.GetDbCollection(param.Db, param.CollectName)
opt := &options.FindOneOptions{}
if len(param.Project) > 0 {
prj := bson.M{}
for _, v := range param.Project {
prj[v] = 1
}
opt.SetProjection(prj)
}
if len(param.Sort) > 0 {
opt.Sort = param.Sort
}
filter := bson.M{}
if len(param.Query) > 0 {
for k, v := range param.Query {
if k == "_id" {
if uid, ok := v.(string); ok {
docId, _ := primitive.ObjectIDFromHex(uid)
filter["_id"] = docId
continue
}
}
filter[k] = v
}
}
err := colls.FindOne(ctx.Ctx, filter, opt).Decode(v)
if err == mongo.ErrNoDocuments {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}
func RepoSeachDocMap(ctx *RepoSession, param *DocSearchOptions) (bool, map[string]interface{}) {
ret := map[string]interface{}{}
ok := true
colls := ctx.Client.GetDbCollection(param.Db, param.CollectName)
opt := &options.FindOneOptions{}
if len(param.Project) > 0 {
prj := bson.M{}
for _, v := range param.Project {
prj[v] = 1
}
opt.SetProjection(prj)
}
if len(param.Sort) > 0 {
opt.Sort = param.Sort
}
filter := bson.M{}
if len(param.Query) > 0 {
for k, v := range param.Query {
if k == "_id" {
if uid, ok := v.(string); ok {
docId, _ := primitive.ObjectIDFromHex(uid)
filter["_id"] = docId
continue
}
}
filter[k] = v
}
}
ok = true
err := colls.FindOne(ctx.Ctx, filter, opt).Decode(ret)
if err == mongo.ErrNoDocuments {
ok = false
}
if err != nil {
ok = false
}
return ok, ret
}
// PageSearch 单表分页查询
func RepoPageSearch(ctx *RepoSession, para *PageSearchOptions) (out *PageResult, err error) {
var colls *mongo.Collection
if len(para.Db) > 0 {
colls = ctx.Client.GetDbCollection(para.Db, para.CollectName)
} else {
colls = ctx.Client.GetCollection(para.CollectName)
}
findoptions := &options.FindOptions{}
if para.Size > 0 {
findoptions.SetLimit(para.Size)
findoptions.SetSkip(para.Size * (para.Page - 1))
}
if para.Sort != nil {
findoptions.SetSort(para.Sort)
}
if len(para.Project) > 0 {
prj := bson.M{}
for _, v := range para.Project {
prj[v] = 1
}
findoptions.SetProjection(prj)
}
filter := bson.M{}
if len(para.Query) > 0 {
for k, v := range para.Query {
if value, ok := v.(string); ok {
if len(value) > 0 {
filter[k] = v
continue
}
} else if v != nil {
filter[k] = v
}
}
}
cur, err := colls.Find(ctx.Ctx, filter, findoptions)
out = &PageResult{
List: []map[string]interface{}{},
Total: 0,
Page: para.Page,
Size: para.Size,
}
if err != nil {
return out, err
}
defer cur.Close(ctx.Ctx)
err = cur.All(ctx.Ctx, &out.List)
out.Total, _ = colls.CountDocuments(ctx.Ctx, filter)
return
}
func RepoDbCountDoc(ctx *RepoSession, db string, collectionName string, Query Map) (int64, error) {
colls := ctx.Client.GetDbCollection(db, collectionName)
filter := bson.M{}
if len(Query) > 0 {
for k, v := range Query {
if value, ok := v.(string); ok {
if len(value) > 0 {
filter[k] = v
continue
}
} else {
filter[k] = v
}
}
}
return colls.CountDocuments(ctx.Ctx, filter)
}
func RepoCountDoc(ctx *RepoSession, collectionName string, Query Map) (int64, error) {
colls := ctx.Client.GetCollection(collectionName)
filter := bson.M{}
if len(Query) > 0 {
for k, v := range Query {
if value, ok := v.(string); ok {
if len(value) > 0 {
filter[k] = v
continue
}
} else {
filter[k] = v
}
}
}
return colls.CountDocuments(ctx.Ctx, filter)
}
// PageSearch 单表分页查询
func RepoDocsSearch(ctx *RepoSession, para *PageSearchOptions, out interface{}) (err error) {
colls := ctx.Client.GetCollection(para.CollectName)
if len(para.Db) > 0 {
colls = ctx.Client.GetDbCollection(para.Db, para.CollectName)
}
findoptions := &options.FindOptions{}
if para.Size > 0 {
findoptions.SetLimit(para.Size)
findoptions.SetSkip(para.Size * (para.Page - 1))
}
if para.Sort != nil {
findoptions.SetSort(para.Sort)
}
if len(para.Project) > 0 {
prj := bson.M{}
for _, v := range para.Project {
prj[v] = 1
}
findoptions.SetProjection(prj)
}
filter := bson.M{}
if len(para.Query) > 0 {
for k, v := range para.Query {
if value, ok := v.(string); ok {
if len(value) > 0 {
filter[k] = v
continue
}
} else {
filter[k] = v
}
}
}
cur, err := colls.Find(ctx.Ctx, filter, findoptions)
if err != nil {
return err
}
defer cur.Close(ctx.Ctx)
err = cur.All(ctx.Ctx, out)
return
}
func RepoDocArrayAppend(ctx *RepoSession, collectName string, idstr string, fieldpath string, arrayItem interface{}) (*mongo.UpdateResult, error) {
colls := ctx.Client.GetCollection(collectName)
arrayOp := bson.M{}
arrayOp[fieldpath] = arrayItem
update := bson.M{"$push": arrayOp}
uid, _ := primitive.ObjectIDFromHex(idstr)
return colls.UpdateByID(ctx.Ctx, uid, update)
}
// { _id: 4, "grades.grade": 85 },
// { $set: { "grades.$.std" : 6 } }
type ArrayOneUpdateOption struct {
Query Map
Set Map
CollectName string
Id string
}
// if len(scene.Stickers) > 0 {
// optSet["scenes.$.stickers"] = scene.Stickers
// }
// option := &repo.ArrayOneUpdateOption{
// CollectName: repo.CollectionDesigns,
// Id: id,
// Query: repo.Map{"scenes.id": scene.Id},
// Set: optSet,
// }
func RepoDocArrayOneUpdate(ctx *RepoSession, options *ArrayOneUpdateOption) (*mongo.UpdateResult, error) {
colls := ctx.Client.GetCollection(options.CollectName)
docId, _ := primitive.ObjectIDFromHex(options.Id)
query := bson.M{"_id": docId}
if len(options.Query) > 0 {
for k, v := range options.Query {
query[k] = v
}
}
setOp := bson.M{}
for k, v := range options.Set {
setOp[k] = v
}
update := bson.M{"$set": setOp}
return colls.UpdateOne(ctx.Ctx, query, update)
}
type ArrayOneRemoveOption struct {
ArrayQuery Map
CollectName string
Id string
}
// { $pull: { "items" : { id: 23 } } }
func RepoDocArrayOneRemove(ctx *RepoSession, options *ArrayOneRemoveOption) (*mongo.UpdateResult, error) {
colls := ctx.Client.GetCollection(options.CollectName)
docId, _ := primitive.ObjectIDFromHex(options.Id)
query := bson.M{"_id": docId}
arrayQuery := bson.M{}
if len(options.ArrayQuery) > 0 {
for k, v := range options.ArrayQuery {
arrayQuery[k] = v
}
}
update := bson.M{"$pull": arrayQuery}
return colls.UpdateOne(ctx.Ctx, query, update)
}
type ArrayOneSearchOption struct {
ArrayQuery Map
CollectName string
Id string
Field string
}
func RepoDocArraySearch(ctx *RepoSession, options *ArrayOneSearchOption, entity interface{}) error {
colls := ctx.Client.GetCollection(options.CollectName)
docId, _ := primitive.ObjectIDFromHex(options.Id)
match := []bson.E{}
match = append(match, bson.E{"_id", docId})
matchStage := bson.D{
{"$match", match},
}
unwindStage := bson.D{
{"$unwind", fmt.Sprintf("%s%s", "$", options.Field)},
}
pipe := mongo.Pipeline{matchStage, unwindStage}
if len(options.ArrayQuery) > 0 {
match2 := []bson.E{}
for k, v := range options.ArrayQuery {
match2 = append(match2, bson.E{k, v})
}
match2Stage := bson.D{
{"$match", match2},
}
pipe = append(pipe, match2Stage)
}
curr, err := colls.Aggregate(ctx.Ctx, pipe)
if err != nil {
return err
}
defer curr.Close(ctx.Ctx)
if curr.Next(ctx.Ctx) {
err = curr.Decode(entity)
if err != nil {
return err
}
return nil
}
return nil
}
type DocsSearchOptions struct {
CollectName string
Query map[string]interface{}
Project []string
Sort interface{} //bson.D{ bson.E{"update_time", -1}, bson.E{"goods_id", -1},}
}
func RepoSeachDocsMap(ctx *RepoSession, param *DocsSearchOptions) (ok bool, list []map[string]interface{}) {
colls := ctx.Client.GetCollection(param.CollectName)
findoptions := &options.FindOptions{}
if len(param.Project) > 0 {
prj := bson.M{}
for _, v := range param.Project {
prj[v] = 1
}
findoptions.SetProjection(prj)
}
if param.Sort != nil {
findoptions.SetSort(param.Sort)
}
filter := bson.M{}
if len(param.Query) > 0 {
for k, v := range param.Query {
if value, ok := v.(string); ok {
if len(value) > 0 {
filter[k] = v
continue
}
} else {
filter[k] = v
}
}
}
cur, err := colls.Find(ctx.Ctx, filter, findoptions)
if err != nil {
ok = false
return
}
defer cur.Close(ctx.Ctx)
listRes := []map[string]interface{}{}
err = cur.All(ctx.Ctx, &listRes)
if err != nil {
log.Error(err)
ok = false
return
}
list = listRes
ok = true
return
}
type DbDocsSearchOptions struct {
Db string
CollectName string
Query map[string]interface{}
Project []string
Sort interface{} //bson.D{ bson.E{"update_time", -1}, bson.E{"goods_id", -1},}
}
func DbRepoSeachDocsMap(ctx *RepoSession, param *DbDocsSearchOptions) (ok bool, list []map[string]interface{}) {
colls := ctx.Client.GetDbCollection(param.Db, param.CollectName)
findoptions := &options.FindOptions{}
if len(param.Project) > 0 {
prj := bson.M{}
for _, v := range param.Project {
prj[v] = 1
}
findoptions.SetProjection(prj)
}
if param.Sort != nil {
findoptions.SetSort(param.Sort)
}
filter := bson.M{}
if len(param.Query) > 0 {
for k, v := range param.Query {
if value, ok := v.(string); ok {
if len(value) > 0 {
filter[k] = v
continue
}
} else {
filter[k] = v
}
}
}
cur, err := colls.Find(ctx.Ctx, filter, findoptions)
if err != nil {
ok = false
return
}
defer cur.Close(ctx.Ctx)
listRes := []map[string]interface{}{}
err = cur.All(ctx.Ctx, &listRes)
if err != nil {
log.Error(err)
ok = false
return
}
list = listRes
ok = true
return
}