Skip to content

Commit bc3b09e

Browse files
author
h3xg
committed
commit 1st
0 parents  commit bc3b09e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+8260
-0
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# 纯后端实现bbs留言板
2+
3+
- 基于express框架搭建服务端
4+
- 渲染模板 pug
5+
- 用户注册邮箱激活,密码邮箱找回
6+
- 密码加盐安全存储

app.js

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
const express = require('express')
2+
const sqlite = require('sqlite'), sqlite3 = require('sqlite3')
3+
const path = require('path')
4+
const cookieParser = require('cookie-parser')
5+
const sessionMiddleware = require('./middleWare/session-middleware')
6+
const signedCookieMountReq = require('./middleWare/signed-cookie-mount-req')
7+
8+
const {
9+
registerRouter,
10+
login,
11+
logout,
12+
post_comments: commentsRouter,
13+
all_comments: allcmtsRouter,
14+
add_post: addPostRouter,
15+
captcha,
16+
userDetail,
17+
edit_profile: editProfile,
18+
pwdForgotRouter,
19+
pwdResetRouter,
20+
activateEmailRouter,
21+
delPostRouter,
22+
}
23+
= require('./routes')
24+
25+
/* temp */ const { PORT, IPADD, PAGE_SIZE, map4vm } = require('./constant')
26+
27+
let db
28+
let dbPromise = sqlite.open({
29+
filename: path.resolve(__dirname, './data/bbs.db'),
30+
driver: sqlite3.Database,
31+
})
32+
; (async function () {
33+
db = await dbPromise
34+
})()
35+
36+
const app = express()
37+
38+
/* 隐藏服务端搭建框架 */
39+
app.set('x-powered-by', false)
40+
41+
/* 声明模板引擎文件所在文件夹 */
42+
app.set('views', path.resolve(__dirname, 'templates'))
43+
app.locals.pretty = true
44+
45+
//#region 另一模板引擎hbs的使用 (未声明路径的情况下,也去声明的模板引擎文件所在文件夹下找)
46+
const hbs = require('hbs')
47+
app.engine('hbs', hbs.__express)
48+
app.get('/demo4hbs', (req, res, next) => {
49+
res.render('test.hbs', { name: 'kiki', favor: 'piano and lol' })
50+
})
51+
//#endregion
52+
53+
/* 如下两行 对请求体做解析的 */
54+
app.use(express.json())
55+
app.use(express.urlencoded({ extended: true })) // 让其能解析扩展URL编码的
56+
57+
/* 允许服务端解析Cookie; gz: 感觉这个第三方库一方面是能够解析签了名的cookie,同时也允许通过形参处的字符串而实现对cookie的签名 */
58+
app.use(cookieParser('secret4cookie'))
59+
60+
/* 声明【静态文件】位置 */
61+
app.use(express.static(path.resolve(__dirname, 'static')))
62+
app.use('/upload', express.static(path.resolve(__dirname, 'upload')))
63+
app.use('/public', express.static(path.resolve(__dirname, 'public')))
64+
app.use('/public/stylesheets', express.static(path.resolve(__dirname, 'public/stylesheets')))
65+
app.use('/public/javascripts', express.static(path.resolve(__dirname, 'public/javascripts')))
66+
67+
//#region 如下的静态文件发送暂时失败
68+
// TODO
69+
// app.get('/demo', (req, res) => {
70+
// res.sendFile('demo.html')
71+
// })
72+
//#endregion
73+
74+
app.use((req, res, next) => {
75+
console.log('------------------▼▼▼---------------------');
76+
console.log(`[req]cookies:`, req.cookies) /* 未签名和签名的cookie是分开的 */
77+
console.log(`[req]signedCookies:`, req.signedCookies);
78+
console.log('[req]method:', req.method, '; url:', req.url, '; host:', req.get('HOST'));
79+
req.method === 'POST' ? console.log(`---[req]body:`, req.body) : null
80+
81+
// /* temp 挂上db到请求体 */ req.database = db
82+
next()
83+
})
84+
85+
//#region 旧:将sessionID自定义中间件写在主函数中
86+
// const {nanoid} = require('nanoid')
87+
// const {MAX_AGE_SESSION} = require('./constant')
88+
// /* sessionMap 作为全局对象的必要性: 对于不同的req实例,都能通过req.session=sessionMap[sid]访问到同份映射 */
89+
// const sessionMap = Object.create(null)/* session 存储的服务端的原理由此一瞥,服务端维护一份sessionMap(sid 与(...如captcha)的映射) */
90+
// app.use( (req, res, next) => {
91+
// if (req.cookies.sessionID) { //一般用于埋sessionID 的下发cookie,不需签名
92+
// const sid = req.cookies.sessionID
93+
// if (!sessionMap[sid]) sessionMap[sid] = {} /* TODO 每次重启服务端时,sessionMap即重建 */
94+
// req.session = sessionMap[sid]
95+
// } else {
96+
// const sid = nanoid()
97+
// res.cookie('sessionID', sid, {
98+
// maxAge: MAX_AGE_SESSION,
99+
// })
100+
// req.session = sessionMap[sid] = {} /*vital: 也挂在req.session上,而后便于访问 */
101+
// }
102+
// next()
103+
// })
104+
//#endregion
105+
app.use(sessionMiddleware())
106+
107+
//#region [db 使用时机]
108+
/* 对上面的中间件封装,不过暂时报错
109+
猜想: 其余各处能正常执行db.get/run 是因为express中间接到的是个函数,而不是一个返回(req,res,next)函数的中间件函数调用,
110+
前者尽管函数体里有db(database), 不过可能【使用时机】是在 express框架 最后把各个use 都整合成一个 原生node 的http.createServer(cb)才使用其db。
111+
??那么有办法为中间件函数传入可使用的db吗 ——
112+
1,直接把数据库挂到app上 (后续通过相关字段访问到);
113+
2,把sqlite.open()的promise对象传递过去;
114+
3 , db挂到req上 (缺陷:不过数据库一般很大,如果挂在请求体上....)
115+
*/
116+
//#endregion
117+
app.use(signedCookieMountReq(dbPromise, {
118+
tableName: 'users',
119+
cookieField: 'loginUser', /* 作用: 查询到loginUser邮箱匹配到的数据库的条目, 分配到req.user上 */
120+
reqField: 'user',
121+
dbvalues: 'email, avatar, createAt, bdcolor, nickname, salt',
122+
}))
123+
124+
app.use((req,res, next) => {
125+
if (!req.cookies.vm) res.cookie('vm', 'h', {})
126+
next()
127+
})
128+
129+
/* TODO - app.get('/:category') 以类别浏览 */
130+
app.get('/', async (req, res, next) => {
131+
let page = +(req.query.p ?? '1')
132+
let vm = req.query.vm ?? 'h'
133+
if (vm == 'undefined') vm='h'
134+
135+
res.cookie('vm', vm,{}) /* 如此可能不是规范操作,因为频繁的跟新cookie */
136+
137+
console.log('[req-query!!!]',req.query);
138+
console.log("req.cookies",req.cookies);
139+
/* 浏览最多排最前 ORDER BY p.clicks DESC*/let hotView = req.query.hot
140+
const posts = await db.all(`
141+
SELECT p.rowid AS id, * FROM posts p
142+
LEFT JOIN users u
143+
ON p.userId = u.rowid
144+
ORDER BY ${map4vm[vm]} DESC
145+
LIMIT ?, ?`,
146+
(+page - 1) * PAGE_SIZE, PAGE_SIZE
147+
)
148+
const postsCou = await db.get(`SELECT count(rowid) as cou FROM posts `)
149+
150+
151+
152+
res.render('index.pug', {
153+
posts,
154+
user: req.user,
155+
pageInfo: {
156+
curPage : page,
157+
pagesCou: Math.ceil(postsCou.cou / PAGE_SIZE)
158+
},
159+
vm: req.cookies.vm ?? 'h'
160+
})
161+
})
162+
163+
app.get('/tnr', (req, res, next) => {
164+
const { loginUser } = req.signedCookies
165+
let html = '<h1>hello WORLD</h1>'
166+
167+
res.type('html')
168+
res.write(`<nav>
169+
<a href='/'>首页</a>
170+
${!loginUser ?
171+
`<a href='/login'>登录</a>
172+
<a href='/register'>注册</a>` :
173+
`<span>hi, ${loginUser}</span><a href='/logout/?next=${req.originalUrl}'>登出</a>`
174+
}
175+
</nav>`)
176+
res.end(html) /* 用send发送数据比end更规范,但前面有type/write,这里用end收尾才能不报错 */
177+
})
178+
179+
app.post('/tnr', (req, res, next) => {
180+
const {name, age} = req.body
181+
res.status(200).json({
182+
reply: `fuck off! ${name} ---> ${age}`,
183+
code: 42,
184+
})
185+
})
186+
187+
/*实际如此使用 router 中间件不合规范!!*/
188+
app.use('/register', registerRouter)
189+
app.use('/active-email', activateEmailRouter)
190+
app.use('/login', login)
191+
app.use('/logout', logout)
192+
app.use('/posts', commentsRouter) /* 删除操作也在此 del posts/:id */
193+
app.use('/allcmts', allcmtsRouter)
194+
app.use('/addpost', addPostRouter)
195+
app.use('/captcha', captcha)
196+
app.use('/users', userDetail)
197+
app.use('/edit-profile', editProfile)
198+
app.use('/pwd-forgot', pwdForgotRouter)
199+
app.use('/pwd-reset', pwdResetRouter)
200+
201+
app.use((err, req, res, next) => { //错误的捕获;若无此,可能输出一大片报错
202+
console.log("@err@", err)
203+
})
204+
205+
206+
const server = app.listen(PORT, IPADD, () => {
207+
208+
/* TODO temp db挂到App上 */ dbPromise.then(db => app.locals.database = db)
209+
const { address, port } = server.address()
210+
console.log(`listening --> http://${address}:${port}`);
211+
})

