尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

StoryBooks 数据建模指南:Mongoose 里 User 与 Story 的关联设计与 populate 用法

StoryBooks 数据建模指南:Mongoose 里 User 与 Story 的关联设计与 populate 用法 StoryBooks 数据建模指南Mongoose 里 User 与 Story 的关联设计与 populate 用法【免费下载链接】storybooksNode.js app with Google OAuth项目地址: https://gitcode.com/gh_mirrors/st/storybooksStoryBooks 是一个基于 Node.js、Express 与 MongoDB 的故事记录应用用户通过 Google OAuth 一键登录即可创建公开或私密的故事。本文以 StoryBooks 的 Mongoose 数据建模为主线完整拆解 User 与 Story 的关联设计并结合 4 个真实路由场景讲透 populate 的用法帮你快速掌握 MongoDB 里「一对多关联 关联查询」这套最常用的数据建模套路。一、项目速览2 个模型撑起整个数据层StoryBooks 的数据层非常轻量全部设计集中在 2 个模型文件里models/User.js— 用户模型存放 Google 登录用户的资料models/Story.js— 故事模型存放标题、正文、公开状态路由层routes/stories.js与routes/index.js基于这两个模型完成增删改查和关联查询数据库连接在config/db.js中通过mongoose.connect建立MongoDB 连接串与 Google OAuth 密钥均配置在.env环境文件中详见readme.md。 对新手来说这种「2 个模型 简单关联」的项目正是学习 Mongoose 关联设计的最佳切入点麻雀虽小五脏俱全。二、User 模型设计OAuth 登录下用户表该怎么存models/User.js的字段设计和传统「用户名 密码」的账号体系完全不同字段说明设计要点googleIdGoogle 用户 ID必填业务上的唯一标识displayName展示名称必填用于列表展示firstName / lastName名 / 姓必填供仪表盘问候语使用image头像地址可选createdAt创建时间默认取当前时间两个关键设计点用 googleId 而不是邮箱当「主身份」。昵称、姓名都可能改但 Google ID 不会变。不存密码。认证交给 Googleconfig/passport.js的逻辑是登录回调时先按googleId查库查到直接登录查不到就自动User.create注册 —— 一次 OAuth 登录同时完成「登录 注册」。配合 Passport 的会话机制serializeUser只存user.id下次请求再deserializeUser查回完整文档路由里随时能拿到req.user为给故事打「作者」标记打下基础。三、Story 模型ref ObjectId一对多关联的标准写法models/Story.js的核心字段如下const StorySchema new mongoose.Schema({ title: { type: String, required: true, trim: true }, body: { type: String, required: true }, status: { type: String, default: public, enum: [public, private] }, user: { type: mongoose.Schema.Types.ObjectId, ref: User }, createdAt: { type: Date, default: Date.now }, })其中user字段是关联设计的灵魂由两部分组成ObjectId只存 User 文档的 ID不复制用户数据 —— 这是 MongoDB「引用式」关联的基本原则避免数据冗余。ref: User告诉 Mongoose「这个 ID 指向 User 集合」populate时才知道去哪个集合查。这是一对多关系一个用户可以写多篇故事关联字段放在「多」的一方Story 上相当于关系型数据库里的外键。创建故事时routes/stories.js的 POST 路由会自动补上作者req.body.user req.user.id await Story.create(req.body)四、populate 用法详解4 个真实场景查询故事时文档里的user字段默认只是一个 ID。想同时拿到作者信息就要用populate(user)—— 它会自动去 User 集合按 ID 查回完整文档并替换进结果。routes/stories.js里有 4 个真实用法场景路由查询写法 公开故事列表GET /storiesfind({ status: public }).populate(user).sort({ createdAt: desc }).lean() 单篇故事详情GET /stories/:idfindById(id).populate(user).lean() 某用户的故事GET /stories/user/:userIdfind({ user, status: public }).populate(user).lean() 按标题搜索GET /stories/search/:queryfind({ title: new RegExp(query, i) }).populate(user).sort(...).lean()两个值得注意的细节.lean()与 populate 是好搭档渲染页面不需要 Mongoose 文档的方法lean()直接返回纯 JSON性能更好。并非每个查询都要 populate。仪表盘路由routes/index.js里的Story.find({ user: req.user.id }).lean()就没带 populate —— 展示的是当前用户自己的故事作者信息已知少查一次数据库。五、关联字段撑起的权限控制私有故事是怎么防住的status: private的私有故事靠的就是关联字段做归属校验。在单篇故事详情路由里先populate(user)拿到作者文档判断「作者不是当前用户且状态为 private」—— 命中则直接返回 404。编辑、更新、删除三个路由也有同样的「非作者则拒绝」检查再配合middleware/auth.js中的ensureAuth拦截未登录请求整个读写权限就闭环了。⚠️ 小细节populate之前story.user是 ID 字符串populate之后是完整用户对象需取story.user._id。写权限判断时留意这一点项目里两种写法都有。六、新手常见坑与最佳实践清单ref 的名字必须和模型注册名一致。models/User.js中注册为mongoose.model(User, ...)所以 ref 写User写错会导致 populate 静默失效。展示作者信息时别忘了 populate否则模板里拿到的只是一串 ID。用enumdefault约束状态字段如status限定为public/private防止脏数据入库。用不变的业务键做登录标识如googleId若担心重复注册可在该字段上加唯一索引。只在需要时才 populate列表、搜索、详情按需添加并配合.lean()控制查询成本。七、动手跑起来git clone https://gitcode.com/gh_mirrors/st/storybooks cd storybooks npm install按readme.md的说明创建.env填入MONGO_URIMongoDB 连接串与 Google OAuth 的GOOGLE_CLIENT_ID、GOOGLE_CLIENT_SECRET然后运行npm run dev启动。登录后新建几篇故事再用 MongoDB 客户端查看两个集合你会看到每条故事文档的user字段就是一个指向用户集合的 ObjectId —— 亲眼确认一遍关联设计就彻底明白了。八、小结一对多关联放在「多」的一方Story 上存ObjectId ref: User展示作者信息时用populate(user)配合.lean()更高效关联字段不只是取数据还能做归属校验撑起私有故事的权限控制。这套「引用 populate」的组合是 Mongoose 数据建模最核心的套路。吃透 StoryBooks 这两个模型再去处理更复杂的关联场景就水到渠成了。【免费下载链接】storybooksNode.js app with Google OAuth项目地址: https://gitcode.com/gh_mirrors/st/storybooks创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表