golang 整合mongo-driver 复杂文档查找aggregate 遇到的问题
现在手头有这样一个文档,用来存储心理测试问卷
{
"_id":"3231231241243141",
"typename":"心理健康测试",
"mental_tests":[{
"testname":"艾森克成人测试",
"describe":"作用1",
"probles":[{
"context":"问题1",
"options":[{
"title":"是"
},{
"title":"否"
}]
},{
"context":"问题2",
"options":[{
"title":"是"
},{
"title":"否"
},{
"title":"一般"
}]
}]
},{
"testname":"xxx测试",
"describe":"作用1",
"probles":[{
"context":"问题3",
"options":[{
"title":"是"
},{
"title":"否"
}]
}
}]
}
字段含义:
- typename:心理测试种类
- mental_tests 该种测试的测试集合,类型为Array
- mental_tests.testname 测试问卷的名称
- mental_tests.describe 测试问卷作用描述
- mental_tests.probles 问卷问题集合
- mental_tests.probles.context 问卷问题题目
- mental_tests.probles.options 问卷问题选项集合
需求:
需要根据测试种类Id,获得该种类下的所有测试问卷名称以及描述
目标:
{
"code": "200",
"data": [
{
"testname": "艾森克成人测试",
"describe": "作用1"
},
{
"testname": "xxx测试",
"describe": "作用2"
}
]
}
data 表示我们要查询的结果
本人刚刚入门MongoDB,开发过程中磕磕绊绊的,最中以很迷惑的方法达成了效果,这边列出几个我到目前还不能解决的疑问
使用aggregate聚合过滤出我们需要的文档
说实话我觉得这个过程挺难的,由于mongoDB并不是太了解,仅仅看了极客世界的一节课就上手了,我是这样实现的
- 首先$match 查找出_id=传入参数的ObjectId
这边有个点,前端传入的_id为string类型,我们需要将string类型转化为MongoDB中的Object(‘Id’),当然是我查过来的
objectId, err := primitive.ObjectIDFromHex(id)
其中ObjectIDFromHex()方法,在primitive包下,传入一个string,返回一个ObjectId
func ObjectIDFromHex(s string) (ObjectID, error) {
if len(s) != 24 {
return NilObjectID, ErrInvalidHex
}
b, err := hex.DecodeString(s)
if err != nil {
return NilObjectID, err
}
var oid [12]byte
copy(oid[:], b)
return oid, nil
}
ObjectId是一个byte数组
// ObjectID is the BSON ObjectID type.
type ObjectID [12]byte
- 通过 $project 过滤出我们需要的字段,
_id :0
表示 _id字段不显示
这时候我们就查找出了一个Array,其中包含了问卷的信息
在golang中的代码
问题出现
我的问题也就在这边出现了,根据上边查询的结果,我们手头应该是一个列表,包含了两张问卷的信息,结合我们的需求,我们不需要problem字段,而是需要一个列表,列表中的每一项包含了测试问卷的名称,以及描述
于是我定义了这么两个结构体
type TestInfo struct { //用于存放每一个元素
TestName string `bson:"testname" json:"testname"`
Describe string `bson:"describe" json:"describe"`
}
type TestInfoDto struct { //存放查询的结果
TestInfos []TestInfo `bson:"mental_tests"`
}
那么按照我的理解,于是有了以下的代码:
type TestInfo struct {
TestName string `bson:"testname" json:"mental_tests.testname"`
Describe string `bson:"describe" json:"mental_tests.describe"`
}
type TestInfoDto struct {
TestInfos []TestInfo `bson:"mental_tests"`
}
接口整体代码:
func FindTestsByType(id string) (dto.TestInfoDto, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
//选取表
collection := config.Client.Database(config.Conf.Mongo.Name).Collection("mental")
objectId, err := primitive.ObjectIDFromHex(id)
matchStage := bson.D{
{"$match", bson.M{"_id": objectId}},
}
projectStage := bson.D{
{"$project", bson.D{{"mental_tests", "$mental_tests"}, {"_id", 0}}},
}
//查询结果为我们TestInfoDto结构体
var result dto.TestInfoDto
cur, err := collection.Aggregate(ctx, mongo.Pipeline{matchStage, projectStage})
if err != nil {
log.Println(err)
return dto.TestInfoDto{}, err
}
//将结果写入到result中
err = cur.Decode(&result)
if err != nil {
log.Println(err)
return dto.TestInfoDto{}, err
}
if err != nil {
log.Println(err)
return dto.TestInfoDto{}, err
}
return result, err
}
Controller层代码,直接返回FindTestsByType找到的TestInfoDto,包含了一个TestInfo切片
按常理来说,这边感觉是真没问题呀,不知道有什么坑,报错EOF,怀疑是problem字段没有写(可能),有没有巨巨可以告诉我为什么
我的解决方法
很捉鸡,很烂的写法,我甚至不知道为什么可以
//将返回参数设置为TestInfoDto切片
func FindTestsByType(id string) ([]dto.TestInfoDto, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
collection := config.Client.Database(config.Conf.Mongo.Name).Collection("mental")
objectId, err := primitive.ObjectIDFromHex(id)
matchStage := bson.D{
{"$match", bson.M{"_id": objectId}},
}
projectStage := bson.D{
{"$project", bson.D{{"mental_tests", "$mental_tests"}, {"_id", 0}}},
}
var result []dto.TestInfoDto
cur, err := collection.Aggregate(ctx, mongo.Pipeline{matchStage, projectStage})
if err != nil {
log.Println(err)
return nil, err
}
//将查询结果以切片方式写入到result中
err = cur.All(ctx, &result)
if err != nil {
log.Println(err)
return nil, err
}
if err != nil {
log.Println(err)
return nil, err
}
//返回结果
return result, err
}
Controller层代码:
由于Tests是一个TypeInfoDto切片,根据_Id查询出的结果肯定只有第一项,所以就直接投机取巧了,拿到Array中的数据
总结:就写这么一个简单的结构,换成MySQL几分钟就搞定了。还是MongoDB不太熟悉,还有好多好多不会,把我心态搞裂开了,对于我的问题,希望看到的大佬能帮我解答一下,非常感谢!!