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