代码戳这里--> codecss
主要开发所用工具:html
参照: MySQL 安装 | 菜鸟教程前端
1.提示:Found option without preceding group in config file:XXX; Fatal error in defaults handling.
解决方法:用电脑的记事本打开my.ini
文件,将其另存为 ANSI 编码格式并替换原来的my.ini
文件vue
2.提示:you must reset your password using ALTER USER statement before executing this statement.
解决方法:从新设置密码
在 mysql 环境下,输入alter user user() identified by "123456";
退出 sql 从新登陆便可。node
cloud-drive
,在该文件下中使用 webpack 生成一个 vue 项目参考代码以下:mysql
mkdir cloud-drive cd cloud-drive cnpm install vue-cli -g vue init webpack "client" //创建一个名称为client的前端项目 cnpm install // 安装依赖 npm run dev
npm run dev
后,在浏览器中输入http://localhost:8080/#
后显示如下界面,则 client 项目生成完毕!1.在cloud-drive
下创建一个文件夹,名称为 server,用于存放服务端的代码。webpack
mkdir server cd server
server
文件夹下利用npm init -f
生成一个package.json
文件.package.json
文件添加启动项目代码... "scripts": { "start": "node src/app.js", // 加入这一条用于启动程序 "test": "echo \"Error: no test specified\" && exit 1" }, ...
4.在server
文件夹下建立src
文件夹,在src
文件夹下建立app.js
文件,在app.js
写入如下信息用于测试ios
console.log('Hello World!');
npm start
命令,输出信息如如下表明成功Hello World!
npm install express --save
npm install body-parser cors morgan nodemon multer md5 --save
src/app.js
用如下内容替代,该内容建立了一个运行于 8081 接口的服务器,创建了一个测试用接口,名称为 postsconst express = require('express') const bodyParser = require('body-parser') const cors = require('cors') const morgan = require('morgan') const app = express() app.use(morgan('combined')) app.use(bodyParser.json()) app.use(cors()) app.get('/posts', (req, res) => { res.send( [{ title: "Hello World!", description: "Hi there! How are you?" }] ) }) app.listen(process.env.PORT || 8081)
9.修改package.json
文件,采用 nodemon 启动git
"scripts": { "start": "nodemon src/app.js", "test": "echo \"Error: no test specified\" && exit 1" },
10.使用npm start
启动应用,在浏览器中访问localhost:8081/posts
地址,如若成功,应该看到如下信息
至此,后端环境准备完毕github
user 表:管理网盘注册用户信息
字段 | 中文释义 | 类型 | 是否为空 | 键 | 默认值 | 其余 |
---|---|---|---|---|---|---|
uid | 用户 id | int(10) unsigned | NO | 主键 | null | auto_increment |
username | 用户名 | varchar(20) | NO | null | ||
password | 密码 | varchar(20) | NO | null |
file 表:管理用户上传文件信息
字段 | 中文释义 | 类型 | 是否为空 | 键 | 默认值 | 其余 |
---|---|---|---|---|---|---|
id | 文件 id | int(10) unsigned | NO | 主键 | auto_increment | |
file_name | 文件名称 | varchar(255) | NO | null | ||
hash_name | 使用 hash 算法生成的文件名称 | varchar(255) | NO | null | ||
upload_time | 上传时间 | varchar(255) | No | null | ||
type | 文件类型 | varchar(255) | No | null | ||
size | 文件大小 | varchar(255) | No | null | ||
download | 下载次数 | varchar(255) | No | null | ||
uid | 上传用户 id | int(10) unsigned | No | 外键 | null |
cloud_drive
和表user
语句DROP DATABASE IF EXISTS cloud_drive; CREATE DATABASE cloud_drive; use cloud_drive; DROP TABLE IF EXISTS user; CREATE TABLE IF NOT EXISTS `user`( `uid` INT UNSIGNED AUTO_INCREMENT COMMENT '用户id', `username` VARCHAR(20) NOT NULL COMMENT '用户名', `password` VARCHAR(20) NOT NULL COMMENT '密码', PRIMARY KEY ( `uid` ) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '用户表';
use cloud_drive; DROP TABLE IF EXISTS file; CREATE TABLE `file` ( id int(10) AUTO_INCREMENT COMMENT '文件id', file_name varchar(200) NOT NULL COMMENT '文件名称', hash_name varchar(200) NOT NULL COMMENT '文件hash名称', upload_time DateTime NOT NULL COMMENT '上传时间', type varchar(20) NOT NULL COMMENT '文件类型', size varchar(20) NOT NULL COMMENT '文件大小', download varchar(50) NOT NULL COMMENT '下载次数', uid int unsigned COMMENT '用户id', PRIMARY KEY (id), foreign key(uid) references user(uid) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '上传文件表';
/server/config/env.js
文件// 数据库链接参数 const env = { database: 'cloud_drive', username: 'root', password: '123456', host: 'localhost', dialect: 'mysql', pool: { max: 5, min: 0, acquire: 30000, idle: 10000 } }; module.exports = env;
/server/config/db.config.js
文件const env = require('./env.js'); const Sequelize = require('sequelize'); const sequelize = new Sequelize(env.database, env.username, env.password, { host: env.host, dialect: env.dialect, operatorsAliases: false, pool: { max: env.max, min: env.pool.min, acquire: env.pool.acquire, idle: env.pool.idle } }); const db = {}; db.Sequelize = Sequelize; db.sequelize = sequelize; // 引入表模型 db.user = require('../model/user.model')(sequelize, Sequelize); db.file = require('../model/file.model')(sequelize, Sequelize); module.exports = db;
sequelize-auto
模块,利用sequelize-auto
模块自动生成 user 表模型和 file 表模型npm install -g sequelize-auto sequelize-auto -h localhost -d cloud_drive -u root -x 123456 -p 3306 -t user sequelize-auto -h localhost -d cloud_drive -u root -x 123456 -p 3306 -t file
注意:此处生成的表模型须要根据实际进行调整
2.复制生成的/models/book.js
文件,粘贴至/model
目录下,并修改文件名后缀为.model.js
,删除生成的models
目录
/server/route/user.route.js
文件// 用户 module.exports = function(app) { const user = require('../controller/user.controller'); // 新增用户 app.post('/user/add', user.create); // 根据用户名和密码查询用户 app.post('/user/validate', user.validate); // 修改密码 app.put('/user/update/:userId', user.updatePassWord); };
/server/route/file.route.js
文件// 文件 module.exports = function(app) { const file = require('../controller/file.controller'); // 新增文件 app.post('/file/add', file.create); // 删除文件 app.delete('/file/delete/:fileName/:fileId', file.delete); // 下载文件 app.get('/file/download/:fileName/:fileId', file.download); // 获取文件信息列表 app.post('/file/list', file.findAll); };
/server/controller/user.controller.js
文件const db = require('../config/db.config.js'); const User = db.user; // 引入表模型 const Sequelize = require('sequelize'); const Op = Sequelize.Op; // 新增用户 exports.create = (req, res) => { if (req.body.username && req.body.password) { User.create(req.body) .then(user => { let msg = {}; if (user) { msg = { flag: 1, msg: '注册成功!', uid: user.uid, username: user.username }; } else { msg = { flag: 0, msg: '注册失败,请稍后注册' }; } res.status(200).json(msg); }) .catch(err => { res.status(500).json('Error -> ' + err); }); } else { let msg = { flag: 0, msg: '用户名或者密码不能为空!' }; res.status(200).json(msg); } }; // 验证用户名和密码 exports.validate = (req, res) => { if (req.body.username && req.body.password) { User.findOne({ where: { [Op.and]: [ { username: req.body.username }, [ { password: req.body.password } ] ] }, attributes: ['uid', 'username'] }) .then(user => { let msg = {}; if (user) { msg = { flag: 1, msg: '用户名和密码正确!', uid: user.uid, username: user.username }; } else { msg = { flag: 0, msg: '用户名或密码错误!' }; } res.status(200).json(msg); }) .catch(err => { res.status(500).json('Error -> ' + err); }); } else { let msg = { flag: 0, msg: '用户名或者密码不能为空!' }; res.status(200).json(msg); } }; // 修改密码 exports.updatePassWord = (req, res) => { User.findOne({ where: { [Op.and]: [ { uid: req.params.userId }, { password: req.body.oldPassword } ] } }).then(user => { if (user) { User.update( { password: req.body.newPassword }, { where: { uid: req.params.uid } } ).then(() => { let msg = { flag: 1, msg: '修改密码成功!' }; res.status(200).json(msg); }); } else { let msg = { flag: 0, msg: '密码不正确!' }; res.status(200).json(msg); } }); };
/server/controller/file.controller.js
文件const db = require('../config/db.config.js'); const File = db.file; // 引入表模型 const Sequelize = require('sequelize'); const Op = Sequelize.Op; const path = require('path'); const fs = require('fs'); // 添加文件 exports.create = (req, res) => { let params = { file_name: req.files[0].originalname, hash_name: req.files[0].filename, upload_time: new Date().toLocaleDateString() + ' ' + new Date().toLocaleTimeString(), type: path.parse(req.files[0].originalname).ext, size: req.files[0].size, download: 0, uid: req.body.uid }; File.create(params) .then(file => { if (file) { let msg = { flag: 1, msg: '文件上传成功!' }; res.status(200).json(msg); } else { let msg = { flag: 0, msg: '文件上传失败,请稍后从新上传!' }; res.status(500).json(msg); } }) .catch(err => { res.status(500).json('Error->' + err); }); }; // 删除文件 exports.delete = (req, res) => { const id = req.params.fileId; File.destroy({ where: { id: id } }) .then(_ => { // 从资源文件夹从删除 let fileName = req.params.fileName; let path = `${__dirname}/../resource/${fileName}`; fs.unlink(path, err => { if (err) { let msg = { flag: 0, msg: '删除失败!' }; res.status(200).json(msg); } else { let msg = { flag: 1, msg: '删除成功!' }; res.status(200).json(msg); } }); }) .catch(err => { res.status(500).json('Error=>', err); }); }; // 下载文件 exports.download = (req, res) => { let fileId = req.params.fileId; File.findById(fileId).then(file => { file .increment('download') .then(file => { let fileName = req.params.fileName; let path = `${__dirname}/../resource/${fileName}`; res.download(path, fileName); }) .catch(err => { res.status(500).json('Error=>', err); }); }); }; // 获取文件列表信息 exports.findAll = (req, res) => { File.findAll({ where: { uid: req.body.uid } }) .then(file => { res.status(200).json(file); }) .catch(err => { res.status(500).json('Error=>', err); }); };
使用 postman 进行测试(略)
axios
模块npm install axios --save
src/utils/http.js
,并引入封装好的 axios 类import axios from 'axios' let httpInstance = axios.create() httpInstance.defaults.baseURL = 'http://localhost:8081/' httpInstance.defaults.timeout = 5000 httpInstance.formurl = (url, data, config) => { return httpInstance.post(url, data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, ...config }) }; // request拦截器 httpInstance.interceptors.request.use( config => { console.log(config) return config }, error => { return Promise.reject(error) } ) // reponse拦截器 httpInstance.interceptors.response.use( response => { if (response.status === 200) { return Promise.resolve(response) } }, error => { return Promise.reject(error) } ) export default httpInstance
main.js
中引入http.js
文件,并将其注册为 vue 全局变量import http from './utils/http' Vue.prototype.$http = http;
element-ui
模块npm install element-ui --save
main.js
中引入element-ui
模块import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI)
components
下新建文件以下,并删除原有的HelloWorld.vue
文件。file-list.vue
: 已上传文件列表界面file-upload.vue
:上传文件界面index.vue
:登陆注册界面tab-list.vue
:tab 页user-set.vue
:用户设置界面import Vue from 'vue' import Router from 'vue-router' import index from '@/components/index' import list from '@/components/tab-list' Vue.use(Router) const router = new Router({ routes: [ { path: '/', name: 'index', component: index }, { path: '/tab-list', name: 'tab-list', component: list } ] }) // 校验登陆 router.beforeEach((to, from, next) => { if (to.name === 'tab-list') { if (!sessionStorage.username) { window.alert('您的登陆信息无效或过时,请从新登陆') return window.location.replace('/') } else { next() } } else { next() } }) export default router
App.vue
文件中如下代码<img src="./assets/logo.png">
index.vue
中写入如下代码:<template> <div> Hello World! </div> </template> <script> export default {} </script> <style scoped> </style>
npm start
运行项目,在浏览器中访问,则会出现npm start
的文字1.index.vue
登陆注册界面
(1)界面预览
(2)代码编写
<template> <div class="index"> <div class="title"> 在线网盘系统 </div> <div class="label"> Cloud Driver </div> <div class="btn"> <el-button type="primary" @click="dialogFormVisible = true">登陆/注册</el-button> </div> <el-dialog title="登陆/注册" :visible.sync="dialogFormVisible" width="400px"> <el-form :model="form"> <el-form-item label="用户名" :label-width="formLabelWidth"> <el-input v-model="form.username" autocomplete="off"></el-input> </el-form-item> <el-form-item label="密码" :label-width="formLabelWidth"> <el-input v-model="form.password" autocomplete="off" type="password"></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="signUp">注 册</el-button> <el-button type="primary" @click="signIn">登 录</el-button> </div> </el-dialog> </div> </template> <script> export default { name: 'index', data () { return { dialogFormVisible: false, form: { username: '', password: '' }, formLabelWidth: '120px' } }, methods: { // 登陆 signIn () { this.dialogFormVisible = false this.$http .post('/user/validate', this.form) .then(res => { if (res.data.flag === 0) { this.$message.error(res.data.msg) } else { this.$message.success(res.data.msg) sessionStorage.setItem('uid', res.data.uid) sessionStorage.setItem('username', res.data.username) // 跳转到其余页面 this.$router.push('tab-list') } }) .catch(err => { console.log(err) }) }, // 注册 signUp () { this.dialogFormVisible = false this.$http .post('/user/add', this.form) .then(res => { if (res.data.flag === 0) { this.$message.error(res.data.msg) } else { this.$message.success(res.data.msg) this.$router.push('tab-list') } }) .catch(err => { console.log(err) }) } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .index { margin-top: 80px; text-align: center; } .title { font-size: 60px; } .label { font-size: 40px; margin-top: 10px; } .btn { margin-top: 10px; } .dialog-footer { text-align: center; } </style>
tab-list.vue
选项卡切换界面<template> <div class="show"> <base-header></base-header> <el-tabs v-model="tabActivedName" class="tab" @tab-click="handleClick"> <el-tab-pane v-for="(item, index) in componentList" :key="index" :label="item.tabLabel" :name="item.tabName"> <component :is="item.compoName" v-if="tabActivedName===item.tabName"></component> </el-tab-pane> </el-tabs> <base-footer></base-footer> </div> </template> <script> import BaseHeader from '../layout/header'; import BaseFooter from '../layout/footer'; import UploadFile from './file-upload'; import FileList from './file-list'; import userSet from './user-set'; export default { name: 'show', components: { BaseHeader, BaseFooter, UploadFile, FileList, userSet }, data () { return { tabActivedName: 'second', componentList: [ { tabName: 'first', compoName: 'upload-file', tabLabel: '上传文件' }, { tabName: 'second', compoName: 'file-list', tabLabel: '文件列表' }, { tabName: 'third', compoName: 'user-set', tabLabel: '用户设置' } ] } }, methods: { handleClick (tab, event) { console.log(tab, event) } } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> .tab { min-height: 400px; padding: 20px 40px; } </style>
3.file-upload
文件上传组件
(1)界面预览
(2)代码
<template> <el-upload drag multiple action="http://localhost:8081/file/add" :data="userInfo" :on-success="dealSuccess" :on-error="dealError"> <i class="el-icon-upload"></i> <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> </el-upload> </template> <script> export default { data () { return { userInfo: { uid: sessionStorage.getItem('uid') } } }, methods: { dealSuccess () { this.$message.success('上传文件成功!') }, dealError () { this.$message.error('上传文件失败,请从新上传!') } } } </script> <style scoped> </style>
file-list.vue
文件上传列表组件<template> <div> <el-table :data="tableData" :cell-style="{'text-align':'center'}" :header-cell-style="{'text-align':'center'}" style="width: 100%"> <el-table-column type="index"> </el-table-column> <el-table-column prop="file_name" label="文件名" width="180px"> </el-table-column> <el-table-column prop="size" label="文件大小" width="180px" :formatter="dealSize"> </el-table-column> <el-table-column prop="upload_time" label="上传时间" width="180px" :formatter="dealTime"> </el-table-column> <el-table-column prop="download" label="下载次数" width="180px"> </el-table-column> <el-table-column prop="type" label="类型" width="180px"> </el-table-column> <el-table-column label="操做"> <template slot-scope="scope"> <a :href="getFile(scope.row)"> <el-button size="mini" type="info" @click="handleDownload">下载</el-button> </a> <el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button> </template> </el-table-column> </el-table> </div> </template> <script> export default { data () { return { tableData: [] } }, methods: { handleDelete (index, row) { this.$http .delete(`/file/delete/${row.hash_name}/${row.id}`) .then(res => { this.$message.success(res.data.msg) this.refreshFileList() }) .catch(err => { console.log('Error=>', err) }) }, handleDownload () { setTimeout(() => { this.refreshFileList() }, 1000) }, refreshFileList () { this.getFileList() }, getFileList () { let params = { uid: sessionStorage.getItem('uid') } this.$http .post('/file/list', params) .then(res => { if (res.data.code === 0) { this.$message.error(res.data.msg) } else { this.tableData = res.data } }) .catch(err => { console.log(err) }) }, getFile (data) { let url = `http://localhost:8081/file/download/${data.hash_name}/${ data.id }` return url }, dealSize (row, column) { let fileSize = (row.size / 1024).toFixed(2) return `${fileSize}kb` }, dealTime (row, column) { return this.formatTime(row.upload_time) }, formatTime (value) { var date = new Date(value) var Y = date.getFullYear() var M = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1 var D = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate() var h = date.getHours() < 10 ? `0${date.getHours()}` : date.getHours() var m = date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes() var s = date.getSeconds() < 10 ? `0${date.getSeconds()}` : date.getSeconds() return `${Y}-${M}-${D} ${h}:${m}:${s}` } }, mounted () { this.getFileList() } } </script>
user-set.vue
用户设置组件<template> <div> <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="change-password"> <el-form-item label="原密码" prop="oldPass"> <el-input type="password" v-model="ruleForm.oldPass" autocomplete="off"></el-input> </el-form-item> <el-form-item label="新密码" prop="newPass"> <el-input type="password" v-model="ruleForm.newPass" autocomplete="off"></el-input> </el-form-item> <el-form-item label="确认新密码" prop="checkNewPass"> <el-input type="password" v-model="ruleForm.checkNewPass" autocomplete="off"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('ruleForm')">提交</el-button> <el-button @click="resetForm('ruleForm')">重置</el-button> </el-form-item> </el-form> </div> </template> <script> export default { data () { // 验证原密码 let validateOldPass = (rule, value, callback) => { if (value === '') { callback(new Error('请输入密码')) } else { if (this.ruleForm.oldPass !== '') { this.$refs.ruleForm.validateField('newPass') } callback() } } // 验证新密码 var validateNewPass = (rule, value, callback) => { if (value === '') { callback(new Error('请输入新密码')) } else if (value === this.ruleForm.oldPass) { callback(new Error('新旧密码不能相同!')) } else { callback() } } // 验证再次输入密码 var validateCheckNewPass = (rule, value, callback) => { if (value === '') { callback(new Error('请再次输入密码')) } else if (value !== this.ruleForm.newPass) { callback(new Error('两次输入密码不一致!!')) } else { callback() } } return { ruleForm: { oldPass: '', newPass: '', checkNewPass: '' }, rules: { oldPass: [{ validator: validateOldPass, trigger: 'blur' }], newPass: [{ validator: validateNewPass, trigger: 'blur' }], checkNewPass: [{ validator: validateCheckNewPass, trigger: 'blur' }] } } }, methods: { submitForm (formName) { this.$refs[formName].validate(valid => { if (valid) { let params = { oldPassword: this.ruleForm.oldPass, newPassword: this.ruleForm.newPass } this.$http .put(`/user/update/${sessionStorage.uid}`, params) .then(res => { this.$message.success('修改密码成功!请从新登陆') sessionStorage.clear() setTimeout(() => { this.$router.push({ path: '/' }) }, 1000) }) .catch(err => { console.log('Error=>', err) }) } else { console.log('error submit!!') return false } }) }, resetForm (formName) { this.$refs[formName].resetFields() } } } </script> <style scoped> .change-password { width: 400px; margin: 10px auto; } </style>