项目大致业务是文章(article)下面有很多评论(comment),属于一对多的关系。
以下是新建comment,涉及到article表,其中的外键target_id指向article 的主键 _id
有段业务逻辑是从article表中根据请求体中的target_id作为article的_id进行查找(findById),返回的article应该是一个article表的mongoose文档对象。
如果返回的文档对象为空时,抛出全局文件中配置好的错误notfound,终止创建评论,然后提示没有找到该文章。
static async createComment(ctx, next) {
// 校验参数
commentValidator(ctx);
const { target_id } = ctx.request.body;
console.log(target_id);
const article = await ArticleModel.findById({_id:target_id});
if (!article) {
throw new global.errs.NotFound("没有找到相关文章");
}
await CommentModel.create(ctx.request.body);
ctx.body = res.success("创建评论成功");
}
测试:
然后当我使用postman进行测试接口时,我首先将存在的article _id作为comment target_id:
结果是成功,数据库当中也有。
以下是终端的返回数据,对应我输入的article _id
然后我将不存在的article _id作为target_id进行添加comment,理论上来说应该会返回没有找到相关文章。然而返回结果并非如此:
postman 中返回的是配置好的另外一个未知错误,而不是我调用的not found.
以下是终端中报错:
提示的是 CastError: Cast to ObjectId failed 强制转换为对象id失败
<-- POST /api/v1/comment
1234567890
xxx POST /api/v1/comment 500 9ms -
false
CastError: Cast to ObjectId failed for value "{ _id: '1234567890' }" (type Object) at path "_id" for model "Article"
at model.Query.exec (F:\gitlab\项目koablog\koa-server最新\node_modules\mongoose\lib\query.js:4498:21)
at model.Query.Query.then (F:\gitlab\项目koablog\koa-server最新\node_modules\mongoose\lib\query.js:4592:15)
at processTicksAndRejections (internal/process/task_queues.js:95:5) {
messageFormat: undefined,
stringValue: `"{ _id: '1234567890' }"`,
kind: 'ObjectId',
value: { _id: '1234567890' },
path: '_id',
reason: Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters
尝试修改:
1.我尝试改代码,把可能涉及到强制将字符串转换成对象的地方去掉。但是发现依然报同样的错
const article = await ArticleModel.findById({target_id});
2.第二次尝试,考虑到mongoose 的findbyid应该会内部自动转化成monogo对象id
static async createComment(ctx, next) {
// 校验参数
commentValidator(ctx);
const { target_id } = ctx.request.body;
const _id = mongoose.Types.ObjectId(target_id);
console.log(typeof _id);//控制台输出类型为object,说明转化类型有效
const article = await ArticleModel.findById({_id});
if (!article) {
throw new global.errs.NotFound("没有找到相关文章");
}
await CommentModel.create(ctx.request.body);
ctx.body = res.success("创建评论成功");
}
结果报出以下错误:
Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters
意思是 传入的mongoose参数必须是12个字节的单个字符串或24个十六进制字符的字符串
<-- POST /api/v1/comment
object
--> POST /api/v1/comment 200 104ms 53b
<-- POST /api/v1/comment
xxx POST /api/v1/comment 500 2ms -
false
Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters
3.第三次尝试,在model中发现target_id 创建时并未声明对文章article的ref引用,只是单纯的字符串。于是对CommentModel.js做出以下修改:
//models/CommentModel.js
// 1.引入mongoose
const mongoose = require("mongoose");
// 2.字义Schema(描述文档结构)
const CommentSchema = new mongoose.Schema({
nickname: { type: String, require: true }, //评论人昵称
content: { type: String, require: true }, //评论内容
target_id: {//评论目标文章的id
type:mongoose.Schema.Types.ObjectId,
//ref属性表示引用 可以直接引用Article模型
ref:'Article'
},
});
// 3.定义Model(与几何对应,可以操作集合)
const CommentModel = mongoose.model("Comment", CommentSchema);
// 4.向外暴露model
module.exports = CommentModel;
发现问题并没有解决,不过通过以上三次尝试,总结了经验。
通过几次输入请求体的尝试,发现了一个限制:
输入的测试参数必须是12个字节的单个字符串或24个十六进制字符的字符串,也就是第二次尝试发现的问题。
总结出来:
- 转换成mongoose的ObjectId 是没有错的,因为findById({})是要从数据库中通过id查找,数据库中的id数据也是ObjectId类型。
- 对应数据库中这种类型,创建mongoose.Schema 时应该将引用type设置为等同于数据库中的类型,例如: target_id: {type:mongoose.Schema.Types.ObjectId,ref:'Article'}
- 输入测试参数也就是koa中ctx.request.body时,应该将输入的id位数严格按照上面提到的限制。
以上总结接下来进行测试
1.CommentModel中的type已经修改好。
2.CommmentController,转换请求体参数类型
static async createComment(ctx, next) {
// 校验参数
commentValidator(ctx);
const { target_id } = ctx.request.body;
//将引用id target_id 转换成mongoose ObjectId
const _id = mongoose.Types.ObjectId(target_id);
//位数限制,如果请求体测试id位数与mongoose生成的ObjectId不匹配,则会报未知错误
//Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters
const article = await ArticleModel.findById({ _id });
console.log(article);
if (!article) {
throw new global.errs.NotFound("没有找到相关文章");
}
await CommentModel.create(ctx.request.body);
ctx.body = res.success("创建评论成功");
}
3.postman测试参数:要达到的效果是未找到相关文章。
命令行终端:
如果将输入的target_id少一位的话就会报未知错误,终端提示位数不对,如下删除一个3:
命令行终端:
最后成功创建一个,让自己开心一把:
终端命令行:
成功返回源文章对象
好了问题解决,congratulation