目录
主页面
功能简介
系统简介
api
数据库表结构
代码目录
运行命令
主要代码
server
apis.js
encry.js
mysql.js
upload.js
client3
index.js
完整代码
主页面
功能简介
-
多用户系统,用户可以在系统中注册、登录及管理自己的账号、相册及照片。
-
每个用户都可以管理及维护相册,及相册的备注。
-
每个用户都可以管理及维护照片,及照片的备注。
-
相册需要可设置是否公开。
-
照片是可评论的,只有一级评论(不需要评论分级)。
-
分享界面(前台界面),需要图片放大预览及轮播图功能。
-
图片删除需要回收站。
系统简介
系统采用前后端分离的方式b-s方式,后台使用nodejs技术,数据库采用MySQL系统。前端使用vue3框架搭建。
后端是负责提供接口(api)
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
主要代码
server
apis.js
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
encry.js
//导入包
const crypto = require('crypto')
//导出一个函数(方法)
module.exports = password => {
//创建一个加密对象sha1、md5
const encry = crypto.createHash('sha1')
//将要加密的字符串存入加密对象中
encry.update(password)
//将解密后的结果以hex的方式输出,hex就是将数字以字符+数字的方式进行输出
return encry.digest('hex')
}
mysql.js
//封装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
upload.js
//引入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
client3
index.js
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~