博客项目笔记(一) | 青训营笔记

68 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天

将项目放到GitHub上

  1. 在GitHub上创建仓库
  2. 将项目克隆 git clone github.com/wjx1117/myb…
  3. 提交项目 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()
  }
})