网络云相册实现--nodejs后端+vue3前端

news2024/11/15 8:46:00

目录

主页面

功能简介

系统简介

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~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1975935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Maven实战(四)- 生命周期和插件

Maven实战&#xff08;四&#xff09;- 生命周期和插件 文章目录 Maven实战&#xff08;四&#xff09;- 生命周期和插件1.何为生命周期2.生命周期2.1.Clean生命周期2.1.Default生命周期2.3.Site生命周期 3.Maven插件3.1.插件目标3.2.插件绑定3.2.1.内置插件3.2.2.自定义插件 4…

算法学习day28

一、寻找右区间(二分法) 题意&#xff1a;题目很容易理解 但是转换为二分法有点晦涩 给你一个区间数组 intervals &#xff0c;其中 intervals[i] [starti, endi] &#xff0c;且每个 starti 都 不同 。区间 i 的 右侧区间 可以记作区间 j &#xff0c;并满足 startj > e…

gptpdf深度解析:开源文档处理技术全攻略

目录 一、引言二、gptpdf 是什么&#xff1f;三、gptpdf 的功能特性1. 精准的 PDF 元素解析能力2. 对复杂文档结构的处理示例3. 高效的处理速度4. 低成本的优势 四、gptpdf 应用场景1. 学术研究与文献处理2. 企业文档管理3. 软件开发中的文档转换 五、gptpdf 代码示例1. 基本的…

【Vue3】组件通信之v-model

【Vue3】组件通信之v-model 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的…

图像梯度与几种算子

“滤波器”也可以称为“卷积核”&#xff0c;“掩膜”&#xff0c;“算子”等。 1、Sobel算子 Sobel算子是一个33的卷积核&#xff0c;利用局部差分寻找边缘&#xff0c;计算得到梯度的近似值。x和y方向的Sobel算子分别为&#xff1a; 梯度有方向&#xff0c;对于一个图像&a…

电子元器件—三极管(一篇文章搞懂电路中的三极管)(笔记)(面试考试必备知识点)

三极管的定义及工作原理 1. 定义 三极管&#xff08;Transistor&#xff09;是一种具有三层半导体材料&#xff08;P-N-P 或 N-P-N&#xff09;构成的半导体器件&#xff0c;用于信号放大、开关控制和信号调制等应用。三极管有三个引脚&#xff1a;发射极&#xff08;Emitter…

SpringBoot智慧旅游在线平台的设计与实现(源码+论文+部署讲解等)

博主介绍&#xff1a;✌全网粉丝10W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HLM…

从地铁客流讲开来:地铁客运量特征

1.数据来源 数据来源&#xff1a;MetroWatch地铁观察 | 地铁客流量数据 在做城市地铁客流数据的整理及可视化这块其实国内已经有很多大牛一直在做无偿免费的更新&#xff0c;其中覆盖多城市且每日更新数据的主要有两个&#xff1a;一个是地铁数据库 | 地铁客流量查询 (metrod…

数字农业农村云平台整体规划建设方案PPT

数字农业农村云平台的规划建设方案是一个全面而复杂的项目&#xff0c;涉及到多个方面的整合与创新。根据搜索结果&#xff0c;以下是一些关键点&#xff1a; 资料下载方式&#xff0c;请看每张图片右下角信息 1. 组织领导与政策支持&#xff1a;加强组织领导&#xff0c;确保…

如何在联络中心使用人工智能驱动的语音分析?

人工智能驱动的语音分析是一种使用自然语言处理和机器学习技术的语音识别软件。借助呼叫中心的语音分析&#xff0c;您可以将实时语音转换为文本。之后&#xff0c;程序会评估此文本以揭示有关客户需求、偏好和情绪的详细信息。 在联络中心&#xff0c;语音分析工具有助于&…

OpenCV函数

1&#xff0c;cv2.imread cv2.imread:这个函数可以直接用cv2.imread(filename, cv2.IMREAD_GRAYSCALE)直接将图片以黑白图像输入&#xff0c;也可以通过cv2.imread(img, 0)来将图片以黑白图像输入。其实这两者是一样的&#xff0c;如下图所示&#xff0c;可以将特定的颜色通道…

C语言程序设计之结构体篇2

程序设计之结构体2 问题2_1的代码2_1结果2_1 问题1_2代码1_2结果1_2 问题1_3代码1_3结果1_3 问题1_4代码1_4结果1_4 问题2_1的 函数 f u n fun fun 的功能是&#xff1a; 对 N N N 名学生的学习成绩&#xff0c;按从高到低的顺序找出前 m m m &#xff08; m < 10 m<1…

『 C++11 』模板可变参数包,Lambda表达式与 function 包装器

文章目录 模板可变参数模板可变参数包的展开可变参数包与STL容器中的emplace函数关系 Lambda 表达式function 包装器function 包装器对成员函数的包装bind 绑定 模板可变参数模板 可变参数模板是C11引入的一个特性,允许模板接收任意数量的参数; 该特性增加了C的泛型编程能力; 可…

搭建jenkins一键部署java项目

一、搭建jenkins 链接: https://pan.baidu.com/s/1jzx15PiyI8EhLd_vg7q8bw 提取码: ydhl 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 直接使用docker导入镜像&#xff0c;运行就好 docker run -di --name jenkins -p 8080:8080 -v /home/jenkins_home:/var/je…

黑神话:悟空

《黑神话&#xff1a;悟空》是由游戏科学公司制作的以中国神话为背景的动作角色扮演游戏&#xff0c;将于2024年8月20日发售 [9] [14]&#xff0c;简体中文PC标准版售价268人民币,数字豪华版售价328人民币。 [27] [34] 游戏中&#xff0c;玩家将扮演一位“天命人”&#xff0c…

洗袜子的小洗衣机哪款好?小户型洗衣机推荐!懒人洗袜子神器分享

市面上的那些迷你的小型洗衣机可以洗袜子&#xff0c;洗涤空间够一次性洗5-6双左右的袜子&#xff01;这种不仅不会因为清洗的衣物数量少而浪费水浪费电&#xff0c;同时使用也很便利&#xff0c;小小个的放在家的任意角落就可以进行清洗&#xff0c;不仅是清洗袜子这些&#x…

jquery.ajax + antd.Upload.customRequest文件上传进度

前情提要&#xff1a;大文件分片上传&#xff0c;需要利用Upload的customRequest属性自定义上传方法。也就是无法通过给Upload的action属性赋值上传地址进行上传&#xff0c;所以Upload组件自带的上传进度条&#xff0c;也没法直接用了&#xff0c;需要在customRequest中加工一…

GraphSAGE (SAmple and aggreGatE)知识总结

1.前置知识 inductive和transductive 模型训练&#xff1a; Transductive learning在训练过程中已经用到测试集数据&#xff08;不带标签&#xff09;中的信息&#xff0c;而Inductive learning仅仅只用到训练集中数据的信息。 模型预测&#xff1a; Transductive learning只能…

6.前端怎么做一个验证码和JWT,使用mockjs模拟后端

流程图 创建一个发起请求 创建一个方法 getCaptchaImg() {this.$axios.get(/captcha).then(res > {console.log(res);this.loginForm.token res.data.data.tokenthis.captchaImg res.data.data.captchaImgconsole.log(this.captchaImg)})}, captchaImg: "", 创…

【数据结构】排序基本概念、插入排序、希尔排序(详解)

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构、LeetCode专栏 &#x1f4da;本系…