想学习点新东西就是开手写,就写了个简单的实现,利用node实现一个博客。主要的内容就在首页也能看到了。
话不多说,expressjs怎么创建项目选择ejs模板,之前的文章都写过了。
首先从用户注册开始,有了用户才能根据id查找文章。
<%- include("../layouts/header", {cssAry: ['/style/reg/index.css']}) %> <div class="body flex center"> <div class="index-main"> <div class="index-header"> <h1 class="logo"></h1> <p class="describe">一条大河波浪宽</p> </div> <div class="form-name flex center"> <a class="form-active" href="/reg">注册</a> <a href="/login">登录</a> </div> <div class="index-form"> <form method="post" action="/reg"> <input type="hidden" name="_csrf" value="<%= csrf %>"> <div class="form-item"> <input type="input" name="email" placeholder="邮箱"/> </div> <div class="form-item"> <input type="password" name="pwd" placeholder="密码(大于六位)"> </div> <div class="form-item"> <input type="password" name="repeatpwd" placeholder="确定密码"> </div> <div class="button-item"> <input class="btn btn-sure" type="submit" value="注册"> </div> </form> </div> </div> </div> <%- include("../layouts/footer", {jsAry: []}) %>include 传递参数时就把需要的css 和js 传递到head 和foot模块里
header.ejs
<!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="renderer" content="webkit"> <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <link rel="shortcut icon" type="image/x-icon" href="/images/favicon3.ico"> <meta name="csrf-token" content="<%= csrf %>"> <title><%= title %></title> <link rel='stylesheet' href='/style/main.css' /> <% for(var i = 0, item; item = cssAry[i++]; ) {%> <link rel='stylesheet' href='<%= item %>'/> <% } %> </head> <body>footer.ejs
<script type="text/javascript" src="http://lib.sinaapp.com/js/jquery/2.2.4/jquery-2.2.4.min.js"></script> <script type="text/javascript" src="/javascripts/main.js"></script> <% for(var i = 0, item; item = jsAry[i++]; ) {%> <script type="text/javascript" src="/<%= item %>"></script> <% } %> </body> </html>route路由,编写对应再到reg.ejs的页面
//注册 router.get('/reg', function(req, res) { res.render('reg/index', { title: '注册', pwd_err: req.session.pwd_err }); });
//注册提交 router.post('/reg', function(req, res) { if(!util.isEqual(req.body.pwd, req.body.repeatpwd)) { req.session.pwd_err = true; return res.redirect('/reg'); } let pwd = util.mix(req.body.pwd); let User = { email: req.body.email, pwd: pwd } userDao.setUser(User, function(err){ if (err) { return res.redirect('/reg'); } req.session.email = req.body.email; return res.redirect('/login'); }); });
同时需要user和数据库的交互创建userDao,并且继承自baseDao
import { ObjectID } from 'mongodb'; import connect from '../../config/connect'; import BaseDao from './BaseDao'; import User from '../models/User'; import util from '../lib/util'; //继承Dao class UserDao extends BaseDao { //获取用户信息 登录等 getUser (user, callback) { this.query(user, 'users', callback); } //用户注册 玩家 和管理员 saveUser (user, col, callback) { let _that = this; let model = Object.assign(JSON.parse(User), user); this.query(model, col, function(err, u) { //用户已经存在 if (u !== null) { err = 'notnull'; return callback(err); } _that.save(model, col, callback); }); }; //普通用户注册 setUser (user, callback) { this.saveUser(user, 'users', callback); } updateUserOne(userUpdate, callback) { let ID = {}; if (util.isString(userUpdate.id) ) { ID = {_id: new ObjectID(userUpdate.id)}; } else { ID = userUpdate.id; } this.updateOne('users', ID, {$set: userUpdate.field}, callback); } // 加入postId updatePostId(userPost, callback) { let _that = this; this.updatePromise(userPost.id, 'postId').then(function(result) { let postId = result.data || []; postId.push(userPost.postId); let userUpdate = { id: result.id, field: { postId: postId } }; _that.updateUserOne(userUpdate, callback); }, function(err) { return err; }); } updatePromise(id, key) { let _that = this; let ID = {_id: new ObjectID(id)}; return new Promise(function(resolve, reject) { _that.query(ID, 'users', function(err, rtn) { if (err) { reject(err); } else { let result = {data: rtn[key], id: ID}; resolve(result); } }); }); } } module.exports = UserDao;
import connect from '../../config/connect'; import { ObjectID } from 'mongodb'; class BaseDao { //查询 field查询字段, col 集合或表 query(field, col, callback) { connect.open(function(err, db) { if (err) { return callback(err); } //要查找的集合 db.collection(col, function(err, collection) { //要查找的字段 collection.findOne(field, function(err, result){ connect.close(); if (err) { return callback(err); } //成功 callback(null, result); }); }); }); } //查找排序 query={field: field, orderby: orderby, } querySort(query, col, callback) { connect.open(function(err, db) { if (err) return callback(err); db.collection(col, function(err, collection) { if (query.limit) { collection.find(query.field).sort(query.orderby).limit(query.limit).toArray(function(err, result) { connect.close(); if (err) return callback(err); callback(null, result); }); } else { collection.find(query.field).sort(query.orderby).toArray(function(err, result) { connect.close(); if (err) return callback(err); callback(null, result); }); } }); }); } //查询首页全部 queryAll(col, query, callback) { connect.open(function(err, db) { if( err ) return callback(err); db.collection(col, function(err, collection) { collection.find().sort(query.sort).limit(query.limit).toArray(function(err, result) { connect.close(); if (err) return callback(err); callback(null, result); }); }); }); } //保存 新建 save(field, col, callback) { connect.open(function(err, db) { if (err) { return callback(err); } db.collection(col, function(err, collection) { //if(field._id) delete field._id; collection.insert(field, { safe: true }, function(err, result) { connect.close(); if (err) { return callback(err); } callback(null, result); }); }); }); } //只修改一个 根据Id查找 updateOne(col, id, updateField, callback) { connect.open(function(err, db) { if (err) { return callback(err); } db.collection(col, function(err, collection) { collection.updateOne(id, updateField, function(err, result) { connect.close(); if (err) { return callback(err); } //成功 callback(null, result); }); }); }); } //更新 updates(col, field, updateFields, callback) { connect.open(function(err, db) { if (err) { return callback(err); } db.collection(col, function(err, collection) { collection.update(field, updateFields, function(err, result) { connect.close(); if (err) { return callback(err); } //成功 callback(null, result); }); }); }); } //删除一个表 removeOne(col, field, callback) { connect.open(function(err, db) { if (err) { return callback(err); } db.collection(col, function(err, collection) { collection.removeOne(field, function(err, result) { connect.close(); if (err) { return callback(err); } //成功 callback(null, result); }); }); }); } } module.exports = BaseDao;
工具类
import crypto from 'crypto'; class util { static isNull(str) { if (str.trim() == null || str.trim() == '') { //为空 return true; } } //密码加密混淆 static mix(str) { let sha1 = crypto.createHash('sha1'); return sha1.update(str).digest('hex'); } //字符串是否相等 static isEqual(str, str2) { if(str.trim() === str2.trim()) { return true; } } //未登录 1 static notLogin = function(req, res) { if(!req.session.user){ req.flash('isLogin', '1'); res.redirect('/login'); return true; } return false; } //已经登录 0 static login(req, res) { if(req.session.user){ if(req.session.user['_id'] === req.params._id) { req.flash('isLogin', '0'); return true; } else { return false; } } return false; } //json里出去空值 static mergeJson(basedata, newdata) { let merge = {}; for (let key1 in basedata) { merge[key1] = basedata[key1]; } for (let key in newdata) { if ( newdata[key] !== '' && newdata !== null) { merge[key] = newdata[key]; } } return merge; } //字符串 static isString(str) { if(typeof str === 'string' && str.constructor === String ) return true; } //图片路径 static getPath(str, aim) { var reg = new RegExp(aim + '\\/(\\S*)'); return str.match(reg)[1]; } //时间格式化 static format(str, mat) { let d, date = new Date(str), year = date.getFullYear(), month = date.getMonth() + 1, day = date.getDate(); switch(mat){ case 'year': d = year break; case 'month': d = month; break case 'day': d = day; default: d = year+'-'+month+'-'+day; } return d; } } module.exports = util;
做到此发觉痛点是和数据库mongodb交互用mongodb = require('mongodb'),很别扭,应该用mongoose,不过既然都这么用了,就都应该学习下。
接下来进入登录界面
登录的路由
//进入登录页面 router.get('/login', function(req, res) { let error_name = req.flash('error_name'); res.render('login/index', { title: '登录', error_name: req.flash('error_name'), error_pwd: req.flash('error_pwd'), isLogin: req.flash('isLogin'), email: req.session.email, }); }); //登录 router.post('/login', function(req, res) { if (util.isNull(req.body.email)) { req.flash('error_name', 'error_name'); return res.redirect('/login'); } if (util.isNull(req.body.pwd)) { req.flash('error_pwd', false); return res.redirect('/login'); } //用户查找 let pwd = util.mix(req.body.pwd); let User = { email: req.body.email, pwd: pwd } userDao.getUser(User, function(err, result) { if (err) { return res.redirect('/login'); } if (result === null) { return res.redirect('/reg'); } if (req.session.user && req.session.user['email'] === req.params.email) { delete req.session.user; } req.session.user = { _id: result._id, email: result.email, }; return res.redirect('/personal/'+ result._id); }); });
<%- include("../layouts/header", {cssAry: ['/style/login/index.css']}) %> <div class="body flex center"> <div class="index-main"> <div class="index-header"> <h1 class="logo"></h1> <p class="describe">一条大河波浪宽</p> </div> <div class="form-name flex center"> <a class="form-active" href="/login">登录</a> <a href="/reg">注册</a> </div> <div class="index-form"> <form method="post" action="/login"> <input type="hidden" name="_csrf" value="<%= csrf %>"> <div class="form-item"> <input type="input" name="email" placeholder="邮箱"/> </div> <div class="form-item"> <input type="password" name="pwd" placeholder="请输入密码"> </div> <div class="button-item"> <input class="btn btn-sure" type="submit" value="注册"> </div> </form> </div> </div> </div> <%- include("../layouts/footer", {jsAry: []}) %>
登录成功后进入个人中心页面personal,根据id进入同时用到
import async from 'async';当然不用的话可以用es6的promise(resolve,reject), resolve就是成功后的参数传递,reject就是错误异常时。
//个人中心 router.get('/personal/:_id', function(req, res) { let ID = req.params._id; let boo = util.login(req, res); async.waterfall([function(callback){ userDao.getUser({_id: new ObjectID(ID)}, function(err, result) { if(err) return false; callback(null, result) }); }, function(arg1, callback) { let options = { field: {id: ID}, orderby: {time: -1} } postDao.queryPostSort(options, function(err, rtn) { let json = { title: '个人中心', id: ID, usermsg: arg1.usermsg, postId: arg1.postId, postSize: arg1['postId'].length, postList: rtn, login: boo } callback(null, json); }); }], function(err, result) { res.render('personal/index', result); }); });
用户主要的字段信息有如下
let user = { "email" : "", "pwd" : "", "postId" :[], "usermsg" : { "username" : "", "userwork" : "", "userdegree" : "", "userarea" : "", "usersex" : "", "userintr" : "", "userhead": "", }, "postclass":{ 0: "博文" }, //作者文章分类 }
当点击资料编辑时,进入资料编辑页面
//个人资料修改 router.get('/personal/edit/:_id', function(req, res) { let boo = util.notLogin(req, res); if(boo) return false; req.session.article = false; let ID = req.session.user['_id']; async.parallel([function(callback) { userDao.getUser({_id: new ObjectID(ID)}, function(err, result) { if(err) return false; callback(null, result.usermsg); }); }, function(callback) { //头像上传 mkdirs('/var/www/near/public/images/users/' + req.session.user['_id'] + '/avatar/', '0777'); callback(null, 'ok'); }], function(err, results){ res.render('personal/edit', { title: '个人资料修改', id: ID, usermsg: results[0], login: true }); }); }); router.post('/personal/edit/:_id', function (req, res) { let boo = util.notLogin(req, res); if(boo) return false; if(req.session.user['_id'] !== req.params._id) return res.redirect('login'); let Upload = upload.single("userhead"); //头像图片上传,未把头像图片单独做出来,选中后ajax提交 Upload(req, res, function(err) { if (err) { return ; } let id = req.session.user['_id']; userDao.updatePromise(id, 'usermsg').then(function(result) { let data = util.mergeJson(result.data, req.body); data['userhead'] = req.session.postcover; req.session['postcover'] = null; let userUpdate = { id: result.id, field: {usermsg: data} }; userDao.updateUserOne(userUpdate, function() { return res.redirect('/personal/'+ id); }) }, function(err) { return res.redirect('/reg'); }); });
import upload from '../lib/multer.config';
import multer from 'multer'; import fs from 'fs' ; var storage = multer.diskStorage({ destination: function (req, file, cb) { let newDestination = '/var/www/near/public/images/users/' + req.session.user['_id']; req.session.article ? (newDestination += '/posts/') : (newDestination += '/avatar/'); cb(null, newDestination); }, filename: function (req, file, cb) { let originalname = file.originalname, start = originalname.lastIndexOf('.'), len = originalname.length, type = originalname.substring(start+1, len), filename = Date.now() + '.' + type; req.session.postcover = filename; cb(null, filename); } }); let upload = multer( { limits: { fieldNameSize: 100, fileSize: 60000000 }, storage: storage } ); module.exports = upload;ejs
<%- include("../layouts/header", {cssAry: ['/style/personal/edit.css']}) %> <div class="body"> <%- include("../layouts/main_head", {publishBtn: true}) %> <div class="main"> <form action="/personal/edit/<%= id %>?_csrf=<%= csrf %>" method="POST" id="userform" enctype="multipart/form-data"> <div class="form-item useravatar" id="useravatar"> <img src="/<%= usermsg.userhead ? ('images/users/' + id +'/avatar/' + usermsg.userhead) : 'images/default_avatar.jpg' %>"> <em class="cover" id="cover"></em> <input class="input-file item-val" type="file" id="input-file" name="userhead"> </div> <div class="form-item"> <div class="flex center"> <span class="profile-name"><%= usermsg.username || "暂未填写" %></span> <a class="modify-btn" data-type="center" href="javascript:;">修改</a> </div> <div class="modify-content flex-center-h"> <input class="modify-name item-val" type="text" placeholder="请填写昵称" name="username"> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> <div class="form-item flex"> <div class="item-name">职业</div> <div> <p class="modify-msg flex"> <span class="modify-file"><%= usermsg.userwork || "暂未填写" %></span> <a class="modify-btn" data-type="com" href="javascript:;">修改</a> </p> <div class="modify-content flex-center-h"> <input class="modify-name item-val" type="text" placeholder="请输入职业" name="userwork"> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> <div class="form-item flex"> <div class="item-name">学位</div> <div> <p class="modify-msg flex"> <span class="modify-file"><%= usermsg.userdegree || "暂未填写" %></span> <a class="modify-btn" data-type="com" href="javascript:;">修改</a> </p> <div class="modify-content flex-center-h"> <input class="modify-name item-val" type="text" placeholder="请输入学位" name="userdegree"> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> <div class="form-item flex"> <div class="item-name">地区</div> <div> <p class="modify-msg flex"> <span class="modify-file"><%= usermsg.userarea || "暂未填写" %></span> <a class="modify-btn" data-type="com" href="javascript:;">修改</a> </p> <div class="modify-content flex-center-h"> <input class="modify-name item-val" type="text" placeholder="请填写地区" name="userarea"> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> <div class="form-item flex"> <div class="item-name">性别</div> <div> <p class="modify-msg flex"> <span class="modify-file"><% if(usermsg.usersex) { %> <%= usermsg.usersex == 0 ? "女" : "男" %> <% } else { %> <%= "暂未填写" %> <% } %></span> <a class="modify-btn" data-type="radio" href="javascript:;">修改</a> </p> <div class="modify-content flex-center-h"> <label><input class="item-val" type="radio" name="usersex" value="1"> <em>男</em></label> <label><input class="item-val" type="radio" name="usersex" value="0"> <em>女</em></label> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> <div class="form-item flex"> <div class="item-name">介绍自己</div> <div> <p class="modify-msg flex"> <span class="modify-file"><%= usermsg.userintr || "暂未填写" %></span> <a class="modify-btn" data-type="com" href="javascript:;">修改</a> </p> <div class="modify-content modify-content-ta"> <textarea class="modify-name item-val" placeholder="请简单的介绍自己" name="userintr"></textarea> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> </form> </div> </div> <%- include("../layouts/footer", {jsAry: ['javascripts/personal/edit.js']}) %>
目录结构
有需要的交流的可以加个好友