Javascript

NodeJS05

本文主要是介绍NodeJS05,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

用node.js写一个博客网站

本项目基于Koa框架,依赖以下第三方库:

  • fs
  • bluebird
  • koa
  • koa-router
  • koa-ejs
  • koa-bodyparser

项目结构:

新建项目文件夹并安装第三方库:

>>> mkdir koa-blog-examples    // 创建项目文件夹
>>> cd koa-blog-examples       // 切换到项目文件夹下
>>> npm init                   // 初始化环境
>>> npm install fs bluebird koa koa-router koa-ejs koa-bodyparser --save    // 安装依赖库

然后你需要创建middlewares, routes, services, templates这四个文件夹,然后开始工作。

  1. 首先创建middlewares/authenticate.js文件,将登陆状态挂载到ctx.state上,供后续使用。
// 认证中间件
module.exports = async function (ctx, next){   // ctx即context;next指下一个中间件
     const logged = ctx.cookies.get('logged', {signed: true});
     ctx.state.logged = !!logged;      // !!logged表示强制将logged转换为true或者false
     await next();     // 异步等待
};
  1. services/post.js,博客文章相关服务,提供文章的 发布 / 编辑 / 详情 / 删除 等功能。
const fs = require('fs');
const bluebird = require('bluebird');
bluebird.promisifyAll(fs);     // 异步化

// 文章数据
const posts = [];
//文章ID
let postId = 1;
//发表文章
exports.publish = function (title, content) {
	const item = {
		id: postId++,
		title: title,
		content: content,
		time: (new Date()).toLocaleString()
	};
	posts.push(item);       // 将新的博客放入文章数据列表中
	return item;            // 返回新的博客
};

//查看文章
exports.show = function (id){     // 根据博客id返回博客,如果没有找到返回null
	id = Number(id);
	for (const post of posts) {
		if (post.id === id){
			return post
		}
	}
	return null;
};

//编辑文章
exports.update = function (id, title, content){       // 改变指定id的博客的标题和内容。
	id = Number(id);
	posts.forEach((post)=>{
		if (post.id === id){
			post.title = title;
			post.content = content;
		}
	});
};

//删除文章
exports.delete = function (id){     // 根据id找到博客并删除
	id = Number(id);
	let index = -1;
	posts.forEach((post, i)=>{
		if (post.id === id){
			index = i;
		}
	});
	if (index > -1){
		posts.splice(index, 1);    // 在位置为index的地方,删除1个项目
	}
};

//文章列表
exports.list = function (){
	return posts.map(item => item);    // 返回一个博客列表(不会改变原始博客列表)
};
  1. 创建routes/post.js文件,博客文章相关路由,提供文章的 发布 / 编辑 / 详情 / 删除 等功能。
const Router = require('koa-router');
const postService = require('../services/post');
const router = new Router();

// 发布表单页
router.get('/publish', async(ctx)=>{
	await ctx.render('publish');
});

// 发布处理
router.post('/publish', async(ctx)=>{
	const data = ctx.request.body;
	if (!data.title || !data.content){
		ctx.throw(400, "您的请求有误!");
	}
	const item = postService.publish(data.title, data.content);
	ctx.redirect(`/post/${item.id}`);
});

// 详情页
router.get('/post/:postId', async(ctx)=>{
	const post = postService.show(ctx.params.postId);
	if (!post){
		ctx.throw(400, '文章不存在');
	}
	await ctx.render('post', {post: post});
});

// 编辑表单页
router.get('/update/:postId', async(ctx)=>{
	const post = postService.show(ctx.params.postId);
	if (!post){
		ctx.throw(400, '文章不存在');
	}
	await ctx.render('update', {post: post});
});

// 编辑处理
router.post('/update/:postId', async (ctx)=>{
	const data = ctx.request.body;
	if (!data.title || !data.content){
		ctx.throw(400, '您的请求有误');
	}
	const postId = ctx.params.postId;
	postService.update(postId, data.title, data.content);
	ctx.redirect(`/update/${postId}`);
});


// 删除
router.get('/delete/:postId', async (ctx)=>{
	postService.delete(ctx.params.postId);
	ctx.redirect('/');
});

module.exports = router;
  1. templates/post.ejs文章详情页。
<div>
    <h1><%= post.title %></h1>
    <time>发表时间: <%= post.time %></time>
    <hr>
    <div><%= post.content %></div>
</div>
  1. routes/site.js 网站首页,负责读取文章列表并渲染到HTML上。
const Router = require('koa-router');
const postService = require('../services/post');

const router = new Router();

//网站首页
router.get('/', async (ctx)=>{
	const list = postService.list();
	await ctx.render('index', {list: list});
});

module.exports = router;
  1. services/user.js 用户业务服务,负责用户登录检测。
const user = {
	Joe: 'password'      // 用户:密码
};

exports.login = function (username, password){
	if (user[username] === undefined){
		return false;
	}
	return user[username] === password;
};
  1. routes/user.js用户相关路由,负责登录和退出登录。
const Router = require('koa-router');
const userService = require('../services/user');

const router = new Router();

// 登录表单页
router.get('/login', async (ctx)=>{
	await ctx.render('login');
});

