7天擼完KTV點歌系統,含後臺管理系統(完整版)

最近手有點癢琢磨着作個啥,牽腸掛肚仍是寫個KTV點歌系統,模擬了一下KTV開戶的思路,7天累死我了,不過技術點還挺多的,但願你能夠看完(〜^㉨^)〜html

用Node(Express)教你寫KTV點歌系統,包括前臺內容和後臺管理系統,整合Express框架和Mongodb數據庫服務器開發;教你用Vue.JS,ElementUI和iViewUI寫出超漂亮的頁面,隨心點歌隨心聽前端

做者原創文章, 轉載前請留言或聯繫做者!!! vue

思惟導圖

技術棧

  • 後端: Express + Mongodb + jsonwebtoken等等node

  • 前端: Vue.JS + ElementUI + iViewUI + Axios等等ios

功能介紹

本項目分前臺開發,後臺開發和服務器開發git

  • 用戶聽歌須要登陸(路由守衛)
  • 用戶須要到管理員申請帳號和密碼
  • 用戶登陸聽歌(風格點歌,語種點歌,明星點歌,熱門歌曲等等...)
  • 剩餘時長30分鐘提醒,到時間自動下機
  • 管理員對歌曲的增刪改查
  • 管理員給用戶開戶,能夠選擇上機的時間
  • 管理員查看訂單,刪除訂單,搜索訂單
  • 管理員收藏歌曲,推薦到ktv推薦歌曲
  • 等等...

項目設計結構

