新闻中心

在Node.js和EJS中从多个MongoDB数据库动态渲染独立HTML卡片

2025-10-27
浏览次数:
返回列表

在Node.js和EJS中从多个MongoDB数据库动态渲染独立HTML卡片

本教程详细讲解了如何在node.js、express和ejs环境下,从多个mongodb集合中获取数据(如图片和文本),并将其动态地渲染到独立的html卡片中。文章涵盖了后端数据获取与传递、前端ejs模板循环渲染技巧,以及如何确保数据正确配对,最终实现结构清晰、内容独立的网页展示。

在Node.js和EJS中动态渲染独立HTML卡片

在构建现代Web应用时,从多个数据源获取信息并将其以结构化、独立的方式呈现在用户界面上是一个常见需求。本教程将以一个具体的案例——从两个不同的MongoDB数据库(或集合)中获取图片和文本数据,并分别渲染到独立的HTML卡片中——来详细阐述如何在Node.js、Express和EJS技术栈下实现这一功能。

1. 技术栈概述

我们将使用的核心技术包括:

  • Node.js: 服务端J*aScript运行环境。
  • Express: 快速、开放、极简的Node.js Web框架。
  • Mongoose: 用于MongoDB的ODM(对象数据模型)库,简化数据操作。
  • EJS: 嵌入式J*aScript模板引擎,用于将后端数据渲染到HTML页面。
  • MongoDB: NoSQL文档数据库,用于存储数据。
  • Multer: Node.js中间件,用于处理multipart/form-data,主要用于文件上传。

2. 后端数据准备与传递 (app.js)

首先,我们需要在后端(app.js)设置Mongoose连接、定义数据模型,并处理数据的存储和检索。

2.1 MongoDB连接与Schema定义

为了将图片和文本分别存储,我们为它们定义了独立的Mongoose Schema和模型。

const express = require("express");
const bodyParser = require("body-parser");
const ejs = require("ejs");
const mongoose = require("mongoose");
const multer = require("multer"); // 用于文件上传

const app = express();

app.set('view engine', 'ejs');
app.use(express.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static("public"));

mongoose.set('strictQuery', true);

// 连接到不同的数据库或集合
// 假设 SiteofAds 存储用户数据,Images 存储图片,Content 存储文本
const db1 = mongoose.createConnection("mongodb://127.0.0.1:27017/SiteofAds"); // 用户数据库
const db2 = mongoose.createConnection("mongodb://127.0.0.1:27017/Images"); // 图片数据库
const db3 = mongoose.createConnection("mongodb://127.0.0.1:27017/Content"); // 内容数据库

// 图片 Schema
const ImageSchema = new mongoose.Schema({
    image: {
        data: Buffer,      // 存储图片二进制数据
        contentType: String // 存储图片MIME类型
    }
});
const Image = db2.model("Image", ImageSchema); // 在Images数据库中创建Image模型

// 内容 Schema
const ContentSchema = new mongoose.Schema({
    text: String // 存储文本内容
});
const Content = db3.model("Content", ContentSchema); // 在Content数据库中创建Content模型

// Multer配置:将文件存储在内存中
const storage = multer.memoryStorage();
const upload = multer({storage: storage});

2.2 数据存储 (/secrets 路由)

当用户上传图片和文本时,我们将它们分别保存到对应的MongoDB集合中。

// ... (之前的代码) ...

// 处理图片和文本上传的 POST 请求
app.post("/secrets", upload.single("image"), async (req, res) => {
    try {
        // 创建新的图片文档
        const image = new Image({
            image: {
                data: req.file.buffer,
                contentType: req.file.mimetype
            }
        });

        // 创建新的文本内容文档
        const content = new Content({
            text: req.body.myTextArea
        });

        // 异步保存图片和文本
        await image.s*e();
        await content.s*e();

        res.render('home'); // 保存成功后渲染首页
    } catch (error) {
        console.error("Error s*ing data:", error);
        res.status(500).send("Error s*ing data.");
    }
});

// ... (其他路由) ...

注意事项:

  • 这里假设每次上传的图片和文本是相互关联的,并且通过顺序插入来保持这种关联。在实际生产环境中,更健壮的方法是为图片和文本数据引入一个共同的adId或其他关联字段,以便在检索时能够精确配对。
  • 将图片直接存储为Buffer在MongoDB中适用于小文件。对于大文件,建议将图片存储到文件系统或对象存储服务(如AWS S3),然后在数据库中只存储其URL。

