├── models ├── init.js ├── tables │ ├── db.js │ ├── classify.js │ ├── articleWord.js │ ├── myWord.js │ ├── user.js │ └── article.js ├── sync.js └── relation.js ├── client ├── .env.development ├── .browserslistrc ├── .env.production ├── babel.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ ├── img │ │ │ ├── dzh.png │ │ │ ├── dzq.png │ │ │ ├── pl.png │ │ │ ├── rs.png │ │ │ ├── sc.png │ │ │ ├── logo1.png │ │ │ ├── logo2.png │ │ │ ├── night.gif │ │ │ ├── white.png │ │ │ ├── wxImg.jpg │ │ │ ├── blacklogo.png │ │ │ ├── rain │ │ │ │ ├── rain.mp3 │ │ │ │ ├── words.png │ │ │ │ ├── rain2_off.png │ │ │ │ ├── rain2_on.png │ │ │ │ ├── rain3_off.png │ │ │ │ ├── rain3_on.png │ │ │ │ ├── rain4_off.png │ │ │ │ ├── rain4_on.png │ │ │ │ ├── rain_off.png │ │ │ │ ├── rain_on.png │ │ │ │ ├── thunder.mp3 │ │ │ │ └── loudThunder.mp3 │ │ │ ├── textlogo.png │ │ │ └── whitelogo.png │ │ ├── font │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.ttf │ │ │ ├── iconfont.woff │ │ │ ├── iconfont.woff2 │ │ │ └── iconfont.css │ │ └── js │ │ │ └── flexible.js │ ├── api │ │ ├── myWord.js │ │ ├── articleWord.js │ │ ├── article.js │ │ └── user.js │ ├── views │ │ ├── backbone.vue │ │ ├── ArticleList │ │ │ ├── components │ │ │ │ ├── scrollBar.vue │ │ │ │ ├── messageInput.vue │ │ │ │ └── messageList.vue │ │ │ ├── index.vue │ │ │ └── detail.vue │ │ ├── Home │ │ │ └── index.vue │ │ ├── Exhibit │ │ │ └── index.vue │ │ ├── MessageBoard │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── messageList.vue │ │ ├── Login │ │ │ └── index.vue │ │ ├── Demo │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── Piano │ │ │ │ └── index.vue │ │ ├── SelfInfo │ │ │ └── index.vue │ │ └── Rainy │ │ │ └── index.vue │ ├── router │ │ ├── index.js │ │ ├── handler.js │ │ └── routes.js │ ├── components │ │ ├── pageLoadScrollBar │ │ │ └── index.vue │ │ ├── Loading │ │ │ └── index.vue │ │ ├── BackTop │ │ │ └── index.vue │ │ ├── Button │ │ │ └── index.vue │ │ ├── loginRegistration │ │ │ ├── loginBg.vue │ │ │ └── index.vue │ │ ├── Contact │ │ │ └── index.vue │ │ ├── GiantScreen │ │ │ └── index.vue │ │ └── Header │ │ │ └── index.vue │ ├── App.vue │ ├── store │ │ └── index.js │ ├── utils │ │ ├── request.js │ │ └── index.js │ └── main.js ├── .postcssrx.js ├── .gitignore ├── README.md ├── package.json └── vue.config.js ├── backstage ├── .env.development ├── .browserslistrc ├── .env.production ├── babel.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ └── font │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.ttf │ │ │ ├── iconfont.woff │ │ │ ├── iconfont.woff2 │ │ │ └── iconfont.css │ ├── api │ │ ├── article.js │ │ ├── word.js │ │ └── user.js │ ├── store │ │ └── index.js │ ├── router │ │ ├── power.js │ │ └── index.js │ ├── views │ │ ├── Layout │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── main.vue │ │ │ │ └── nav.vue │ │ ├── AboutMe │ │ │ └── index.vue │ │ ├── Comment │ │ │ └── index.vue │ │ ├── Work │ │ │ └── index.vue │ │ ├── Login │ │ │ └── index.vue │ │ ├── Setting │ │ │ └── index.vue │ │ └── AddArt │ │ │ └── index.vue │ ├── App.vue │ ├── main.js │ ├── utils │ │ ├── uploadFile.js │ │ └── request.js │ └── components │ │ └── markdown.vue ├── .gitignore ├── README.md ├── package.json └── vue.config.js ├── index.js ├── .vscode └── settings.json ├── .gitignore ├── nodemon.json ├── routes ├── errorMiddleware.js ├── apiLoggerMiddleware.js ├── getSendMessage.js ├── sessionConfig.js ├── jwt.js ├── api │ ├── articleWord.js │ ├── upload.js │ ├── sync.js │ ├── myWord.js │ ├── article.js │ ├── ossUpload.js │ └── user.js ├── tokenMiddleware.js ├── init.js └── captchaMiddleware.js ├── services ├── utils.js ├── myCommServ.js ├── commentServ.js ├── userServ.js └── articleServ.js ├── logger.js └── package.json /models/init.js: -------------------------------------------------------------------------------- 1 | require('./relation') 2 | -------------------------------------------------------------------------------- /client/.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_URL = http://localhost:5008 -------------------------------------------------------------------------------- /backstage/.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_URL = http://localhost:5008 -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./models/init') 2 | require('./routes/init') -------------------------------------------------------------------------------- /backstage/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /backstage/.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_URL = https://www.llongjie.top:5008 -------------------------------------------------------------------------------- /client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /client/.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_URL = https://www.llongjie.top:5008 -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } -------------------------------------------------------------------------------- /backstage/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | encrypt 4 | 5 | dist 6 | 7 | dist.zip 8 | 9 | public 10 | 11 | logs -------------------------------------------------------------------------------- /backstage/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/backstage/public/favicon.ico -------------------------------------------------------------------------------- /client/src/assets/img/dzh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/dzh.png -------------------------------------------------------------------------------- /client/src/assets/img/dzq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/dzq.png -------------------------------------------------------------------------------- /client/src/assets/img/pl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/pl.png -------------------------------------------------------------------------------- /client/src/assets/img/rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rs.png -------------------------------------------------------------------------------- /client/src/assets/img/sc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/sc.png -------------------------------------------------------------------------------- /client/src/assets/img/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/logo1.png -------------------------------------------------------------------------------- /client/src/assets/img/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/logo2.png -------------------------------------------------------------------------------- /client/src/assets/img/night.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/night.gif -------------------------------------------------------------------------------- /client/src/assets/img/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/white.png -------------------------------------------------------------------------------- /client/src/assets/img/wxImg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/wxImg.jpg -------------------------------------------------------------------------------- /client/src/assets/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/font/iconfont.eot -------------------------------------------------------------------------------- /client/src/assets/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/font/iconfont.ttf -------------------------------------------------------------------------------- /client/src/assets/img/blacklogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/blacklogo.png -------------------------------------------------------------------------------- /client/src/assets/img/rain/rain.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/rain.mp3 -------------------------------------------------------------------------------- /client/src/assets/img/textlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/textlogo.png -------------------------------------------------------------------------------- /client/src/assets/img/whitelogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/whitelogo.png -------------------------------------------------------------------------------- /client/src/assets/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/font/iconfont.woff -------------------------------------------------------------------------------- /client/src/assets/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/font/iconfont.woff2 -------------------------------------------------------------------------------- /client/src/assets/img/rain/words.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/words.png -------------------------------------------------------------------------------- /backstage/src/assets/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/backstage/src/assets/font/iconfont.eot -------------------------------------------------------------------------------- /backstage/src/assets/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/backstage/src/assets/font/iconfont.ttf -------------------------------------------------------------------------------- /backstage/src/assets/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/backstage/src/assets/font/iconfont.woff -------------------------------------------------------------------------------- /backstage/src/assets/font/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/backstage/src/assets/font/iconfont.woff2 -------------------------------------------------------------------------------- /client/src/assets/img/rain/rain2_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/rain2_off.png -------------------------------------------------------------------------------- /client/src/assets/img/rain/rain2_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/rain2_on.png -------------------------------------------------------------------------------- /client/src/assets/img/rain/rain3_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/rain3_off.png -------------------------------------------------------------------------------- /client/src/assets/img/rain/rain3_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/rain3_on.png -------------------------------------------------------------------------------- /client/src/assets/img/rain/rain4_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/rain4_off.png -------------------------------------------------------------------------------- /client/src/assets/img/rain/rain4_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/rain4_on.png -------------------------------------------------------------------------------- /client/src/assets/img/rain/rain_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/rain_off.png -------------------------------------------------------------------------------- /client/src/assets/img/rain/rain_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/rain_on.png -------------------------------------------------------------------------------- /client/src/assets/img/rain/thunder.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/thunder.mp3 -------------------------------------------------------------------------------- /client/src/assets/img/rain/loudThunder.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheizhiya/myBlog/HEAD/client/src/assets/img/rain/loudThunder.mp3 -------------------------------------------------------------------------------- /backstage/src/api/article.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/request.js' 2 | 3 | // 新增一篇文章 4 | export const add = data => axios.post('/api/article/addArticle', data) 5 | -------------------------------------------------------------------------------- /client/.postcssrx.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | // 'postcss-px2rem': { 4 | // remUnit: 35 5 | // }, 6 | 'autoprefixer':{} 7 | } 8 | } -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "NODE_ENV": "development" 4 | }, 5 | "watch": ["*.js", "*.json"], 6 | "ignore": ["package*.json", "nodemon.json", "node_modules", "public"] 7 | } -------------------------------------------------------------------------------- /routes/errorMiddleware.js: -------------------------------------------------------------------------------- 1 | // 处理错误的中间件 2 | module.exports = async (ctx, next) => { 3 | try { 4 | await next(); 5 | } catch (e) { 6 | ctx.status = e.statusCode || e.status || 500 7 | ctx.body = e.message 8 | } 9 | }; -------------------------------------------------------------------------------- /backstage/src/api/word.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/request.js' 2 | 3 | export const list = data => axios.post('/api/myWord/getMyComList', data) 4 | 5 | export const deleteWord = data => axios.post('/api/myWord/deleteCom', data) 6 | -------------------------------------------------------------------------------- /client/src/api/myWord.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/request.js' 2 | 3 | // 新增评论 4 | export const add = data => axios.post('/api/myWord/addmyCom', data) 5 | 6 | // 获取评论列表 7 | export const list = data => axios.post('/api/myWord/getMyComList', data) -------------------------------------------------------------------------------- /client/src/api/articleWord.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/request.js' 2 | 3 | // 新增评论 4 | export const add = data => axios.post('/api/articleWord/addComment', data) 5 | 6 | // 获取评论列表 7 | export const list = data => axios.post('/api/articleWord/getArtComList',data) -------------------------------------------------------------------------------- /client/src/views/backbone.vue: -------------------------------------------------------------------------------- 1 | 7 | 15 | -------------------------------------------------------------------------------- /models/tables/db.js: -------------------------------------------------------------------------------- 1 | const { Sequelize } = require('sequelize'); 2 | 3 | const { localDbInfo, aliDbInfo: { dbName, userName, password, host } } = require('../../encrypt/dbEncrypt') // 引入数据库数据 4 | 5 | const sequelize = new Sequelize( dbName, userName, password, host ); // 定义数据库对象 6 | 7 | module.exports = sequelize -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /backstage/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /backstage/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | 4 | Vue.use(Vuex); 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | userInfo: {} 9 | }, 10 | mutations: { 11 | setUserInfo (state, data) { 12 | state.userInfo = data 13 | } 14 | }, 15 | actions: {}, 16 | modules: {} 17 | }); 18 | -------------------------------------------------------------------------------- /backstage/src/router/power.js: -------------------------------------------------------------------------------- 1 | import router from './index' 2 | 3 | const whiteRouter = ['/login'] 4 | 5 | router.beforeEach((to, from, next) => { 6 | const isToken = !!localStorage.getItem('token') 7 | if (isToken) { 8 | next() 9 | } else { // 没有token 10 | whiteRouter.includes(to.path) ? next() : next('/login') 11 | } 12 | }) 13 | 14 | export default router -------------------------------------------------------------------------------- /backstage/src/views/Layout/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import routeArr from "./routes" 4 | Vue.use(VueRouter); 5 | 6 | const routes = routeArr 7 | 8 | const router = new VueRouter({ 9 | mode: "history", 10 | routes, 11 | scrollBehavior () { // 页面刷新后回到顶部 12 | return { x: 0, y: 0} 13 | } 14 | }); 15 | 16 | export default router; 17 | -------------------------------------------------------------------------------- /routes/apiLoggerMiddleware.js: -------------------------------------------------------------------------------- 1 | const { apiLogger } = require('../logger') 2 | 3 | // 处理错误的中间件 4 | module.exports = async (ctx, next) => { 5 | try { 6 | await next(); 7 | } 8 | finally { 9 | if (ctx.type === 'application/json') { 10 | apiLogger.debug(`${ctx.method} ${ctx.path} ${JSON.stringify(ctx.body)}`) 11 | } 12 | } 13 | 14 | }; -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # blog 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /backstage/README.md: -------------------------------------------------------------------------------- 1 | # backstage 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /models/sync.js: -------------------------------------------------------------------------------- 1 | require('./tables/user') 2 | require('./tables/article') 3 | require('./tables/articleWord') 4 | require('./tables/classify') 5 | require('./tables/myWord') 6 | require('./relation') // 关系 7 | require('./sync') // 同步表 8 | const sequelize = require('./tables/db'); 9 | (async () => { 10 | // await sequelize.sync({force: true}) // 清空数据库同步 11 | await sequelize.sync({alter: true}) // 只查看修改的地方并进行同步 12 | console.log("同步完成") 13 | })() -------------------------------------------------------------------------------- /client/src/api/article.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/request.js' 2 | 3 | // 新增一篇文章 4 | export const add = data => axios.post('/api/article/addArticle', data) 5 | 6 | // 获取文章列表 7 | export const list = data => axios.post('/api/article/getArtList', data) 8 | 9 | // 获取文章详情 10 | export const detail = (id, userId) => axios.get('/api/article/getArtDetail', { params: { id, userId } }) 11 | 12 | // 喜欢这篇文章 13 | export const setLike = data => axios.post('/api/article/likeArt', data) -------------------------------------------------------------------------------- /models/tables/classify.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./db') 2 | const { DataTypes } = require("sequelize") 3 | const moment = require("moment") 4 | const Classify = sequelize.define('classify', { 5 | name: { // 留言用户ID 6 | type: DataTypes.STRING, 7 | allowNull: false 8 | }, 9 | createdAt: { 10 | type: DataTypes.DATE, 11 | get(){ 12 | return moment(this.getDataValue('createdAt')).format('YYYY-MM-DD HH:mm:ss') 13 | } 14 | } 15 | }, 16 | { 17 | freezeTableName: true, // 固定表名字 18 | updatedAt: false 19 | }) 20 | module.exports = Classify -------------------------------------------------------------------------------- /backstage/src/api/user.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/request.js' 2 | import uploadFile from '../utils/uploadFile' 3 | 4 | // 登录 5 | export const login = (account, password) => axios.post('/api/user/login',{account, password}) 6 | 7 | // 获取我的个人信息 8 | export const whoami = () => axios.get('/api/user/whoami') 9 | 10 | // 修改用户信息 11 | export const updateUserInfo = data => axios.post('/api/user/updateUserInfo', data) 12 | 13 | // 获取首页信息 14 | export const getHomeInfo = () => axios.get('/api/user/getHomeInfo') 15 | 16 | // 上传图片 17 | export const uploadImg = data => uploadFile.post('/ossUpload', data) -------------------------------------------------------------------------------- /backstage/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 32 | -------------------------------------------------------------------------------- /routes/getSendMessage.js: -------------------------------------------------------------------------------- 1 | exports.getErr = function (err = '服务器出错', errCode = 500) { 2 | return { 3 | code: errCode, 4 | msg: err 5 | } 6 | } 7 | exports.getResult = function (data) { 8 | return { 9 | code: 200, 10 | msg: '成功', 11 | data 12 | } 13 | } 14 | /** 15 | * 16 | * @param {*} ctx koa返回的对象 17 | * @param {*} result 返回的结果 18 | * @param {*} err 错误消息 19 | * @param {*} cb 回调 20 | */ 21 | exports.apiHandle = function (ctx, result, err, cb) { 22 | if (!result) { 23 | ctx.body = exports.getErr(err, 401) 24 | } else { 25 | ctx.body = exports.getResult(result) 26 | cb && cb(result) 27 | } 28 | } -------------------------------------------------------------------------------- /routes/sessionConfig.js: -------------------------------------------------------------------------------- 1 | 2 | const KoaSession = require('koa-session') 3 | const seessionKey = ['longjie'] 4 | const sessionConfig = { 5 | key: 'Koa:sess', // key值 默认是Koa:sess 6 | maxAge: 1000 * 60 * 5, // 已毫秒为计算 7 | autoCommit: true, //是否自动提交到响应头 默认是true 8 | overwrite: true, //是否允许重写。默认是true 9 | httpOnly: true, //cookie是否只有服务器端可以访问 httpOnly or not (默认为 true) 可以防止XSS攻击 10 | signed: true, //签名默认true 11 | rolling: false, //在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false) 12 | renew: false, //是否在session快过期时刷新session有效期 13 | } 14 | exports.KoaSession = KoaSession 15 | exports.sessionConfig = sessionConfig 16 | exports.sessionKey = seessionKey -------------------------------------------------------------------------------- /services/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 限制评论 3 | * @param {*} num ${time}分钟内最多允许发多少条 4 | * @param {*} duration 分钟 5 | */ 6 | exports.limitComment = function (ctx, num, duration) { 7 | const session = ctx.session 8 | if (!session.commentLen) session.commentLen = [] 9 | const now = Date.now() 10 | duration = duration * 1000 * 60 11 | session.commentLen.push(now) 12 | session.commentLen = session.commentLen.filter(time => now - time < duration) 13 | console.log(session.commentLen, num) 14 | return session.commentLen.length <= num 15 | } 16 | 17 | // 得到最大小跟最小值之间的随机数 18 | exports.getRandom = (min, max) => Math.floor(Math.random() * max + min) -------------------------------------------------------------------------------- /client/src/api/user.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/request' 2 | 3 | // 登陆 4 | export const login = (account, password) => axios.post('/api/user/login',{account, password}) 5 | 6 | // 增加用户 7 | export const addUser = (data) => axios.post('/api/user/addUser', data) 8 | 9 | // 注册 按我的博客逻辑是需要验证码的修改用户信息。。。 10 | export const registry = (data) => axios.post('/api/user/registry', data) 11 | 12 | // 修改用户信息 13 | export const updateUserInfo = data => axios.post('/api/user/updateUserInfo', data) 14 | 15 | // 获取个人信息 16 | export const whoami = () => axios.get('/api/user/whoami') 17 | 18 | // 获取管理员的个人信息,必须在数据库里面id为 **1** 19 | export const getAboutMe = () => axios.get('/api/user/getAboutMe') -------------------------------------------------------------------------------- /routes/jwt.js: -------------------------------------------------------------------------------- 1 | const secrect = "longjiegege" 2 | const jwt = require("jsonwebtoken") 3 | const { getResult } = require('./getSendMessage') 4 | // 颁发jwt 5 | exports.publish = function (ctx, maxAge = 3600 * 24 * 7, info = {}) { 6 | const token = jwt.sign(info, secrect, { 7 | expiresIn: maxAge 8 | }) 9 | ctx.response.set('authorization', token) 10 | ctx.body = getResult(token) 11 | } 12 | // 验证 13 | exports.verify = function (ctx) { 14 | let token = ctx.request.get('authorization') 15 | if (!token) return 16 | token = token.split(" ") 17 | token = token.length === 1 ? token[0] : token[1] 18 | try { 19 | const result = jwt.verify(token, secrect) 20 | return result 21 | } catch (e) { 22 | return 23 | } 24 | } -------------------------------------------------------------------------------- /routes/api/articleWord.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | 3 | const artWordServ = require('../../services/commentServ') 4 | 5 | const { apiHandle } = require('../getSendMessage') 6 | 7 | let articleWord = new Router() 8 | 9 | articleWord.post('/addComment', async ctx => { 10 | const data = ctx.request.body 11 | const result = await artWordServ.addComment(data, ctx) 12 | apiHandle(ctx, result, '不能刷屏哦,过几分钟在试试~') 13 | }) 14 | 15 | articleWord.post('/getArtComList', async ctx => { 16 | const { pageSize = 10, pageNum = 1, articleId } = ctx.request.body 17 | const result = await artWordServ.getCommentList(pageNum, pageSize, articleId) 18 | apiHandle(ctx, result, '获取评论列表失败, 刷新一下试试~~') 19 | }) 20 | 21 | 22 | 23 | module.exports = articleWord -------------------------------------------------------------------------------- /models/tables/articleWord.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./db') 2 | const { DataTypes } = require("sequelize") 3 | const moment = require("moment") 4 | const ArticleWord = sequelize.define('articleword', { 5 | floorId: { 6 | type: DataTypes.INTEGER 7 | }, 8 | content: { 9 | type: DataTypes.STRING, 10 | allowNull: false 11 | }, 12 | createdAt: { 13 | type: DataTypes.DATE, 14 | get(){ 15 | return moment(this.getDataValue('createdAt')).format('YYYY-MM-DD HH:mm:ss') 16 | } 17 | }, 18 | updatedAt: { 19 | type: DataTypes.DATE, 20 | get(){ 21 | return moment(this.getDataValue('updatedAt')).format('YYYY-MM-DD HH:mm:ss') 22 | } 23 | } 24 | }, 25 | { 26 | freezeTableName: true // 固定表名字 27 | }) 28 | module.exports = ArticleWord -------------------------------------------------------------------------------- /models/tables/myWord.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./db') 2 | const { DataTypes } = require("sequelize") 3 | const moment = require("moment") 4 | const MyWord = sequelize.define('myword', { 5 | userId: { // 留言用户ID 6 | type: DataTypes.INTEGER, 7 | allowNull: false 8 | }, 9 | content: { 10 | type: DataTypes.STRING, 11 | allowNull: false 12 | }, 13 | createdAt: { 14 | type: DataTypes.DATE, 15 | get(){ 16 | return moment(this.getDataValue('createdAt')).format('YYYY-MM-DD HH:mm:ss') 17 | } 18 | }, 19 | updatedAt: { 20 | type: DataTypes.DATE, 21 | get(){ 22 | return moment(this.getDataValue('updateAt')).format('YYYY-MM-DD HH:mm:ss') 23 | } 24 | } 25 | }, 26 | { 27 | freezeTableName: true // 固定表名字 28 | }) 29 | module.exports = MyWord -------------------------------------------------------------------------------- /backstage/src/views/Layout/components/main.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /routes/api/upload.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | const multer = require('koa-multer'); 3 | const path = require("path") 4 | const { getResult } = require('../getSendMessage') 5 | const storage = multer.diskStorage({ 6 | destination: (req, file, cb) => { 7 | cb(null, path.resolve(__dirname, '../../public', "upload")) 8 | }, 9 | filename: (req, file, cb) => { 10 | // 时间戳-6位随机字符.文件后缀 11 | cb(null, `${Date.now()}-${Math.random().toString(36).slice(-6)}${path.extname(file.originalname)}` ) 12 | } 13 | }) 14 | const upload = multer({ storage }); 15 | let fileHandle = new Router() 16 | 17 | fileHandle.post("/upload", upload.single('file'), async ctx => { 18 | const url = `/upload/${ctx.req.file.filename}` 19 | ctx.body = getResult(url) 20 | }) 21 | 22 | module.exports = fileHandle -------------------------------------------------------------------------------- /routes/api/sync.js: -------------------------------------------------------------------------------- 1 | const user = require('./user') 2 | const article = require('./article') 3 | const articleWord = require('./articleWord') 4 | const { code } = require('../captchaMiddleware') 5 | const upload = require('./upload') 6 | const ossUpload = require('./ossUpload') 7 | const myWord = require('./myWord') 8 | const Router = require('koa-router'); 9 | const router = new Router() 10 | 11 | const routerArr = [ 12 | { key: '/api/user', router: user }, 13 | { key: '/api/article', router: article }, 14 | { key: '/api/articleWord', router: articleWord}, 15 | { key: '/api/myWord', router: myWord}, 16 | { key: '', router: code }, 17 | { key: '', router: upload }, 18 | { key: '', router: ossUpload } 19 | ] 20 | 21 | routerArr.forEach(r => {router.use(r.key, r.router.routes(), r.router.allowedMethods())}) 22 | 23 | module.exports = router 24 | -------------------------------------------------------------------------------- /backstage/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router/power"; 4 | import store from "./store"; 5 | 6 | import ElementUI from 'element-ui'; 7 | import 'element-ui/lib/theme-chalk/index.css'; 8 | 9 | // 引入 iconfont 10 | import '@/assets/font/iconfont.css'; 11 | // 引入 iconfont 12 | 13 | // 引入markDown 编辑器 14 | import mavonEditor from 'mavon-editor' 15 | import 'mavon-editor/dist/css/index.css' 16 | Vue.use(mavonEditor) 17 | 18 | Vue.use(ElementUI) 19 | 20 | Vue.prototype.mainUrl = process.env.VUE_APP_URL 21 | 22 | Vue.config.productionTip = false; 23 | 24 | new Vue({ 25 | router, 26 | store, 27 | render: h => h(App) 28 | }).$mount("#app"); 29 | 30 | if (process.env.NODE_ENV == 'development') { 31 | Vue.config.devtools = true 32 | } else { 33 | Vue.config.devtools = false 34 | Vue.config.productionTip = false 35 | } -------------------------------------------------------------------------------- /routes/api/myWord.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | 3 | const myCommServ = require('../../services/myCommServ') 4 | 5 | const { apiHandle } = require('../getSendMessage') 6 | 7 | let myWord = new Router() 8 | 9 | myWord.post('/addmyCom', async ctx => { 10 | const data = ctx.request.body 11 | const result = await myCommServ.addMyComment(data, ctx) 12 | apiHandle(ctx, result, '不能刷屏哦,过几分钟在试试~') 13 | }) 14 | 15 | myWord.post('/getMyComList', async ctx => { 16 | const { pageSize = 10, pageNum = 1 } = ctx.request.body 17 | const result = await myCommServ.getMyCommentList(pageNum, pageSize) 18 | apiHandle(ctx, result, '获取留言列表失败, 刷新一下试试~~') 19 | }) 20 | 21 | myWord.post('/deleteCom', async ctx => { 22 | const { id } = ctx.request.body 23 | const result = await myCommServ.deleteWord(id) 24 | apiHandle(ctx, result, '删除失败,请重新试试~~') 25 | }) 26 | 27 | module.exports = myWord -------------------------------------------------------------------------------- /models/relation.js: -------------------------------------------------------------------------------- 1 | // 这是用来处理关系的 2 | const Article = require('./tables/article') 3 | const User = require('./tables/user') 4 | const ArticleWord = require('./tables/articleWord') 5 | const MyWord = require('./tables/myWord') 6 | const Classify = require('./tables/classify') 7 | 8 | Article.belongsToMany(User, { through: 'like', foreignKey:'articleId' }) 9 | User.belongsToMany(Article, { through: 'like', foreignKey:'userId' }) 10 | 11 | Classify.hasMany(Article, { 12 | foreignKey: 'classifyId' 13 | }) 14 | Article.belongsTo(Classify) 15 | 16 | // 一篇文章可以有多个评论 17 | Article.hasMany(ArticleWord, { 18 | foreignKey: 'articleId' 19 | }) 20 | ArticleWord.belongsTo(Article) 21 | 22 | // 一个用户可以评论多篇文章 23 | User.hasMany(ArticleWord, { 24 | foreignKey: 'userId' 25 | }) 26 | ArticleWord.belongsTo(User) 27 | 28 | // 一个用户可以留言多次 29 | User.hasMany(MyWord, { 30 | foreignKey: 'userId' 31 | }) 32 | MyWord.belongsTo(User) -------------------------------------------------------------------------------- /backstage/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 后台系统 12 | 13 | 14 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /client/src/components/pageLoadScrollBar/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /client/src/router/handler.js: -------------------------------------------------------------------------------- 1 | import router from './index' 2 | import store from "../store" 3 | import Vue from 'vue' 4 | import { Loading } from 'element-ui'; 5 | let load = '' 6 | 7 | router.beforeEach((to, from, next) => { 8 | store.commit('setShowPageLoadScrollBar', true) 9 | if (to.name === 'Exhibit') { 10 | load = Loading.service({ 11 | lock: true, 12 | background: 'rgba(255,255,255,.8)' 13 | }); 14 | } 15 | if (_hmt) { 16 | if (to.path) { 17 | _hmt.push(['_trackPageview', to.fullPath]); 18 | } 19 | } 20 | const { meta } = to 21 | for (let name in meta) { 22 | document[name] = meta[name] 23 | } 24 | next() 25 | }) 26 | router.afterEach((to, from) => { 27 | setTimeout(() => { 28 | if (to.name === 'Exhibit') { 29 | load.close() 30 | } 31 | store.commit('setShowPageLoadScrollBar', false) 32 | }, 1200) 33 | }) 34 | 35 | export default router -------------------------------------------------------------------------------- /backstage/src/utils/uploadFile.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message } from "element-ui" 3 | 4 | let baseURL = '' 5 | process.env.NODE_ENV === 'production' ? baseURL = 'http://www.llongjie.top:5008' : baseURL = 'http://localhost:5008' 6 | 7 | const ins = axios.create({ 8 | baseURL, 9 | withCredentials: true 10 | }) 11 | 12 | ins.interceptors.request.use(req => { 13 | const token = localStorage.getItem("token") 14 | if (token) { 15 | req.headers['authorization'] = "bearer " + token 16 | req.headers['Content-Type'] = 'multipart/form-data' 17 | } 18 | return req 19 | },err => { 20 | return Promise.reject(err) 21 | }) 22 | 23 | 24 | ins.interceptors.response.use(resp => { 25 | const data = resp.data 26 | if (data.code !== 200 ) { 27 | Message.error(data.msg) 28 | return Promise.reject(data) 29 | } 30 | return resp 31 | }, err => { 32 | if (err.response.status === 403) { 33 | localStorage.removeItem("token") 34 | Message.error('登陆已过期,请重新登陆') 35 | } 36 | return Promise.reject(err) 37 | }) 38 | 39 | export default ins -------------------------------------------------------------------------------- /models/tables/user.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./db') 2 | const { DataTypes } = require("sequelize") 3 | const moment = require("moment") 4 | const User = sequelize.define('user', { 5 | userName: { 6 | type: DataTypes.STRING 7 | }, 8 | account: { 9 | type: DataTypes.STRING 10 | }, 11 | password: { 12 | type: DataTypes.STRING 13 | }, 14 | aboutMe: { 15 | type: DataTypes.TEXT 16 | }, 17 | birthday: { 18 | type: DataTypes.DATE, 19 | get(){ 20 | if (this.getDataValue('birthday')) { return moment(this.getDataValue('birthday')).format('YYYY-MM-DD HH:mm:ss') } else return '' 21 | } 22 | }, 23 | avatar: { 24 | type: DataTypes.STRING 25 | }, 26 | motto: { 27 | type: DataTypes.STRING 28 | }, 29 | createdAt: { 30 | type: DataTypes.DATE, 31 | get(){ 32 | return moment(this.getDataValue('createdAt')).format('YYYY-MM-DD HH:mm:ss') 33 | } 34 | } 35 | }, 36 | { 37 | freezeTableName: true, // 固定表名字 38 | paranoid: true, // 从此以后,该表的数据不会真正的删除,而是增加一列deleteAt,记录删除时间 39 | updatedAt: false // 不要修改时间 40 | }) 41 | module.exports = User -------------------------------------------------------------------------------- /logger.js: -------------------------------------------------------------------------------- 1 | const log4js = require('koa-log4') 2 | const path = require('path') 3 | 4 | log4js.configure({ 5 | appenders: { 6 | api: { 7 | type: 'dateFile', 8 | filename: path.resolve(__dirname, 'logs', 'api', 'logging.log'), 9 | maxLogSize: 1024 * 1024, // 配置文件的最大字节数 10 | keepFileExt: 3, // 最多保存3天 11 | layout: { 12 | type: 'pattern', 13 | pattern: '%c [%d{yyyy-MM-dd hh:mm:ss}] [%p]:%m%n' 14 | } 15 | }, 16 | default: { 17 | type: 'stdout', 18 | maxLogSize: 1024 * 1024, // 配置文件的最大字节数 19 | keepFileExt: 3, // 最多保存3天 20 | } 21 | }, 22 | categories: { 23 | api: { 24 | appenders: ['api'], 25 | level: 'all' 26 | }, 27 | default: { 28 | appenders: ['default'], 29 | level: 'all' 30 | } 31 | } 32 | }) 33 | 34 | process.on("exit", () => { 35 | log4js.shutdown() 36 | }) 37 | 38 | const apiLogger = log4js.getLogger("api") 39 | 40 | exports.apiLogger = apiLogger -------------------------------------------------------------------------------- /backstage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backstage", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.19.2", 12 | "core-js": "^3.6.5", 13 | "element-ui": "^2.13.2", 14 | "mavon-editor": "^2.9.0", 15 | "vue": "^2.6.11", 16 | "vue-router": "^3.2.0", 17 | "vuex": "^3.4.0" 18 | }, 19 | "devDependencies": { 20 | "@vue/cli-plugin-babel": "^4.4.0", 21 | "@vue/cli-plugin-eslint": "^4.4.0", 22 | "@vue/cli-plugin-router": "^4.4.0", 23 | "@vue/cli-plugin-vuex": "^4.4.0", 24 | "@vue/cli-service": "^4.4.0", 25 | "@vue/eslint-config-prettier": "^6.0.0", 26 | "babel-eslint": "^10.1.0", 27 | "compression-webpack-plugin": "^5.0.1", 28 | "eslint": "^6.7.2", 29 | "eslint-plugin-prettier": "^3.1.3", 30 | "eslint-plugin-vue": "^6.2.2", 31 | "node-sass": "^4.14.1", 32 | "prettier": "^1.19.1", 33 | "sass-loader": "^8.0.2", 34 | "vue-template-compiler": "^2.6.11" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /backstage/src/utils/request.js: -------------------------------------------------------------------------------- 1 | // 发送请求的时候,如果有token,需要附带在请求头中 2 | // 响应的时候,如果响应的消息码是403( 没有token,token失效),在本地删除token 3 | import axios from 'axios' 4 | import { Message } from "element-ui" 5 | 6 | let baseURL = process.env.VUE_APP_URL 7 | 8 | const ins = axios.create({ 9 | baseURL, 10 | timeout: 15000, 11 | withCredentials: true 12 | }) 13 | 14 | ins.interceptors.request.use(req => { 15 | const token = localStorage.getItem("token") 16 | if (token) { 17 | req.headers['authorization'] = "bearer " + token 18 | } 19 | return req 20 | },err => { 21 | return Promise.reject(err) 22 | }) 23 | 24 | ins.interceptors.response.use(resp => { 25 | // if (resp.headers.authorization) { 26 | // localStorage.setItem("token", resp.headers.authorization) 27 | // } 28 | const data = resp.data 29 | if (data.code !== 200 ) { 30 | Message.error(data.msg) 31 | return Promise.reject(data) 32 | } 33 | return resp 34 | }, err => { 35 | if (err.response.status === 403) { 36 | localStorage.removeItem("token") 37 | Message.error('登陆已过期,请重新登陆') 38 | } 39 | return Promise.reject(err) 40 | }) 41 | 42 | export default ins -------------------------------------------------------------------------------- /routes/tokenMiddleware.js: -------------------------------------------------------------------------------- 1 | const { getErr } = require("./getSendMessage"); 2 | const { pathToRegexp } = require("path-to-regexp"); 3 | const jwt = require('./jwt') 4 | const needTokenApi = [ 5 | { method: "POST", path: "/api/article/addArticle" }, 6 | { method: "GET", path: "/api/user/whoami" }, 7 | { method: "GET", path: "/api/user/getHomeInfo" }, 8 | { method: "POST", path: "/ossUpload" }, 9 | { method: "POST", path: "/upload" } 10 | ]; 11 | module.exports = async (ctx, next) => { 12 | const apis = needTokenApi.filter(api => { 13 | const reg = pathToRegexp(api.path) 14 | return api.method === ctx.method && reg.test(ctx.path) 15 | }) 16 | if (apis.length === 0) { 17 | await next() 18 | return 19 | } 20 | const result = jwt.verify(ctx) 21 | const isPass = result && (result.id === 1 || ctx.path === '/api/user/whoami' || ctx.path === '/api/user/getHomeInfo') 22 | if (isPass) { 23 | ctx.request.userId = result.id 24 | await next() 25 | } else { 26 | handleNonToken(ctx) 27 | } 28 | } 29 | //处理没有认证的情况 30 | function handleNonToken(ctx) { 31 | ctx.status = 403 32 | ctx.body = getErr('您还没有登陆', 403) 33 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-blog", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon index", 8 | "sync": "node models/sync" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/qiheizhiya/myBlog.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/qiheizhiya/myBlog/issues" 18 | }, 19 | "homepage": "https://github.com/qiheizhiya/myBlog#readme", 20 | "devDependencies": { 21 | "@types/node": "^14.0.23", 22 | "nodemon": "^2.0.4" 23 | }, 24 | "dependencies": { 25 | "@koa/cors": "^3.1.0", 26 | "ali-oss": "^6.10.0", 27 | "jsonwebtoken": "^8.5.1", 28 | "koa": "^2.13.0", 29 | "koa-bodyparser": "^4.3.0", 30 | "koa-log4": "^2.3.2", 31 | "koa-multer": "^1.0.2", 32 | "koa-router": "^9.1.0", 33 | "koa-session": "^6.0.0", 34 | "koa-sslify": "^5.0.0", 35 | "koa-static": "^5.0.0", 36 | "mime": "1.3.6", 37 | "moment": "^2.27.0", 38 | "mysql2": "^2.1.0", 39 | "path-to-regexp": "^6.1.0", 40 | "sequelize": "^6.3.3", 41 | "svg-captcha": "^1.4.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 24 | 45 | -------------------------------------------------------------------------------- /routes/api/article.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | 3 | const artServ = require('../../services/articleServ') 4 | const { apiHandle } = require('../getSendMessage') 5 | 6 | let article = new Router() 7 | 8 | article.post('/getArtList', async ctx => { 9 | const { pageSize = 10, pageNum = 1, isHome = false } = ctx.request.body 10 | const result = await artServ.getArtList(pageNum, pageSize, isHome) 11 | apiHandle(ctx, result, '获取文章列表失败, 刷新一下试试~~') 12 | }) 13 | 14 | article.get('/getArtDetail', async ctx => { 15 | const { id, userId } = ctx.query 16 | const result = await artServ.getArtDetail(id, userId, ctx) 17 | apiHandle(ctx, result, '获取文章详情, 刷新一下试试~~') 18 | }) 19 | 20 | article.post('/addArticle', async ctx => { 21 | try { 22 | const bodyData = ctx.request.body 23 | bodyData.userId = ctx.request.userId 24 | const result = await artServ.addArticle(bodyData) 25 | apiHandle(ctx, result, '增加文章失败, 刷新一下试试~~') 26 | } catch (e) { 27 | console.log(e) 28 | } 29 | }) 30 | 31 | article.post('/likeArt', async ctx => { 32 | const data = ctx.request.body 33 | const result = await artServ.likeArt(data) 34 | apiHandle(ctx, result, '喜欢文章失败,刷新一下试试~~') 35 | }) 36 | 37 | module.exports = article 38 | -------------------------------------------------------------------------------- /routes/init.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const path = require("path") 3 | const http = require('http') 4 | const staticMid = require('koa-static') // 静态资源中间件 5 | const staticPath = path.resolve(__dirname, '../public') // 静态资源目录 6 | const cors = require('@koa/cors') 7 | const { KoaSession, sessionConfig, sessionKey } = require('./sessionConfig') // 引入session相关配置 8 | const bodyParserMid = require('koa-bodyparser') // 解析body中间件 9 | 10 | const router = require('./api/sync') // 引入总路由 11 | 12 | const app = new Koa() // 创建一个koa实例 13 | const port = 5008 14 | 15 | app.use(require('./errorMiddleware')) // 错误处理中间件 16 | 17 | app.use(require('./apiLoggerMiddleware')) // API请求日志 18 | 19 | app.use(cors({ 20 | credentials: true // 服务端下发到客户端的 response 中头部字段,意义是允许客户端携带验证信息 21 | })); 22 | 23 | // 引入session 24 | const session = KoaSession(sessionConfig, app) 25 | app.keys = sessionKey 26 | app.use(session) 27 | // 引入session 28 | 29 | app.use(require("./tokenMiddleware")); // 应用token中间件 30 | app.use(staticMid(staticPath)) // 引入静态资源中间件 31 | app.use(bodyParserMid()) // 引入解析body的中间件 32 | 33 | app.use(router.routes()).use(router.allowedMethods()) 34 | 35 | http.createServer(app.callback()).listen(port, () => { 36 | console.log('5008, http链接成功 '); 37 | }) -------------------------------------------------------------------------------- /client/src/components/Loading/index.vue: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /models/tables/article.js: -------------------------------------------------------------------------------- 1 | const sequelize = require('./db') 2 | const { DataTypes } = require("sequelize") 3 | const moment = require("moment") 4 | const Article = sequelize.define('article', { 5 | userId: { 6 | type: DataTypes.INTEGER, 7 | allowNull: false 8 | }, 9 | title: { 10 | type: DataTypes.STRING, 11 | allowNull: false 12 | }, 13 | imgUrl: { 14 | type: DataTypes.STRING, 15 | allowNull: false 16 | }, 17 | musicUrl: { 18 | type: DataTypes.STRING 19 | }, 20 | musicName: { 21 | type: DataTypes.STRING 22 | }, 23 | content: { 24 | type: DataTypes.TEXT, 25 | allowNull: false 26 | }, 27 | description: { 28 | type: DataTypes.STRING 29 | }, 30 | visitsNum: { 31 | type: DataTypes.INTEGER, 32 | defaultValue: 0 33 | }, 34 | likeNum: { 35 | type: DataTypes.INTEGER, 36 | defaultValue: 0 37 | }, 38 | createdAt: { 39 | type: DataTypes.DATE, 40 | get(){ 41 | return moment(this.getDataValue('createdAt')).format('YYYY-MM-DD HH:mm:ss') 42 | } 43 | }, 44 | updatedAt: { 45 | type: DataTypes.DATE, 46 | get(){ 47 | return moment(this.getDataValue('updatedAt')).format('YYYY-MM-DD HH:mm:ss') 48 | } 49 | } 50 | }, 51 | { 52 | freezeTableName: true // 固定表名字 53 | }) 54 | module.exports = Article -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import { list } from "@/api/article" 4 | Vue.use(Vuex); 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | isToken: !!localStorage.getItem('token'), // 有没有token 9 | showPageLoadScrollBar: false 10 | }, 11 | mutations: { 12 | setIsToken (state, data) { 13 | state.isToken = data 14 | }, 15 | setShowPageLoadScrollBar (state, data) { 16 | state.showPageLoadScrollBar = !!data 17 | } 18 | }, 19 | actions: { 20 | // 清除token 21 | clearToken ({ commit }) { 22 | localStorage.removeItem('token') 23 | commit('setIsToken', false) 24 | }, 25 | // 获取文章列表 26 | async getArtList (state, page) { 27 | return await list(page) 28 | }, 29 | /** 30 | * 把日期变成 月份 哪一天 年份 31 | */ 32 | async dataHandle(state, result) { 33 | const arr = ['', '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'] 34 | let [y, m, other] = result.createdAt.split("-") 35 | const d = other.split(" ")[0] 36 | m = arr[parseInt(m)] 37 | const resultDate = [y, m, d] 38 | const keys = ['year', 'month', 'day'] 39 | resultDate.forEach((item, index) => result[keys[index]] = resultDate[index]) 40 | return result 41 | } 42 | }, 43 | modules: {} 44 | }); 45 | -------------------------------------------------------------------------------- /client/src/utils/request.js: -------------------------------------------------------------------------------- 1 | // 发送请求的时候,如果有token,需要附带在请求头中 2 | // 响应的时候,如果有token,保存到本地 3 | // 响应的时候,如果响应的消息码是403( 没有token,token失效),在本地删除token 4 | import axios from 'axios' 5 | import { Message } from "element-ui" 6 | 7 | const baseURL = process.env.VUE_APP_URL 8 | 9 | const ins = axios.create({ 10 | baseURL, 11 | timeout: 15000, 12 | withCredentials: true 13 | }) 14 | 15 | ins.interceptors.request.use(req => { 16 | const token = localStorage.getItem("token") 17 | if (token) { 18 | req.headers['authorization'] = "bearer " + token 19 | } 20 | return req 21 | },err => { 22 | return Promise.reject(err) 23 | }) 24 | 25 | ins.interceptors.response.use(resp => { 26 | // if (resp.headers.authorization) { 27 | // localStorage.setItem("token", resp.headers.authorization) 28 | // } 29 | const data = resp.data 30 | if (data.code !== 200 ) { 31 | Message({ 32 | type: 'error', 33 | message: data.msg, 34 | offset: 60 35 | }) 36 | return Promise.reject(data) 37 | } 38 | return resp 39 | }, err => { 40 | if (err.response.status === 403) { 41 | localStorage.removeItem("token") 42 | localStorage.removeItem('userInfo') 43 | Message({ 44 | type: 'error', 45 | message: '登陆已过期,请重新登陆', 46 | offset: 60 47 | }) 48 | } 49 | return Promise.reject(err) 50 | }) 51 | 52 | export default ins -------------------------------------------------------------------------------- /client/src/views/ArticleList/components/scrollBar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 30 | 31 | -------------------------------------------------------------------------------- /routes/api/ossUpload.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | const path = require("path") 3 | const multer = require('koa-multer'); 4 | const { getResult } = require('../getSendMessage') 5 | const fs = require('fs') 6 | let OSS = require('ali-oss') 7 | 8 | const ossEncrypt = require('../../encrypt/ossEncrypt') 9 | 10 | let client = new OSS(ossEncrypt); 11 | 12 | const storage = multer.diskStorage({ 13 | destination: (req, file, cb) => { 14 | cb(null, path.resolve(__dirname, '../../public', "upload")) 15 | }, 16 | filename: (req, file, cb) => { 17 | // 时间戳-6位随机字符.文件后缀 18 | cb(null, `${Date.now()}-${Math.random().toString(36).slice(-6)}${path.extname(file.originalname)}`) 19 | } 20 | }) 21 | const upload = multer({ storage }); 22 | let ossUpload = new Router() 23 | 24 | ossUpload.post("/ossUpload", upload.single('file'), async ctx => { 25 | const filename = ctx.req.file.filename 26 | const url = await put(filename) 27 | ctx.body = getResult(url) 28 | }) 29 | 30 | 31 | async function put(filename) { 32 | const localPath = path.resolve(__dirname, '../../public', `upload/${filename}`) 33 | try { 34 | let result = await client.put(`image/${filename}`, localPath); 35 | fs.unlink(localPath, () => {}) 36 | return result.url 37 | } catch (err) { 38 | console.log(err); 39 | } 40 | } 41 | module.exports = ossUpload -------------------------------------------------------------------------------- /services/myCommServ.js: -------------------------------------------------------------------------------- 1 | const myWord = require('../models/tables/myWord') 2 | const User = require('../models/tables/user') 3 | const { limitComment } = require('./utils') 4 | 5 | exports.addMyComment = async function (commentInfo, ctx) { 6 | const isAllow = limitComment(ctx, 2, 2) // 2分钟内不能超过两条 7 | if (isAllow) { 8 | const ins = await myWord.create(commentInfo) 9 | return ins.toJSON() 10 | } else { 11 | return 12 | } 13 | } 14 | 15 | exports.getMyCommentList = async function (page = 1, limit = 10) { 16 | const result = await myWord.findAndCountAll({ 17 | offset: (page - 1) * limit, 18 | limit: +limit, 19 | order: [ 20 | ['createdAt', 'DESC'] 21 | ], 22 | include: [ 23 | { 24 | model: User, 25 | attributes: ['userName', 'avatar', 'id'], 26 | required: false 27 | } 28 | ] 29 | }) 30 | const { rows, count } = result 31 | let len = rows.length 32 | if (rows.length === 0 && !rows) return { total: 0, datas: [], len } // 如果没有文章 33 | rows.forEach(item => item.dataValues.index = page - 1) 34 | return { 35 | total: count, 36 | datas: rows, 37 | len 38 | } 39 | } 40 | 41 | // 删除文章 42 | exports.deleteWord = async function (id) { 43 | const result = await myWord.findByPk(id) 44 | if (result) { // 只有查到了 45 | await result.destroy() 46 | return true 47 | } else { 48 | return false 49 | } 50 | } -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "autoprefixer": "^9.8.4", 12 | "axios": "^0.19.2", 13 | "core-js": "^3.6.5", 14 | "css-loader": "^3.6.0", 15 | "element-ui": "^2.13.2", 16 | "github-markdown-css": "^4.0.0", 17 | "highlight.js": "^10.1.2", 18 | "marked": "^1.1.1", 19 | "parallax-js": "^3.1.0", 20 | "postcss-px2rem": "^0.3.0", 21 | "vue": "^2.6.11", 22 | "vue-router": "^3.2.0", 23 | "vuex": "^3.4.0", 24 | "webpack-bundle-analyzer": "^3.8.0", 25 | "wowjs": "^1.1.3" 26 | }, 27 | "devDependencies": { 28 | "@vue/cli-plugin-babel": "^4.4.0", 29 | "@vue/cli-plugin-eslint": "^4.4.0", 30 | "@vue/cli-plugin-router": "^4.4.0", 31 | "@vue/cli-plugin-vuex": "^4.4.0", 32 | "@vue/cli-service": "^4.4.0", 33 | "@vue/eslint-config-prettier": "^6.0.0", 34 | "babel-eslint": "^10.1.0", 35 | "compression-webpack-plugin": "^5.0.1", 36 | "eslint": "^6.7.2", 37 | "eslint-plugin-prettier": "^3.1.3", 38 | "eslint-plugin-vue": "^6.2.2", 39 | "less": "^3.0.4", 40 | "less-loader": "^5.0.0", 41 | "mini-css-extract-plugin": "^1.2.1", 42 | "prettier": "^1.19.1", 43 | "style-resources-loader": "^1.3.3", 44 | "vue-cli-plugin-style-resources-loader": "^0.1.4", 45 | "vue-template-compiler": "^2.6.11" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /backstage/src/views/AboutMe/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 51 | 52 | -------------------------------------------------------------------------------- /client/src/components/BackTop/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | 38 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <% for (var i in htmlWebpackPlugin.options.cdn && 14 | htmlWebpackPlugin.options.cdn.css) { %> 15 | 19 | <% } %> 20 | 漆黑之牙 21 | 22 | 23 | 26 |
27 | 28 | 29 | 30 | <% for (var i in htmlWebpackPlugin.options.cdn && 31 | htmlWebpackPlugin.options.cdn.js) { %> 32 | 33 | <% } %> 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /client/src/components/Button/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 23 | -------------------------------------------------------------------------------- /client/src/router/routes.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | path: "/", 4 | name: "Home", 5 | component: () => import(/* webpackChunkName: "main" */ "../views/Home"), 6 | meta: { 7 | title: '漆黑之牙' 8 | } 9 | }, 10 | { 11 | path: "/exhibit", 12 | name: "Exhibit", 13 | component: () => import(/* webpackChunkName: "exhibit" */ "../views/Exhibit"), 14 | meta: { 15 | title: '展览 | 漆黑之牙' 16 | } 17 | }, 18 | { 19 | path: "/articleList", 20 | name: "ArticleList", 21 | component: () => import(/* webpackChunkName: "article" */ "../views/ArticleList"), 22 | meta: { 23 | title: 'Article | 漆黑之牙' 24 | } 25 | }, 26 | { 27 | path: '/detail/:id', 28 | name: 'Detail', 29 | component: () => import(/* webpackChunkName: "article" */ "../views/ArticleList/detail"), 30 | }, 31 | { 32 | path: "/demo", 33 | name: "Demo", 34 | component: () => import(/* webpackChunkName: "other" */ "../views/Demo"), 35 | meta: { 36 | title: 'Demo | 漆黑之牙' 37 | } 38 | }, 39 | { 40 | path: "/self", 41 | name: "Self", 42 | component: () => import(/* webpackChunkName: "other" */ "../views/SelfInfo") 43 | }, 44 | { 45 | path: "/message", 46 | name: "Message", 47 | component: () => import(/* webpackChunkName: "article" */ "../views/MessageBoard") 48 | }, 49 | { 50 | path: "/rainy", 51 | name: "Rainy", 52 | component: () => import(/* webpackChunkName: "rainy" */ "../views/Rainy") 53 | }, 54 | { 55 | path: "/login", 56 | name: "Login", 57 | component: () => import(/* webpackChunkName: "other" */ "../views/Login") 58 | }, 59 | ] -------------------------------------------------------------------------------- /routes/captchaMiddleware.js: -------------------------------------------------------------------------------- 1 | const svgCaptcha = require('svg-captcha'); 2 | const Router = require('koa-router') 3 | const { getErr } = require('./getSendMessage') 4 | let code = new Router() 5 | 6 | code.get('/captcha', async ctx => { 7 | const captcha = svgCaptcha.createMathExpr({ 8 | width: 100, 9 | height: 50, 10 | ignoreChars: '0oO1ilI', // 排除 0oO1ilI 11 | noise: 2, // 干扰线条数量 12 | color: true // 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有 13 | }) 14 | ctx.session.captcha = captcha.text.toLocaleLowerCase() 15 | ctx.set('Content-Type', 'image/svg+xml') 16 | ctx.body = captcha.data 17 | }) 18 | 19 | exports.captchaHandler = async (ctx, next) => { 20 | if (ctx.request.url !== '/captcha') { 21 | await next() 22 | return 23 | } 24 | if (!ctx.session.records) { 25 | ctx.session.records = [] 26 | } 27 | const now = Date.now() 28 | ctx.session.records.push(now) 29 | // 如果在一小段时间中请求达到了一定的数量,就可能是机器 30 | const duration = 10000; 31 | const repeat = 3; 32 | ctx.session.records = ctx.session.records.filter(time => now - time < duration) // 如果10s内请求大于3次 33 | if (ctx.session.records.length >= repeat || 'captcha' in ctx.request.body) { // 就要出现验证码 34 | validateCaptcha(ctx, next) 35 | } else { 36 | await next() 37 | } 38 | } 39 | 40 | async function validateCaptcha (ctx, next) { 41 | const reqCaptcha = ctx.request.body.captcha ? ctx.request.body.captcha.toLocaleLowerCase() : '' // 如果用户传了验证码 42 | if (reqCaptcha !== ctx.session.captcha) { // 验证码不对 43 | ctx.status = 401 44 | // ctx.body = getErr('验证码有问题', 401) 45 | await next() 46 | } else { 47 | await next() 48 | } 49 | } 50 | 51 | 52 | exports.code = code -------------------------------------------------------------------------------- /services/commentServ.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/tables/user') 2 | const Article = require('../models/tables/article') 3 | const ArticleWord = require('../models/tables/articleWord') 4 | const { limitComment } = require('./utils') 5 | 6 | exports.addComment = async function (commentInfo, ctx) { 7 | console.log(commentInfo) 8 | const isAllow = limitComment(ctx, 2, 2) // 2分钟内不能超过两条 9 | if (isAllow) { 10 | const ins = await ArticleWord.create(commentInfo) 11 | return ins.toJSON() 12 | } else { 13 | return 14 | } 15 | } 16 | 17 | exports.getCommentList = async function (page = 1, limit = 10, articleId) { 18 | const result = await ArticleWord.findAndCountAll({ 19 | offset: (page - 1) * limit, 20 | limit: +limit, 21 | order: [ 22 | ['createdAt', 'DESC'] 23 | ], 24 | where: { 25 | articleId 26 | }, 27 | include: [ 28 | { 29 | model: User, 30 | attributes: ['userName', 'avatar', 'id'], 31 | required: false 32 | } 33 | ] 34 | }) 35 | const { rows, count } = result 36 | let len = rows.length 37 | if (rows.length === 0 && !rows) return { total: 0, datas: [], len } // 如果没有文章 38 | const datas = handleCommentList(rows) 39 | return { 40 | total: count, 41 | datas, 42 | len 43 | } 44 | } 45 | 46 | 47 | function handleCommentList (datas) { 48 | const listArr = datas.map(row => { 49 | row.dataValues.children = datas.filter(item => row.id === item.floorId) 50 | return row 51 | }) // 把楼层和子楼层分组 52 | return listArr.filter(item => !item.floorId) 53 | } -------------------------------------------------------------------------------- /backstage/src/components/markdown.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 59 | 60 | -------------------------------------------------------------------------------- /client/src/views/Home/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 24 | -------------------------------------------------------------------------------- /routes/api/user.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | const User = require('../../services/userServ') 3 | const { apiHandle } = require('../getSendMessage') 4 | const jwt = require('../jwt'); 5 | let user = new Router() 6 | 7 | // 增加一个用户 8 | user.post('/addUser', async ctx => { 9 | const bodyData = ctx.request.body 10 | const result = await User.addUser(bodyData) 11 | apiHandle(ctx, result, '该账号已被使用,请换个账号') 12 | }) 13 | 14 | // 登录 15 | user.post('/login', async ctx => { 16 | const { account, password } = ctx.request.body 17 | const result = await User.login(account, password) 18 | apiHandle(ctx, result, '账号不存在, 请先注册账号', ( {id} ) => { 19 | jwt.publish(ctx, undefined, { id }) 20 | }) 21 | }) 22 | 23 | // 按我博客逻辑来说这个注册是修改用户信息 24 | user.post('/registry', async ctx => { 25 | const bodyData = ctx.request.body 26 | if (bodyData.captcha !== ctx.session.captcha) { 27 | apiHandle(ctx, '', '验证码错误') 28 | return 29 | } 30 | const result = await User.registry(bodyData) 31 | apiHandle(ctx, result, '该账号已被使用,请换个账号') 32 | }) 33 | 34 | // 不需要验证码的修改用户信息 35 | user.post('/updateUserInfo', async ctx => { 36 | const bodyData = ctx.request.body 37 | const result = await User.updateUserInfo(bodyData) 38 | apiHandle(ctx, result, '失败了...请重试呜呜') 39 | }) 40 | 41 | // 获取登录的用户个人信息 42 | user.get("/whoami", async ctx => { 43 | const result = await User.getSelf(ctx.request.userId) 44 | apiHandle(ctx, result, '') 45 | }) 46 | 47 | // 获取后台系统首页信息 48 | user.get("/getHomeInfo", async ctx => { 49 | const result = await User.getHomeInfo(ctx.request.userId) 50 | apiHandle(ctx, result, '还没有文章呢,赶紧去发一篇文章吧..') 51 | }) 52 | 53 | user.get("/getAboutMe", async ctx => { 54 | const result = await User.getAboutMe(1) 55 | apiHandle(ctx, result, '获取个人信息失败...请重试呜呜') 56 | }) 57 | 58 | module.exports = user -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | 2 | import Vue from "vue"; 3 | if (process.env.NODE_ENV !== 'production') { 4 | 5 | import('element-ui').then(ele => { 6 | Vue.use(ele) 7 | }) 8 | import('element-ui/lib/theme-chalk/index.css') 9 | 10 | } 11 | 12 | import App from "./App.vue"; 13 | 14 | import router from "./router/handler"; 15 | 16 | import store from "./store"; 17 | 18 | 19 | // 引入全局组件Header 20 | import Header from "@c/Header" 21 | Vue.component('Header', Header) 22 | // 引入全局组件Header 23 | 24 | // 引入 iconfont 25 | import '@/assets/font/iconfont.css'; 26 | // 引入 iconfont 27 | 28 | // 高亮富文本 29 | import hljs from 'highlight.js/lib/core'; 30 | import javascript from 'highlight.js/lib/languages/javascript'; 31 | hljs.registerLanguage('javascript', javascript); 32 | Vue.directive('highlight',function (el) { 33 | let blocks = el.querySelectorAll('pre code'); 34 | blocks.forEach((block)=>{ 35 | hljs.highlightBlock(block) 36 | }) 37 | }) 38 | // 高亮富文本 39 | 40 | // 引入markdown-css 41 | import 'github-markdown-css/github-markdown.css' 42 | // 引入markdown-css 43 | 44 | Vue.config.productionTip = false; 45 | 46 | // 设置请求域名 47 | let mainUrl = process.env.VUE_APP_URL 48 | Vue.prototype.mainUrl = mainUrl 49 | 50 | // 添加百度统计 51 | var _hmt = _hmt || []; 52 | window._hmt = _hmt; // 必须把_hmt挂载到window下,否则找不到 53 | (function () { 54 | var hm = document.createElement("script"); 55 | hm.src = "https://hm.baidu.com/hm.js?47c4ba855b7925913cdf701208a1dc0f"; 56 | var s = document.getElementsByTagName("script")[0]; 57 | s.parentNode.insertBefore(hm, s); 58 | })(); 59 | 60 | new Vue({ 61 | router, 62 | store, 63 | render: h => h(App) 64 | }).$mount("#app"); 65 | 66 | 67 | if (process.env.NODE_ENV == 'development') { 68 | Vue.config.devtools = true 69 | Vue.config.productionTip = false 70 | } else { 71 | Vue.config.devtools = false 72 | Vue.config.productionTip = false 73 | } -------------------------------------------------------------------------------- /client/src/views/Exhibit/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 58 | -------------------------------------------------------------------------------- /client/src/utils/index.js: -------------------------------------------------------------------------------- 1 | import { Message } from 'element-ui' 2 | export function debounce(fn, delay) { 3 | var delay = delay || 200; 4 | var timer; 5 | return function () { 6 | var th = this; 7 | var args = arguments; 8 | if (timer) { 9 | clearTimeout(timer); 10 | } 11 | timer = setTimeout(function () { 12 | timer = null; 13 | fn.apply(th, args); 14 | }, delay); 15 | }; 16 | } 17 | 18 | export function throttle(fn, delay) { 19 | var last; 20 | var timer; 21 | var interval = interval || 200; 22 | return function () { 23 | var th = this; 24 | var args = arguments; 25 | var now = +new Date(); 26 | if (last && now - last < interval) { 27 | clearTimeout(timer); 28 | timer = setTimeout(function () { 29 | last = now; 30 | fn.apply(th, args); 31 | }, interval); 32 | } else { 33 | last = now; 34 | fn.apply(th, args); 35 | } 36 | } 37 | } 38 | export async function valiFunc(arr) { 39 | let res = true 40 | for (let item of arr) { 41 | if (!item.data || item.data.length === 0) { 42 | res = false 43 | Message({ 44 | message: item.msg, 45 | type: 'error', 46 | offset: 60 47 | }) 48 | break 49 | } 50 | } 51 | return res 52 | } 53 | 54 | export function bottomHandle (isScroll, callback) { 55 | // window.onscroll = throttle(scrollChange.bind(null, isScroll, callback), 200) 56 | window.addEventListener('scroll', throttleScroll(isScroll, callback), 200) 57 | } 58 | 59 | export function clearBottomHandle () { 60 | window.removeEventListener('scroll', throttleScroll) 61 | } 62 | 63 | 64 | function scrollChange (isScroll, callback) { 65 | if (!isScroll()) return 66 | const scrollTop = Math.ceil(document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset) 67 | const windowHeight = document.documentElement.clientHeight || document.body.clientHeight 68 | const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight || window.scrollHeight 69 | if (scrollTop + windowHeight >= scrollHeight) { 70 | console.log(scrollTop, windowHeight, scrollHeight) 71 | callback() 72 | } 73 | } 74 | 75 | // 节流滚动方法 76 | const throttleScroll = (isScroll, callback) => throttle(scrollChange.bind(null, isScroll, callback), 200) 77 | 78 | -------------------------------------------------------------------------------- /backstage/src/views/Comment/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 68 | 69 | -------------------------------------------------------------------------------- /client/src/components/loginRegistration/loginBg.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 36 | 37 | -------------------------------------------------------------------------------- /client/src/components/Contact/index.vue: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /client/src/views/MessageBoard/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 83 | 84 | -------------------------------------------------------------------------------- /client/src/views/Login/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 62 | 63 | -------------------------------------------------------------------------------- /client/src/assets/js/flexible.js: -------------------------------------------------------------------------------- 1 | !function(){var a="@charset \"utf-8\";html{color:#000;background:#fff;overflow-y:scroll;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}html *{outline:0;-webkit-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}html,body{font-family:sans-serif}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td,hr,button,article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{margin:0;padding:0}input,select,textarea{font-size:100%}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}abbr,acronym{border:0;font-variant:normal}del{text-decoration:line-through}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:500}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:500}q:before,q:after{content:''}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}a:hover{text-decoration:underline}ins,a{text-decoration:none}",b=document.createElement("style");if(document.getElementsByTagName("head")[0].appendChild(b),b.styleSheet)b.styleSheet.disabled||(b.styleSheet.cssText=a);else try{b.innerHTML=a}catch(c){b.innerText=a}}();!function(a,b){function c(){var b=f.getBoundingClientRect().width;b/i>540&&(b=540*i);var c=b/10;f.style.fontSize=c+"px",k.rem=a.rem=c}var d,e=a.document,f=e.documentElement,g=e.querySelector('meta[name="viewport"]'),h=e.querySelector('meta[name="flexible"]'),i=0,j=0,k=b.flexible||(b.flexible={});if(g){ 2 | // console.warn("灏嗘牴鎹凡鏈夌殑meta鏍囩鏉ヨ缃缉鏀炬瘮渚�"); 3 | var l=g.getAttribute("content").match(/initial\-scale=([\d\.]+)/);l&&(j=parseFloat(l[1]),i=parseInt(1/j))}else if(h){var m=h.getAttribute("content");if(m){var n=m.match(/initial\-dpr=([\d\.]+)/),o=m.match(/maximum\-dpr=([\d\.]+)/);n&&(i=parseFloat(n[1]),j=parseFloat((1/i).toFixed(2))),o&&(i=parseFloat(o[1]),j=parseFloat((1/i).toFixed(2)))}}if(!i&&!j){var p=(a.navigator.appVersion.match(/android/gi),a.navigator.appVersion.match(/iphone/gi)),q=a.devicePixelRatio;i=p?q>=3&&(!i||i>=3)?3:q>=2&&(!i||i>=2)?2:1:1,j=1/i}if(f.setAttribute("data-dpr",i),!g)if(g=e.createElement("meta"),g.setAttribute("name","viewport"),g.setAttribute("content","initial-scale="+j+", maximum-scale="+j+", minimum-scale="+j+", user-scalable=no"),f.firstElementChild)f.firstElementChild.appendChild(g);else{var r=e.createElement("div");r.appendChild(g),e.write(r.innerHTML)}a.addEventListener("resize",function(){clearTimeout(d),d=setTimeout(c,300)},!1),a.addEventListener("pageshow",function(a){a.persisted&&(clearTimeout(d),d=setTimeout(c,300))},!1),"complete"===e.readyState?e.body.style.fontSize=12*i+"px":e.addEventListener("DOMContentLoaded",function(){e.body.style.fontSize=12*i+"px"},!1),c(),k.dpr=a.dpr=i,k.refreshRem=c,k.rem2px=function(a){var b=parseFloat(a)*this.rem;return"string"==typeof a&&a.match(/rem$/)&&(b+="px"),b},k.px2rem=function(a){var b=parseFloat(a)/this.rem;return"string"==typeof a&&a.match(/px$/)&&(b+="rem"),b}}(window,window.lib||(window.lib={})); -------------------------------------------------------------------------------- /backstage/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import Layout from '@/views/Layout' 4 | Vue.use(VueRouter); 5 | 6 | const routes = [ 7 | { 8 | path: "/", 9 | redirect: 'login', 10 | hidden: true, 11 | meta: { 12 | name: '主页', 13 | icon: 'icon-zhengli' 14 | } 15 | }, 16 | { 17 | path: "/login", 18 | name: "Login", 19 | hidden: true, 20 | meta: { 21 | name: "登陆", 22 | icon: 'icon-dili' 23 | }, 24 | component: () => import(/* webpackChunkName: "login" */ "../views/Login/index.vue") 25 | }, 26 | { 27 | path: "/home", 28 | name: "Home", 29 | redirect: 'index', 30 | meta: { 31 | name: '主页', 32 | icon: 'el-icon-s-home' 33 | }, 34 | component: Layout, 35 | children: [ 36 | { 37 | path: "/index", 38 | name: "index", 39 | component: () => import(/* webpackChunkName: "home" */ "../views/Work") 40 | } 41 | ] 42 | }, 43 | { 44 | path: "/article", 45 | name: "Article", 46 | redirect: 'addArt', 47 | meta: { 48 | name: '发布文章', 49 | icon: 'el-icon-position' 50 | }, 51 | component: Layout, 52 | children: [ 53 | { 54 | path: "/addArt", 55 | name: "AddArt", 56 | component: () => import( /* webpackChunkName: "article" */"../views/AddArt") 57 | } 58 | ] 59 | }, 60 | { 61 | path: "/about", 62 | name: "About", 63 | redirect: 'aboutMe', 64 | meta: { 65 | name: '关于我', 66 | icon: 'el-icon-watermelon' 67 | }, 68 | component: Layout, 69 | children: [ 70 | { 71 | path: "/aboutMe", 72 | name: "aboutMe", 73 | component: () => import(/* webpackChunkName: "about" */"../views/AboutMe") 74 | } 75 | ] 76 | }, 77 | { 78 | path: "/comment", 79 | name: "Comment", 80 | redirect: 'commentList', 81 | meta: { 82 | name: '评论列表', 83 | icon: 'el-icon-chat-dot-square' 84 | }, 85 | component: Layout, 86 | children: [ 87 | { 88 | path: "/commentList", 89 | name: "CommentList", 90 | component: () => import(/* webpackChunkName: "comment" */ "../views/Comment") 91 | } 92 | ] 93 | }, 94 | { 95 | path: "/set", 96 | name: "Set", 97 | redirect: 'setting', 98 | meta: { 99 | name: '设置', 100 | icon: 'el-icon-setting' 101 | }, 102 | component: Layout, 103 | children: [ 104 | { 105 | path: "/setting", 106 | name: "Setting", 107 | component: () => import(/* webpackChunkName: "set" */"../views/Setting") 108 | } 109 | ] 110 | }, 111 | ]; 112 | 113 | let base = '' 114 | process.env.NODE_ENV === 'production' ? base = '/back/' : base = '' 115 | 116 | const router = new VueRouter({ 117 | mode: 'hash', 118 | base, 119 | routes 120 | }); 121 | 122 | 123 | export default router; 124 | -------------------------------------------------------------------------------- /backstage/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CompressionPlugin = require("compression-webpack-plugin"); // gzip压缩 3 | 4 | 5 | module.exports = { 6 | // 基本路径 7 | publicPath: process.env.NODE_ENV === 'production' ? '/back/' : '/', 8 | // 输出文件目录 9 | outputDir: process.env.NODE_ENV === 'production' ? path.resolve(__dirname, '../public/back') : 'devdist', 10 | // eslint-loader 是否在保存的时候检查 11 | lintOnSave: false, 12 | /** 13 | * webpack配置,see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md 14 | **/ 15 | configureWebpack: (config) => { 16 | config.resolve = { // 配置解析别名 17 | extensions: ['.js', '.json', '.vue'], 18 | alias: { 19 | 'vue': 'vue/dist/vue.js', 20 | '@': path.resolve(__dirname, './src'), 21 | '@img': path.resolve(__dirname, './src/assets/img'), 22 | '@c': path.resolve(__dirname, './src/components'), 23 | } 24 | }, 25 | // config.externals = { // 不打包elementUi和Vue 使用cdn的方式引入 26 | // "element-ui": "ELEMENT", 27 | // 'vue': 'Vue' 28 | // }, 29 | process.env.NODE_ENV === 'production' ? config.mode = 'production' : config.mode = 'development' 30 | // config.optimization.minimizer[0].options.terserOptions.compress = { drop_console: process.env.NODE_ENV === 'production', drop_debugger: false, pure_funcs: ['console.log'] } // 移除console 31 | config.plugins.push(new CompressionPlugin({ 32 | filename: '[path].gz[query]', 33 | //压缩算法 34 | algorithm: 'gzip', 35 | //匹配文件 36 | test: /\.js$|\.css$|\.html$|/, 37 | //压缩超过此大小的文件,以字节为单位 38 | threshold: 1024, 39 | minRatio: 0.8, 40 | //删除原始文件只保留压缩后的文件 41 | // deleteOriginalAssets: process.env.NODE_ENV === 'production' 42 | })) 43 | }, 44 | // 生产环境是否生成 sourceMap 文件 45 | productionSourceMap: false, 46 | // css相关配置 47 | css: { 48 | // 是否使用css分离插件 ExtractTextPlugin 49 | extract: true, 50 | // 开启 CSS source maps? 51 | sourceMap: false, 52 | // css预设器配置项 53 | loaderOptions: { 54 | scss: { 55 | prependData: `@import "./src/styles/main.scss";` 56 | } 57 | } 58 | }, 59 | // use thread-loader for babel & TS in production build 60 | // enabled by default if the machine has more than 1 cores 61 | parallel: require('os').cpus().length > 1, 62 | /** 63 | * PWA 插件相关配置,see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa 64 | */ 65 | pwa: {}, 66 | // webpack-dev-server 相关配置 67 | // devServer: { 68 | // hot: true, // 开启热加载 69 | // hotOnly: false, 70 | // proxy: { // 设置代理 71 | // '/api': { 72 | // target: 'http://localhost:5008', //API服务器的地址 73 | // changeOrigin: true 74 | // } 75 | // }, 76 | // overlay: { // 全屏模式下是否显示脚本错误 77 | // warnings: true, 78 | // errors: true 79 | // }, 80 | // before: app => {} 81 | // }, 82 | /** 83 | * 第三方插件配置 84 | */ 85 | pluginOptions: { 86 | 'style-resources-loader': { 87 | preProcessor: 'less', 88 | // 需要通过less-loader自动引入的资源,集合类型 89 | patterns: [path.resolve(__dirname, 'src/style/normalize.less')] 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /services/userServ.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/tables/user') 2 | const Article = require('../models/tables/article') 3 | const MyWord = require('../models/tables/myWord') 4 | const moment = require('moment') 5 | const { getRandom } = require('./utils') 6 | 7 | // 增加一个用户 8 | exports.addUser = async function (adminObj) { 9 | const [result, created] = await User.findOrCreate({ 10 | where: { account: adminObj.account }, 11 | attributes: { 12 | exclude: ['password'] 13 | }, 14 | defaults: { ...adminObj } 15 | }) 16 | // 如果已经被创建了 17 | if (!created) return 18 | const avatarSrc = `https://efiles.oss-cn-shenzhen.aliyuncs.com/file/avatar${getRandom(1, 9)}.jpg` 19 | result.avatar = avatarSrc 20 | result.save() 21 | return result.toJSON() 22 | } 23 | // 用户登陆 24 | exports.login = async function (account, password) { 25 | const result = await User.findOne({ 26 | where: { 27 | account, 28 | password 29 | }, 30 | attributes: { 31 | exclude: ['deletedAt'] 32 | } 33 | }) 34 | if (result && result.account === account && result.password === password) { 35 | const val = result.toJSON() 36 | delete val.password 37 | return val 38 | } 39 | return null 40 | } 41 | // 注册用户(按我的博客逻辑是 修改用户信息) 42 | exports.registry = async function (data) { 43 | const result = await User.findByPk(data.id) 44 | if (!result) return // 没有这个ID 45 | if (result.account === data.account) return // 账号已存在 46 | for (let key in data) { 47 | if (data[key]) { 48 | result[key] = data[key] 49 | } 50 | } 51 | await result.save() 52 | delete result.password 53 | delete result.deletedAt 54 | return result 55 | } 56 | 57 | exports.updateUserInfo = async function (data) { 58 | const result = await User.findByPk(data.id) 59 | if (!result) return // 没有这个ID 60 | for (let key in data) { 61 | if (data[key]) { 62 | result[key] = data[key] 63 | } 64 | } 65 | await result.save() 66 | delete result.password 67 | delete result.deletedAt 68 | return result 69 | } 70 | 71 | // 获取自己 72 | exports.getSelf = async function (id) { 73 | const result = await User.findByPk(id) 74 | if (!result) return 75 | let val = result.toJSON() 76 | delete val.password 77 | delete val.deletedAt 78 | return val 79 | } 80 | 81 | exports.getHomeInfo = async function() { 82 | const result = await Article.findAndCountAll({ 83 | attributes: ['createdAt'], 84 | order: [ 85 | ['createdAt', 'DESC'] 86 | ] 87 | }) 88 | const { count, rows } = result 89 | if (rows.length === 0) return {articleCount: 0, articleDiff: 0} 90 | const x = new moment() 91 | const y = new moment(rows[0].createdAt) 92 | const articleDiff = x.diff(y, 'd') 93 | 94 | const wordCount = await MyWord.count() 95 | 96 | return { 97 | articleCount: count, 98 | articleDiff, 99 | wordCount 100 | } 101 | } 102 | 103 | // 获取指定的用户信息 104 | exports.getAboutMe = async function (id) { 105 | const result = await User.findOne({ 106 | where: { id: 1 }, 107 | attributes: ['aboutMe'] 108 | }) 109 | return result.toJSON() 110 | } 111 | -------------------------------------------------------------------------------- /backstage/src/views/Work/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 52 | 53 | -------------------------------------------------------------------------------- /backstage/src/views/Layout/components/nav.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | 52 | -------------------------------------------------------------------------------- /backstage/src/views/Login/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 64 | 129 | -------------------------------------------------------------------------------- /services/articleServ.js: -------------------------------------------------------------------------------- 1 | const Article = require('../models/tables/article') 2 | const ArticleWord = require('../models/tables/articleWord') 3 | const User = require('../models/tables/user') 4 | // 增加文章 5 | exports.addArticle = async function (artObj) { 6 | const ins = await Article.create(artObj) 7 | return ins.toJSON() 8 | } 9 | // 删除文章 10 | exports.deleteArt = async function (id) { 11 | const result = await Article.findByPk(id) 12 | if (result) { // 只有查到了 13 | await result.destroy() 14 | return true 15 | } else { 16 | return false 17 | } 18 | } 19 | // 获取文章列表 20 | exports.getArtList = async function (page = 1, limit = 10, isHome = false) { 21 | const result = await Article.findAndCountAll({ 22 | offset: ( page - 1 ) * limit, 23 | limit: +limit, 24 | order: [ 25 | ['createdAt', 'DESC'] 26 | ], 27 | attributes: { 28 | exclude: ['content'] 29 | }, 30 | include: [ 31 | { 32 | model: ArticleWord 33 | } 34 | ] 35 | }) 36 | let { rows, count } = result 37 | let len = rows.length 38 | if (rows.length === 0 && !rows) return { total: 0, datas: [], len } // 如果没有文章 39 | rows.forEach(item => item.dataValues.index = page - 1) 40 | if (!isHome) { rows = listGroup(rows) } 41 | count = await Article.count() 42 | return { 43 | total: count, 44 | datas: rows, 45 | len 46 | } 47 | } 48 | 49 | // 把文章数据进行时间分组 50 | function listGroup (rows) { 51 | const yearMap = new Map() 52 | const yearArr = [] // 记录文章出现的年份 53 | const monthArr = ['12', '11', '10', '09', '08', '07', '06', '05', '04', '03', '02', '01'] 54 | const yearGroupArr = [] // 把文章按照年份分组 55 | rows.forEach(item => { 56 | const year = item.createdAt.split("-")[0] 57 | !yearMap.has(year) ? yearMap.set(year, year) : '' // 如果年份没出现过,记录起来 58 | }) 59 | yearMap.forEach(item => yearArr.push(item)) // 把年份处理成数组 60 | 61 | yearArr.forEach(year => { // 把文章按年份分组 然后在按月份分组....其实就是按年份和月份降序排序 62 | const resultArr = rows.filter(item => { 63 | const [ rowYear, rowMonth, surplus ] = item.createdAt.split("-") 64 | let rowDay = surplus.split(" ")[0] 65 | rowDay[0] === '0' ? rowDay = rowDay.substr(1, 1) : '' 66 | if (rowYear === year) { 67 | item.dataValues.year = year 68 | } 69 | item.dataValues.month = rowMonth 70 | item.dataValues.day = rowDay 71 | return rowYear === year 72 | }) 73 | if (resultArr.length === 0) return 74 | monthArr.forEach(m => { 75 | const resultMonth = resultArr.filter(d => d.dataValues.month === m) 76 | resultMonth.length !== 0 && yearGroupArr.push(resultMonth) 77 | }) 78 | }) 79 | 80 | return yearGroupArr 81 | } 82 | 83 | /** 84 | * 获取文章详情 85 | * @param {*} id 文章Id 86 | * @param {*} userId 用户Id 87 | */ 88 | exports.getArtDetail = async function (id, userId, ctx) { 89 | let result = await Article.findOne({ 90 | where: { 91 | id 92 | }, 93 | include: [{ 94 | attributes: ['id'], 95 | model: User, 96 | required: false, 97 | through: { 98 | attributes: [] 99 | }, 100 | where: { 101 | id: userId 102 | } 103 | }] 104 | }) 105 | if (!result) return result 106 | result.increment('visitsNum', {by: 1}) 107 | result = result.toJSON() 108 | handlerData(result) 109 | return result 110 | } 111 | // 给数据增加字段 112 | function handlerData (result) { 113 | result.users.length === 0 ? result.isLike = false : result.isLike = true // 如果等于0就是不喜欢该文章 114 | const len = result.content.replace(/<\/?.+?>/g, "").replace(/(\r\n|\n|\r)/gm, "").length 115 | result.textLen = len // 统计字数 116 | delete result.users 117 | } 118 | 119 | exports.likeArt = async function ({ articleId, userId }) { 120 | try { 121 | const artIns = await Article.findByPk(articleId) 122 | const userIns = await User.findByPk(userId) 123 | await artIns.addUser(userIns) 124 | await artIns.increment('likeNum') 125 | return true 126 | } catch (e) { 127 | return false 128 | } 129 | } -------------------------------------------------------------------------------- /client/src/views/Demo/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 60 | 61 | -------------------------------------------------------------------------------- /backstage/src/views/Setting/index.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 101 | 102 | -------------------------------------------------------------------------------- /client/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const isProduction = process.env.NODE_ENV === 'production'; 3 | const CompressionPlugin = require("compression-webpack-plugin"); 4 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 5 | 6 | const cdn = { 7 | externals: { 8 | 'vue': 'Vue', 9 | 'vuex': 'Vuex', 10 | 'vue-router': 'VueRouter', 11 | 'axios': 'axios', 12 | "element-ui": "ELEMENT", 13 | }, 14 | css: [ 15 | 'https://lib.baomitu.com/element-ui/2.13.2/theme-chalk/index.css' 16 | ], 17 | js: [ 18 | 'https://cdn.bootcss.com/vue/2.6.10/vue.min.js', 19 | 'https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js', 20 | 'https://cdn.bootcss.com/vuex/3.1.2/vuex.min.js', 21 | 'https://lib.baomitu.com/element-ui/2.13.2/index.js', 22 | 'https://cdn.bootcss.com/axios/0.19.2/axios.min.js' 23 | ] 24 | } 25 | 26 | module.exports = { 27 | // 基本路径 28 | publicPath: '/', 29 | assetsDir: './', 30 | // 输出文件目录 31 | outputDir: isProduction ? path.resolve(__dirname, '../public/pc') : 'devdist', 32 | // eslint-loader 是否在保存的时候检查 33 | lintOnSave: false, 34 | // 默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。 35 | filenameHashing: true, 36 | // 设置生成的 HTML 中 90 | 91 | -------------------------------------------------------------------------------- /client/src/views/SelfInfo/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 67 | 68 | -------------------------------------------------------------------------------- /client/src/components/GiantScreen/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 69 | -------------------------------------------------------------------------------- /client/src/views/Demo/components/Piano/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 125 | 126 | -------------------------------------------------------------------------------- /client/src/views/MessageBoard/components/messageList.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 65 | 66 | -------------------------------------------------------------------------------- /backstage/src/assets/font/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1599581849729'); /* IE9 */ 3 | src: url('iconfont.eot?t=1599581849729#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), 5 | url('iconfont.woff?t=1599581849729') format('woff'), 6 | url('iconfont.ttf?t=1599581849729') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1599581849729#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-Music1:before { 19 | content: "\e794"; 20 | } 21 | 22 | .icon-yinle:before { 23 | content: "\e60a"; 24 | } 25 | 26 | .icon-jurassic_image:before { 27 | content: "\e69c"; 28 | } 29 | 30 | .icon-zhengli:before { 31 | content: "\e613"; 32 | } 33 | 34 | .icon-dili:before { 35 | content: "\e60c"; 36 | } 37 | 38 | .icon-tiyu:before { 39 | content: "\e60d"; 40 | } 41 | 42 | .icon-shuxue:before { 43 | content: "\e60e"; 44 | } 45 | 46 | .icon-jiaoliu:before { 47 | content: "\e60f"; 48 | } 49 | 50 | .icon-tianwen:before { 51 | content: "\e610"; 52 | } 53 | 54 | .icon-xiaoche:before { 55 | content: "\e611"; 56 | } 57 | 58 | .icon-xiaoyuanka:before { 59 | content: "\e612"; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /client/src/views/ArticleList/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 86 | 87 | -------------------------------------------------------------------------------- /backstage/src/views/AddArt/index.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 137 | 138 | -------------------------------------------------------------------------------- /client/src/views/ArticleList/detail.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 155 | 156 | 214 | -------------------------------------------------------------------------------- /client/src/views/ArticleList/components/messageList.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 79 | 80 | -------------------------------------------------------------------------------- /client/src/views/Rainy/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 97 | 98 | 218 | -------------------------------------------------------------------------------- /client/src/components/loginRegistration/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 130 | -------------------------------------------------------------------------------- /client/src/components/Header/index.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 134 | 135 | -------------------------------------------------------------------------------- /client/src/assets/font/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "iconfont"; 2 | src: url('iconfont.eot?t=1599576878291'); /* IE9 */ 3 | src: url('iconfont.eot?t=1599576878291#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), 5 | url('iconfont.woff?t=1599576878291') format('woff'), 6 | url('iconfont.ttf?t=1599576878291') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ 7 | url('iconfont.svg?t=1599576878291#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .iconfont { 11 | font-family: "iconfont" !important; 12 | font-size: 16px; 13 | font-style: normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-wei-:before { 19 | content: "\e606"; 20 | } 21 | 22 | .icon-user:before { 23 | content: "\e611"; 24 | } 25 | 26 | .icon-zhedie2:before { 27 | content: "\e620"; 28 | } 29 | 30 | .icon-xinheart118:before { 31 | content: "\e755"; 32 | } 33 | 34 | .icon-xin:before { 35 | content: "\e6c2"; 36 | } 37 | 38 | .icon-zanting:before { 39 | content: "\e626"; 40 | } 41 | 42 | .icon-bofang:before { 43 | content: "\e66e"; 44 | } 45 | 46 | .icon-view:before { 47 | content: "\e63f"; 48 | } 49 | 50 | .icon-dianzan:before { 51 | content: "\e644"; 52 | } 53 | 54 | .icon-pinglun:before { 55 | content: "\e614"; 56 | } 57 | 58 | .icon-shijian:before { 59 | content: "\e661"; 60 | } 61 | 62 | .icon-zhucehuise:before { 63 | content: "\e61c"; 64 | } 65 | 66 | .icon-ico-:before { 67 | content: "\e80f"; 68 | } 69 | 70 | .icon-weixin1:before { 71 | content: "\e619"; 72 | } 73 | 74 | .icon-yonghu:before { 75 | content: "\e60a"; 76 | } 77 | 78 | .icon-GitHub:before { 79 | content: "\ea0a"; 80 | } 81 | 82 | .icon-xiangce:before { 83 | content: "\e607"; 84 | } 85 | 86 | .icon-ziyuan:before { 87 | content: "\e623"; 88 | } 89 | 90 | .icon-QQ:before { 91 | content: "\e667"; 92 | } 93 | 94 | .icon-liuyanban:before { 95 | content: "\e610"; 96 | } 97 | 98 | .icon-weixin:before { 99 | content: "\e66c"; 100 | } 101 | 102 | .icon-wenzhangguanli:before { 103 | content: "\e652"; 104 | } 105 | 106 | --------------------------------------------------------------------------------