本博客的一些设计思路

四糸乃赛高 Lv.5 冰封大地
539 2022/7/5公告
前言

本人比较菜,了解的知识很少,所以肯定存在不少问题,如果您有哪些地方存在问题或建议,欢迎在留言区指出。本博客还存在不少问题,完善后会在github上开源,感兴趣可以话可以看看。这次重构主要是为了SEO优化,完善用户系统和后台。代码拆分为三个项目,客户端、后台、服务端。

本站已开源  https://gitee.com/yoshinosk/blog ,欢迎感兴趣的朋友一起学习。

技术栈

前端: NuxtJS 、 Bootstrap 、 ElementUI 、 Vue.js 

后端: Node.js 、 express 

数据库: MongoDB 、Redis

原本有些功能(登录、排行榜、签到、点赞)是用redis做的,但是做完后觉得反正数据量不大,没必要用redis,就改成用mongodb了。


UI

可能不少人会觉得本站的UI花里胡哨的,我也觉得。但我的思路就是要个性化,毕竟是个人博客,主要还是为自己服务,自己喜欢就好了。


登录

使用Refresh Token保持登录状态。实现思路:用户登录后生成并返回access_tokenrefresh_token,服务端将access_tokenrefresh_token存进数据库,用于校验是否匹配access_token存在Vuex中,refresh_token存在cookie实现持久化,access_token过期时调用刷新TOKEN接口,返回并保存新的access_token。

自动登录

检测cookie中是否存在refresh_token,存在则调用刷新TOKEN接口,返回用户信息和新的access_token。请求时如果token过期,则拦截本次请求,调用刷新TOKEN接口,拿到新的token后重新请求。


文章

普通用户发布文章需要管理员审核后才会显示,编辑文章后需要重新审核,审核结果会发送消息给用户,通过后可以获得经验值和硬币。发布文章时会将标签保存到文章标签数据库。每日签到可以获得1-10硬币和(10+连续签到天数 *5)经验值。发布评论可以获得10经验值,不做限制(反正没用户)。

匿名点赞,点赞后将点赞的文章存在cookie中判断是否已点赞该文章。

富文本编辑器使用 wangEditor 。服务端对文章内容使用xss过滤。

数据库模型

const mongoose = require("mongoose");

// 文章模型
const ArticleScheam = new mongoose.Schema({
    _id: { type: Number, unique: true, index: true, required: true },
    title: { type: String, trim: true, required: true }, // 标题
    content: { type: String, required: true },// 内容
    desc: { type: String, required: true },// 描述
    author: { type: Number, required: true, ref:'User' }, //作者id
    createTime: { type: Number , default: () => Math.ceil(Date.now() / 1000) },// 创建时间
    updateTime: { type: Number },// 编辑时间
    deleteTime: {type:Number},// 删除时间
    sort: { type: Number, default: 0 ,ref: 'Article_Sort' },// 分类id
    views: { type: Number, default: 0 },// 浏览数
    tag: { type: Array },// 标签
    coverImg: { type: String  }, // 封面图
    status: { type: Number, default: 0 },// -1:已删除 0:待审核 1:审核通过 2:审核未通过 3:隐藏
    order: { type: Number, default: 0 }, // 排序 越大越靠前
    goods: { type: Number, default: 0 },// 点赞
    allowComment:{ type:Number,default: 1 }, // 允许评论
    type:{ type:Number,default: 1 }, // 文章类型 默认1 - HTML 2 - MARKDOWN
},{ _id: false, autoIndex: false , versionKey: false})

// 文章分类模型
const SortScheam = new mongoose.Schema({
    _id: { type: Number, require: true  , index: true},
    name: { type: String, require: true , index: true},
    desc: { type: String, require: true },
    order: { type: Number, default: 0 }, // 排序 越大越靠前
},{ _id: false, autoIndex: false ,versionKey: false})

// 文章标签模型
const TagsScheam = new mongoose.Schema({
    name: { type: String, require: true },
    total: { type: Number, default: 1 }
},{ versionKey: false}) 

module.exports = {
    Article: mongoose.model('Article', ArticleScheam),
    Sort: mongoose.model('Article_Sort', SortScheam),
    Tags: mongoose.model('Article_Tags', TagsScheam),
}
评论

需要登录后才能发表评论,点赞功能因为没用户所以感觉没必要就没做。就是懒,哪天闲了再加上

数据库模型

const Schema = new mongoose.Schema({
    article: { type: Number, required: true, ref: "Article" }, // 文章id
    from: { type: Number, required: true, ref: "User" }, // 用户id
    comment: { type: mongoose.Types.ObjectId, ref: "Comment" }, // 顶级评论id 
    reply: { type: mongoose.Types.ObjectId, ref: "Comment" }, // 回复的楼中楼id 
    content: { type: String, required: true, trim: true},
    createTime: { type: Number, default: () => Math.ceil(Date.now() / 1000) },// 创建时间
    goods: { type:Number, default: 0 }, //点赞数
    bads: { type:Number, default: 0 }, // 踩
    status: { type: Number, default: 1 } // 1 正常 0 已删除
}, {versionKey: false})


用户

为了避免一些麻烦需要邮箱验证后才能注册,昵称不可重复。用户等级参考了四糸乃吧的等级。

数据库模型

