基于MEAN的仿豆瓣电影网站开发实战(1)
来源:互联网 发布:mac光盘怎么弹出 编辑:程序博客网 时间:2024/04/30 14:02
版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/lilythy2016/article/details/52810082。
本帖讲的是仿豆瓣电影网的电影录入功能实现,环境采用的是全JavaScript的MEAN框架实现,对MEAN框架有不清楚的读者可以参考Webstorm下MEAN环境搭建。
服务端实现
在项目根目录下新建一个src目录,用于存放后端代码。要实现电影信息录入功能,我们需要先定义一个与数据库相对应的模板文件,主要用于定义电影的相关属性和方法。在src目录下新建schemas目录,在里面新建一个movieSchema.js文件,代码如下
movieSchema.js
//引入mongoose模块var mongoose = require('mongoose');//引用mongoose的Schema模块var Schema = mongoose.Schema;// 创建一个MovieSchema对象,并定义其相关属性var MovieSchema = new Schema({ moviename:{ //电影名称,设置unique为true表示电影名称必须唯一 unique: true, type: String }, director:String, //导演 writers:String, //编剧 actors:String, //主演 type:String, //电影类型 countries:String, //制片国家 language:String, //语言 meta: { //把时间相关的属性封装在meta对象里 createAt: { //创建数据的时间,默认为录入时的系统时间 type: Date, default: Date.now() }, updateAt: { //更新数据的时间,默认为更新数据时的系统时间 type: Date, default: Date.now() }, showDate:{ //上映时间,默认为录入数据时的系统时间 type:Date, default: Date.now() } }, moviepic:String, //电影图片的名称 runtime:String, //时长 starnum:Number, //电影评分 starclass:Number //电影评价的星级数});/*调用mongoose的pre方法,它起到了起到中间件的作用,会在执行save方法之前调用,这里会给movie对象设置createAt、updateAt的值,我们在处理逻辑的时候就不用管这两个属性了*/MovieSchema.pre('save',function(next){ var movie = this; if(this.isNew){ this.meta.createAt = this.meta.updateAt = Date.now(); }else{ this.meta.updateAt = Date.now(); } next();});//给MovieSchema定义一些静态的方法,可以在model层直接调用,跟mongoose封装的model上的save,find等方法平级MovieSchema.statics = { //定义了遍历数据库方法fetch fetch: function(cb) { return this .find({}) .sort('meta.updateAt') .exec(cb); }, //定义了通过ID查找某条数据的findById方法 findById: function(id, callback) { return this.findOne({_id: id}).exec(callback); }};//导出MovieSchema,以便其他文件使用module.exports = MovieSchema;
然后在src目录下新建目录models,用于存放由schema文件生成的model,怎么理解schema和model呢?schema只是定义movie对象的骨架,设置了movie对象的属性和方法,而Model定义了具体的操作数据库的增删改查等方法,我们可以调用mongoose.model()方法来根据具体的schema生成一个具体的model实体,将该实体暴露出去就可以供控制层文件使用,进行操作数据库了。在models目录下新建一个movieModel.js,代码如下
movieModel.js
//引用mongoose模块var mongoose = require('mongoose');//引用movieSchema.js文件var MovieSchema = require('../schemas/movieSchema.js');//通过MovieSchema来创建MovieModel。第一个参数表示MovieModel的名字,第二个参数表示依赖的scheme名称,// 第三个参数表示数据库中collection的名字(相当于关系型数据库中的表名)var MovieModel = mongoose.model('Movie',MovieSchema,'Movie');//导出MovieModel供控制层文件使用module.exports = MovieModel;
这里说明一下mongoose.model(‘Movie’, MovieSchema, ‘Movie’) 这句代码,如果不设置第三个参数的话,mongodb数据库会以model的名称忽略掉大小写,然后变成复数来作为表名,也就是写成mongoose.model(‘Movie’, MovieSchema’)的话,数据库里只有movies表,注意第一个参数是model的名字,并不是表名!如果想要清楚地知道自己数据存在哪里,建议设置第三个参数。
由于后面代码会用到上传路径这个参数,不妨将这个参数提出来设为全局变量,方便维护。所以先在src目录下新建config文件夹,用于存放全局性的配置文件,在config目录下新建paramsConf.js,代码如下
paramsConf.js
/**///设置文件上传的路径,第一个斜杠为转义符exports.uploadDir = '.\\public\\upload\\';
紧接着来看具体的逻辑处理代码,该文件定义了一个处理图片上传的方法uploadPic和一个解析表单数据并将数据存入数据库中的create方法。因为我是把电影海报和其他电影信息异步提交的,先上传图片,然后服务端的uploadPic方法会返回唯一的图片名称给前端,防止图片重名,并将这个图片名称赋值给表单里的隐藏input,最后点击提价按钮的时候和其他电影信息一起提交至后台处理。在src目录下新建controllers文件夹,在里面新建movies.js,调用上面的配置文件paramsConf.js,定义这两个后台处理方法,代码如下
movies.js
//引用movieModel.js文件var MovieModel = require('../models/movieModel');//引入express模块var express = require('express');//引入formidable,用于解析表单提交的数据var formidable = require('formidable');//引入fs模块,用于文件操作var fs = require('fs');//引入superagent模块,superagent是客户端请求代理模块,用于处理get,post,put,delete,head请求var request = require('superagent');//引入自定义的参数配置文件var paramsConfig = require('../config/paramsConf.js');//上传图片方法exports.uploadPic = function(req,res){ //创建Formidable.IncomingForm对象 var form = new formidable.IncomingForm(); //设置上传图片的位置,就是配置文件里定义的路径 form.uploadDir = paramsConfig.uploadDir; //保留后缀格式 form.keepExtensions = true; //解析表单数据,如果有key:value的键值对数据则保存在fields里,这里只处理图片文件,所以fields里面没有数据,files为解析出来的文件对象集合 form.parse(req, function(err, fields, files) { if (err) { console.log(err); } //获取文件对象集合里的file对象路径,如'.\\public\\upload\\123.jpg' var path = files.file.path; //获取路径中最后一个'\'的索引,"\\"表示\,前面一个斜杠表示转义符 var index = path.lastIndexOf("\\"); //截取path中最后一个斜杠后面的字符串,即图片存入数据库的名称('123.jpg') var serverPicName = path.substring(index + 1, path.length); //把数据库中的图片名称返回给客户端 res.send(serverPicName); });};// 新增一条电影数据方法exports.create = function(req, res){ //将req.body赋值给一个新对象mymovie var mymovie = req.body; //将req.body提交过来的showDate放入movieSchema里定义的meta属性里 mymovie.meta = {}; mymovie.meta.showDate = mymovie.showDate; //以mymovie对象实例化一个MovieModel对象:newMovie var newMovie = new MovieModel(mymovie); //newMovie调用mongoose的save方法将数据存入mongodb数据库 newMovie.save(function (err, movie) { if (err) { console.log(err); } //res.send({message: 'add a movie'}) });};
上述代码引用的formidable和superagent这两个模块还没有安装的话,需要先通过npm自行安装一下,否则引用会报错哈。
写完逻辑处理代码之后,我们需要将url路径对应到具体的处理方法上,这时就需要express的路由了。在src目录下创建routers目录,在里面新建movieRouter.js,代码如下。
movieRouter.js
//引入express模块var express = require('express');//由express创建一个路由var router = express.Router();//引入movies.js文件var movies = require('../controllers/movies.js');/* 设置post方法路由映射. */router.post('/',movies.create);router.post('/pic',movies.uploadPic);//导出routermodule.exports = router;
路由编写完成后需要在app.js中使用才有效,打开app.js,加上引用路由的代码
//引用自定义的路由文件var router = require('./src/routers/movieRouter.js'); //http://localhost:3000/api下的请求都经过router文件拦截app.use('/movie', router);
这里要说一下app.js文件里通过app.use 是用于加载处理http请求的中间件,当一个请求来的时候,会依次被这些中间件处理。执行的顺序是你在app.js里定义的顺序,所以顺序一定要写正确,不然会在运行的时候出现错误。就像这里,如果不把加载bodyParser的代码写在路由之前,后台则获取不到解析的json对象,正确的书写顺序如下图所示
接下来需要配置mongoose来连接mongodb数据库,以便进行数据存储。在config目录下新建dbConfig.js,用来配置连接数据库的参数,代码如下:
dbConfig.js
//mongodb数据库参数配置var user_name = 'lilythy'; //用户名var password = '123456'; //密码var db_url = 'localhost'; //主机名var db_port = 27017; //端口var db_name = 'moviesite'; //database名称//导出mongodb数据库的连接信息exports.db_str = 'mongodb://' + user_name + ':' + password + '@' + db_url + ':' + db_port + '/' + db_name;
然后在app.js文件里引用这个数据库配置文件,通过mongoose的connect方法来连接数据库,代码如下
var mongoose = require('mongoose'); var Conf = require('./src/config/dbConfig.js'); //引入数据库配置文件,提供了连接数据库所需的参数mongoose.connect(Conf.db_str); //通过配置文件内的链接连接mongodb数据库
完整的app.js文件如下
app.js
var express = require('express'); //引入express框架var path = require('path'); //引用NodeJS中的Path模块,用于处理和转换文件路径var favicon = require('serve-favicon'); //引入serve-favicon中间件,可以用于请求网页的logovar logger = require('morgan'); //引入用于记录日志的中间件morganvar cookieParser = require('cookie-parser'); //引入cookieParser中间件,用于获取web浏览器发送的cookie中的内容var bodyParser = require('body-parser'); //引入body-parser模块,用于对请求进行拦截和解析var app = express(); //express()表示创建express应用程序。var router = require('./src/routers/movieRouter.js'); //引用自定义的路由文件var mongoose = require('mongoose'); //引入mongoose模块var Conf = require('./src/config/dbConfig.js'); //引入数据库配置文件,提供了连接数据库所需的参数mongoose.connect(Conf.db_str); //通过配置文件内的链接连接mongodb数据库app.use(logger('dev')); //将请求信息打印在控制台,便于开发调试app.use(cookieParser()); //装载cookie-parser模块,之后便可以解析cookieapp.use(bodyParser.json()); //装载一个只解析json的中间件body-parserapp.use(bodyParser.urlencoded({extended: false})); // bodyParser.urlencoded是用来解析我们通常的form表单提交的数据,也就是请求头中包含这样的信息: Content-Type: application/x-www-form-urlencodedapp.use('/movie', router); //http://localhost:3000/api下的请求都经过router文件拦截app.set('views', path.join(__dirname, 'public')); //设置模版文件夹的路径为/publicapp.engine('.html', require('jade').__express); //设置jade引擎支持.html后缀app.set('view engine', 'html'); //在调用render函数时能自动为我们加上'.html' 后缀app.use(express.static(path.join(__dirname, 'public'))); //设置静态文件目录为/public//所有http://localhost:3000下的请求都被拦截,然后渲染为/public目录下的index.html页面app.use('/', function (req, res) { res.sendFile('index.html', {root: path.join(__dirname, 'public')});});// 如果404错误就交给错误处理程序app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err);});// 开发环境错误处理程序将会打印出错误if (app.get('env') === 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: err }); });}app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: {} });});module.exports = app;
接下来需要在mongodb数据库那边做一些相应的配置,打开cmd窗口,然后通过cd命令进入到mongodb安装目录的bin文件夹,然后输入mongod –dbpath db文件夹路径 来启动mongodb,这样数据库文件就会存放到这个db文件夹,如下图所示来启动mongodb
看到启动端口号为27017就说明连接成功了,然后点击bin文件夹下的mongo.exe,输入下面这段代码来创建用户名和密码,设置该用户有读写权限,并设置db的名字为moviesite。
db.createUser( { user: "lilythy", pwd: "123456", roles: [ { role: "readWrite", db: "moviesite" } ] })
设置成功后,在cmd窗口按ctrl+c退出连接,然后按方向键“↑”重新启动mongodb,启动后千万不要关闭cmd窗口,mongod.exe窗口可以关,你就可以大胆地点击webstorm的运行按钮了,控制台没有报错则说明连接成功了。
后端编写完成后的项目目录结构如下图所示
4.前端实现
在public目录下新建一个 js文件夹,然后在里面新建一个module.js文件,声明一个 ‘app’模块,并设置其依赖模块,有需要的话还可以定义全局常量或变量,代码如下
module.js
//在js文件第一行加上'use strict'使该文件在严格模式下执行,就是对代码书写规范更严格,否则就会报错'use strict';//声明一个'app'模块,并设置该模块依赖ui.router、ui.bootstrap和ngFileUploadvar app = angular.module('app', [ 'ui.router', 'ui.bootstrap', 'ngFileUpload']);//声明'app'模块的全局常量'DIR'app.constant('DIR','\\upload\\') ;
接着在js文件夹下新建一个routes.js文件,定义路由状态,前端页面会根据链接的状态进行页面跳转,代码如下
routes.js
//# (function (app) {})(angular.module('app')),将angular声明的app模块传给function的参数'app',表示在app模块的作用域里执行以下语句(function (app) { //# 使用严格模式 'use strict'; /*# 利用config方法做一些注册工作,这些工作需要在模块加载时完成 *# $stateProvider用于配置路由状态; **'# $urlRouterProvider负责监视$location,当$location改变后,$urlRouterProvider将从一个列表,一个接一个查找匹配项,直到找到; *# $locationProvider用于配置$location服务,去掉单页面应用链接中的"#" */ app.config(function ($stateProvider, $urlRouterProvider, $locationProvider) { //AngularJS框架提供了一种HTML5模式的路由,设置为true就可以直接去掉#号 $locationProvider.html5Mode(true); //访问其他不存在的路径时都跳到'/' $urlRouterProvider.otherwise('/'); $stateProvider /* 设置路由状态'add',路由为'/addmovie',对应的html页面为views文件夹下的'addmovie.html',作用于该页面的控制器名称为'AddController' */ .state('add', { url: '/addmovie', templateUrl: '/views/addmovie.html', controller: 'AddController' }); });})(angular.module('app'));
然后在js目录下新建一个service文件夹,在里面新建movieService.js,用来定义一些请求后台数据的方法,可供conreoller或config使用,代码如下
movieService.js
/*在app模块下定义自定义服务*/(function (app) { 'use strict'; //通过factory()方法创建一个服务MovieService,可以供controller或config使用 app.factory('MovieService', function ($http, $q) { return { //将表单数据提交至后台处理 addMovie:function(movieEntity){ //后台处理链接 var url = "http://localhost:3000/movie/"; /*利用$q服务实现Deferred/Promise方法,利用$q.defer()生成deffered 对象,该对象有三个方法: * 1.resolve(value):如果异步操作成功,resolve方法将Promise对象的状态变为“成功”。 * 2.reject(reason):如果异步操作失败,则用reject方法将Promise对象的状态变为“失败”。 * 3.notify(value) :表明promise对象为“未完成”状态,在resolve或reject之前可以被多次调用。 * 当创建deferred实例时会创建一个新的promise对象,并可以通过 deferred.promise 得到该引用。*/ var deferred = $q.defer(); //通过angular的$http服务将表单的Json数据提交给后台,并监听结果 $http.post(url, movieEntity).then( //成功则将数据返回给deferred对象 function success(respData){ var movies = respData.data; deferred.resolve(movies); }, //失败则返回原因给deferred对象 function error(reason) { deferred.reject(reason); } ) //通过deferred.promise获得返回给deferred对象的结果 return deferred.promise; } } });})(angular.module('app'));
定义完service后就可以在controller里调用这个服务了,接下来在js目录下新建controller目录,然后在里面新建addController.js文件,代码如下
addController.js
(function (app) { 'use strict'; //app.controller()方法的第一个参数是controller名称,第二个参数为数组,数组的前面是声明注入的内容,可以是n个,最后一个是个function,function的参数个数也必须是n个,必须跟前面声明注入的内容一一对应 app.controller('AddController',['$scope', 'Upload', '$state','MovieService', 'DIR', function ($scope, Upload, $state, MovieService, DIR) { //初始化数据库存的图片名称 $scope.serverPicName = ''; //引用自定义路径,并赋值给该作用域的dir变量 $scope.dir = DIR; //定义图片预览的img标签是否显示,true则显示 $scope.isShow = false; //定义上传图片方法 $scope.uploadFiles = function(file, errFiles) { //将文件参数赋值给该作用域的f $scope.f = file; //$scope.size = ($scope.f.size/1024/1024).toFixed(2); //将文件错误参数传给该作用域的errFile $scope.errFile = errFiles && errFiles[0]; //如果是文件的话,则调用Upload插件上传该文件 if (file) { //调用Upload的upload()方法将data数据(即文件)上传至后台url处理 file.upload = Upload.upload({ url: 'http://localhost:3000/movie/pic' data: {file: file} }); //then方法定义文件上传成功后执行的代码,这里表示上传成功后则html页面显示预览图片的img标签并将返回的数据赋给serverPicName file.upload.then(function (response) { $scope.isShow = true; $scope.serverPicName = response.data; }, function (response) { //不成功则返回错误信息 if (response.status > 0) $scope.errorMsg = response.status + ': ' + response.data; }, function (evt) { //执行上传行为时返回上传进度 file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total)); }); } } //点击提交按钮后提交表单数据给后台的方法 $scope.postMovie = function(){ //获得隐藏input的值,即上传图片后后台返回前端唯一的图片名称 var moviepic = document.getElementById("serverPicName").value; //生成一个1~10之间的星级数 $scope.starNum = (Math.random()*10+1).toFixed(1); //将后台返回的图片名称赋给movie对象的moviepic属性 $scope.movie.moviepic = moviepic; //将生成的星级数赋值给movie对象的starnum属性 $scope.movie.starnum = $scope.starNum; //生成决定html页面使用哪个类的参数 var starClass = (Math.round($scope.starNum)/2)*10; $scope.movie.starclass = starClass; //调用自定义服务MovieService里的addMovie方法,并将返回结果赋值给promise var promise = MovieService.addMovie($scope.movie); //数据提交成功后,刷新页面 promise.then(function (data) { window.location.reload(); }); } }]);})(angular.module('app'));
最后提供一下录入界面的html,由于这里注重的是功能代码的实现,前端页面的表单验证和时间选择插件等还没有优化,以后有时间我会持续完善已上传至github的项目代码,本帖最后会给出链接。
addmovie.html
<form class="container formwidth" role="form" name="form" id="movieForm"> <div class="form-group"> <div class="imgPreview"><img ng-show="isShow" ng-src='{{dir}}{{serverPicName}}' alt="{{f.name}}"/></div> <button class="upload-btn" type="file" ngf-select="uploadFiles($file, $invalidFiles)" accept="image/*" ngf-max-height="1000" ngf-max-size="1MB"> Select File</button> <input id="serverPicName" value="{{serverPicName}}" type="hidden"/> <span id="fileName">{{f.name}} {{errFile.name}} {{errFile.$error}}</span> <span class="progress" ng-show="f.progress >= 0 && f.progress < 100"> <div class="progress-bar" style="width:{{f.progress}}%" ng-bind="f.progress + '%'"></div> </span> </div> <div class="form-group movieNameBar"> <label for="moviename">电影名称</label> <input type="text" class="form-control" id="moviename" placeholder="请输入电影名称" ng-model="movie.moviename"> </div> <div class="form-group"> <label for="director">导演</label> <input type="text" id="director" class="form-control" ng-model="movie.director"> </div> <div class="form-group"> <label for="writers">编剧</label> <input type="text" id="writers" class="form-control" ng-model="movie.writers"> </div> <div class="form-group"> <label for="actors">主演</label> <input type="text" id="actors" class="form-control" ng-model="movie.actors"> </div> <div class="form-group"> <label for="type">类型</label> <input type="text" id="type" class="form-control" ng-model="movie.type"> </div> <div class="form-group"> <label for="countries">制片国家</label> <input type="text" id="countries" class="form-control" ng-model="movie.countries"> </div> <div class="form-group"> <label for="language">语言</label> <input type="text" id="language" class="form-control" ng-model="movie.language"> </div> <div class="form-group"> <label for="showDate">上映日期</label> <input type="text" id="showDate" class="form-control" ng-model="movie.showDate"> </div> <div class="form-group"> <label for="runtime">片长</label> <input type="text" id="runtime" class="form-control" ng-model="movie.runtime"> </div> <button type="submit" class="btn btn-prima。y" ng-click="postMovie()">提交</button></form>
完成后的录入界面如下图所示
点击完提交按钮之后,打开mongod.exe,输入use moviesite(你定义的db名字) 进入我们定义的db,然后输入db.Movie.find() (Movie为你定义的表名) 查看数据,就可以看到刚插入的数据,如下图所示
至于怎么实现豆瓣电影网站首页数据的展示,这个我会在下篇帖子中介绍。
本帖实例代码已上传至github,后面会持续完善,有需要的话请点击这里查看。
- 基于MEAN的仿豆瓣电影网站开发实战(1)
- 基于MEAN框架的仿豆瓣电影开发实战(2)
- Vue2.0开发仿豆瓣电影WebApp
- 仿豆瓣电影星级排名
- 一些仿豆瓣的网站——猫扑也来抄豆瓣
- 基于微信公众平台的Python开发——豆瓣电影搜索
- 基于python的豆瓣“我看过的电影”的爬虫
- 基于Python的豆瓣电影评分查询器
- 基于Spark和Hive进行的豆瓣电影数据分析
- 基于BeautifulSoup爬取豆瓣网上的电影信息
- 【初学者】基于vue的webapp——豆瓣电影
- 基于大数据的电影网站项目开发之Hadoop2.6.0伪分布式设置(二)
- react-douban 仿豆瓣电影app项目
- Python爬虫(1)——基于BeautifulSoup爬取豆瓣电影信息
- 模仿豆瓣网做一个电影网站
- 爬取豆瓣的电影
- 豆瓣电影的爬虫示例
- 豆瓣top250电影抓取(1)
- jquery修改a标签的href链接和文字
- Rest API学习笔记 --- 实现的 Flask 一个 RESTful API 服务器端 Demo
- Session(2)
- 生活,新的开始
- javascript和jquery修改a标签的href属性
- 基于MEAN的仿豆瓣电影网站开发实战(1)
- 顺序线性表、闭环(10月12日 学习总结)
- pd.read_excel('文件名',sheetname=k,header=0,encoding=utf-8)
- 顺序环形队列的操作实现
- Android项目目录结构
- 解析 FBX 模型文件作为 Direct3D 的渲染模型
- 每天一个常用的linux命令(9)--cp
- idl之结构体
- 机器学习知识体系结构