Node.js 实战:狗屁不通文章生成器写一些无意义的检讨、反思、罚抄,是我们从小到大都一直做的事情,我不知道这些东西到
引言
写一些无意义的检讨、反思、罚抄,是我们从小到大都一直做的事情,我不知道这些东西到底有没有用处😭,也许唯一的用处就是浪费时间,我们虽然没有办法消灭它,但是我们有办法去绕开它🐍。这次我们将会完成一个Node.js的小项目——狗屁不通文章生成器,让我们通过这篇文章去学会如何对抗形式主义!
项目开始
主入口文件
index.js 文件是项目的主入口文件,负责加载语料库、生成文章、并将生成的文章保存到指定位置。
导入所需模块
const { generate } = require('./lib/generator.js');
const { createRandomPicker } = require('./lib/random.js');
const { loadCorpus, saveCorpus } = require('./lib/corpus.js');
这些都是我们自己定义的文件模块,将这些功能封装到了外部的文件,而不是直接写在 index.js入口文件,这样会使得我们的主入口文件更加简洁,便于维护和阅读!
其中这三个模块
-
generator.js:负责生成文章的逻辑。 -
random.js:包含生成随机数和随机选择的功能。 -
corpus.js:包含读取和保存语料库的功能。
定义存储的变量
let article = [];
let title = '';
article:用于存储生成的文章内容, title:用于存储生成的文章标题。
处理函数,调用其他模块功能
const corpus = loadCorpus('corpus/data.json');
先调用 loadCorpus 函数,读取 corpus/data.json 文件中的内容,并将其解析为一个对象,赋值给 corpus 变量。
title = createRandomPicker(corpus.title)();
调用 createRandomPicker 函数,生成一个随机选择器,然后调用该选择器,从 corpus.title 中随机选择一个标题,并赋值给 title 变量。
article.push(title);
再将生成的标题添加到 article 数组中,确保标题是文章的第一个元素。
article.push(...generate(title, { corpus }));
调用 generate 函数,生成文章内容,并将生成的内容添加到 article 数组中。
导出模块
module.exports = {
article,
handle,
title
};
最后我们将这些在主入口函数获取到值的变量抛出,方便后续的调用。
标题生成
我们在lib目录下创建一个random.js用来随机从语料库中选取句子组成段落。
function randomInt(min,max){
const n = Math.random()
// 生成一个介于最小字数和最大字数之间的字数
return Math.floor(min*(1-n)+max*n)
}
function createRandomPicker(arr){
// 使用闭包,相当于新建一个数组,再对数组进行操作
arr = [...arr]
// 保证连续两次取到的不一样
// 每次将取出的序号与最后一位对调位置,且每次取出的都是上一次的最后一个
function randomPick() {
const len = arr.length-1
const index = randomInt(0,len);
[arr[index],arr[len]] = [arr[len],arr[index]]
return arr[index]
}
// 先调用一次放弃掉,使得每次第一次调用出的不一定是原数组的最后一位
randomPick()
return randomPick
}
module.exports = {
randomInt,
createRandomPicker
}
首先实现一个生成随机数的函数
function randomInt(min, max) {
const n = Math.random();
// 生成一个介于最小值和最大值之间的随机整数
return Math.floor(min * (1 - n) + max * n);
}
min * (1 - n) + max * n 通过加权计算生成一个介于 min 和 max 之间的浮点数。Math.floor() 将生成的浮点数向下取整,得到一个整数。
然后我们再去实现一个去从语料库中随机取出要的标题的函数
function createRandomPicker(arr) {
// 使用闭包,相当于新建一个数组,再对数组进行操作
arr = [...arr];
// 保证连续两次取到的不一样
// 每次将取出的序号与最后一位对调位置,且每次取出的都是上一次的最后一个
function randomPick() {
const len = arr.length - 1;
const index = randomInt(0, len);
[arr[index], arr[len]] = [arr[len], arr[index]];
return arr[index];
}
// 先调用一次放弃掉,使得每次第一次调用出的不一定是原数组的最后一位
randomPick();
return randomPick;
}
arr = [...arr]:使用扩展运算符创建 arr 的副本,以免修改原数组。然后定义 randomPick 函数用于随机选择数组中的元素。
[arr[index], arr[len]] = [arr[len], arr[index]]:交换 arr[index] 和 arr[len] 的位置,以保证连续两次选择的元素不同,相当于每次取出最末尾的元素,然后再将最末尾的换到前面,再依此往复,相必这里大家会有疑惑,第一个被选取的不都是最后一个元素吗,所以我们在后面加上了randomPick(),先调用一次 randomPick,使得第一次调用返回的元素不是原数组的最后一个元素。
最后达成了返回一个随机选择器函数 randomPick,用于从数组 arr 中随机选择元素,并保证连续两次选择的元素不同。
文章内容生成
我们定义 generate 和 sentence 函数,用于生成文章内容并进行文本替换,然后将它们导出,以便在其他文件中使用。
const {randomInt, createRandomPicker} = require('./random.js');
// 生成文章
function generate(title, { corpus, min = 1000, max = 2000 }) {
const articleLength = randomInt(min, max);
const { famous, bosh_before, bosh, conclude, said } = corpus;
// 使用 map 循环对五个数组生成随机选择器
const [pickFamous, pickBoshBefore, pickBosh, pickConclude, pickSaid] =
[famous, bosh_before, bosh, conclude, said].map(createRandomPicker);
const article = [];
let totalLength = 0;
while (totalLength < articleLength) {
let section = '';
const sectionLength = randomInt(100, 500);
while (section.length < sectionLength) {
const n = randomInt(0, 100);
if (n < 20) { // 名人名言
section += sentence(pickFamous, { said: pickSaid, conclude: pickConclude });
} else if (n < 50) { // 前置废话
section += sentence(pickBoshBefore, { title }) + sentence(pickBosh, { title });
} else { // 废话
section += sentence(pickBosh, { title });
}
}
totalLength += section.length;
article.push(section);
}
return article;
}
我们首先用const { famous, bosh_before, bosh, conclude, said } = corpus解构语料库对象,提取出五种类型的句子。
const [pickFamous, pickBoshBefore, pickBosh, pickConclude, pickSaid] = [famous, bosh_before, bosh, conclude, said].map(createRandomPicker)
这是一种解构赋值语法和map的方法。
解构赋值是一种从数组或对象中提取数据的简洁语法。通过解构赋值,我们可以将数组或对象中的元素直接赋值给变量。map 方法用于创建一个新数组,其结果是对原数组的每个元素调用一个提供的函数后的返回值。
通过这一行代码我们优雅地实现了:
-
从
corpus对象中解构出famous、bosh_before、bosh、conclude和said这五个数组。 -
将这五个数组组成一个新的数组
[famous, bosh_before, bosh, conclude, said]。 -
使用
map方法对这个新数组中的每一个元素(即每一个数组)应用createRandomPicker函数。createRandomPicker函数会返回一个新的函数,这个新函数是一个随机选择器,用于从对应的数组中随机选择一个元素。 -
将
map方法返回的新数组(包含五个随机选择器函数)解构赋值给pickFamous、pickBoshBefore、pickBosh、pickConclude和pickSaid这五个变量。
然后我们用了 while 循环生成文章的段落,直到 totalLength 达到 articleLength,
根据随机数 n 的值决定插入哪种类型的句子(名人名言、前置废话、废话)。 生成句子时调用 sentence 函数进行文本替换。 将生成的段落添加到 article 数组中,并更新 totalLength。
随后是 sentence 函数
// 文本替换
function sentence(pick, replacer) {
let res = pick();
for (let key in replacer) {
// 使用正则表达式替换模板中的占位符
res = res.replace(
new RegExp(`{{${key}}}`, 'g'),
typeof replacer[key] === 'function' ? replacer[key]() : replacer[key]
);
}
return res;
}
-
调用
pick()函数获取一个随机句子res。 -
使用
for...in循环遍历replacer对象的键:- 对每个键,创建一个新的正则表达式
new RegExp({{${key}}}, 'g')用于匹配模板中的占位符。 - 使用
replace方法将占位符替换为replacer[key]对应的值,如果replacer[key]是一个函数则调用它,否则直接使用其值。
- 对每个键,创建一个新的正则表达式
将生成的文章显示到页面
这里我们将使用 Node.js 的一个核心模块 http,我们先导入
const http = require('http');
const fs = require('fs');
const { article, handle, title } = require('./index.js');
handle();
这里的handle是index.js中抛出的,所以我们这里调用会生成一篇文章并存在 article中。
然后再去创建一个 HTTP 服务器
const server = http.createServer((req, res) => {
if (req.url === '/') {
let html = fs.readFileSync(__dirname + '/index.html', 'utf-8'); // Change const to let
const title = article[0]; // Assuming the title is the first element in the article array
const content = article.slice(1).join('<br/>'); // Rest of the article
html = html.replace('{{title}}', title).replace('{{article}}', content);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(html);
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
如果请求的 URL 是 /(根路径),则:
- 使用
fs.readFileSync同步读取index.html文件的内容,并将其编码为 UTF-8 字符串。使用let声明html变量来存储 HTML 内容。 - 假设
article数组的第一个元素是文章的标题,将其赋值给title变量。 - 使用
article.slice(1).join('<br/>')将article数组中的剩余元素(即文章内容)连接成一个字符串,每个段落用<br/>标签分隔。 - 使用
html.replace方法将{{title}}和{{article}}占位符替换为实际的文章标题和内容。 - 设置响应头,指明响应内容的类型为
text/html。 - 使用
res.end方法发送响应,将生成的 HTML 返回给客户端。
如果请求的 URL 不是 /,则: 设置响应头,指明响应内容的类型为 text/plain。 使用 res.end 方法发送 404 响应,指明请求的资源未找到。
最后启动服务器
server.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});
使用 server.listen 方法启动服务器,监听 3000 端口。 启动后,服务器会输出一条消息提醒。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>狗屁不通文章生成器</title>
</head>
<body>
<h2>{{title}}</h2>
<article>{{article}}</article>
</body>
</html>
效果展示

我们的狗屁不通文章生成器便完成了!
总结
通过这个项目,我们不仅实现了一个简易的文章生成器,还深入理解了 Node.js 文件系统操作、随机数生成、文本处理和 HTTP 服务器搭建的基础知识。这种项目在实际应用中有广泛的前景,无论是用于生成娱乐内容,还是辅助生成初步的文档,都有很大的实用价值。如果这篇文章对你有帮助,可以点个赞哦😊!
转载自:https://juejin.cn/post/7397816165207621658