目录
主页面
功能简介
系统简介
api
数据库表结构
代码目录
运行命令
主要代码
server
apis.js
encry.js
mysql.js
upload.js
client3
index.js
完整代码
多用户系统,用户可以在系统中注册、登录及管理自己的账号、相册及照片。
每个用户都可以管理及维护相册,及相册的备注。
每个用户都可以管理及维护照片,及照片的备注。
相册需要可设置是否公开。
照片是可评论的,只有一级评论(不需要评论分级)。
分享界面(前台界面),需要图片放大预览及轮播图功能。
图片删除需要回收站。
系统采用前后端分离的方式b-s方式,后台使用nodejs技术,数据库采用MySQL系统。前端使用vue3框架搭建。
后端是负责提供接口(api)
用户管理 | ||
---|---|---|
用户注册 | (post)/api/userlogin | 用户名、密码 |
修改密码 | (post)/api/userpasswordmodify | 原始密码、新密码 |
相册管理 | ||
新建相册 | (get)/api/addalbum | 相册名及简介 |
修改相册 | (get)/api/modifyalbum | |
移除相册 | (get)/api/removealbum | 相册必须为空才可以移除 |
照片管理 | ||
上传照片 | (*)/api/addpic | 加上备注 |
修改(备注) | (post)/api/modifyps | 修改备注 |
删除照片 | (get)/api/removepic | |
评论 | ||
新增评论 | (get)/api/addcomment | |
移除评论 | (get) /api/removecomment |
users | ||
---|---|---|
id | 无序号、递增 | |
createAt | 创建时间 | |
updateAt | 最后更新时间 | |
username | 用户名 | |
password | 密码 |
pics | ||
---|---|---|
id | 无序号、递增 | |
createAt | 创建时间 | |
updateAt | 最后更新时间 | |
url | 存放图片上传后相对服务器访问的地址(相对地址) | |
ps | 图片的备注 | |
removed | 图片是否被移除 | |
userid | 照片隶属于哪个用户 |
albums | ||
---|---|---|
id | 无序号、递增 | |
createAt | 创建时间 | |
updateAt | 最后更新时间 | |
title | 相册名称 | |
ps | 备注 | |
userid | 相册隶属于哪个用户 |
comments | ||
---|---|---|
id | 无序号、递增 | |
createAt | 创建时间 | |
updateAt | 最后更新时间 | |
content | 评论内容 | |
userid | 发表评论的用户 | |
picid | 被评论的照片 |
后端启动命令:npm start
前端启动命令:npm run dev
var express = require('express') var router = express.Router() //引入封装的mysql访问函数 const query = require('../utils/mysql') const encry = require('../utils/encry') const jwt = require('jsonwebtoken') const { expressjwt } = require('express-jwt') const key = 'yuaner' //引入上传对象 const upload = require('../utils/upload') //用户注册的api router.post('/userreg', async (req, res) => { //来自url的参数,使用req.query来获取 //来自请求体的参数,使用req.body来获取 const { username, password } = req.body //判断用户名是否重复 const result = await query('select * from users where username=?', [ username, ]) if (result.length > 0) { res.json({ flag: false, msg: '用户名已存在', }) } else { //插入 await query( 'insert into users (username,password,createAt,updateAt) values (?,?,?,?)', [username, encry(password), new Date(), new Date()] ) res.json({ flag: true, msg: '用户注册成功', }) } }) //用户登录 router.post('/userlogin', async (req, res) => { //获取参数 const { username, password } = req.body const result = await query( 'select * from users where username=? and password=?', [username, encry(password)] ) if (result.length > 0) { res.json({ flag: true, msg: '登录成功', token: jwt.sign({ userid: result[0].id }, key, { expiresIn: '10h', }), }) } else { res.json({ flag: false, msg: '用户名或密码错误', }) } }) router.all( '*', expressjwt({ secret: key, algorithms: ['HS256'] }), function (req, res, next) { next() } ) /** * 新建相册 */ router.post('/addalbum', async (req, res) => { //获取数据 const { title, ps } = req.body const userid = req.auth.userid const result = await query( 'select * from albums where title=? and userid=?', [title, userid] ) if (result.length > 0) { res.json({ flag: false, msg: '同名相册已存在', }) } else { await query( 'insert into albums (title,ps,userid,createAt,updateAt) Values(?,?,?,?,?)', [title, ps, userid, new Date(), new Date()] ) res.json({ flag: true, }) } }) /** * 修改相册 * /modifyalbum * 参数 title ,ps,albumid */ router.post('/modifyalbum', async (req, res) => { const { title, ps, albumid } = req.body //从token中获取userid const userid = req.auth.userid const result = await query( 'select * from albums where title=? and userid=? and id<>?', [title, userid, Number(albumid)] ) if (result.length > 0) { res.json({ flag: true, msg: '相册名已存在', }) } else { //进行修改的查询 await query('update albums set title=?,ps=? where id=?', [ title, ps, albumid, ]) res.json({ flag: true, msg: '修改成功', }) } }) /** * 移除相册 * /removealbum * 参数 albumid,userid */ /** router.get('/removealbum', async (req, res) => { //获取参数 const { albumid } = req.body //判断当前相册是否为空 const result = await query( 'select COUNT(*) as count from pics where albumid = ?', [albumid] ) if (result[0].count > 0) { res.json({ flag: false, msg: '相册不为空,请先移除所有照片再删除相册', }) } await query('delete from albums where id = ?', [albumid]) res.json({ flag: true, msg: '相册已删除', }) }) */ router.get('/removealbum', async (req, res) => { //获取参数 let { albumid } = req.query albumid = Number(albumid) //获取userid const userid = req.auth.userid //判断当前相册是否为空 const result = await query( //可以限制为1,不用查询很多,此处用'limit 1'进行优化 'select * from pics where albumid=? limit 1', [albumid] ) if (result.length == 0) { //如果为空则删除 //删除工作不需要赋值,直接wait await query('delete from albums where id=? and userid=?', [ albumid, userid, ]) res.json({ flag: true, msg: '删除成功!', }) } //如果不为空则不能删除 else { res.json({ flag: false, msg: '相册不为空', }) } }) /** * 分页查看相册内的图片列表 * /getPiclist * 参数:albumid、pageIndex、pageRecord、、 * 需要每一页记录数 * 当前页数 */ router.get('/getPicList', async (req, res) => { //获取参数 let { albumid, pageIndex, pageRecord } = req.query const result = await query( 'select * from pics where albumid=? and removed =0 ORDER BY updateAt desc limit ?,? ', [ Number(albumid), (pageIndex - 1) * pageRecord, Number(pageRecord), ] ) const result2 = await query( 'select count(*) as t from pics where albumid=? and removed =0 ', [Number(albumid)] ) res.json({ flag: true, result: result, pageCount: Math.ceil(result2[0].t / pageRecord), }) }) /** * 获取当前用户的相册列表 * /getAlbumList */ router.get('/getAlbumList', async (req, res) => { //获取参数 const { userid } = req.auth let result = await query( 'select a.id,a.title,a.ps,count(a.id) as t,max(b.url) as url from albums a ' + 'left join pics b on a.id = b.albumid ' + 'where a.userid=? ' + 'group by a.id,a.title,a.ps,a.userid', [Number(userid)] ) result = result.map(item => { if (!item.url) { item.t = 0 //'/default.jpg' 是 public作为根 // item.url='/default.jpg' } return item }) res.json({ flag: true, result: result, }) }) /** * 上传图片 */ router.post('/addpic', upload.single('pic'), async (req, res) => { // 图片上传的路径由multer写入req.file对象中 const path = req.file.path.split('\\').join('/').slice(6) //除了上传的文件之外,其他的表单数据也被multer存放在req.body中 const ps = req.body.ps //userid由token解析得到 const albumid = req.body.albumid //存储到数据库中 await query( 'insert into pics (url,ps,removed,albumid,createAt,updateAt) values (?,?,?,?,?,?)', [path, ps, 0, Number(albumid), new Date(), new Date()] ) res.json({ flag: true, msg: '照片添加成功! very good!', }) }) /** * 照片删除 * 接口地址:deletepic * 客户端参数:picid * 接受客户端参数,将数据库中的记录删除,并返回客户端删除成功 * /api/removepic */ router.get('/deletepic', async (req, res) => { const { picid } = req.query //or const picid =req.query.picid await query( 'delete from pics where id=?', [Number(picid)], res.json({ flag: true, msg: '照片删除成功', }) ) }) /** * 照片放入回收站 * 接口地址:removepic * 客户端参数:picid * 接受客户端参数,将数据库中的记录删除,并返回客户端删除成功 * /api/removepic */ router.get('/removepic', async (req, res) => { const { picid } = req.query //or const picid =req.query.picid await query( 'update pics set removed=1 where id=?', [picid], res.json({ flag: true, msg: '照片已放入回收站', }) ) }) /** * 修改照片备注 * modifyps,ps * 参数picid */ router.post('/modifyps', async (req, res) => { //获取参数 const { picid, ps } = req.body //修改,调用数据库 await query('update pics set ps=?,updateAt=? where id=?', [ ps, new Date(), Number(picid), ]) res.json({ flag: true, msg: '备注修改成功!', }) }) /** * 新增评论 * addComment * 参数:content,userid(req.auth)、picid * 类型:post */ router.post('/addComment', async (req, res) => { //获取参数 const { picid, content } = req.body const { userid } = req.auth await query( 'insert into comments (content,createAt,updateAt,userid,picid) values (?,?,?,?,?)', [content, new Date(), new Date(), Number(userid), Number(picid)] ) res.json({ flag: true, msg: '评论成功', }) }) /** * 删除评论 * deleteComment * 参数:commitid,content,userid(req.auth) * 类型:get * 删除前需保证当前用户是这条评论的发表人才能删除 */ router.get('/deleteComment', async (req, res) => { //获取参数 const { commentid } = req.query const { userid } = req.auth const result = await query( 'select a.id from comments a' + ' left join pics b ON a.picid=b.id' + ' left join albums c ON b.albumid=c.id' + ' where a.id=? and (a.userid=? or c.userid=?)', [Number(commentid), Number(userid), Number(userid)] ) if (result.length > 0) { await query('delete from comments where id=?', [ Number(commentid), ]) res.json({ flag: true, msg: '删除成功', }) } else { res.json({ flag: false, msg: '权限不足', }) } // let key=false // const result = await query( // 'delete * from comments where id=? and userid =? limit 1)', // [Number(userid),Number(commentid)] // ) // if(result.length>0){ // key=true // } // if(key==false){ // const result=await query('select *from comment where id=?',[Number(commentid)]) // const picid=result[0].picid // } // res.json({ // flag: true, // msg: '删除成功', // }) }) /** * 评论列表 * /getCommentList * 参数:picid、pageIndex、pageRecord * get */ router.get('/getCommentList', async (req, res) => { //获取参数,需要后续修改,不使用const let { picid, pageIndex, pageRecord } = req.query const result = await query( 'select * from comments a' + ' left join pics b ON a.picid=b.id' + ' where picid=? and b.removed=0 order by a.updateAt desc limit ?,? ', [ Number(picid), Number(pageIndex - 1) * pageRecord, Number(pageRecord), ] ) res.json({ flag: true, result: result, }) }) module.exports = router
//导入包 const crypto = require('crypto') //导出一个函数(方法) module.exports = password => { //创建一个加密对象sha1、md5 const encry = crypto.createHash('sha1') //将要加密的字符串存入加密对象中 encry.update(password) //将解密后的结果以hex的方式输出,hex就是将数字以字符+数字的方式进行输出 return encry.digest('hex') }
//封装mysql的连接,导出一个执行sql语句并返回结果的函数 //先引入mysql的驱动--也就是mysql2 //const用来替代var const mysql = require(`mysql2`) //创建连接池 {}是存对象的 const pool = mysql.createPool({ //极限值 connectionLimit: 10, //默认最大的连接数量 host: `127.0.0.1`, user: `root`, password: '123456', //填自己的密码 database: 'album', }) //query函数用来执行sql语句 //参数是sql语句及参数 //nodejs是一个弱类型 //lamada表达式 const query = (sql, params) => new Promise( //promise对象将异步操作包装成可控的结果 //resolve,reject两个参数,一个成功,一个失败 (resolve, reject) => { //先从连接池中取出一条连接 //异步操作 pool.getConnection((err, connection) => { //如果失败。执行reject,表示失败 if (err) { reject(err) //拿取连接失败,则直接返回失败 } else { connection.query(sql, params, (err, result) => { //查询完成后,无论结果是什么,都不再需要连接 //将连接换回连接池 connection.release() if (err) { reject(err) } else { resolve(result) } }) } }) } ) //导出query函数 module.exports = query
//引入multer、fs(读写操作模块)、path(路径模块)、 const multer = require('multer') const fs = require('fs') const path = require('path') //创建磁盘存储引擎 const storage = multer.diskStorage({ destination, filename }) //创建上传对象 const upload = multer({ storage }) //它是上传时目录的生成函数 function destination(req, res, callback) { const date = new Date() //创建动态目录 const path = `public/upload/${date.getFullYear()}/${ date.getMonth() + 1 }` //生成路径 fs.mkdirSync(path, { recursive: true }) callback(null, path) } //用来自动生成随机的不重复的文件名 function filename(req, file, callback) { //file.originalname 是上传文件的原始名 //a.jpg a.b.c.jpg //a.b.c.jpg >['a','b','c','jpg'] 扩展名 console.log(file) const arr = file.originalname.split('.') const extname = arr[arr.length - 1] const date = new Date() const filename = date.getTime() + Math.random() + '.' + extname callback(null, filename) } module.exports = upload
import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', //有人访问根,我导向/home(重定向) redirect: '/home', }, { path: '/home', name: 'home', component: HomeView, children: [ { path: 'albummanage', component: () => import('../views/AlbumManage.vue'), }, { path: 'album/:id', component: () => import('../views/PicsView.vue'), }, ], }, { path: '/login', name: 'login', component: () => import('../views/LoginView.vue'), }, { path: '/reg', name: 'reg', component: () => import('../views/RegView.vue'), }, ], }) //全局路由前置守卫 router.beforeEach(async (to, from) => { //从本地存储中尝试获取token if (to.name == 'home' && !localStorage.getItem('token')) { return { name: 'login' } } }) export default router
更新ing~