欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

mgo关系维护v2

程序员文章站 2022-04-22 08:18:19
...
package main

import (
    "dog/util/conman"
    "dog/util/log"
    "dog/util/v"
    "dog/util/val"
    "errors"
    "fmt"
    "reflect"
    "strings"

    mgo "gopkg.in/mgo.v2"
)

// 暂不支持跨数据库

/*B,C 都有外键指向A*/
// 多对一
// 有对象的外键指向A,A被删除时抛出异常
const (
    A_CName = "a"
    B_CName = "b"
    C_CName = "c"
    D_CName = "d"
)

type A struct {
    AId   int64  `json:"a_id" bson:"_id,omitempty"`
    AName string `json:"a_name" bson:"a_name"`
    AAge  string `json:"a_age" bson:"a_age"`

    BS []B `bson:"-" rel:"o2m:b"` //一对多,B对象的外键:a_id(在B对象中遍历到即可,也可指定fk:a_id),关联到本端的_id键(在B对象中遍历到即可,也可指定ref:_id)
    // C  C   `bson:"-" rel:"o2o:c"` //关系为一对多改为一对一,可作为一对一查询,此时不能构建反向一对一查询结构体,会成为无效的递归类型
}

type B struct {
    BId   int64  `json:"b_id" bson:"_id,omitempty"`
    BName string `json:"b_name" bson:"b_name"`
    AId   int64  `json:"a_id"  bson:"a_id,omitempty" fk:"a._id"` //外键指向a表的_id字段
    CId   int64  `json:"c_id"  bson:"c_id" fk:"c._id"`           //外键指向a表的_id字段
    // DS    []D    `bson:"-" rel:"o2m:d"`
    C C `bson:"-" fkv:"c"` //a->外键值对应的表
    // A     A      `bson:"-" fkv:"a"` //a->外键值对应的表
}

type C struct {
    CId   int64  `json:"c_id" bson:"_id,omitempty"` //一对一关系,主键作为外键
    CName string `json:"c_name" bson:"c_name"`
    DS    []D    `bson:"-" rel:"o2m:d"` //a->外键值对应的表
}

type D struct {
    Id    int64 `json:"d_id" bson:"_id,omitempty"`
    DName string
    CId   int64 `json:"c_id"  bson:"c_id" fk:"c._id"`
}

// 多对多
// E或F被删除时,关系表自动被删除
// 默认关系表e_f,且不可设置
// 主关系表名在关系表开头
const (
    E_CName = "e"
    F_CName = "f"
)

type E struct {
    Eid   int64  `json:"e_id" bson:"_id"`
    EName string `json:"e_name" bson:"e_name"`

    DS []D `bson:"-" rel:"m2m:_id.d.2"` //_id本端键,对应关系表的键为e_id(表名+键名),f->对多个f表对象,.1主关系标识->可用来扩展很多功能
    FS []F `bson:"-" rel:"m2m:_id.f.1"` //_id本端键,对应关系表的键为e_id(表名+键名),f->对多个f表对象,.1主关系标识->可用来扩展很多功能
}

type F struct {
    Fid   int64  `json:"_id" bson:"_id"`
    FName string `json:"f_name" bson:"f_name"`
    CS    []C    `bson:"-" rel:"m2m:_id.c.2"` //_id本端键,对应关系表的键为f_id(表名+键名),e->对多个e表对象,.2从关系标识->可用来扩展很多功能
    ES    []E    `bson:"-" rel:"m2m:_id.e.2"` //_id本端键,对应关系表的键为f_id(表名+键名),e->对多个e表对象,.2从关系标识->可用来扩展很多功能
}