2.3 数据获取与传递 (/ 路由)

在首页 (/) 路由中,我们从各自的集合中获取所有图片和文本数据,并将它们传递给EJS模板进行渲染。

// ... (之前的代码) ...

// 首页路由:获取并渲染所有图片和内容
app.get("/", async (req, res) => {
    try {
        // 从Images数据库获取所有图片,按_id降序排列(最新在前)
        const images = await Image.find().sort({_id: -1});
        // 从Content数据库获取所有文本,按_id降序排列(最新在前)
        const contents = await Content.find().sort({_id: -1});

        // 将获取到的图片和内容数据传递给 home.ejs 模板
        res.render("home", {images: images, contents: contents});
    } catch (error) {
        console.error("Error fetching data:", error);
        res.status(500).send("Error fetching data.");
    }
});

// ... (其他路由和app.listen) ...

关键点:

  • Image.find().sort({_id: -1}) 和 Content.find().sort({_id: -1}) 确保我们获取到的数据是按最新创建的顺序排列的。
  • 通过 res.render("home", {images: images, contents: contents}); 将两个独立的数据数组传递给 home.ejs 模板。

3. 前端EJS模板渲染 (home.ejs)

现在,我们来修改 home.ejs 文件,使其能够正确地遍历 images 和 contents 数组,并为每一对图片和文本生成一个独立的HTML卡片。

3.1 原始问题的EJS代码分析

原始的 home.ejs 代码尝试在单个卡片内迭代图片和文本,导致所有图片堆叠在一起,所有文本也堆叠在一起,而不是每个图片和文本形成一个独立的卡片。

<!-- 原始问题的EJS代码片段 -->
<div class="grid">
    <div class="grid-item">
        <div class="card">
            <% if (images.length> 0) { %>
                <% images.forEach((image)=> { %>
                    <div class="card"> <!-- 错误:在这里创建了嵌套卡片 -->
                        @@##@@;base64,<%= image.image.data.toString('base64') %>" />
                    </div>
                <% }); %>
            <% } else { %>
                    <p>No images uploaded yet.</p>
                    <div class="aritcle_card">
                        <a class="aritcle_card_img" href="/ai/1186">
                            <img src="https://img.php.cn/upload/ai_manual/000/000/000/175680124823650.jpg" alt="BrandCrowd">
                        </a>
                        <div class="aritcle_card_info">
                            <a href="/ai/1186">BrandCrowd</a>
                            <p>一个在线Logo免费设计生成器</p>
                            <div class="">
                                <img src="/static/images/card_xiazai.png" alt="BrandCrowd">
                                <span>200</span>
                            </div>
                        </div>
                        <a href="/ai/1186" class="aritcle_card_btn">
                            <span>查看详情</span>
                            <img src="/static/images/cardxiayige-3.png" alt="BrandCrowd">
                        </a>
                    </div>
                
            <% } %>

            <div class="card-content">
                <h1 class="card-header">Oglas br.1</h1>
                <p slass="card-text">
                    <% if (contents.length> 0) { %>
                        <% contents.forEach((content)=> { %>
                            <div> <!-- 错误:在这里将所有内容堆叠在同一卡片内 -->
                                <h2>
                                    <%= content.text %>
                                </h2>
                            </div>
                        <% }); %>
                    <% } else { %>
                        <p>No content yet.</p>
                    <% } %>
                    <button class="button2"> Detalji</button>
            </div>
        </div>
    </div>
</div>

问题在于 images.forEach 和 contents.forEach 循环都发生在同一个

内部,并且它们是独立的循环。这会导致所有图片和所有文本都显示在同一个卡片中。为了创建独立的卡片,我们需要在循环的每次迭代中创建一个新的卡片结构。

3.2 改进后的EJS代码示例

为了确保每个图片和对应的文本都显示在一个独立的卡片中,我们需要在一个循环中同时访问图片和文本数据,并为每次迭代生成一个完整的卡片结构。由于后端我们是按相同顺序(_id:-1)获取图片和文本的,我们可以假设 images[i] 和 contents[i] 是相互对应的。

<%- include("partials/header") %>

