一、新闻分类
MongoDB中聚合的方法使用aggregate()。
因为新闻和分类之间是有关联关系的,一篇新闻属于一个分类,一个分类是存在多条新闻的。那么这个关系我们称之为一对多;(1个分类对多个新闻)需要使用的mongoose 里面的聚合操作
aggregate:http://www.mongoosejs.net/docs/api.htmL#aggregate_Aggregate
https://juejin.cn/post/6844904008537243661
/* 5. 新闻资源列表 */
router.get('/list', async (req, res) => {
const infos=await newsModel.aggregate([{
/*联表操作 新闻表里的cateId要和分类表的_id产生联系*/
$lookup:
{
/*要关联的表category*/
from: "category",
/*自己表里和别的表产生关系的id*/
localField: "cateId",
/*关联表的id*/
foreignField: "_id",
/*查询到的关联信息的key值*/
as: "cateInfo"
}
}]);
console.log(infos);
res.render('news-list', {title: '新闻资源的展示', infos,moment});
});
html
<td>{{@ $value.cateInfo[0].cateName }}</td>
二、分页操作
db.news.find({},{title:1}).limit(1);
limit(x) 获取指定x条数的信息
db.news.find({},{title:1}).skip(2).limit(1);
跳过两条数据取一条
skip(y) 跳过y条数据
router.get('/list', async (req, res) => {
let{page=1,size=2}=req.query;
page=parseInt(page);
size=parseInt(size);
//总记录数
let count=await newsModel.count();
//偏移量
let offset=(page-1)*size;
//有时候会出现小数,向上取整Math.ceil()
let totalPage=Math.ceil(count/size);
const infos=await newsModel.aggregate([
{
$skip:offset
},
{
$limit: size
},
{
/*联表操作 新闻表里的cateId要和分类表的_id产生联系*/
$lookup:
{
/*要关联的表category*/
from: "category",
/*自己表里和别的表产生关系的id*/
localField: "cateId",
/*关联表的id*/
foreignField: "_id",
/*查询到的关联信息的key值*/
as: "cateInfo"
}
}]);
console.log(infos);
res.render('news-list', {page,size,count,totalPage, title: '新闻资源的展示', infos,moment});
});
html
<div class="page">
<div>
<a class="prev" href=""><<</a>
<!-- 根据总的页数 totalPage ,生成页码链接-->
<!-- 循环操作-->
<!-- 如果遍历的 i 和 传递的 page 相等,则代表是当前页,当前页就要使用 span标签 -->
<% for(let i = 1; i <= totalPage; i++ ){ %>
<% if( i == page ) {%>
<span class="current"><%= page %></span>
<%}else{%>
<a class="num" href="/admin/news/list?page=<%= i %>&size=<%= size %>"><%= i %></a>
<%}%>
<% } %>
<a class="next" href="">>></a>
</div>
</div>
三、新闻详情
<a href="/admin/news/detail/{{@ $value._id}}" title="详情">
<i class="layui-icon"></i>
</a>
定义查看详情操作的路由
/*9.新闻的详情 detail*/
router.get('/detail/:id', async (req, res) => {
//接收一个id
let newsId = req.params.id;
console.log('newId', newsId);
/*尝试把 字符串_id 转换为 MongoDB 里面 ObjectId 类型 */
const info = await newsModel.aggregate([
{
$match: {_id: mongoose.Types.ObjectId(newsId)} // $match 代表根据条件查询
},
{
$lookup:
{
/*要关联的表 category*/
from: "category",
/*自己表里面和别的表产生关系的id*/
localField: "cateId",
/*关联表的id*/
foreignField: "_id",
/*查询到的关联信息的key值*/
as: "cateInfo"
}
}]);
// res.json( info );
res.render('news-detail', { title: '新闻资源的详情', info: info[0], moment});
});
四、后台登录
登录之后才能进入后台页面进行操作
在路由目录新建back.js作为后台登录路由
const express = require('express');
const router = express.Router(); // 路由器
/*后台登录*/
router.get('/login',(req,res)=>{
res.render('login');
});
module.exports = router;
app.js将back路由引入
const backRouter = require('./routes/back.js');
app.use('/admin', backRouter);
http://localhost:3000/admin/login
一般管理员是没有注册的,会在最初始创建一张表初始化数据给到数据登录
登录表单
<form method="post" action="/admin/checklogin" enctype="application/x-www-form-urlencoded" class="layui-form" >
在back.js里新增checklogin路由
const express = require('express');
const router = express.Router(); // 路由器
/*引入admin模型*/
const adminModel=require('../models/admin.js');
/*后台登录*/
router.get('/login',(req,res)=>{
res.render('login',{title: '后台登录'});
});
router.post('/checklogin',async(req,res)=>{
/*接收用户输入的用户名和密码*/
let{username,password}=req.body;
/*用户身份校验*/
/*用户身份的校验*/
/*需要根据用户名去查询用户的信息;
* 1.存在
*1.1 校验密码是否正确
* 正确:登录
* 错误:未登录
*2.不存在,直接提示用户信息不存在**/
const userInfo=await adminModel.findOne({username});
if(userInfo){
//校验密码是否正确
if(userInfo.password==password){
//密码正确
res.redirect('/admin/index');
}else{
res.redirect('back');
}
}else{
//用户信息不存在
res.redirect('back');
}
// res.json(userInfo);
});
module.exports = router;
定义表的模型admin
models/admin.js
const mongoose = require('../db/mongodb.js');
const Schema = mongoose.Schema({
username: {
type: String,
},
password: {
type: String,
},
}, {timestamps: true});
const Model = mongoose.model('Admin', Schema, 'admin');
module.exports = Model;
这样登录后输入用户名和密码就能跳转后台页面
一般数据不存储明文密码 ,一般都是加密之后的混淆密码,一般使用md5加密
npm install md5
1.通过 md5函数可以把一个字符串转换为一个32位的16进制字符串:78e731027d8fd50ed642340b7c9a63b3
2. md5是单向不可逆,只能有md5---->32位字符串,没办法反推回去。
3. 相同输入,输出是一样的
先创建了一个测试test.js
var md5=require('md5');
console.log(md5('admin'));
这里打印出来的密文我修改给数据库
然后修改back.js
const md5=require('md5');
//修改这里
if(userInfo.password==md5(password)){
再去尝试登录 这时候输入的密码还是明文密码
是可以登录的!
五、登录优化
虽然现在可以登录了,但是没有什么实质性的操作,我通过http地址还是可以随意访问后台页面,所以现在要进行修改,经过登录之后才能进入后台页面和操作后台页面。
但是http协议无状态。不知道多次会话之问是否有关系。该如何处理?专业术语:跨请求共享状态。
什么样的技术可以实现跨请求共亨状态?答: cookie或者session 或者token
cookie的原理:
1.第一次的请求的时候,服务器会生成标识信息;然后发生一个指令,返回给浏览器。然后浏览器会把这个生成的标识保存起来。
2.下一次请求的时候。浏览器会自发的携带这个生成标识到服务器。
里面生成一个共享状态
来标识用户是否登录。然后在其他的请求里面是否得到状态。
npm install cookie-parser
在app.js里引入
const cookieParser = require('cookie-parser')
/*cookie处理*/
app.use(cookieParser())
routes/back.js修改
if(userInfo.password==md5(password)){
//密码正确1.记录登录的标识,实现跨请求共享数据2.前往后台的首页
req.cookie( 'isLogin', 1);//给浏览器发送设置cookie的指令
req.cookie( 'username' , username);//给浏览器发送设置cookie的指令
res.send('cookie ok');
// res.redirect('/admin/index');
}else{
res.redirect('back');
}
测试一下是否成功
routes/back.js修改
if(userInfo.password==md5(password)){
//密码正确1.记录登录的标识,实现跨请求共享数据2.前往后台的首页
req.cookie( 'isLogin', 1);//给浏览器发送设置cookie的指令
req.cookie( 'username' , username);//给浏览器发送设置cookie的指令
res.redirect('/admin/index');
}else{
res.redirect('back');
}
index.js修改
router.get('/index', (req, res) => {
console.log(req.cookies);
if(req.cookies['isLogin']!=1){
// 用户未登录
res.redirect('/admin/login')
}else {
res.render('index');
}
});
news.js修改
/* 在5.新闻资源列表里最开始增加这段判断 */
router.get('/list', async (req, res) => {
if(req.cookies['isLogin']!=1){
// 用户未登录
res.redirect('/admin/login');
return;
}
这样登录成功才能进到后台,cookie清楚后进入后台之前需要先登录。
但是这样子也很麻烦,要在每个页面路由文件里都这样配置,想统一配置,怎么办?
–使用express中间件
app.js
app.use(function (req, res, next) {
console.log('Time:', Date.now(),req.url)
//登录操作不需要做cookies验证
if(req.url=='/admin/login/'){
next();
return;
}
//未登录时请求cookie肯定是没有的 cookie有的时候才做以下判断
if(req.cookies && req.cookies['isLogin']!=1){
// 用户未登录
res.redirect('/admin/login')
}else {
next();
}
// next() //进行路由规则的匹配 req.url=== xxx
})
定义后台相关的外置路由 一般我们不在 app.use 直接使用中间件做验证。因为有些请求不一定要做验证,例如前台的请求 。所以我们在单个的路由的配置里面做中间件验证。
app.use('/admin', (req, res, next)=>{
/* 我们在定义每一个路由关系的时候,还可以设置的中间件 */
console.log('我们在定义每一个路由关系的时候,还可以设置的中间件', req.url);
next();
// res.send('index路由信息!');
} , indexRouter);
封装成一个函数,这样更方便使用。
新建目录middleware/verifyLogin.js
/*使用这个中间件函数去验证用户是否登录*/
function verifyLogin(req, res, next) {
if (req.cookies && req.cookies['isLogin'] != 1) {
res.redirect('/admin/login');
} else {
next();
}
}
module.exports = {
verifyLogin,
}
app.js
let verifyFn = verifyLoginMiddleware.verifyLogin;
/*下面的路由是不要做登录检测*/
app.use('/admin', commonRouter);
// 这里面是登录的业务代码,需做登录检测的
app.use('/admin', verifyFn, indexRouter);
app.use('/admin/website', verifyFn, websiteRouter);
app.use('/admin/news', verifyFn, newsRouter);
app.use('/admin', verifyFn, backRouter);
六、退出登录
/*后台登录退出*/
router.get('/logout',(req,res)=>{
res.cookie('isLogin',0);
res.cookie('username','');
res.redirect('/admin/login');
});
七、代码优化
controller:控制器,主要是负责业务代码。为了后期的维护和新功能的增加,不建议把 routes下的文件写的过于臃肿。
新建controllers文件夹,里面的文件和路由的文件相关:
路由里的new.js在控制文件夹为newsControll.js
const newsModel = require('../models/news.js');
const moment = require('moment');
/* 类名:文件名的大驼峰 */
class NewsController {
/* 5. 新闻资源列表 */
static getNewsList = async (req, res) => {
/* MongoDB 提供了一个 limit(可以获取指定的条数) 和 skip(跳过多少条记录进行获取)
* 利用这两个 api 可以完成分页功能。
* 分页的原理:
* 1. 当前页(page)
* 2. 当前页显示的数量(size)
* 3. 总的记录数(count)
* 4. 总共的页数:总记录数 / 每页显示的数量(totalPage)*/
/* http://localhost:3000/admin/news/list?page=1&size=2 */
let {page = 1, size = 2} = req.query;
// res.json( req.query);
page = parseInt(page);
size = parseInt(size);
let count = await newsModel.count();
let offset = (page - 1) * size;
let totalPage = Math.ceil(count / size); // 小数 11条/2条 = 5.5 向上取整 5.3 = 6
const infos = await newsModel.aggregate([
{
$skip: offset
},
{
$limit: size
},
{
/* 这个我们称之为联表操作。我们现在是新闻表(cateId)要和分类表(_id)产生联系*/
$lookup:
{
/*要关联的表 category*/
from: "category",
/*自己表里面和别的表产生关系的id*/
localField: "cateId",
/*关联表的id*/
foreignField: "_id",
/*查询到的关联信息的key值*/
as: "cateInfo"
}
}]);
console.log(infos);
console.log('page, size, count, totalPage', page, size, count, totalPage);
res.render('news-list', {page, size, count, totalPage, title: '新闻资源的展示', infos, moment});
}
}
module.exports = NewsController;
news.js对应修改
const NewsController = require('../controllers/newsController.js');
/* 5. 新闻资源列表 */
router.get('/list', NewsController.getNewsList);