app_copy.js

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
const express = require('express')
2+
const path = require('path')
3+
const cookieParser = require('cookie-parser')
4+
const {
5+
register,
6+
login,
7+
logout,
8+
post_comments: commentsRouter,
9+
all_comments: allcmtsRouter,
10+
}
11+
= require('./routes')
12+
13+
//#region
14+
const {comments} = require('./temp/data')
15+
16+
/* temp */ const { PORT, IPADD } = require('./constant')
17+
/* temp */ const { users, posts } = require('./temp/data')
18+
const app = express()
19+
20+
/* 隐藏服务端搭建框架 */
21+
app.set('x-powered-by', false)
22+
23+
/* 如下两行 对请求体做解析的 */
24+
app.use(express.json())
25+
app.use(express.urlencoded({ extended: true })) // 让其能解析扩展URL编码的
26+
27+
/* 允许服务端解析Cookie; gz: 感觉这个第三方库一方面是能够解析签了名的cookie,同时也允许通过形参处的字符串而实现对cookie的签名 */
28+
app.use(cookieParser('secret4cookie'))
29+
30+
//#region 如下的静态文件发送暂时失败
31+
app.use(express.static(path.resolve(__dirname, 'static')))
32+
app.get('/demo', (req, res) => {
33+
res.sendFile('demo.html')
34+
})
35+
//#endregion
36+
37+
app.use((req, res, next) => {
38+
console.log('------------------▼▼▼---------------------');
39+
console.log(`[req]cookies:`, req.cookies) /* 未签名和签名的cookie是分开的 */
40+
console.log(`[req]signedCookies:`, req.signedCookies);
41+
console.log('[req]method:', req.method, '; url:', req.url, '; host:', req.get('HOST'));
42+
req.method === 'POST' ? console.log(`[req]body:`, req.body) : null
43+
next()
44+
})
45+
46+
app.get('/', (req, res, next) => {
47+
const { loginUser } = req.signedCookies
48+
let html = posts.map(
49+
post =>
50+
`<li>
51+
<a href='/posts/${post.id}'>
52+
${post.title}--${post.content}
53+
</a>
54+
</li>`
55+
).join('')
56+
57+
res.type('html')
58+
res.write(`<nav>
59+
<a href='/'>首页</a>
60+
${!loginUser ?
61+
`<a href='/login'>登录</a>
62+
<a href='/register'>注册</a>` :
63+
`<span>hi, ${loginUser}</span><a href='/logout/?next=${req.originalUrl}'>登出</a>`
64+
}
65+
</nav>`)
66+
res.end(html) /* 用send发送数据比end更规范,但前面有type/write,这里用end收尾才能不报错 */
67+
})
68+
69+
app.use('/register', register)
70+
app.use('/login', login)
71+
app.use('/logout', logout)
72+
73+
// app.use('/posts', commentsRouter)
74+
// app.use('/allcmts', allcmtsRouter)
75+
76+
/* temp--------------------------- */
77+
app.get('/posts/:id', (req, res, next) => {
78+
/*TODO 评论不更新问题 */
79+
80+
const {loginUser} = req.signedCookies
81+
const {id:postId} = req.params
82+
let post = posts.find(post => post.id == postId)
83+
if (post) {
84+
var cmtsOfCurPost = comments.filter(cmt => cmt.postId === post.id)
85+
}
86+
87+
res.type('html')
88+
res.write(`<nav>
89+
<a href='/'>首页</a>
90+
${!loginUser ?
91+
`<a href='/login'>登录</a>
92+
<a href='/register'>注册</a>` :
93+
`<span>hi, ${loginUser}</span><a href='/logout/?next=${req.originalUrl}'>登出</a>`
94+
}
95+
</nav>`)
96+
97+
post ?
98+
res.end(`
99+
<h3>${post.title}</h3><h4>${post.content}</h4>
100+
<ul>
101+
${cmtsOfCurPost.map(cmt => `<li>${cmt.content}<br/>${new Date(cmt.timestamp).toLocaleString()}</li>`)}
102+
</ul>
103+
${loginUser ? `
104+
<form method='post' action='/allcmts'>
105+
<input type='hidden' name='postId' value=${postId}>
106+
<textarea name='content'>hold world</textarea>
107+
<button>评论</button>
108+
</form>
109+
`: null}
110+
`
111+
) :
112+
res.status(404).end(`<h3>帖子不存在</h3>`)
113+
114+
115+
})
116+
117+
app.post('/allcmts', (req, res, next) => {
118+
const {loginUser} = req.signedCookies
119+
console.log('tp-->',loginUser);
120+
if (loginUser) {
121+
const userId = users.find(user => user.email == loginUser).id
122+
const comment = {
123+
...req.body, //此时的评论仅有content和postId
124+
id: comments.slice(-1)[0].id + 1, /* TODO 如此赋ID待优化 */
125+
userId,
126+
timestamp: Date.now()
127+
}
128+
comments.push(comment)
129+
console.log(comments);
130+
res.redirect(req.get('referer'))
131+
} else {
132+
res.send(`你的post请求被拒绝,登录后才允许提交评论`)
133+
}
134+
})
135+
136+
137+
/* ------------------------------------- */
138+
app.use((err, req, res, next) => {
139+
console.log("@err@", err)
140+
})
141+
142+
const server = app.listen(PORT, IPADD, () => {
143+
const { address, port } = server.address()
144+
console.log(`listening --> http://${address}:${port}`);
145+
})

0 commit comments

Comments
 (0)