<div class="grid">
    <% // 确保 images 和 contents 数组都有数据,并且长度至少为1 %>
    <% if (images.length > 0 && contents.length > 0) { %>
        <% // 遍历数组,使用索引 i 来同时访问 images[i] 和 contents[i] %>
        <% // 循环次数取两者中较短的长度,以避免索引越界 %>
        <% for (let i = 0; i < images.length && i < contents.length; i++) { %>
            <div class="grid-item">
                <div class="card">
                    <% // 渲染图片 %>
                    @@##@@;base64,<%= images[i].image.data.toString('base64') %>"
                        alt="Ad Image">
                    <div class="card-content">
                        <h1 class="card-header">Ad No.<%= i + 1 %></h1> <%-- 可以根据需要显示更具体的ID --%>
                        <p class="card-text">
                            <%= contents[i].text %>
                        </p>
                        <button class="button2"> Details</button>
                    </div>
                </div>
            </div>
        <% } %>
    <% } else { %>
        <p>No ads uploaded yet.</p>
    <% } %>
</div>

<%- include("partials/footer") %>

代码解释:

  1. 外部条件判断: if (images.length > 0 && contents.length > 0) 确保只有当两个数组都有数据时才进行渲染,避免空数组导致的错误。
  2. 单循环遍历: 我们使用一个 for 循环,通过索引 i 同时访问 images[i] 和 contents[i]。循环条件 i
  3. 独立卡片结构: 在循环的每次迭代中,我们都创建了一个完整的 和 结构。这意味着每次循环都会生成一个新的独立卡片。
  4. 图片渲染: // 修改后的 Schema 示例 const ImageSchema = new mongoose.Schema({ adId: String, // 用于关联的 ID image: { data: Buffer, contentType: String } }); const ContentSchema = new mongoose.Schema({ adId: String, // 用于关联的 ID text: String }); // 在保存数据时 app.post("/secrets", upload.single("image"), async (req, res) => { const newAdId = new mongoose.Types.ObjectId().toString(); // 生成一个唯一的ID const image = new Image({ adId: newAdId, image: { ... } }); const content = new Content({ adId: newAdId, text: req.body.myTextArea }); await image.s*e(); await content.s*e(); }); // 在获取数据时 app.get("/", async (req, res) => { const images = await Image.find().sort({ _id: -1 }); // 遍历 images,为每个 image 查找对应的 content const ads = await Promise.all(images.map(async img => { const content = await Content.findOne({ adId: img.adId }); return { image: img, content: content }; })); res.render("home", { ads: ads }); // 将组合好的数据传递给模板 });

    然后,EJS模板就可以直接遍历 ads 数组,每个 ad 对象都包含 image 和 content。

    4.2 错误处理与健壮性

    • 数组长度不匹配: 如果 images 和 contents 数组长度不一致,当前的 for 循环会以较短的数组为准。如果数据关联性很重要,并且数组长度可能不一致,应采用上述的 adId 关联方案,或在EJS中添加更详细的逻辑来处理缺失数据。
    • 空数据: 在EJS中添加 if (data.length > 0) 判断,可以优雅地处理没有数据的情况,显示友好的提示信息。

    4.3 性能优化

    • 图片大小: 直接在数据库中存储图片二进制数据并以Base64形式嵌入HTML,会增加页面大小,尤其是在图片数量多或图片尺寸大的情况下。这会显著影响页面加载速度和用户体验。
      • 推荐方案: 将图片文件存储到文件系统、CDN(内容分发网络)或专门的对象存储服务(如AWS S3、七牛云等),数据库中只存储图片的URL。然后EJS直接使用图片URL作为 src 属性。
    • 数据库查询: 避免在循环中进行数据库查询(N+1问题),尽量一次性获取所有必要数据。

    4.4 代码模块化

    对于复杂的卡片结构,可以考虑使用EJS partials(局部模板)来提高代码的可维护性和复用性。

    <!-- partials/adCard.ejs -->
    <div class="grid-item">
        <div class="card">
            @@##@@;base64,<%= ad.image.data.toString('base64') %>"
                alt
    在Node.js和EJS中从多个MongoDB数据库动态渲染独立HTML卡片

以上就是在Node.js和EJS中从多个MongoDB数据库动态渲染独立HTML卡片的详细内容,更多请关注其它相关文章!


