Система заказа песен KTV через 7 дней, включая систему фонового управления (полная версия)

Node.js

В последнее время у меня немного чешутся руки и я думаю что делать.Я думал написать систему заказа песен КТВ.Смоделировал идею открытия счета КТВ.Я был измотан за 7 дней , но там довольно много технических моментов. Надеюсь, вы сможете это прочитать. (~^㉨^)~

Используйте Node (Express), чтобы научить вас писать систему заказа песен KTV, включая интерфейсную систему управления контентом и фоном, интегрировать платформу Express и разработку сервера базы данных Mongodb; научить вас писать супер красивые страницы с Vue.JS, ElementUI и iViewUI. , и заказывать песни по желанию слушать свое сердце

作者原创文章, 转载前请留言或联系作者!!!

карта разума

стек технологий

  • Бэкэнд: Express + Mongodb + jsonwebtoken и т. д.

  • Внешний интерфейс: Vue.JS + ElementUI + iViewUI + Axios и др.

Функции

Этот проект разделен на front-end разработку, back-end разработку и серверную разработку.

  • Пользователям необходимо войти в систему, чтобы слушать песни (защита маршрутизации)
  • Пользователям необходимо обратиться к администратору, чтобы подать заявку на получение учетной записи и пароля.
  • Войдите в систему, чтобы слушать песни (песня в стиле, песня на языке, песня о звезде, популярная песня и т. д.)
  • Напоминание с оставшимся временем 30 минут и автоматический выход из машины, когда время
  • Добавления, удаления и изменения песен администратором
  • Администратор открывает учетную запись для пользователя и может выбрать время доступа к компьютеру.
  • Порядок просмотра администратором, порядок удаления, порядок поиска
  • Администратор собирает песни и рекомендует их на ктв, чтобы рекомендовать песни
  • так далее...

структура дизайна проекта

-- 服务器基本架构
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. Не меняйте сначала порт сервера, иначе будет сообщено об ошибке.

  2. Вам необходимо провести тестирование в среде с установленными Node и Vue, если у вас их нет, сначала загрузите (Загрузка узла,vue скачать).если нетMongodbБазу данных, пожалуйста, скачайте сами, и я не буду много говорить об окружении.

  3. Сначала загрузите зависимости в самой внешней папке: npm install для загрузки внутренних зависимостей,

  4. Затем введите ktv-client, npm install, чтобы загрузить пользовательские интерфейсные зависимости.

  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:"未知错误,注册失败"})
                    })
            }
        })
})

Затем зарегистрируйтесь в почтальоне или других инструментах.

Технические исследования

Метод даты

По причине того, что мозг не прост в использовании, а в 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-капча)

Использование проверочного кода 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();
},

最后用 填写的验证码进行对比

Загрузить песню или картинку

грозный для обработки информации о загрузке файлов, он удобен в использовании и очень дружелюбен.Если вы не сталкивались с файловыми операциями, поторопитесь и соберите его

Упаковать метод песни 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

Над

-- 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;
        });
    },
}

Она ушла? Да, пейджинг — это так просто! Вы научились этому? Некоторые студенты, изучающие фронтенд-разработку, никогда не были знакомы с пейджингом. Изучив это, вы больше не будете беспокоиться!

Законность токена и пользовательской проверки

jsonwebtoken — это токен, который шифрует информацию о пользователе в необратимо взломанный токен. Что касается паспорта-jwt, он используется для проверки истечения срока действия информации о токене, предоставленной по запросу пользователя. Если срок действия визы превышен, стойка регистрации будет предложено отправить сообщение о том, что токен недействителен. , предлагая пользователю повторно получить легальную информацию о токене, иначе он не сможет продолжать запрашивать зашифрованную информацию;

Применение

- 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:"帐号过期,请联系管理员"})
    }
})

Подробный адрес документа:Юридическая проверка паспорта-Jwt,шифрование токена

снимок экрана

Система фонового управления

интерфейс песни на стойке регистрации

Исходный код здесь

Приведенный выше код был загружен на github.

GitHub.com/персиковая IC-карта/кондиционер…

Малая станция чтения

Портал:blog.US word.capable/views/pro JE…

отсканируй это: