2024年7月

去年(2023年)年底我初学flutter,看了一些文档和教程,想找个东西*练练手。

小时候看过一个关于历史名人儿时事迹的短片,有一集是讲*总理的,有一个细节我记得很清楚:幼年***经常要做一个游戏--有一堆纸片,每片纸上一个字,他要一个一个字拼起*拼成一首诗。

很多年前我就想,或许可以把这个游戏做成手机应用。可惜,一直没有动手。恰好可以用flutter*实现这个想法。

于是找了github上的唐诗三百首做数据源,做了个原始版本。

一开始它是这样的:

https://www.bilibili.com/video/BV1C7421K7MH

初始版本的代码在这:
https://github.com/stardust1900/poem_puzzle

后*,我想,或许我可以把它美化一下,增加小学到高中教科书中的诗句,可以选择年级以便调整难度。于是在github上搜一下,在这个repo
https://github.com/chinese-poetry/huajianji
找到了我想要的数据。

然后,又觉得可以再增加一些英文翻译。恰巧,我又搜到了维吉尼亚大学图书馆网站上的唐诗300首
https://cti.lib.virginia.edu/frame.htm
于是经过几番调整,现在它变成了这样。



description


description


description


description


description

https://www.bilibili.com/video/BV1D7421K7Xg

曾几何时,我幻想着能把这个应用上架到应用商店。还申请了软著。

后*发现,是我天真了... 在这个时代,个人想上架个应用太特么难了。

唉,算了,算了,开源吧!

https://github.com/stardust1900/chinese_poem

VMware Cloud Builder 是用于构建 VMware Cloud Foundation 第一个管理域的自动化部署工具,通过将一个预定义信息的 Excel 参数表导入到 Cloud Builder 以启动 VCF 的初始构建过程(Bring-up)。VMware Cloud Builder 通常是以 OVA 文件的形式与 VMware Cloud Foundation 一同发行并在 Broadcom 支持门户上下载,要构建 VMware Cloud Foundation SDDC 解决方案首先你需要准备 VMware Cloud Builder 工具。

VMware Cloud Builder 是 VMware 官方推荐的用于构建 VMware Cloud Foundation 实例的部署工具,其实在社区当中还有一个非常受欢迎的工具叫
Holodeck Toolkit
。Holodeck 工具支持快速在单个 ESXi 主机上部署 VMware Cloud Foundation 嵌套环境,以用于实验学习或者客户现场演示目的,这个工具不受 VMware 官方支持也不应用于生产环境。

一、VCF 5.x BOM 清单

推荐通过
VMware Cloud Foundation 产品文档
查看最新的发行说明,通常在更新说明中包含了该版本发行的 Bill of Materials(BOM)物料清单,即产品所兼容并配套的核心组件。也可以通过
VMware KB 52520
查看 VCF 发行版本及所包含相关组件的版本和内部版本号的对应关系,关于 VMware Cloud Foundation 5.x 版本的 Bill of Materials(BOM)物料清单如下图所示。我将基于 VMware Cloud Foundation 5.1.0 版本进行后续部署和相关测试,主要原因是从该版本开始支持 vSAN ESA 部署架构,到 VMware Cloud Foundation 5.1.1 版本增加了 VMware Private AI Foundation with NVIDIA 企业级 AI/ML 解决方案以及 VCF 单个解决方案授权许可的支持。当然,其实
VMware Cloud Foundation 5.2
最近已经公布并带来了许多变化与功能的更新,比如支持 vSAN Max 和 vSAN ESA 延伸集群、VCF Import(将 vSphere/vSAN 环境直接集成到 VCF 环境)以及解耦 Tanzu Kubernetes Grid (TKG)作为其独立服务运行等,而正式版可能要等到
VMware Explore 2024
大会结束后才进行发布。

二、Cloud Builder 下载

文件名称 MD5 百度网盘
vcf-ems-deployment-parameter.xlsx aa91cbf1320989729fc3edb9d22ff441 https://pan.baidu.com/s/1fzhSMqsMk-NXAydHpvOoKw?pwd=wrce
vcf-vxrail-deployment-parameter.xlsx 5d61b06c19b96202a268885f4eed040e
VMware-Cloud-Builder-5.1.0.0-22688368_OVF10.ova 9b43c8665b69f5cf089dfcd17fe42e41

三、Cloud Builder 部署

VMware Cloud Builder 设备支持部署在个人电脑的 Workstation/Fusion 上,也可以部署在 vSphere 环境中的 ESXi 主机上,选择适合你的任何一种方式即可。注意,由于部署 Cloud Builder 要求使用 DNS 和 NTP 服务器,请确保该虚拟机所使用的网络能够和这些服务器相互通信,并提前配置 DNS 的正反向解析;由于后期需要通过 Cloud Builder 部署 VCF 管理域,请确保该虚拟机所使用的网络能够和管理域相关组件(如 ESXi 主机、NSX Manager、SDDC Manager 等)的管理网络相互通信。

登录到 vSphere Client UI 管理界面,在 ESXi 主机上右击选择“部署 OVF 模板”,选择从“本地文件”上传 Cloud Builder 文件。

设置 Cloud Builder 虚拟机的名称并选择所放置的虚拟机文件夹,点击下一步。

选择 Cloud Builder 虚拟机所使用的计算资源并勾选自动打开虚拟机电源,点击下一步。

确认 Cloud Builder 设备的详细信息,点击下一步。

接受 Cloud Builder 安装许可协议条款,点击下一步。

选择 Cloud Builder 虚拟机所使用的存储资源,点击下一步。

选择 Cloud Builder 虚拟机所使用的网络端口组,点击下一步。

设置 Cloud Builder 虚拟机的 Admin 和 Root 的账户登录密码、主机名、IP 地址、子网掩码、网关、DNS 服务器、DNS 域名、DNS 搜索域、NTP 服务器。

确认 Cloud Builder 虚拟机所配置的信息,点击完成并开始部署。

因环境不同,安装部署的时间会有所差异,当前环境大概花了 20~30 分钟完成部署和启动。通过浏览器可以访问 Cloud Builder UI(https://vcf-builder.mulab.local)管理界面,如下图所示。

登录 Cloud Builder,查看当前部署版本。

原子性的意义

原子性特别是在并发编程领域,是一个极其重要的概念,原子性指的是一个操作或一组操作要么全部执行成功,要么全部不执行,不会出现部分执行的情况。这意味着原子性操作是不可分割的,它们在执行过程中不会被其他操作中断或干扰。

原子性的意义在于它保证了数据的一致性和程序的正确性。在多线程或多进程的环境中,当多个操作同时访问和修改共享数据时,如果没有原子性保证,可能会导致数据不一致或不确定的结果。例如,如果一个线程在读取某个数据时,另一个线程同时修改了这个数据,那么第一个线程读取到的数据可能是不正确的。通过确保操作的原子性,可以避免这种情况,从而维护数据的完整性和程序的正确执行。

了解了上面的原子性的重要概念后,接下来一起聊一聊 volatile 关键字。

volatile 关键字在 Java 中用于确保变量的更新对所有线程都是可见的,但它并不保证复合操作的原子性。这意味着当多个线程同时访问一个 volatile 变量时,可能会遇到读取不一致的问题,尽管它们不会看到部分更新的值。

Volatile 的限制

  • 不保证原子性:volatile 变量的单个读写操作是原子的,但复合操作(如自增或同步块)不是原子的。
  • 不保证顺序性:volatile 变量的读写操作不会与其他操作(如非 volatile 变量的读写)发生重排序。

一个例子

用一个示例来解释会更清楚点,假如我们有一段代码是这样的:

class Counter {
    private volatile int count = 0;

    void increment() {
        count++;
    }

    int getCount() {
        return count;
    }
}

尽管 count 是 volatile 变量,但 increment 方法中的复合操作 count++(读取-增加-写入)不是原子的。因此,在多线程环境中,多个线程可能会同时读取相同的初始值,然后增加它,导致最终值低于预期。

volatile 不保证原子性的代码验证

以下是一个简单的 Java 程序,演示了 volatile 变量在多线程环境中不保证复合操作原子性的问题:


public class VolatileTest {
    private static volatile int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        int numberOfThreads = 10000;
        Thread[] threads = new Thread[numberOfThreads];

        for (int i = 0; i < numberOfThreads; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    counter++;
                }
            });
            threads[i].start();
        }

        for (int i = 0; i < numberOfThreads; i++) {
            threads[i].join();
        }

        System.out.println("Expected count: " + (numberOfThreads * 100));
        System.out.println("Actual count: " + counter);
    }
}

在这个例子中:

  • counter 是一个 volatile 变量。
  • 每个线程都会对 counter 执行 100 次自增操作。
  • 理论上,如果 counter++ 是原子的,最终的 counter 值应该是 10000 * 100。

然而,由于 counter++ 包含三个操作:读取 counter 的值、增加 1、写回 counter 的值,这些操作不是原子的。因此,在多线程环境中,最终的 counter 值通常会小于预期值,这证明了 volatile 变量不保证复合操作的原子性。

解决方案

1. 使用 synchronized 方法或块:

  • 将访问 volatile 变量的方法或代码块声明为 synchronized,确保原子性和可见性。
class Counter {
    private volatile int count = 0;

    synchronized void increment() {
        count++;
    }

    synchronized int getCount() {
        return count;
    }
}

2. 使用 AtomicInteger 类:

java.util.concurrent.atomic 包中的 AtomicInteger 提供了原子操作,可以替代 volatile 变量。


import java.util.concurrent.atomic.AtomicInteger;

class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    void increment() {
        count.incrementAndGet();
    }

    int getCount() {
        return count.get();
    }
}

3. 使用锁(如 ReentrantLock):

使用显式锁(如 ReentrantLock)来同步访问 volatile 变量的代码块。


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Counter {
    private volatile int count = 0;
    private final Lock lock = new ReentrantLock();

    void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

使用volatile变量的正确使用场景

如果操作是简单的读写,并且你只需要保证可见性,可以使用 volatile。但对于复合操作,可以使用上述其他方法来实现,通过这些方法,可以确保在多线程环境中对共享资源的正确同步和可见性。

原因

新板子回来后,测试flash 烧录正常,但是无法BOOT,此时SPI设置为X4模式,使用内部时钟,速度90M。烧录过程不报错,校验也正常。

FLASH理论支持最大速度108M,90M应该还好。另外板卡预留了EMCCLK外部时钟模式,速率100M 也不可行。

此时约束如下:


set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property CFGBVS VCCO [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]
set_property CONFIG_MODE SPIx4 [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 90 [current_design] 
#set_property BITSTREAM.CONFIG.EXTMASTERCCLK_EN div-1 [current_design]
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
set_property BITSTREAM.CONFIG.UNUSEDPIN Pullup [current_design]

分析

90M偏高,尝试降低速率尝试。实际多次测试发现82M以下均正常。不同的器件支持的速度是有差异的,具体参考UG908
https://www.xilinx.com/support/documents/sw_manuals/xilinx2022_1/ug908-vivado-programming-debugging.pdf

SPIX8 模式

实际为了加快boot速度,硬件上支持SPIX8模式,这样实际速度可以提高到原有速度的2倍,从而加快启动过程。这在一些pice 应用上是必要的。


set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property CFGBVS VCCO [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 8 [current_design]
set_property CONFIG_MODE SPIx8 [current_design]
set_property BITSTREAM.CONFIG.CONFIGRATE 82 [current_design] 
#set_property BITSTREAM.CONFIG.EXTMASTERCCLK_EN div-1 [current_design]
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
set_property BITSTREAM.CONFIG.UNUSEDPIN Pullup [current_design]

实际生成下载固件的时候会有2个 bin文件,对应按照提示下载即可。

如果不做multiboot,生成bin文件时候的使用方式和SPIX4没有什么差异。

cd [get_property DIRECTORY [current_project]]/[current_project].runs/impl_1
write_cfgmem  -format bin -size 32 -interface SPIx8 -loadbit "up 0x00000000 [get_property top [current_fileset]].bit" -file [get_property DIRECTORY [current_project]]/test.bin -force

首先聊下node.js的优缺点和应用场景

Node.js的优点和应用场景

Node.js作为后端开发的选择具有许多优点,以下是其中一些:

  • 高性能: Node.js采用了事件驱动、非阻塞I/O模型,使得它能够处理大量并发请求而不会阻塞线程,从而具有出色的性能表现。

  • 轻量级和高效: Node.js的设计简洁而轻量,启动速度快,内存占用低,适合构建轻量级、高效的应用程序。

  • JavaScript全栈: 使用Node.js,开发者可以使用同一种语言(JavaScript)进行前后端开发,简化了开发人员的学习成本和代码维护成本。

  • 丰富的生态系统: Node.js拥有丰富的第三方模块和库,可以轻松集成各种功能和服务,提高开发效率。

  • 可扩展性: Node.js具有良好的可扩展性,可以通过添加更多的服务器实例来横向扩展应用程序,满足不断增长的用户需求。

  • 实时应用: 由于Node.js对于事件驱动和非阻塞I/O的支持,它非常适合构建实时应用,如即时通讯、在线游戏、实时分析等。

  • 微服务架构: Node.js可以作为微服务架构中的一个服务组件,通过轻量级的设计和快速的响应能力,使得微服务之间的通信更加高效。

  • 数据流处理: Node.js适合处理大量的数据流,例如文件操作、网络流量分析等,可以通过流式处理来有效地管理和处理数据。

应用场景包括但不限于:

  • Web应用程序: 开发基于Node.js的Web应用程序,如社交网络、电子商务平台、博客、内容管理系统等。

  • API服务: 使用Node.js构建RESTful API服务,为移动应用、前端应用提供数据接口。

  • 实时应用: 构建实时应用程序,如聊天应用、在线游戏、实时地图等,利用Node.js的事件驱动和非阻塞I/O模型实现高效的实时通讯。

  • 数据流处理: 使用Node.js处理大量的数据流,例如日志处理、实时监控、数据分析等。

  • 微服务架构: 将Node.js作为微服务架构中的一个服务组件,通过轻量级的设计和快速的响应能力,实现服务之间的高效通信。

总的来说,Node.js作为一种高性能、轻量级的后端开发工具,适用于各种类型的应用场景,尤其在需要处理大量并发请求和实时通讯的应用中表现突出。

node.js的局限性

尽管Node.js在许多方面都表现出色,但它也有一些局限性和适用场景的限制。以下是一些Node.js的局限性:

  • 单线程阻塞: 虽然Node.js采用了非阻塞I/O的模型,但在处理CPU密集型任务时,单线程的特性可能导致性能瓶颈。由于Node.js是单线程的,处理阻塞操作(如大量计算或长时间的同步操作)会影响整个应用程序的响应性。

  • 回调地狱(Callback Hell): 在复杂的异步操作中,嵌套的回调函数可能导致代码难以理解和维护,这被称为“回调地狱”问题。虽然可以使用Promise、async/await等来缓解这个问题,但在某些情况下仍可能存在。

  • 相对较小的标准库: Node.js的标准库相对较小,相比于其他后端语言,需要依赖第三方模块来实现一些常见的功能。这可能需要花费额外的时间来选择、学习和整合这些模块。

  • 不适合密集型计算: 由于Node.js是单线程的,不适合用于处理大量的计算密集型任务。如果应用程序主要依赖于大量的数学计算或复杂的数据处理,其他多线程的语言可能更合适。

  • Callback错误处理: 在回调模式下,错误处理可能变得繁琐,需要在每个回调中检查错误对象。这使得开发者需要更加小心地处理错误,以确保它们不会被忽略。

  • 相对较新的技术栈: 相较于一些传统��后端技术栈,Node.js是相对较新的技术,一些企业可能仍然更倾向于使用更成熟的技术。

  • 不适合长连接: 对于长连接的应用场景,如传统的即时通讯(IM)系统,Node.js的单线程模型可能不是最佳选择,因为它会导致长时间占用一个线程。

尽管有这些局限性,但Node.js在许多应用场景下仍然是一个强大且高效的工具。选择使用Node.js还是其他后端技术应该根据具体项目的需求、团队的技术栈和开发者的经验来做出。

node.js常用的几种主流框架

Node.js是一个非常灵活的JavaScript运行时环境,它可以用于构建各种类型的应用程序,从简单的命令行工具到大型的网络应用程序。以下是一些常用的Node.js框架:

  • Express.js:Express.js是Node.js最流行的Web应用程序框架之一,它提供了一组强大的功能,使得构建Web应用变得更加简单和快速。Express.js具有路由、中间件、模板引擎等功能,可以满足大多数Web应用的需求。

  • Koa.js:Koa.js是由Express.js原班人马打造的下一代Node.js Web框架,它使用了ES6的新特性,如async/await,使得编写异步代码更加简洁。Koa.js更加轻量级和灵活,它提供了更强大的中间件功能,可以更方便地实现定制化的功能。

  • Nest.js:Nest.js是一个用于构建高效、可扩展的服务器端应用程序的渐进式Node.js框架。它基于Express.js,但引入了许多现代化的概念,如依赖注入、模块化、类型检查等,使得构建复杂应用变得更加简单。

  • Hapi.js:Hapi.js是一个专注于提供配置简单、可测试性强的Web服务器框架。它提供了一系列的插件,可以轻松地扩展其功能,同时具有强大的路由、验证、缓存等功能,适用于构建大型和高可靠性的Web应用程序。

  • Meteor.js:Meteor.js是一个全栈JavaScript框架,它可以同时构建客户端和服务器端的应用程序。Meteor.js提供了一整套的工具和库,包括数据库访问、实时数据同步、用户认证等功能,使得构建实时Web应用变得更加简单和快速。

  • Sails.js:Sails.js是一个基于Express.js的MVC框架,它提供了类似于Ruby on Rails的开发体验,使得构建数据驱动的Web应用变得更加简单。Sails.js具有自动生成API、蓝图路由、数据关联等功能,适用于构建RESTful API和实时Web应用。

Express框架:实践与技术探索

Express框架简介:

Express是一个轻量级且灵活的Node.js Web应用程序框架,它提供了一组简洁而强大的工具,帮助开发者快速构建Web应用。Express的核心理念是中间件,通过中间件可以处理HTTP请求、响应以及应用程序的逻辑。

基础搭建与路由:

在开始实践之前,首先需要搭建Express应用程序的基础结构。通过使用
express-generator
工具或手动创建
package.json

app.js
文件,可以快速启动一个Express项目。接下来,我们将学习如何定义路由以及如何处理HTTP请求和响应。

const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(3000, () => {
  console.log('Express app listening on port 3000');
});

中间件:

Express中间件是一个函数,它可以访问请求对象(
req
)、响应对象(
res
)以及应用程序的下一个中间件函数(通常命名为next)。中间件函数可以用来执行任何代码,修改请求和响应对象,以及终止请求-响应周期。

app.use((req, res, next) => {
  console.log('Time:', Date.now());
  next();
});

模板引擎与视图:

Express框架允许使用各种模板引擎来生成动态HTML内容。常用的模板引擎包括EJS、Pug和Handlebars。通过配置模板引擎,可以将动态数据嵌入到静态模板中,以生成最终的HTML页面。

app.set('view engine', 'ejs');

数据库集成与ORM:

在实际应用中,数据库是不可或缺的一部分。Express框架与各种数据库集成良好,可以通过ORM(对象关系映射)工具来简化数据库操作。常用的ORM工具包括Sequelize、Mongoose等,它们可以帮助开发者更轻松地进行数据模型定义、查询和操作。

RESTful API设计与实现:

Express框架非常适合构建RESTful API。通过定义不同的HTTP动词和路由,可以实现资源的创建、读取、更新和删除操作。此外,Express还提供了一系列中间件来处理请求体、响应格式等,使得构建API变得更加简单。

app.get('/api/users', (req, res) => {
  // 获取所有用户信息
});

app.post('/api/users', (req, res) => {
  // 创建新用户
});

实践案例:

为了更好地理解Express框架的实践,我们将以一个简单的博客应用为例。在这个应用中,我们可以拓展一下用户的注册、登录、文章的创建和展示等功能,并且结合数据库和
RESTful API
设计。在这个示例中,我们将使用
MongoDB
作为数据库,并使用
Mongoose
作为
MongoDB
的对象建模工具。首先,确保您已经安装了
Node.js

MongoDB
,并创建了一个名为blogApp的文件夹来存放我们的项目。

  • 首先,在项目文件夹中初始化npm,并安装Express、Mongoose和body-parser依赖:

      npm init -y
      npm install express mongoose body-parser
    
  • 在项目文件夹中创建
    app.js
    文件,并编写以下代码:

      // 导入所需的模块
      const express = require('express');
      const bodyParser = require('body-parser');
      const mongoose = require('mongoose');
    
      // 连接MongoDB数据库
      mongoose.connect('mongodb://localhost:27017/blog', { useNewUrlParser: true, useUnifiedTopology: true });
      const db = mongoose.connection;
    
      // 检测数据库连接状态
      db.on('error', console.error.bind(console, 'MongoDB connection error:'));
      db.once('open', function() {
      	console.log('Connected to MongoDB');
      });
    
      // 创建Express应用
      const app = express();
    
      // 使用body-parser中间件解析请求体
      app.use(bodyParser.json());
      app.use(bodyParser.urlencoded({ extended: true }));
    
      // 定义用户模型
      const User = mongoose.model('User', new mongoose.Schema({
      	username: String,
      	password: String
      }));
    
      // 注册用户
      app.post('/api/register', async (req, res) => {
      	try {
      		const { username, password } = req.body;
      		const user = await User.create({ username, password });
      		res.json({ success: true, message: 'User registered successfully', user });
      	} catch (error) {
      		res.status(500).json({ success: false, message: error.message });
      	}
      });
    
      // 用户登录
      app.post('/api/login', async (req, res) => {
      	try {
      		const { username, password } = req.body;
      		const user = await User.findOne({ username, password });
      		if (user) {
      			res.json({ success: true, message: 'User logged in successfully', user });
      		} else {
      			res.status(401).json({ success: false, message: 'Invalid username or password' });
      		}
      	} catch (error) {
      		res.status(500).json({ success: false, message: error.message });
      	}
      });
    
      // 启动Express服务器
      const port = 3000;
      app.listen(port, () => {
      	console.log(`Server is running on port ${port}`);
      });
    

以上代码实现了用户注册和登录的功能,使用了MongoDB作为数据库存储用户信息,并提供了RESTful风格的API接口。

您可以通过以下命令启动服务器:

node app.js
  • 接下来,我们添加文章模型和相关的路由来实现文章的创建和展示功能。在app.js文件中添加以下代码:

      // 定义文章模型
      const Article = mongoose.model('Article', new mongoose.Schema({
      	title: String,
      	content: String,
      	author: {
      		type: mongoose.Schema.Types.ObjectId,
      		ref: 'User'
      	}
      }));
    
      // 创建文章
      app.post('/api/articles', async (req, res) => {
      	try {
      		const { title, content, author } = req.body;
      		const article = await Article.create({ title, content, author });
      		res.json({ success: true, message: 'Article created successfully', article });
      	} catch (error) {
      		res.status(500).json({ success: false, message: error.message });
      	}
      });
    
      // 获取所有文章
      app.get('/api/articles', async (req, res) => {
      	try {
      		const articles = await Article.find().populate('author', 'username');
      		res.json({ success: true, articles });
      	} catch (error) {
      		res.status(500).json({ success: false, message: error.message });
      	}
      });
    

以上代码实现了创建文章和获取所有文章的功能,每篇文章都与特定的作者相关联。

现在,您可以使用POST请求来创建新的用户和文章,使用GET请求来获取所有文章。例如:

  • 注册新用户:发送POST请求到
    /api/register
    ,传递
    username

    password
    字段。
  • 用户登录:发送POST请求到
    /api/login
    ,传递
    username

    password
    字段。
  • 创建新文章:发送POST请求到
    /api/articles
    ,传递
    title、content

    author
    字段(注意,
    author
    字段应该是已注册用户的ID)。
  • 获取所有文章:发送GET请求到
    /api/articles

这个示例演示了如何使用Express框架结合MongoDB实现一个简单的博客应用,并提供了RESTful API接口。可以根据需求扩展和定制这个应用,例如添加用户身份验证、文章编辑和删除功能等。