代碼戳這裏--> 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>