const UserScheam = new mongoose.Schema({
    _id: { type: Number, unique: true, index: true },
    nickname: { type: String, trim: true, required: true, unique: true },// 昵称
    password: {
        type: String, trim: true, required: true, set(val) {
            return md5(val + SECRET_PWD)
        }
    },
    ip: { type: String },
    email: {
        type: String, trim: true, required: true, unique: true, index: true, set(val) {
            return String(val).toLowerCase()
        }
    },
    avatar: {
        type: String, default: () => `/static/img/default_avatar.png`, get: (val) => {
            return new URL(val, HOST).toString()
        }
    }, // 头像
    registerTime: { type: Number, default: () => Math.ceil(Date.now() / 1000) }, // 注册时间
    roles: { type: Array, default: ['user'] }, // 角色组
    lastLoginTime: { type: Number },// 最后登录日期 
    exp: { type: Number, default: 0 }, // 经验
    coin: { type: Number, default: 0 },// 积分
    signInTol: { type: Number, default: 0 }, // 累计签到
    usersign: { type: String }, // 用户签名
    status: { type: Number, default: 1 } // 状态 0-封禁 1-正常
}, { _id: false, autoIndex: false, versionKey: false, toJSON: { virtuals: true, getters: true }, toObject: { getters: true } })

UserScheam.virtual('role', {
    ref: 'Role',
    localField: 'roles',
    foreignField: 'name',
    justOne: false,
})


留言板

允许匿名留言,不做限制。

数据库模型


const Schema = new mongoose.Schema({
    from: { type: Number, ref: "User" }, // 用户id
    email: { type: String },
    nickname: { type: String },
    content: { type: String, required: true, trim: true },
    createTime: { type: Number, default: () => Math.ceil(Date.now() / 1000) },// 创建时间
    ip: { type: String }
}, { versionKey: false })


过滤器

服务端在发布文章和编辑文字处对内容加入了xss过滤,其他允许用户输入的地方加入了违规词检测,虽然感觉没必要(反正小破站没人看),过滤器使用mint-filter。词库在github找的,感觉也蛮全的,共5w条。但为了避免一些麻烦还是加上了。


鉴权

一个用户可以拥有多个角色,每个角色都包含了不同的权限。在需要鉴权的接口加入中间件,判断用户是否拥有指定权限。

数据库模型

/**
 * @description 用户角色模型
 */
const RoleScheam = new mongoose.Schema({
    _id: { type: Number, unique: true, index: true, required: true },
    name: { type: String, trim: true, unique: true, required: true }, // 角色名
    title: { type: String, default: '' }, // 称号
    rights: { type: Array, default: [] } // 权限数组
},{ _id: false, autoIndex: false ,versionKey: false})


/**
 * @description 用户权限模型
 */
const RightsScheam = new mongoose.Schema({
    _id: { type: Number, unique: true, index: true, required: true },
    name: { type: String, trim: true, unique: true, required: true }, // 权限名
    field: { type: String, unique: true, index: true }, // 列名
    pid: { type: Number, default: null }, // 父级
    isRights: { type: Number, default: 0, set(val){ // 是否为权限菜单
        if(typeof val == 'boolean'){
            return val ? 1 : 0
        }else return val ;
    } }
}, { _id: false, autoIndex: false ,versionKey: false})


完成本项目后学到不少东西,觉得最重要的还是一些设计思路。期间也踩过不少坑,我遇到觉得最难的问题是查询回复用户的消息,类似贴吧的消息,

需求:

1、在用户发布的文章下的评论 comment.article = 文章(article.author = uid) ;

2、回复用户发布的评论 comment.comment = 评论(comment.from = uid);

3、楼中楼里的回复 comment.comment = 评论(comment.reply.from = uid); 

mongodb里联合查询实在麻烦,里面又需要嵌套查询,所以不知道怎么用管道操作符写,基础不够扎实 

最后的实现方式是先把用户发布的文章和评论都查出来,然后再根据条件查一遍,也就是要查三遍,但如果用管道实现的话只用查一遍,不知道这两者性能差距有多少。

代码:

// 先筛选出用户发表过的文章和评论
        let userArticle = await Article.find({ author: uid }, { _id: 1 }).lean();
        let userComment = await Comment.find({ from: uid }, { _id: 1 }).lean();
        let commentIds = userComment.map(item => item._id)

        let list = await Comment.find({
            $or:
                [
                    {
                        article: { $in: userArticle.map(item => item._id) }
                    },
                    {
                        comment: { $in: commentIds }
                    },
                    {
                        comment: { $nin: commentIds },
                        reply: { $in: commentIds }
                    }
                ],
            from: { $ne: uid },
            status: 1
        })
            .sort({ createTime: -1 })
            .skip((page - 1) * pageSize).limit(pageSize)
            .populate('from', 'nickname avatar')
            .populate('comment', 'content')
            .populate('reply', 'content')
            .populate('article', 'title coverImg')

数据库模型:

const Schema = new mongoose.Schema({
    article: { type: Number, required: true, ref: "Article" }, // 文章id
    from: { type: Number, required: true, ref: "User" }, // 用户id
    comment: { type: mongoose.Types.ObjectId, ref: "Comment" }, // 顶级评论id 
    reply: { type: mongoose.Types.ObjectId, ref: "Comment" }, // 回复的楼中楼id 
    content: { type: String, required: true, trim: true},
    createTime: { type: Number, default: () => Math.ceil(Date.now() / 1000) },// 创建时间
    goods: { type:Number, default: 0 }, //点赞数
    bads: { type:Number, default: 0 }, // 踩
    status: { type: Number, default: 1 } // 1 正常 0 已删除
}, {versionKey: false})


后台

使用了模板vue-manage-system

评论

后参与评论
还没有评论,来说点什么吧~