# 首页  # 营销推广方式策划方案  # 盐山微网站建设  # 惠州网站优化了吗现在  # seo店铺描述食品  # 常州推广网站市场价  # 应聘网站免费推广  # 靠谱网络推广营销哪家好  # 街舞营销推广策略分析  # 外贸营销软件推广方案设计  # 淄博网站建设详细教程  # 并为  # 文档  # 在这里  # 都有  # 迭代  # javascript  # 后端  # 遍历  # 数据库中  # 多个  # a  # 编码  # mongodb  # go  # node  # json  # node.js  # 前端  # js  # html  # java 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: b站赚钱渠道_b站收益来源  邮政编码查询不到怎么办_邮政编码查询不到的常见原因与对策  TikTok网页版直接登录 TikTok网页端官方平台入口  在J*a中如何使用BigDecimal进行高精度计算_BigDecimal类应用指南  Pandas DataFrame 多条件优先级排序与排名  126邮箱账号注册 电脑版登录入口  Shopware订单对象中获取产品自定义字段的正确方法  夸克浏览器网页版最新地址 夸克浏览器官方入口合集  LINQ to XML为何解析失败? 深入理解C# XDocument的异常处理  拼多多购物车商品数量无法修改如何处理 拼多多购物车操作优化方法  C++如何连接MySQL数据库_C++使用Connector/C++操作MySQL数据库教程  c++ dfs和bfs代码 c++深度广度优先搜索算法  CSS Flexbox如何实现多行排列_flex-wrap wrap自动换行显示  word邮件合并后日期格式不对怎么改_Word邮件合并日期格式修改方法  J*aScript中安全有效地处理localStorage字符串数据  b站怎么删除评论_b站评论管理与删除操作  Go Martini框架:动态服务解码后的图片内容  抖音怎么赚钱_抖音创作者变现方法与途径指南  163邮箱注册官网 免费申请163个人邮箱  在J*a里如何理解依赖关系的方向_依赖方向在模块结构中的作用  Win11如何开启讲述人功能 Win11屏幕阅读器(讲述人)开启与关闭【教程】  cad怎么合并重叠的线段_cad清理重复重叠线条的操作方法  小红书商家版怎样在笔记嵌入商品卡路径_小红书商家版在笔记嵌入商品卡路径【挂载教程】  如何使用CaptainHook和Composer管理Git钩子_在提交前自动运行代码检查的Composer配置  铁路12306改签能改到更早的车次吗_铁路12306改签提前车次规则  Centos/Linux 系统下安装 composer 的完整步骤  QQ邮箱官方登录入口_QQ邮箱网页版快捷使用平台  Go调试环境为何无法启动_Go调试器启动失败原因与解决策略  解决Rails应用中内容错位与Turbo警告:meta标签误用导致富文本渲染异常  Android Studio计算器C键功能异常排查与修复教程  京东京造J1和网易云音乐氧气真无线有什么不同_国产电商蓝牙耳机音质对比  Go语言中JSON数据解析与字段访问教程  Golang如何通过reflect操作map_Golang reflect map操作与遍历技巧  Tabulator表格日期时间排序问题及自定义解决方案  Node.js CSV 数据处理:基于字段空值条件过滤整条记录的策略  b站怎么取消点赞_b站点赞取消操作方法  怎么在html里运行vbs脚本_html中运行vbs脚本方法【教程】  一加手机电池耗电快怎么办_一加手机电池耗电快的解决方法  动漫共和国防屏蔽稳定域名-动漫共和国官方正版直达通道  微博网页版怎么开启两步验证_微博网页版账号安全两步验证设置方法  windows10怎么查看本机ip_windows10命令提示符ipconfig使用  qq浏览器如何查看和导出已保存的密码 qq浏览器密码管理器数据备份教程  AO3网页版合集入口 Archive of Our Own同人作品浏览指南  AO3官方在线访问地址 Archive of Our Own最新镜像合集  Golang如何安装Swagger工具_GoSwagger文档生成环境  2026春节假期票务安排_2026春节放假购票指南  mysql备份恢复性能优化_mysql备份恢复性能优化方法  MongoDB Aggregation:在嵌套对象数组中精确匹配ObjectId  css子元素高度不一致导致布局错位怎么办_使用align-items:stretch解决高度差异  利用Bokeh CustomJS动态控制DataTable列可见性 

搜索