这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天
将项目放到GitHub上
- 在GitHub上创建仓库
- 将项目克隆 git clone github.com/wjx1117/myb…
- 提交项目 git add . --> git commit -m '初始化' --> git push www.bilibili.com/video/BV157…
项目所需
- 雪花id SnowFlake
- 数据库:MySQL
- wang editor 富文本编辑器
服务端搭建 server
app.js
安装模块
multer:处理上传功能的中间介 sqlite3:数据库 方便服务端的移植 这里改为mysql2 uuid:很难重复的id生成,认为可以生成唯一的标志 例如token
雪花id SnowFlake
迄今为止最全面的分布式主键ID生成器 gitee.com/yitter/idge…
开放跨域请求
json:前后端交互
// 中间介
app.use(express.json()) // json支持
// 跨域请求
app.use(function (req, res, next) {
// 设置允许跨域的域名,*代表允许任意域名跨域
res.header("Access-Control-Allow-Origin", "*");
// 允许的header类型
res.header("Access-Control-Allow-Headers", "*");
// 跨域允许的请求方式
res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
if(req.method == "OPTIONS") res.sendStatus(200); // 让options尝试请求快速结束
else next();
});
报错:src refspec main does not match any 解决:blog.csdn.net/bjbz_cxy/ar…
连接数据库 SnowFlake生成ID
数据库 myblog
- 表1:admin --- 后台管理员 , account --- 登陆账号
- 表2:blog
- 表3:category
代码:
// 连接数据库,方便之后操作数据库
const mysql = require('mysql2')
const GenId = require("../utils/SnowFlake")
const genid = new GenId({ WorkerId: 1 });
// 创建连接配置
let db = mysql.createConnection({
host : 'localhost', // ip地址
port : '3306', // 端口 默认3306
user: 'root', // 数据库账号密码
password : '123456',
database : 'myblog' // 数据库名字
})
module.exports = {db, genid}
配置路由
// 要在app.js里注册路由 app.use('/test', require('./routers/TestRouter'))
router.get('/test',(req, res) => {
db.query('select * from admin',[],(err, res)=> {
console.log(res);
})
res.send({
id : genid.NextId()
})
})
测试
访问http://localhost:8080/test/test 测试是否成功配置路由以及连接数据库
使用Promise封装数据库操作
后续可能我们会对数据库进行好几层的操作,一层套一层,会造成回调地狱,这时候代码很难阅读 所以要进行promise封装,变成同步操作
// 封装 DbUtils.js
db.async = {}
// promise封装
db.async.query = (sql, params) => {
return new Promise((resolve, reject) => {
db.query(sql, params, (err, rows) => {
resolve({err, rows})
})
})
}
// TestRouter.js
router.get('/test', async (req, res) => {
// 同步写法
// 把回调函数变成可异步执行 添加async
let out = await db.async.query('select * from admin', [])
res.send({
id : genid.NextId(),
out
})
})
管理员登陆和token生成
const {v4 : uuidv4} = require('uuid')
router.post('/login',async (req, res) => {
let { account, password} = req.body
let { err, rows} = await db.async.query('select * from admin where account=? and password=?', [account, password])
if(err == null && rows.length > 0){
let login_token = uuidv4()
let update_token_sql = 'UPDATE admin SET token=? where id=?'
await db.async.query(update_token_sql,[login_token, rows[0].id])
let admin_info = rows[0]
admin_info.token = login_token
admin_info.password = ''
// 返回给前端的数据不应包含password
res.send({
code : 200,
msg : '登陆成功',
data : admin_info
})
}else {
res.send({
code : 500,
msg : '登陆失败'
})
}
})
分类表增删改查
-
添加接口 post('/add' insert into category (id, name) values (?, ?)
-
列表接口 不写分页 因为分类数据量不大 get('/list' select * from category
-
修改接口 put('/update' update category set name =? where id=?
-
删除接口 delete('/delete' delete from category where id =?
报错: Unexpected token n in JSON at position 6 解决:JSON规定,JSON数据的key与value必须使用**双引号""**包裹,否则在转换过程中会导致错误 报错:Unexpected string in JSON at position 36 解决:category拼写错误 json写错
博客表增删改查
- 添加博客
- 修改博客
- 删除博客
- 查询博客
查询分页接口
前端传递的参数:
- keyword 关键字 在标题或内容中包含关键字
- category_id 分类编号 按照分类进行查询
- age 页码
- page_size 分页大小
分页:将数据按照pagesize分为几页,想要显示第几页
模糊查询 like
WHERE 子句中可以使用等号 = 来设定获取数据的条件。
使用LIKE子句代替等号 = ,获取含有字符的所有记录。
使用百分号 %字符来表示任意字符,类似于UNIX或正则表达式中的星号 *
如果没有使用百分号 %, LIKE 子句与等号 = 的效果是一样的。
代码
router.get('/search', async (req, res) => {
let {keyword, category_id, page, page_size} = req.query
// 进行判断 前端不用必须传值
page = page == null ? 1 : page
page_size = page_size == null ? 10 : page_size
keyword = keyword == null ? '' : keyword
category_id = category_id == null ? 0 : category_id
// 组装 where 条件
let where_sqls = []
let params = []
if(category_id != 0) {
where_sqls.push(" category_id = ? ")
params.push(category_id)
}
if(keyword != '') {
// 这里的语句要加括号,否则之后拼接时会造成错误
// 语句前后最好加空格,隔开,以免之后拼接字符串出现问题
where_sqls.push(" (title like ? or content like ?) ")
params.push('%'+ keyword + '%')
params.push('%'+ keyword + '%')
}
// 组装 where 语句
let where_sql_str = ''
if(where_sqls.length > 0){
where_sql_str = ' where ' + where_sqls.join(" and ")
}
// where_sqls里:[" category_id = ? ", " (title like ? or content like ?) "]
// 组装完: where category_id = ? and (title like ? or content like ?)
// 查分页数据
let search_sql = 'select * from blog' + where_sql_str + " order by create_time desc limit ?,?"
let search_params = params.concat((page - 1) * page_size, page_size)
// 使用 LIMIT 属性来设定返回的记录数 limit通常用来实现分页 从几开始,取几个
// 如果是 第 2 页 每页 10 条数据
// 那么要查询 从数组的 (2 - 1) * 10 开始,取10个
// 查数据总数
// COUNT(expression) 返回查询的记录总数,expression 参数是一个字段或者 * 号
// 不用 as 取别名的话,数据名为 count(*) 也可以使用["count(*)"] 数组下标
let search_count_sql = "select count (*) as count from blog " + where_sql_str
let search_count_params = params
let search = await db.async.query(search_sql, search_params)
let count = await db.async.query(search_count_sql, search_count_params)
上传接口
使用 wang editor 富文本编辑器 , 编写博客时需要上传图片
在 app.js 中设置静态资源路径
app.use(express.static(path.join(__dirname, "public")))
fs.renameSync()方法用于将给定旧路径下的文件同步重命名为给定新路径。 如果目标文件已经存在,它将覆盖目标文件。 重命名的过程中,可以间接实现移动文件的效果
substring使用详解:blog.csdn.net/qq_31676483…
process.cwd():程序运行的目录的路径
// 解析
for(let file of files) {
// 获取文件后缀 aaa.jpg 找到最后一个. 进行裁剪jpg
let file_ext = file.originalname.substring(file.originalname.lastIndexOf(".") +1)
// 随机文件名
let file_name = genid.NextId() + "." + file_ext
// 修改文件名加移动文件
fs.renameSync(
process.cwd() + "/public/upload/temp/" + file.filename,
process.cwd() + "/public/upload/" + file_name
)
// 保存名字
ret_files.push("/upload/" + file_name)
}
token验证端口
一些接口应该 登录 后才能进行相关操作
解决:登陆后前端保存返回的token,之后将 token 添加到请求头中,
token 验证代码
let {token} = req.headers
console.log(token);
let admin_token_sql = "select * from admin where token = ?"
let admin_res = await db.async.query(admin_token_sql, [token])
if(admin_res.err != null || admin_res.rows.length == 0) {
res.send({
code : 403,
msg : '请先登录'
})
return
}
中间件验证登录
如果有很多接口都需要 token验证,此时一个个的去添加代码很麻烦,使用中间件更加方便 给需要登陆验证的接口的路径添加/_token
// all:所有接口 类似中间件
// 验证约定:ADMIN_TOKEN_PATH
// 原先:category/add 添加token验证:category/_token/add
// 只要路径中含有_token 都会进行token验证
const ADMIN_TOKEN_PATH = "/_token"
app.all("*", async (req, res, next) => {
if(req.path.indexOf(ADMIN_TOKEN_PATH) > -1) {
// token 验证代码
} else {
next()
}
})