-- 服務器基本架構
ktv-select_music-system
├── README.md
├── index.js  -- 後臺文件入口
├── test.http  -- 測試文件
├── api  -- 路由文件
│    ├── admin.js  -- 配置管理員的操做
|    ├── music.js  -- 配置歌曲信息
|    ├── user.js  -- 配置用戶的相關操做
|    └── safecode.js  -- 配置安全碼
├── config -- 配置
|    ├── Date.js  -- 配置日期格式化插件
|    ├── delNoUse.js  -- 封裝閒置刪除閒置資源方法
|    ├── http.js  -- 配置跨域
|    ├── isBadAccount.js  -- 封裝帳戶是否合法
|    ├── newaccount.js  -- 封裝隨機開戶方法
|    ├── passport.js  -- 驗證token是否合法
|    ├── uploadImg.js  -- 封裝上傳圖片方法
|    └── uploadMusic.js  -- 封裝上傳歌曲方法
├── ktv-admin  --後臺管理系統界面
├── ktv-client  --前臺用戶點歌項目界面
├── dbModel
|    └── **  -- Mongodb數據庫的一些模型
├── mongodb
|    └── mongodb.js  -- 配置Mongodb,連接數據庫 
├── secret
|    ├── mongodbURI.js  -- Mongodb地址
|    └── jwtkey.js  -- token的私鑰
├── static -- 資源存放處
|    ├── music  -- 歌曲上傳目標文件夾 
|    ├── poster  -- 歌曲海報上傳目標文件夾
└──  └── view  -- 配置404文件
複製代碼
-- 後臺管理系統架構
ktv-admin
├── README.md
├── public 
|    ├── index.html  -- vue掛載頁面
|    └── **  -- 你能夠在這裏連接少許靜態資源
├── src  -- 開發文件夾
|    ├── App.vue  -- Vue掛載根頁面
|    ├── main.js  -- Vue程序入口文件,掛載各類組件
|    ├── router.js  -- Vue路由配置文件
|    ├── store.js  -- Vuex的狀態管理文件
|    ├── assets  -- 靜態資源文件夾
|    ├── components  --公共組件
|    |      └── nav.vue  -- 後臺導航欄
|    ├── plugins  --插件
|    |      ├── axios.js   -- 配置跨域,攔截器等等 
|    |      ├── Date.js   -- 格式化日期 
|    |      └── Date.js   -- 加載動畫Loading
|    ├── stores  -- 狀態管理文件夾
|    |      └── adminStore.js  -- 管理員狀態 
|    ├── views  -- 頁面文件夾
|    |      ├── 404.vue   -- 404頁面
|    |      ├── adminlikes.vue   -- 管理員處理ktv收藏歌曲
|    |      ├── allorders.vue   -- 訂單管理
|    |      ├── Home.vue   -- 後臺根頁面
|    |      ├── Index.vue   -- 後臺首頁
|    |      ├── managemusic.vue   -- 音樂管理
|    |      ├── user_service.vue   -- 給用戶開戶
|    |      └── login.vue   -- 後臺登陸
└── babel.config.js  -- babel配置
複製代碼
-- 前臺用戶聽歌架構
ktv-client
├── README.md
├── public 
|    ├── index.html  -- vue掛載頁面
|    └── **  -- 你能夠在這裏連接少許靜態資源
├── src  -- 開發文件夾
|    ├── App.vue  -- Vue掛載根頁面
|    ├── main.js  -- Vue程序入口文件,掛載各類組件
|    ├── router.js  -- Vue路由配置文件
|    ├── store.js  -- Vuex的狀態管理文件
|    ├── assets  -- 靜態資源文件夾
|    ├── components  --公共組件
|    |      ├── bottomNav.vue  -- 底部音樂控制區域
|    |      └── topNav.vue  -- 頂部信息區域
|    ├── config  --配置
|    |      ├── addSong.js    --封裝選取歌曲方法
|    |      ├── isBadAccount.js    --驗證帳戶合法性
|    |      ├── isLogin.js    --是否登陸
|    |      ├── nextSong.js    --封裝下一首歌曲方法
|    |      └── prevSong.js    --封裝上一首歌曲方法
|    ├── plugins  --插件
|    |      ├── axios.js   -- 配置跨域,攔截器等等 
|    |      └── wsmLoading.js   -- 加載動畫Loading
|    ├── stores  -- 狀態管理文件夾
|    |      └── song.js  -- 存儲歌曲信息 
|    ├── views  -- 頁面文件夾
|    |      ├── 404.vue   -- 404頁面
|    |      ├── abc.vue   -- 拼音點歌
|    |      ├── artist.vue   -- 明星點歌
|    |      ├── Home.vue   -- 後臺根頁面
|    |      ├── Index.vue   -- 後臺首頁
|    |      ├── hot.vue   -- 熱播歌曲
|    |      ├── ktvlikes.vue   -- ktv推薦歌曲
|    |      ├── selected.vue   -- 已選歌曲
|    |      ├── style.vue   -- 風格點歌
|    |      └── language.vue   -- 語種點歌
├── babel.config.js  -- babel配置
└── vue.config.js  -- vue配置
複製代碼

項目啓動介紹

首先

  1. 首先不要改變服務器端口,不然報錯.github

  2. 你須要在裝有Node和Vue的環境中測試,若是其中一個沒有請先下載(Node下載,Vue下載).web

  3. 首先在最外層文件夾下載依賴:npm install 下載後端依賴,mongodb

  4. 接着進入ktv-client, npm install 下載用戶前端依賴.chrome

  5. 接着進入ktv-admin, npm install 下載管理員前端依賴.

  6. 以上工做完成後,使用命令npm run server 或者 node index命令啓動Node服務器,啓動成功會顯示:

    Server is running on port [8633].

    Mongodb is Connected.Please have a great coding.

  7. 進入ktv-client,打開命令板,使用命令npm run client啓動前臺用戶項目,啓動成功後用瀏覽器訪問http://localhost:xxxx

  8. 進入ktv-admin,打開命令板,使用命令npm run admin啓動後臺管理系統項目,啓動成功後用瀏覽器訪問http://localhost:xxxx

  9. 本例中將Mongodb部署在本地電腦上,若是你仔細閱讀了這篇文檔,啓動項目應該是很容易的。若是你把Mongodb部署在其餘地方,請自行修改secret/mongodbURI.js配置文件信息。

  10. 項目啓動成功,最好用 chrome 瀏覽器打開, 美化了滾動條

接着註冊管理員帳號

admin.js最下面有個註冊接口

// 管理員註冊
router.post("/account/register", (req, res) => {
    const email = req.body.email;
    Admin.findOne({email})
        .then(hasOne => {
            if(hasOne){
                return req.status(422).json({status:"422", result:"郵箱被佔用"});
            }else{
                const username = req.body.username;
                const password = req.body.password;
                const identity = req.body.identity ? req.body.identity : null;
                const date = new Date().format("yyyy/MM/dd HH:mm:ss");
                const newAdmin = new Admin({
                    email,
                    username,
                    password,
                    identity,
                    date
                });
                newAdmin.save()
                    .then(() => {
                        res.json({status:"200", result:"註冊成功"})
                    }).catch(err => {
                        console.log(err);
                        res.status(500).json({status:"500", result:"未知錯誤,註冊失敗"})
                    })
            }
        })
})

複製代碼

而後用postman註冊或者其餘工具

技術攻關

Date方法

因爲腦殼很差使的緣由加上js沒有元素格式化日期的方法,就瞎掰一個(值得學習)

Date.js

/** 
*
*  @author: Mr_Wei 
*  @version: 1.0.0 
*  @description: 格式化日期
*  @Date: 2019/10/16 09:32
*
*/ 

Date.prototype.format = function(format) {
    var o = {
		"M+": this.getMonth() + 1, //月份
		"d+": this.getDate(), //日
		"H+": this.getHours(), //小時
		"m+": this.getMinutes(), //分
		"s+": this.getSeconds(), //秒
		"q+": Math.floor((this.getMonth() + 3) / 3), //季度
		"f+": this.getMilliseconds() //毫秒
	};
	if (/(y+)/.test(format))
		format = format.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
	for (var k in o)
		if (new RegExp("(" + k + ")").test(format))
			format = format.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
	return format;
}

export default Date.prototype.format


而後咱們使用其格式日期
require(Date);
// const now = new Date().format("yyyy/MM/dd HH:mm:ss.S");
const now = new Date().format("yyyy/MM/dd HH:mm:ss");

複製代碼

驗證碼(svg-captcha)

svg-captcha驗證碼的運用,防止暴力破解密碼,增強安全性. 詳細的文檔地址:svg-captcha

使用驗證碼

// 後臺生成驗證碼
router.get("/getCaptcha", (req, res) => {
    var captcha = svgCaptcha.create({  
        // 翻轉顏色  
        inverse: false,  
        // 字體大小  
        fontSize: 38,  
        // 噪聲線條數  
        noise: 3,  
        // 寬度  
        width: 80,  
        // 高度  
        height: 32,  
      });  
      // 保存到session,忽略大小寫  
      req.session = captcha.text.toLowerCase(); 
      console.log(req.session); //0xtg 生成的驗證碼
      //保存到cookie 方便前端調用驗證
      res.cookie('captcha', req.session); 
      res.setHeader('Content-Type', 'image/svg+xml');
      res.send(String(captcha.data));
      res.end();
})


// 前臺獲取驗證碼
--HTML
<img width="80" style="background:#EEE9E9;margin-left:30px;" ref="captcha" height="32" src="http://localhost:3001/api/user/getCaptcha" @click="refreshCaptcha">

--js
// 獲取驗證碼cookie
getCookie(cname){
    var name = cname + "=";
    var ca = document.cookie.split(';');
    for(var i=0; i<ca.length; i++){
        var c = ca[i].trim();
        if (c.indexOf(name)==0) return c.substring(name.length,c.length);
    }
    return "";
},
// 刷新驗證碼
refreshCaptcha(){
    this.$refs.captcha.src = "http://localhost:3001/api/user/getCaptcha?d=" + Math.random();
},