func main() {
    icm := conman.NewConMan()
    defer icm.Free()
    // insB(icm)
    // insA(icm)
    // setB(icm)
    // DelA(icm)
    // updateB(icm)
    // insD(icm)
    // insC(icm)

    // a := new(A)
    // if err := FindOne(icm.MgoConn().DB("test").C(A_CName), v.M{"_id": 3}, a); err != nil {
    //  log.Error.Fatal(err)
    // }
    // log.Info.Println(val.JsonPretty(a))
    // insE(icm)
    // insF(icm)

    // e := new(E)
    // if err := FindId(icm.MgoConn().DB("test").C(E_CName), 1, e); err != nil {
    //  log.Error.Fatal(err)
    // }
    // log.Info.Println(val.JsonPretty(e))

    as := &[]A{}
    if err := Find(icm.MgoConn().DB("test").C(A_CName), nil, as, 1, 5); err != nil {
        log.Error.Fatal(err)
    }
    log.Info.Println(val.JsonPretty(as))

    // if err := UpdateRel(icm.MgoConn().DB("test").C(E_CName), &E{Eid: 1, FS: []F{{Fid: 1}, {Fid: 2}}}); err != nil {
    //  log.Error.Fatal(err)
    // }

    // if err := DelRel(icm.MgoConn().DB("test").C(E_CName), &E{Eid: 1},
    //  &F{Fid: 1}); err != nil {
    //  log.Error.Fatal(err)
    // }
}

func updateB(icm conman.IConMan) {
    b := &B{BName: "bname000001", AId: 5, BId: 1}
    if err := UpdateId(icm.MgoConn().DB("test").C(B_CName), b); err != nil {
        log.Error.Fatal(err)
    }
}

func setB(icm conman.IConMan) {
    // if err := Set(icm.MgoConn().DB("test").C(B_CName), new(B), v.M{"_id": 1}, v.M{"a_id": 3}); err != nil {
    //  log.Error.Fatal(err)
    // }
    if err := SetById(icm.MgoConn().DB("test").C(B_CName), new(B), 3, v.M{"a_id": 3}); err != nil {
        log.Error.Fatal(err)
    }
}

func insA(icm conman.IConMan) {
    a := &A{AName: "aname1"}
    if err := Insert(icm.MgoConn().DB("test").C(A_CName), a); err != nil {
        log.Error.Fatal(err)
    }
}
func insE(icm conman.IConMan) {
    a := &E{EName: "ename3"}
    if err := Insert(icm.MgoConn().DB("test").C(E_CName), a); err != nil {
        log.Error.Fatal(err)
    }
}
func insF(icm conman.IConMan) {
    a := &F{FName: "fname3"}
    if err := Insert(icm.MgoConn().DB("test").C(F_CName), a); err != nil {
        log.Error.Fatal(err)
    }
}
func insD(icm conman.IConMan) {
    d := &D{DName: "dname1", CId: 1}
    if err := Insert(icm.MgoConn().DB("test").C(D_CName), d); err != nil {
        log.Error.Fatal(err)
    }
}

func insC(icm conman.IConMan) {
    c := &C{CName: "cname1"}
    if err := Insert(icm.MgoConn().DB("test").C(C_CName), c); err != nil {
        log.Error.Fatal(err)
    }
}
func DelA(icm conman.IConMan) {
    if err := Del(icm.MgoConn().DB("test").C(A_CName), new(A), nil); err != nil {
        log.Error.Fatal(err)
    }
}

func insB(icm conman.IConMan) {
    b := &B{BName: "bname1", AId: 0, CId: 1}
    if err := Insert(icm.MgoConn().DB("test").C(B_CName), b); err != nil {
        log.Error.Fatal(err)
    }
}

/*=================================================================================*/
func Skip(page, item int) int {
    if page == 0 {
        return 0
    }
    return (page - 1) * Limit(page, item)
}

func Limit(page, item int) int {
    if item == 0 && page != 0 {
        return 10
    } else if item != 0 && page != 0 {
        return item
    }
    return 0
}

// 查询
// 一个外键
func Find(c *mgo.Collection, query, ptr interface{}, page, item int) error {
    log.Info.Println("find")
    log.Info.Println(query)
    if err := c.Find(query).Sort("-_id").Skip(Skip(page, item)).Limit(Limit(page, item)).All(ptr); err != nil {
        return err
    }
    // log.Info.Println(c.Name, val.JsonPretty(query), val.JsonPretty(ptr))
    fvs := reflect.ValueOf(ptr).Elem()
    for i := 0; i < fvs.Len(); i++ { //遍历每一个元素
        setRelVal(c, fvs.Index(i).Addr().Interface())
    }
    return nil
}

// 设置关联值
func setRelVal(c *mgo.Collection, ptr interface{}) error {
    fv := reflect.ValueOf(ptr).Elem()
    ft := fv.Type()
    for i := 0; i < ft.NumField(); i++ { //找关系
        j := i
        // 外键值查找(注意,避免死循环)
        fkvCName := ft.Field(i).Tag.Get("fkv") //外键值对应的表
        if fkvCName != "" {
            for i := 0; i < ft.NumField(); i++ {
                fk := strings.Split(ft.Field(i).Tag.Get("fk"), ".")
                if fk[0] != fkvCName {
                    if i == ft.NumField()-1 {
                        return fmt.Errorf("外键:%s 值,对应的外键没有找到", ft.Field(i).Name)
                    }
                    continue
                }
                fviPtr := fv.Field(j).Addr().Interface()
                if err := FindOne(c.Database.C(fkvCName), v.M{fk[1]: fv.Field(i).Interface()}, fviPtr); err != nil {
                    return err
                }
                break //找到
            }
        }
        // 关系对象查找
        relTag := ft.Field(i).Tag.Get("rel")
        if relTag == "" {
            continue
        }
        rel := strings.Split(relTag, ":")
        switch rel[0] {
        case "o2m":
            log.Info.Println("o2m")
            if fv.Field(i).Kind() != reflect.Slice {
                return errors.New("关系列类型错误,需为对象值的切片")
            }
            vi := fv.Field(i)
            vivElemt := vi.Type().Elem()
            FEPtr := reflect.New(vivElemt)
            fit := reflect.TypeOf(FEPtr.Interface()).Elem()
            for i := 0; i < fit.NumField(); i++ { //找外键
                fkTag := fit.Field(i).Tag.Get("fk")
                if fkTag == "" {
                    if i == fit.NumField()-1 {
                        return fmt.Errorf("关系对象:%s没有设置外键", fit.Name())
                    }
                    continue
                }
                fk := strings.Split(fkTag, ".")
                if len(fk) < 2 {
                    return errors.New("外键格式错误")
                }
                relBsonTag := strings.Split(fit.Field(i).Tag.Get("bson"), ",")[0]
                if relBsonTag == "" {
                    return errors.New("设置外键所在的列没有设置bson标签")
                }
                var bsonTag string
                var fvRefVal interface{}
                for i := 0; i < ft.NumField(); i++ {
                    bsonTag = strings.Split(ft.Field(i).Tag.Get("bson"), ",")[0]
                    if bsonTag != fk[1] {
                        if i == ft.NumField()-1 {
                            return errors.New("外键所指向的列不存在")
                        }
                        continue
                    }
                    fvRefVal = fv.Field(i).Interface()
                    break //找到
                }
                if err := Find(c.Database.C(rel[1]), v.M{relBsonTag: fvRefVal}, fv.Field(j).Addr().Interface(), 0, 0); err != nil {
                    return err
                }
                log.Info.Println(fv.Field(i))
                break //只支持一个外键
            }
        case "m2m": //暂只支持主从查询不支持反向查询
            log.Info.Println("m2m")
            relSets := strings.Split(rel[1], ".")
            if relSets[2] != "1" {
                continue
            }
            relCName := GetRelCName(c, relSets)
            // 关系键
            relKey1 := c.Name + "_id"
            relKey2 := relSets[1] + "_id"
            log.Info.Println(relKey1, relKey2)
            // 本端键对应的值
            idVal1, err := GetFieldValByBsonTagId(fv)
            if err != nil {
                return err
            }
            // 对端关系值
            relValMaps := []v.M{}
            if err := c.Database.C(relCName).Find(v.M{relKey1: idVal1}).Select(v.M{"_id": 0, relKey2: 1}).All(&relValMaps); err != nil {
                return err
            }
            relVals := []interface{}{}
            for _, relValMap := range relValMaps {
                relVals = append(relVals, relValMap[relKey2])
            }
            log.Info.Println(relVals)
            // 查询关系值
            if err := Find(
                c.Database.C(relSets[1]),
                v.M{"_id": v.M{"$in": relVals}},
                fv.Field(i).Addr().Interface(), 0, 0,
            ); err != nil {
                return err
            }
        default:
            return fmt.Errorf("关系标签格式错误,列:%s,%s", "rel:\"o2m:b\"", "rel:\"m2m:_id.f.1\"")
        }
    }
    return nil
}

// 从多对多关系设置中获取关系表名
// 本端关系_id.f.1
// 对端关系_id.e.2
func getCNameByM2MSet(set1, set2 string) (string, error) {
    set1s, set2s := strings.Split(set1, "."), strings.Split(set2, ".")
    if len(set1s) < 3 || len(set2s) < 3 {
        return "", errors.New("getCNameByM2MSet:关系配置错误")
    }
    relCName := ""
    if set1s[2] == "1" {
        relCName = set2s[1] + "_"
        if set2s[2] == "2" {
            relCName += set1s[1]
        } else {
            return "", errors.New("从端关系设置错误")
        }
    }
    if set2s[2] == "1" {
        relCName = set1s[1] + "_"
        if set1s[2] == "2" {
            relCName += set2s[1]
        } else {
            return "", errors.New("从端关系设置错误")
        }
    }
    if relCName == "" {
        return "", errors.New("主从端关系设置错误")
    }
    return relCName, nil
}

// 获取多对多关系的主的关系键名
func getKey1ByM2MSet(set1, set2 string) (string, error) {
    set1s, set2s := strings.Split(set1, "."), strings.Split(set2, ".")
    if len(set1s) < 3 || len(set2s) < 3 {
        return "", errors.New("getKey1ByM2MSet:关系配置错误")
    }
    key1 := ""
    if set1s[2] == "1" {
        key1 = set1s[0] + "_" + set2s[1]
    } else {
        key1 = set2s[0] + "_" + set1s[1]
    }
    return key1, nil
}

// 获取多对多关系的从的关系键名
func getKey2ByM2MSet(set1, set2 string) (string, error) {
    set1s, set2s := strings.Split(set1, "."), strings.Split(set2, ".")
    if len(set1s) < 3 || len(set2s) < 3 {
        return "", errors.New("getKey1ByM2MSet:关系配置错误")
    }
    key1 := ""
    if set1s[2] == "2" {
        key1 = set1s[0] + "_" + set2s[1]
    } else {
        key1 = set2s[0] + "_" + set1s[1]
    }
    return key1, nil
}

func FindOne(c *mgo.Collection, query, ptr interface{}) error {
    if err := c.Find(query).One(ptr); err != nil {
        return err
    }
    return setRelVal(c, ptr)
}

func FindId(c *mgo.Collection, id int64, ptr interface{}) error {
    return FindOne(c, v.M{"_id": id}, ptr)
}

// 更新
func Update(c *mgo.Collection, query, ptr interface{}) error {
    // 判断添加的对象是否有外键
    if err := CheckFK(c, ptr); err != nil {
        return err
    }
    err := c.Update(query, ptr)
    return err
}

func UpdateId(c *mgo.Collection, ptr interface{}) error {
    id, err := GetIdVal(ptr)
    if err != nil {
        return err
    }
    return Update(c, v.M{"_id": id}, ptr)
}

// 修改
// 全部外键
func Set(c *mgo.Collection, ptr interface{}, query, setFields v.M) error {
    // 判断修改的文档是否有外键
    ft := reflect.TypeOf(ptr).Elem()
    for i := 0; i < ft.NumField(); i++ {
        fk := ft.Field(i).Tag.Get("fk")
        if fk == "" {
            continue
        }
        fkvs := strings.Split(fk, ".")
        if len(fkvs) < 2 {
            return errors.New("外键配置错误,正确格式:fk:\"a,_id\",a->表名,_id->指向的列")
        }
        // 检查外键是否被修改fk:"a._id"
        bsonTag := strings.Split(ft.Field(i).Tag.Get("bson"), ",")[0]
        if val, ok := setFields[bsonTag]; ok {
            if count, err := c.Database.C(fkvs[0]).Find(v.M{fkvs[1]: val}).Count(); err != nil {
                return err
            } else if count == 0 {
                return fmt.Errorf("外键:%s,指向值:%d,不存在", ft.Field(i).Name, val)
            }
        }
    }
    _, err := c.UpdateAll(query, v.M{"$set": setFields})
    return err
}

func SetById(c *mgo.Collection, ptr interface{}, id int64, setFields v.M) error {
    return Set(c, ptr, v.M{"_id": id}, setFields)
}

/*目前不做内嵌结构*/
// 并发没有加锁
// 添加对象,要检查关系
func Insert(c *mgo.Collection, ptr interface{}) error {
    // 判断添加的对象是否有外键
    if err := CheckFK(c, ptr); err != nil {
        return err
    }
    // 配置_id
    if err := SetId(c, ptr); err != nil {
        return err
    }
HERE:
    if err := c.Insert(ptr); err != nil {
        if strings.Contains(err.Error(), "index: _id_ dup key") { //_id重复
            maxIdM := map[string]int64{}
            if err := c.Pipe([]v.M{v.M{"$group": v.M{"_id": nil, "max_id": v.M{"$max": "$_id"}}}, v.M{"$project": v.M{"_id": 0}}}).One(&maxIdM); err != nil {
                return err
            }
            if err := SetIntId4Max(c, maxIdM["max_id"]+1); err != nil {
                return err
            }
            ft := reflect.TypeOf(ptr).Elem()
            fv := reflect.ValueOf(ptr).Elem()
            for i := 0; i < ft.NumField(); i++ {
                bfv := strings.Split(ft.Field(i).Tag.Get("bson"), ",")[0]
                if bfv != "_id" {
                    if i == ft.NumField()-1 {
                        return errors.New("插入的对象没有指定主键bson:\"_id\"")
                    }
                    continue
                }
                if _, ok := fv.Field(i).Interface().(int64); !ok {
                    return errors.New("主键只支持int64类型")
                }
                fv.Field(i).SetInt(maxIdM["max_id"] + 1)
                break
            }
            goto HERE
        }
        return err
    }
    // 更新多对多关系
    return UpdateRel(c, ptr)
}

// 多对多关系创建
// 只能从主端开始创建
// 没检查外键表关系是否存在
// 删除对象删除关系键TODO
func UpdateRel(c *mgo.Collection, ptr interface{}) error {
    fv := reflect.ValueOf(ptr).Elem()
    ft := reflect.TypeOf(ptr).Elem()
    j := 0
    for i := 0; i < fv.NumField(); i++ {
        relSets, ok, err := getM2MRelSetByRelTag(ft.Field(i))
        if err != nil {
            return err
            // } else if !ok {
        } else if !ok || relSets[2] != "1" { //过滤非主端
            continue
        }
        log.Info.Println(relSets)
        relCName := GetRelCName(c, relSets)
        log.Info.Println(relCName)
        // 获取本端键值map
        idVal1, err := GetFieldValByBsonTagId(fv)
        if err != nil {
            return err
        }
        log.Info.Println(idVal1)
        relKey1 := c.Name + "_id"
        relkey2 := relSets[1] + "_id"
        j = i
        relC := c.Database.C(relCName)
        // 删除之前关系
        _, err = relC.RemoveAll(v.M{relKey1: idVal1})
        if err != nil {
            return err
        }
        // 遍历对端对象
        for i := 0; i < fv.Field(j).Len(); i++ {
            IdVal2, err := GetFieldValByBsonTagId(fv.Field(j).Index(i))
            if err != nil {
                return err
            }
            // 添加关系
            if err := relC.EnsureIndex(mgo.Index{Key: []string{relKey1, relkey2}, Unique: true}); err != nil {
                return err
            }
            relId, err := GenIntId(relC)
            if err != nil {
                return err
            }
            rel := v.M{"_id": relId, relKey1: idVal1, relkey2: IdVal2}
            if err := relC.Insert(rel); err != nil {
                if strings.Contains(err.Error(), relKey1) {
                    continue
                }
                return err
            }
        }
    }
    return nil
}