// 登录处理
router.post('/login', async (ctx)=>{
	const data = ctx.request.body;
	if (!data.username || !data.password){
		ctx.throw(400, '您的请求有误');
	}
	const logged = userService.login(data.username, data.password);
	if (!logged){
		ctx.throw(400, '账号或密码错误');
	}
	ctx.cookies.set('logged', 1, {signed: true, httpOnly: true});
	ctx.redirect('/', '登陆成功');
});

// 退出登陆
router.get('/logout', async (ctx)=>{
	ctx.cookies.set('logged', 0, {maxAge: -1, signed: true});
	ctx.redirect('/', '退出登录成功');
});

module.exports = router;
  1. templates/index.ejs 网站首页模板,负责渲染文章列表。
<p>文章列表</p>
<% if(list.length == 0) { %>
    <p>没有文章发表</p>
<% } else { %>
    <table>
        <tr>
            <th>ID</th>
            <th>标题</th>
            <th>发表时间</th>
            <% if (logged){ %>
                <th>操作</th>
            <% } %>
        </tr>
        <% list.forEach((post) => { %>
            <td><%= post.id %></td>
            <td><a href="/post/<%= post.id %>"><%= post.title %></a><td>
            <td><%= post.time %></td>
            <% if (logged) { %>
                <th>
                    <a href="/update/<%= post.id %>">编辑</a>
                    <a href="/delete/<%= post.id %>" οnclick="return confirm('确认删除吗?')">删除</a>
                </th>
            <% } %>
        <% }) %>
    </table>
<% } %>
  1. templates/login.ejs 用户登录页面,负责收集用户信息并发送给服务器。
<form action="/login" method="POST" enctype="application/x-www-form-urlencoded">
    <fieldset>
        <legend>登录</legend>
        <div>
            <label for="username">账号</label>
            <input type="text" name="username" id="username" required>
        </div>
        <div>
            <label for="password">密码</label>
            <input type="password" name="password" id="password" required>
        </div>
        <div>
            <button type="submit">登录</button>
        </div>
    </fieldset>
</form>
  1. templates/main.ejs 根据登录状态显示不同的导航页
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css">
    <script src="https://cdn.staticfile.org/popper.js/2.9.3/umd/popper.min.js"></script>
    <script src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.min.js"></script>
    <title>博客</title>
</head>
<body style="background-color: #dddddd">
    <div class="p-3 mb-3 border-bottom" style="background-color: #333">
        <div class="container-fluid">
            <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
                <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
                    <% if(logged){ %>
                        <a href="/">首页</a>
                        <a href="/publish">发表文章</a>
                        <a href="/logout">退出登录</a>
                    <% } else { %>
                        <a href="/login">登录</a>
                    <% } %>
                </ul>
            </div>
        </div>
    </div>
<%- body%>
</body>
</html>
  1. templates/publish.ejs 文章发布页,收集博客信息发送给服务器。
<form action="/publish" method="POST" enctype="application/x-www-form-urlencoded">
    <fieldset>
        <legend>发表文章</legend>
        <div>
            <label for="title">标题</label>
            <input type="text" name="title" id="title" required>
        </div>
        <div>
            <label for="content">内容</label>
            <textarea name="content" id="content" required></textarea>
        </div>
        <div>
            <button type="submit">发布</button>
            <button type="reset">重置</button>
        </div>
    </fieldset>
</form>
  1. templates/update.ejs文章编辑器,填充已有博客信息到输入框,并将新输入的博客信息提交给服务器。
<form action="/update/<%= post.id %>" method="POST" enctype="application/x-www-form-urlencoded">
    <field>
        <legend>编辑文章</legend>
        <div>
            <label for="title">标题</label>
            <input type="text" value="<%= post.title %>" name="title" id="title" required>
        </div>
        <div>
            <label for="content">内容</label>
            <textarea name="content" id="content" required><%= post.content %></textarea>
        </div>
        <div>
            <button type="submit">发布</button>
            <button type="reset">重置</button>
        </div>
    </field>
</form>
  1. index.js 程序入口JS文件,负责挂载中间件、路由、应用配置和启动服务器。
const Koa = require("koa");
const render = require("koa-ejs");
const bodyParser = require("koa-bodyparser");
const authenticate = require("./middlewares/authenticate");
// 路由
const siteRoute = require("./routes/site");
const userRoute = require("./routes/user");
const postRoute = require("./routes/post");

const app = new Koa();
app.keys = ["jiohd4654nidh46-05+/*69d"];
// 使用中间件
app.use(bodyParser());     // 解析请求体的中间件
app.use(authenticate);     // 挂载登录状态
render(app, {
	root: './templates',     // 网页模板位置
	layout: 'main',          // 网页布局/主题风格使用main.ejs
	viewExt: 'ejs'
});
// 挂载路由
app.use(siteRoute.routes()).use(siteRoute.allowedMethods());
app.use(userRoute.routes()).use(userRoute.allowedMethods());
app.use(postRoute.routes()).use(postRoute.allowedMethods());

app.listen(3000, ()=> {
	console.log("listen on 3000");
});

最后,在终端输入node index.js再打开浏览器的:localhost:3000就可以看到登录内容。

这篇关于NodeJS05的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!