最後用 填寫的驗證碼進行對比
複製代碼

上傳歌曲或圖片

formidable來處理文件上傳信息,用起來方便,很友好,若是你沒有接觸過文件操做,趕忙收藏起來

封裝歌曲方法uploadMusic.js

/** 
*
*  @author: Mr_Wei 
*  @version: 1.0.0 
*  @description: 封裝上傳音樂方法
*  @Date: 2019/10/16 08:35
*
*/ 

const fs = require('fs');
const path = require('path');
const formidable = require('formidable');  // 文件處理庫
const formatTime = require('silly-datetime');  // 格式化數據

module.exports = (req, res) => {
    
    let form = new formidable.IncomingForm();  //建立上傳表單
    form.encoding = 'utf-8';  // 設置編碼格式
    form.uploadDir = path.join(__dirname, '../static/music'); // 設置上傳目錄(這個目錄必須先建立好)
    form.keepExtensions = true;  // 保留文件後綴名
    form.maxFieldsSize = 20 * 1024 *1024; // 設置文件大小

    /* 格式化form數據 */
    form.parse(req, (err, fields, files) => {
        let file = files.file;
        /* 獲取異常 */
        if(err) {
            return res.status(500).json({'status': 500, result: '服務器內部錯誤'});
        }
        if(file.size > form.maxFieldsSize) {
            fs.unlink(file.path);
            return res.status(412).json({'status': 412, result: '音頻不能超過20M'});
        }

        /* 存儲後綴名 */
        let extName = '';
        switch (file.type) {
            case 'audio/mp3':
                extName = 'mp3';
                break;
        }
        if(extName.length == 0) {
            fs.unlink(file.path);
            return res.status(412).json({'status': 412, result: '只支持mp3格式音頻'});
        }
        /* 拼接新的文件名 */
        let time = formatTime.format(new Date(), 'YYYYMMDDHHmmss');
        let num = Math.floor(Math.random() * 8999 + 10000);
        let songName = `${time}_${num}.${extName}`;
        let newPath = form.uploadDir + '/' + songName;

        /* 更更名字和路徑 */
        fs.rename(file.path, newPath, (err) => {
            if(err) {
                return res.status(500).json({'status': 500, result: '音頻上傳失敗'});
            } else {
                return res.send({'status': 200, 'msg': '音頻上傳成功', result: {src: songName}});
            }
        })
        
    })
};
複製代碼

Vue、ElementUI分頁使用

關於ElementUI分頁詳細請見:ElementUI的Pagination分頁學習

上圖

-- html
<el-pagination
    v-if='paginations.total > 0'
    :page-sizes="paginations.page_sizes"
    :page-size="paginations.page_size"
    :layout="paginations.layout"
    :total="paginations.total"
    :current-page.sync='paginations.page_index'
    @current-change='handleCurrentChange'
    @size-change='handleSizeChange'>
</el-pagination>

-- js
data(){
    return{
        allUsers:[],  // 用來存儲最終信息, 被顯示的dom點調用
        allTableData:[],  // 用戶承接分頁設置的數據
        paginations: {   // 分頁組件信息
            page_index: 1, // 當前位於哪頁
            total: 0, // 總數
            page_size: 5, // 1頁顯示多少條
            page_sizes: [5, 10, 15, 20], //每頁顯示多少條
            layout: "total, sizes, prev, pager, next, jumper" // 翻頁屬性
        },
    }
},
methods:{
    // 獲取當前頁
    handleCurrentChange(page) {
        let sortnum = this.paginations.page_size * (page - 1);
        let table = this.allTableData.filter((item, index) => {
            return index >= sortnum;
        });
        // 設置默認分頁數據
        this.getAllUsers = table.filter((item, index) => {
            return index < this.paginations.page_size;
        });
        this.getAllUsers = table.filter((item, index) => {
            return index < this.paginations.page_size;
        });
    },
    // 切換size
    handleSizeChange(page_size) {
        this.paginations.page_index = 1;
        this.paginations.page_size = page_size;
        this.getAllUsers = this.allTableData.filter((item, index) => {
            return index < page_size;
        });
    },
     // 總頁數
    setPaginations() {
        this.paginations.total = this.allTableData.length;
        this.paginations.page_index = 1;
        this.paginations.page_size = 5;
        // 設置默認分頁數據
        this.getAllUsers = this.allTableData.filter((item, index) => {
            return index < this.paginations.page_size;
        });
    },
}
複製代碼