// 获取_id标签对应的值
func GetFieldValByBsonTagId(fv reflect.Value) (int64, error) {
    for i := 0; i < fv.NumField(); i++ {
        if strings.Split(fv.Type().Field(i).Tag.Get("bson"), ",")[0] == "_id" {
            return fv.Field(i).Int(), nil
        }
    }
    return 0, errors.New("没有找到_id标签对应的值")
}

// 获取关系集合名
func GetRelCName(c *mgo.Collection, relSets []string) string {
    if relSets[2] == "1" {
        return c.Name + "_" + relSets[1]
    }
    return relSets[1] + "_" + c.Name
}

// 获取多对多关系配置
func getM2MRelSetByRelTag(sf reflect.StructField) ([]string, bool, error) {
    rel1Tag := sf.Tag.Get("rel")
    if rel1Tag == "" {
        return nil, false, nil
    }
    if len(strings.Split(rel1Tag, ":")) < 2 {
        return nil, false, errors.New("关系配置错误")
    }
    rel1Sets := strings.Split(strings.Split(rel1Tag, ":")[1], ".") //找到关系配置
    if len(rel1Sets) < 3 {
        return nil, false, errors.New("关系内容配置错误")
    }
    return rel1Sets, true, nil
}

// ptr只检查关系,不作为查询条件
// 只支持一个外键
func Del(c *mgo.Collection, ptr interface{}, query v.M) error {
    ft := reflect.TypeOf(ptr).Elem()
    fv := reflect.ValueOf(ptr).Elem()
    for i := 0; i < ft.NumField(); i++ {
        // 关系标签
        relTag := ft.Field(i).Tag.Get("rel")
        if relTag == "" {
            continue
        }
        rel := strings.Split(relTag, ":")
        if len(rel) < 2 {
            return fmt.Errorf("关系标签格式错误,列:%s", "rel:\"o2m:b\"")
        }
        // 获取外键配置
        switch fv.Field(i).Kind() {
        case reflect.Slice:
            vi := fv.Field(i)
            vivElemt := vi.Type().Elem()
            FEPtr := reflect.New(vivElemt)
            fit := reflect.TypeOf(FEPtr.Interface()).Elem()
            for i := 0; i < fit.NumField(); i++ {
                fkTag := fit.Field(i).Tag.Get("fk")
                if fkTag == "" {
                    if i == fit.NumField()-1 {
                        return fmt.Errorf("关系对象:%s没有设置外键", fit.Name())
                    }
                    continue
                }
                fk := strings.Split(fkTag, ".")
                if len(fk) < 2 {
                    return errors.New("外键格式错误")
                }
                relBsonTag := strings.Split(fit.Field(i).Tag.Get("bson"), ",")[0]
                if relBsonTag == "" {
                    return errors.New("设置外键所在的列没有设置bson标签")
                }
                var bsonTag string
                for i := 0; i < ft.NumField(); i++ {
                    bsonTag = strings.Split(ft.Field(i).Tag.Get("bson"), ",")[0]
                    if bsonTag != fk[1] {
                        if i == ft.NumField()-1 {
                            return errors.New("外键所指向的列不存在")
                        }
                        continue
                    }
                    break //找到
                }
                result := []v.M{}
                if err := c.Find(query).Select(v.M{bsonTag: 1}).All(&result); err != nil {
                    return err
                }
                relV := []interface{}{}
                for _, m := range result {
                    relV = append(relV, m[bsonTag])
                }
                count, err := c.Database.C(rel[1]).Find(v.M{relBsonTag: v.M{"$in": relV}}).Count()
                if err != nil {
                    return err
                } else if count != 0 {
                    return fmt.Errorf("表:%s,有外键:%s,指向要删除的数据,列:%s,值:%#v", rel[1], relBsonTag, fk[1], relV)
                }
                break //只支持一个外键
            }
        default:
            log.Error.Panicln("暂不支持的类型:", fv.Field(i).Kind())
        }
    }
    err := c.Remove(query)
    return err
}

