我做了一个专属于东京大学生的论坛(初学者Node.js)
我想尝试制作一个Web应用程序,所以我选择了Node.js来学习,并制作了一个专属于东京大学学生的论坛。
制作过程非常有趣,同时也非常困难,所以我打算将其记录下来。
我是个纯新手,完全是个业余爱好者,所以请将这篇文章当作非常基本的内容。可能这篇文章中存在错误或者安全风险等方面的问题,如果有的话,请在评论中告诉我,我将不胜感激。
此外,我使用的词语很大程度上是基于氛围和形象的,比如包和库之间的细微差别,请对我宽容一些,因为我是文科生。
顺便说一下,由于目前还没有任何用户,如果你正在阅读这个,你是东京大学生,或者你认识东京大学的人,请务必与我们分享一下。
1. 概念和基本原则
引子 zǐ)
因为知道扎克伯格是从哈佛大学开始创办Facebook,我觉得在大学这个框架下创造一些东西很有趣。而且尽管Facebook有这样的起源,最终还是发展壮大并对大众开放,但现在实际上没有像大学社交网站这样的存在。特别是大学都为每个学生分发了独特的电子邮件地址,如果能够制作一个只限制大学邮箱登录的功能,就可以轻松创建一个限定的社区。
像我这样的初学者自己能否判断出自己所想到的创意是否能够实际实现是相当困难的,但是至少可以尝试在论坛上实现,因为我在阅读Nodejs入门书时已经能够制作一个简单的评论网站,所以只需要在此基础上添加功能,就能够自然地完成它。
基本方针 –
我决定基本上照搬Hacker News这个网站的功能和设计。
https://news.ycombinator.com/news
这是由美国的YCombinator孵化器运营的一个论坛,主要用于在技术领域发布链接并进行评论。可以说是一个较简化版的Reddit。
我觉得这个设计在简约的设计和功能上很容易被模仿。虽然在学习编码方面,自己设计功能还可以接受,但是自己设计整个界面非常麻烦且耗费时间无限,所以模仿策略非常高效而且好使。
2. 用于制作的材料
技術組成
-
- 言語・フレームワーク
Node.js、Express
テンプレートエンジン
EJS
データベース
当初SQLiteだったけどデプロイで詰まってPostgreSQLに変えた
Vercel Postgresを利用
ORM
Sequelize
公開する場所
Azureでやろうとしたけど金かかりそうだからVercelにした
ドメイン
お名前.comの1円枠を使ってたから、XServerの1円/年にした
我对如何巧妙地结合前端和后端不太了解,而且要学的东西实在太多了,所以我没有使用React或Vue这些东西。
这种情况下应该怎么说呢?使用纯HTML/CSS?但是我使用了EJS,那算是前端吗?
至于CSS,我有点使用Bootstrap。
為了解決問題
聊天GPT
初次使用编程时,我才真正体会到ChatGPT的真谛。如果没有ChatGPT,我可能需要花上十倍的时间依赖Google搜索才能完成,而且还有可能放弃。因为它能够在我输入错误或者想实现的功能时直接给出代码,所以我只需复制粘贴就能创建物品。然而,这样做的弊端是一旦复制的代码能够正常运行,我就会满足并直接进行下一步,而实际上可能会出现我没有理解的代码部分。嗯,尽管如此,提高效率的好处仍然是巨大的,并且与以往的Google搜索主要是复制粘贴并没有什么变化。顺便说一下,我没有为ChatGPT付费,所以评分是3.5分。
Node.js入门指南
非常有用。讲解得非常透彻,网站制作简单明了,可以通过添加功能来进行各种实验。以前我也看过视频教程,但再次通过这本书重新认识到书籍的优点。顺便说一下,我在技术架构中使用EJS和Sequelize是因为这本书中使用了这些技术。对于像我这样已经学习了HTML/CSS/Javascript的基本写法,但想尝试制作使用数据库的动态网站的人来说,这本书是推荐的。
谷歌搜索
对于ChatGPT无法解决的复杂问题,我会单独使用Google搜索。尤其是在部署时,关于Azure或Vercel的信息和错误处理方法主要通过Google搜索获取。由于Heroku已取消了免费使用计划,关于此后的信息相当新,所以ChatGPT的能力较弱。在部署时需要注意的事项通常需要借助有相关经验的人的经验,所以我会进行Google搜索并查看StackOverflow等。另外,建议使用英语进行搜索。
3. 功能和使用方法 hé
在论坛上,基本上只有以下三个功能。
-
- 投稿(リンクとタイトルのセット)
-
- コメント(投稿に対してテキストコメント)
- いいね(投稿に対するいいね)
递交投稿
投稿的内容是链接+标题,通过分享新闻文章和网站。
評論
对于每个提交的链接,都可以进行评论。
喜欢
可以对投稿点赞。通过编号旁边的三角形图标可以点赞/取消点赞。无法对评论点赞。
4. 技术的重点
整理该网站的各种功能和我的所学技术的原理。
赞功能部分
为了不需要特意刷新页面就能像Twitter的”赞”按钮那样,在点击”赞”按钮时实现即时更新,我使用了名为Ajax的机制。虽然出现了新的术语有点吓人和麻烦,但意外地很简单,只需要在使用Express创建的Public文件夹中创建一个JS文件,并写入少量代码。之后在Routes中,通过类似服务器端的router.post的方式来处理像修改数据库之类的操作。
const upvotes = document.querySelectorAll(".upvote")
// いいねボタンのクリックハンドラ
upvotes.forEach((upvote)=>{
upvote.addEventListener('click', (event) => {
const id = event.target.id; // 投稿ID (適切なIDに置き換える)
const upDown = event.target.className.split(" ")[1]
// サーバーにAjaxリクエストを送信
fetch(`like/${id}/${upDown}`, {method: 'POST'})
.then(response => response.json())
.then(data => {
if (data.success) {
let newLikeCount = data.newLikeCount
document.getElementById(`${event.target.id}point`).innerText = newLikeCount
event.target.className = upDown == "up" ? "upvote down" : "upvote up"
event.target.src = upDown == "up" ? "https://news.ycombinator.com/triangle.svg" : "https://upload.wikimedia.org/wikipedia/commons/4/4f/Simple_triangle.svg"
}
}).catch(err=>console.error("エラー", err))
});
})
在客户端上放置这个JS脚本,然后进行POST处理,就像这样?虽然不太清楚在做什么,但只是在进行两个动作:1.更新点赞的数量,2.改变点赞图片。
数据库结构
在数据库中有一些名为“表”的表格,但这一次我创建了四个表。还有一些由于会话保存而自动创建的表格,但我会在之后进行解释。
-
- Users
-
- Boards
-
- Comments
- Likes
我不知道公開这样的事情是否危险,但还是写下来吧。
正如其名,分别保存用户、投稿、评论和点赞。这次是第一次接触到关系型数据库的概念,需要牢记4个表之间的关系。例如,每个用户在Users表中都有一个分配的ID,但是在Boards和Comments表中,需要明确识别是谁发布了帖子,谁发表了评论,因此需要将”Users的ID”与”Boards的UserID”和”Comments的UserID”进行关联。
特别复杂的是,Comments(评论)不仅仅与一个帖子相关联,还与一个用户相关联,因此有与其他表相关的两个条目。此外,Boards(板块)的帖子归属于一个用户,但是Comments的评论则属于多个父级角色,因此也需要区分开来。在Sequelize这个ORM中,可以通过belongsTo和hasMany等指令来明确指示关系。
static associate(models) {
Board.belongsTo(models.User, { foreignKey: 'userId' });
Board.hasMany(models.Comment, {foreignKey: "boardId"})
Board.hasMany(models.Like, {foreignKey: "boardId"})
}
在Sequelize的models文件夹中进行表格设置。这是与Boards表格的其他关联。
用户密码
最初的开发阶段,将密码直接存储在数据库中,但后来意识到这存在安全风险,于是学习并实施了哈希化/加盐的方法。
发现这一点是因为看了Tom Scott在YouTube上的一个视频,他非常清楚地解释了密码存储方式和相关风险。
通过各种算法对数据进行哈希化,使其变成类似鬼一般的长度,并且通过盐值进一步增加难度,看起来有点困难。不过,通过在npm上安装并使用bcrypt,可以相当轻松地实现。
实际上,在数据库的表格中进行确认时,字符串变成不可理解的字符,说明成功了。
其他安全性
在同一个YouTube频道的Tom Scott的解释中,我第一次了解到了SQL注入和跨站脚本攻击。如果用户有多种可以输入的功能,那么通过在其中写入script标签或SQL查询会导致执行的风险似乎是存在的。
经过调查和验证,我认为我的网站基本上是安全的。使用像Sequelize这样的ORM可以减少对数据库的风险,并且通过转义处理等方法也可以降低风险。可能还存在一些高级技术,但我并没有保存信用卡信息,目前已经掌握了安全的基本线,所以我觉得还是不错的。如果有注意事项,请务必在评论中告诉我。
邮件认证,忘记密码功能
在中国有一个非常简单的工具叫做nodemailer,可以轻松地发送电子邮件。我目前只是使用免费的Outlook邮箱,但它的运行一切正常。
在账户注册阶段,认证功能会生成验证令牌,并且只有在点击该链接后才会进行认证。未认证的账户将在一定时间后被删除。
密码忘记功能与之前的感觉相同,只需发送密码重设链接给用户并要求他们点击,然后就需要修改数据库。同样,如果在一定时间内未点击链接,链接将失效。
顺便说一下,我不是将电子邮件地址和密码直接写入源代码中,而是将它们作为环境变量设置在.env文件中,并在.gitignore文件中不保存到Git中。我在vercel的托管环境中设置了环境变量。
会话
由于每次刷新页面都会很麻烦地要求登录,所以有了一种功能可以在一段时间内以用户会话的形式保持登录状态。这个功能我在Node.js入门书上学到了,非常有用。它通过Express Session来记住用户的信息,在进行get或post处理时可以通过req.session轻松获取用户名等信息。
一点需要注意的是,默认情况下,Express Session会将登录信息保存在内存中,存在泄漏的风险,因此不能在生产环境中使用。在Express Session的npm页面的底部,有许多用于不同数据库的存储包,需要安装并将数据保存在某个数据库中。顺便提一下,我使用了一个名为Connect Session Sequelize的包,它可以使用Sequelize将数据保存在PostgreSQL中。
https://www.npmjs.com/package/express-session#user-content-compatible-session-stores
const session = require("express-session")
const SequelizeStore = require('connect-session-sequelize')(session.Store);
// SequelizeStoreの設定
const myStore = new SequelizeStore({
db: db.sequelize,
checkExpirationInterval: 15 * 60 * 1000,
expiration: 24 * 60 * 60 * 1000,
});
let session_opt = {
store: myStore,
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {maxAge: 60 * 60 * 1000}
}
myStore.sync();
app.use(session(session_opt))
5. 绊倒后成功战胜了困难
由于遇到了许多问题,所以我无法完整地记住整个工程,但我会写下让我特别困扰的部分以及如何克服它们。特别是与部署有关的事情,我记得最清楚且经历较多。
然后呢Promise和async await之类的东西
在学习Node.js时,异步处理是一个相当复杂的主题。尽管我们理解了它所指的内容,但实际在代码中如何执行以及需要注意哪些方面等等,这些都是困难的。
对于.then(),非常容易理解和使用,就像字面上的意思一样,在其后执行函数。但是,如果嵌套太深,会变得非常难以阅读,因此需要使用promise或者async/await来使代码更加清晰易懂。
因为我很害怕Promise、async await,所以我用.then很乱的长代码请求ChatGPT重新改写。ChatGPT很聪明的,所以我请它帮忙重写。
SQLite的部署
可能的中文翻译如下:
这可能是最令人困扰的事情了。因为在Node.js入门书中使用了SQLite,所以我想直接用SQLite发布,但是完全行不通。SQLite并不是有一个独立的数据库服务器,而是在项目文件夹中有一个像.sqlite的文件供操作。它非常简单,学习时在书上真的非常容易理解,但显然不适合部署。
首先尝试使用Vercel,发现无法使用SQLite,然后在Medium文章中看到Azure支持SQLite的信息,于是尝试了一下,但是还是不行。
真的,因为这篇文章我浪费了很多时间。
Azure是类似于大学生账户的,所以不需要注册信用卡,我想这是个好东西,我尝试在Web App上部署了,但不行,试了一下在论坛上提到的只分离SQLite并进行存储绑定等机制,但还是不行,试过了各种SQLite的设置,但无济于事。真的是看了所有的文章和StackOverflow上的答案,尽力想解决,结果还是失败了,所以SQLite估计真的不适合用在Web应用的发布上。
最终,我不得已放弃了,决定使用PostgreSQL。这样我才第一次接触到MySQL和PostgreSQL这两个著名的巨头之一。但是选定这个也需要考虑一番,本来想在同一个Azure上进行,但听说Azure的数据库服务器似乎会产生一些费用,所以我又回到了可以免费使用的Vercel,并且正好有Vercel Postgres,所以我选择了它。
我第一次学习SQL时学了关于大象(译注:初学者常用例子)的例子,但其实并不太难。因为在以前的Sequelize中可以使用相同的操作方式,所以实际上并不需要改写代码,只需要调整Sequelize的配置等。不过,我之前在SQLite中写Model的Associate部分相对比较随意,现在需要完整地写出来,这一点让我有些困扰。
如何以更低的价钱来实现。
因为没有钱,所以需要找到可以免费使用的方法。可以通过搜索类似于”Heroku免费套餐取消 迁移目标”这样的关键词来高效地查找有免费套餐的服务。
尽管仅仅是一个免费档,但是否需要注册信用卡是一个相当重要的分水岭,1%和0%的收费可能性之间差距巨大。特别是对于像我这样的初学者业余文科程序员来说,云配置错误的概率非常高,所以使用信用卡的可能性大约是40%,远不止1%。我不想因为刚刚注册信用卡而发生像一个月的账单一百万这样的错误爆炸,所以这是必须坚守的底线。
我经过各种调查后最终选择了Vercel。Hobby计划是免费的,而且还可以使用Postgres,非常完美。无需注册信用卡,只需直接与GitHub账户和仓库连接,就可以通过Git Push进行更新,简直如梦幻般的方便。
虽然作为初学者,我一开始觉得Vercel是那些使用Next.js的高手程序员才会用的东西,但是我也变成了它的粉丝。嗯,事实上Vercel在幕后还是使用了AWS,所以亚马逊大大真是太棒了,但是Vercel提供的便利性真是太好了,很有价值。
作為其他公開選項的考慮,
根据以上情况分析,主要费用集中在akamonnews.com这个域名的购买上。而且,由于选择了XServer域名,在初年度仅需支付1日元的费用,合计仅为1日元。此外,由于不愿意在XServer上注册信用卡,因此选择了用1日元硬币在便利店付款的方式进行结算。
部署到 Vercel
在Vercel上使网站真正运行实际上是一场相当艰苦的战斗。
修改App.js的根文件为Index.js,或者准备Vercel.json文件来正确引导它们,这些细节性的操作相当复杂,让人有些困难。
尤其是PostgreSQL的设置非常麻烦,Sequelize的迁移一直无法成功,只能自己编写SQL并进行配置,才能勉强让它正常运行。
这个过程就是不断地查看显示在Vercel上的日志,然后复制粘贴出现的红色错误信息去ChatGPT或者Google上询问。真的无论重复多少次都一直没有显示出来,非常辛苦,但当网站终于能够运行时,感动得很。实际上,当我注册账户或者发布内容的时候,非常非常开心。
6: 总结或者说感想
制作东西非常有趣啊。把自己想的东西变成实体并且运作起来的感觉非常好。特别是有了ChatGPT,编写代码的速度快得惊人,让像我这样的初学者更容易达到目标。以前可能会放弃编程的人,也许通过ChatGPT整个世界发生了相当大的变化。不过我觉得像「不只是用ChatGPT编程!」这样的人并不太多,所以我希望将其用途扩大化。
这次我真正理解的是一个逐步扩展功能的过程,这是一个相当重要的学习。以前想象过程序员创建巨大项目的情景,不明白为什么能理解成千行代码,也不知道从哪里开始构建,感到绝望。但实际上,并不是这样,我明白了实际上是逐步添加功能的。即使只有一个人,也会通过逐步添加周围的本质功能来进行工作,这真的很有趣,也能增加动力。
因为完全不懂这种写法类似于”〇〇做了一下”系列文章的写法,所以不知道这样的内容是否足够,但我觉得该结束了。可能没有深入了解代码的细节。
其实在这篇文章里我是第一次学到Markdown格式,所以花了好长时间,搞得我感觉一头雾水。虽然比HTML快,但绝对比Word之类的慢。