沒了嗎?對,分頁就是這麼簡單!你學會了嗎?有些前端開發的同窗老是對分頁比較陌生,學會這個,讓你再也不產生煩惱!

token和自定義驗證合法性

jsonwebtoken是對用戶信息加密成不可逆向破解的token.關於passport-jwt,是用來對用戶請求時所帶的token信息進行過時驗證,若是超過簽證的合法時間,則會請前臺發出token失效的信息,提示用戶從新獲取合法的token信息,不然沒法繼續請求加密的信息;

用法

- passport-jwt
const key = require("../config/keys").KEYORSECRET;
const JwtStrategy = require('passport-jwt').Strategy,
      ExtractJwt = require('passport-jwt').ExtractJwt;
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = key;

module.exports = passport => {
    passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
        UserInfo.findById(jwt_payload.id)
                .then(user => {
                    if (user) {
                        return done(null, user);
                    } else {
                        return done(null, false);
                        // or you could create a new account
                    }
                })
    }));
}



// 設置token
// 規則
 const rule = {
    id:String(userinfo._id),
    username:userinfo.username,
    email:userinfo.email,
    date:user.date,
    signdate:userinfo.signdate,
    signcount:userinfo.signcount,
    avatar:userinfo.avatar,
    phone:userinfo.phone
};

// 簽證加密
// jwt.sign(規則, key(私鑰), {配置:好比過時時長}, (err, token){ 響應程序 })
jwt.sign(rule,key,{expiresIn:7200},(err, token) => {
    if(err) throw err;
    res.json({"token" : "Bearer " + token})
})



自定義驗證方法
/** 
*
*  @author: Mr_Wei 
*  @version: 1.0.0 
*  @description: 判斷是否過時用戶
*  @Date: 2019/10/19 12:19
*
*/ 
const UserOrOrders = require("../dbModel/user");
module.exports = async params => {
    
    
    const flag = await new Promise((resolve) => {
        if(params){
            const account = params.account;
            UserOrOrders.findOne({account})
                .then(user => {
                    if(user){
                        if(new Date().getTime() > new Date(user.endTime).getTime()){
                            console.log("過時用戶");
                            // 處理
                            return resolve(false);
                        }else{
                            console.log("合法用戶");
                            return resolve(true);
                        }
                    }else{
                        return resolve(false);
                    }
                })
        }else{
            console.log("不合法用戶");
            return resolve(false);
        }
        
    }) 
    return flag;
}




使用:
// 測試  isBadAccount(params)方法
router.post("/test", passport.authenticate("jwt", {session:false}), async (req, res) => {
    // console.log(req.user)
    if(await isBadAccount(req.user)){
        // do something
        res.send("OK");
    }else{
        res.status(401).json({status:"401", result:"賬號過時,請聯繫管理員"})
    }
})

複製代碼

詳細的文檔地址:Passport-Jwt合法驗證,token加密

截圖

後臺管理系統

前臺點歌界面

源碼在這裏

以上代碼均已上傳 github

github.com/1046224544/…

聯繫

若是你們有興趣,歡迎star. 歡迎你們加入個人前端交流羣:866068198 ,一塊兒交流學習前端技術。博主目前一直在自學Node中,技術有限,若是能夠,會盡力給你們提供一些幫助,或是一些學習方法.

  • 羣二維碼

其餘

最後

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

若是對你有幫助,請賞個star~ github地址

相關文章
相關標籤/搜索