// 插入对象前,为对象配置_id
func SetId(c *mgo.Collection, ptr interface{}) error {
    ft := reflect.TypeOf(ptr).Elem()
    fv := reflect.ValueOf(ptr).Elem()
    for i := 0; i < ft.NumField(); i++ {
        bfv := strings.Split(ft.Field(i).Tag.Get("bson"), ",")[0]
        if bfv != "_id" {
            if i == ft.NumField()-1 {
                return errors.New("插入的对象没有指定主键bson:\"_id\"")
            }
            continue
        }
        if _, ok := fv.Field(i).Interface().(int64); !ok {
            return errors.New("主键只支持int64类型")
        }
        if fv.Field(i).Int() == 0 {
            id, err := GenIntId(c)
            if err != nil {
                return err
            }
            fv.Field(i).SetInt(id)
        }
        break
    }
    return nil
}

func GenIntId(c *mgo.Collection) (int64, error) {
    cName := c.Name
    IDInt64 := struct {
        Value int64 `bson:"max_id"`
    }{Value: 1}
    _, err := c.Database.C("gen_id").Find(v.M{"_id": cName}).Apply(mgo.Change{Update: v.M{"$inc": IDInt64}, Upsert: true, ReturnNew: true}, &IDInt64)
    return IDInt64.Value, err
}

// _id生成容错
func SetIntId4Max(c *mgo.Collection, maxId int64) error {
    cName := c.Name
    return c.Database.C("gen_id").Update(v.M{"_id": cName}, v.M{"max_id": maxId})
}

// 检查全部外键
func CheckFK(c *mgo.Collection, ptr interface{}) error {
    ft := reflect.TypeOf(ptr).Elem()
    fv := reflect.ValueOf(ptr).Elem()
    for i := 0; i < ft.NumField(); i++ {
        fk := ft.Field(i).Tag.Get("fk")
        if fk == "" {
            continue
        }
        fkvs := strings.Split(fk, ".")
        if len(fkvs) < 2 {
            return errors.New("外键配置错误,正确格式:fk:\"a,_id\",a->表名,_id->指向的列")
        }
        // 外键为空不检查
        switch x := fv.Field(i).Interface().(type) {
        case int64:
            if x == 0 {
                continue
            }
        case string:
            if x == "" {
                continue
            }
        default:
            return errors.New("暂不支持的外键值类型")
        }
        // 检查外键是否存在
        if count, err := c.Database.C(fkvs[0]).Find(v.M{fkvs[1]: fv.Field(i).Interface()}).Count(); err != nil {
            return err
        } else if count == 0 {
            return fmt.Errorf("外键:%s,指向值:%d,不存在", ft.Field(i).Name, fv.Field(i).Int())
        }
    }
    return nil
}

func GetIdVal(ptr interface{}) (int64, error) {
    ft := reflect.TypeOf(ptr).Elem()
    fv := reflect.ValueOf(ptr).Elem()
    for i := 0; i < ft.NumField(); i++ {
        bfv := strings.Split(ft.Field(i).Tag.Get("bson"), ",")[0]
        if bfv != "_id" {
            if i == ft.NumField()-1 {
                goto THERE
            }
            continue
        }
        if _, ok := fv.Field(i).Interface().(int64); !ok {
            return 0, errors.New("主键只支持int64类型")
        }
        return fv.Field(i).Int(), nil
    }
THERE:
    return 0, errors.New("插入的对象没有指定主键bson:\"_id\"")
}