标题
- 这是一篇写给自己的总结web应用的笔记
- 内容包括nodejs的express框架,前端html的设计,css盒子模型的思考和css3新属性的试水
- angularjs在写购物页面时的便利
- django在做server时遇到的一堆关于post,get的问题
核心科技
- nodejs
- express
- bootstrap
- django
- selenium
- angular.js
一点说明
- vps来自vultr,domain买自某鹅,centos7.4
- 安装nodejs和python3这种事情就不写了
- 原本使用apache提供web服务,后面使用nginx反向代理
- database用mysql(原谅我知识匮乏),安装的phpmyadmin给显示操作数据库数据
- phpmyadmin在apache下,所以重置了httpd的端口
Chapter 1
基于express的简单展示页面(注册+登陆)
-
成果
login (1) (1).gif
说个笑话,所有的logo图片都是100%纯手绘的 = =
-
基本功能
实现了注册登陆的功能,注册后会发送验证email
-
role
前端:静态html加express渲染
后端:nodejs -
先讲一段废话
step1 新建express工程
通过以下代码:
#myApp 你的工程目录名
mkdir myApp
cd myApp
npm install express --save
npm install express-generator -g
express
#遇到提示,输入y
npm install
#功能依次为安装express,安装express生成器,通过express生成器生成一个自带目录结构的工程,再安装所有依赖的包
#如果报错,请确保自己是拥有这台服务器的控制管理权的用户,可以在指令前面添加sudo
然后你的myApp工程就有以下结构
工程目录结构.png
我们首先把目光转向package.json,毕竟此类配置文件决定了整个工作流程,自动生成的package应该长这样:
//包名,version啥的就不bb了
//scripts里的start,说明当执行npm start时,实际运行的指令是什么
{
"name": "myapp",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www" //start指令的执行内容
},
"dependencies": {
"cookie-parser": "~1.4.3",
"debug": "~2.6.9",
"express": "~4.16.0",
"http-errors": "~1.6.2",
"jade": "~1.11.0",
"morgan": "~1.9.0"
}
}
so当你执行npm start
时,实际运行的是node ./bin/www
,我们再去看www里写的啥玩意。
需要多说一句的是,此文基于express4.x,如果是3.x,不会生成bin/www文件夹,而是需要
node app.js
来作为入口运行整个工程,为啥呢?
以下内容参考stackflow
在express4.0中,所有关于中间件的设置从主入口文件中分类开,如果是之前全部写在app.js中,现在有关中间件的设置全部写在bin下可单独维护,app.js也不会显得那么臃肿,更专注业务本身。
bin中可以存放很多文件对应不同startup的脚本,你可以添加test,stop,restart等文件并对应到package.json的script中,每个文件里的配置可以不同,可以使用不同端口,而工程的核心部分由app.js引导的部分却不需要变动。
www里的代码如下,实在是太长了,摘几个比较关键的句子出来分析一下。
#!/usr/bin/env node
var app = require('../app'); //app,入口文件
var http = require('http'); //要架http服务器,需要引入http包
var port = normalizePort(process.env.PORT || '3000');// express内置说明的端口和3000中间选一个,3000是你想设置的端口号,可以随便改
app.set('port', port);
var server = http.createServer(app);//新建http服务
function onListening() { //http服务器监听函数
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
//如果你想知道自己的服务器运行在什么地址or端口的话,可以添加一句:
console.log("Now sever is listening on" + server.address().address + ":" + port);
如果你的服务器ipv6可用时,上面的server.address会显示:::(any ipv6 address), 或者任何ipv4地址(0.0.0.0)。如果你特意指定的,比如通过语句
var server = app.listen(7777, "127.0.0.1", function(){})
服务器就会监听在127.0.0.1:7777
看完了www,because这里不是引入了app.js,所以接下来看app.js里的内容。依旧是太长了,摘几个重要的出来看看
var express = require('express'); //express框架跑不掉
//对应routes文件下的两个js文件,可以把这个路由看成管理不同功能模块的部分
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var app = express(); //使用express框架
app.set('view engine', 'jade'); //默认使用jade模版
app.use(express.static(path.join(__dirname, 'public')));
//设置静态目录,所有静态文件(在html里进入js,img,css文件时的目录)
//假设需要引入的js文件叫main.js,它放在`public/javascirpts/main.js`,那在页面引入的时候只需写/javascripts/main.js,它会自动联系到public目录下寻找
app.use('/', indexRouter);
app.use('/users', usersRouter);
//当http访问路径为/打头的,自动转入indexRouter中处理,由index.js管理
//当http访问路径由/users打头的,转入users.js中管理
ok,当我们运行npm start
的时候,按理说你应该在浏览器里输入
htttp://你的服务器ip:你开放的端口
,就能看到express的hello world,然而可能由于以下原因你看不到。
(1)端口开了嘛?
firewall-cmd --zone=public --add-port=80/tcp --permanent
把80改为自己要开放的端口,然后reload
firewall-cmd --reload
就能开放端口,这个时候,运行commad firewall-cmd --list-ports
,如果看到自己设置的端口在里面,说明端口已开,再运行npm start,在go to that page就能看到
(2)port is already in use
你选择的端口已经在使用中了,要么换端口再来一次,或者暴力直接关了现在用这个端口的任务,然后再开启
暴力关端口正在运行任务的commad
fuser -k port/tcp
//port是端口号(3)other error
-
如果实在npm start指令后出现的错误,错误应该在代码里(然而这是自动生成的多半不可能)
-
如果是npm正常工作,但是怎么都不能在浏览器里打开网页,那就是服务器设置问题
(1)端口问题
(2)centos7以下用iptables看一下
(3)查看自己是否用了127.0.0.1,这样的话用外网无法访问的,要用0.0.0.0 or:::: -
废话讲完了,进正片
step2 写html和配置app.js
我不会jade,改用html的view engine
app.set('view engine', 'html');
app.engine('.html', ejs.__express);
以后静态html都放在view目录下
然后由于之前设置的当访问url是以/打头的都由index.js管理,所以第一步的网页设计基本全部基于index.js,接下来基本在修改index.js。
此时的index.js非常simple
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
刚才我们访问htttp://你的服务器ip:你开放的端口
,由上面第一个router控制,即我们访问这个url,是向你的web应用发送了一个get请求,myApp接收到这个请求后,运行里面的函数
res.render('index', {title: 'Express'}
render函数为渲染一个页面,可以是jade可以是html,之前设置了view engine的默认路径为view,所以它会在view目录下寻找名为index的,并将其返回给浏览器,由浏览器解析这个view并展示。
第二个参数为服务器传给前端的参数,这里传了个参数title,值为Express,以这种键值对的object的形式传递,在index这个view里可以使用这个title这个变量
我们改完html的view engine后,要写属于自己的页面了,在view下新建一个html,名为main.html(取名什么的随意啦),然后写点东西。
写html真不是我擅长的,总之就是先写
<!doctype html>
指定页面解析格式,然后<head></head>
and<body></body>
我的第一个html写的也是乱七八糟,有几个重点,我用了bootstrap所以在head里引入它的css文件(用的官方给的cdn),然后引入自己的css文件,自己的css文件放在public/stylesheets下面,这样就可以以<link rel="stylesheet" type="text/css" href="stylesheets/main.css">
的方式引入,然后写body
受不了了,这么写blog要写到猴年马月啊,我就讲讲重点好了。
(1)写完html,改index.js
res.render('main');
重新npm start,再访问页面即展现刚写的html的内容
(2)要为html添加静态js文件处理有关页面dom操作
(1)引入时,如果在js中使用了jquery的方法,要在html中先引入jquery,并且放在静态js文件前,如果倒置,先解析静态js就无法识别里面的jquery方法
(2)js文件放在public/javascrcipts下
(3)js文件放在<body>里的下面,因为html渲染过程中,<head>先于<body>解析,这时候编译js里的东西有牵扯到dom操作的就会无法识别。且慢
(3)登陆和注册页面分别写了两个html
(3)两个页面都是以/login.html 和/siginup.html的url访问,所以都在index.js里添加
router.get('/login.html', function(req, res, next) {
res.render('login',);
});
(4) 静态js文件里对登陆页面的,登陆和注册按钮分别添加onclick事件
- 注册按钮点击完会实现页面跳转,页面跳转使用语句
window.location.href = "signup.html";
but! pay attetion! 如果你的登陆页面里的按钮被嵌套在了form下,form有自己的action,这个时候即使写了页面跳转也不会运行,需要添加一句window.event.returnValue=false;
,使表单的提交事件失效,不在form里就没有关系- 登陆按钮的监听事件是发post请求到服务器验证账号和密码,采用ajax的方式,如果遇到
$()不识别这样的错误
,添加jQuery.noConflict();
来解决$间的冲突,下面为一个简单的post请求的范本
$.post('/post_login',
{
username: username,
password: password,
rempwd: Number($('#rempwd')[0]['checked'])
},
function(data, status) {
console.log(data);
});
(5)前端写好了页面,验证账号和注册账号这些事件应该由后端处理,所以在index.js里添加对应请求的处理
router.post('/post_login', async(req, res) => {})
来处理post请求
req.body.xxx
来获取post请求data中的xx
这里牵扯到数据库,考虑到nodejs是非阻塞的异步处理,所以将数据库操作用promise,写在下面
res.cookie()
自动登陆使用的方法是使用cookies(当然写在网页里的应该是账号id和token,不能把密码直接以暴露的形式),使用cookies需要npm下载对应的包,然后require
var username = req.body.username;
var password = req.body.password;
console.log(username);
var sql = `SELECT * FROM user WHERE username = '${username}' OR email = '${username}'`;
var status = "";
var code;
var data = "";
var result = await query(sql, null, pool1);
if (result.length == 0) {
status = 'error';
code = 401;// wrong username;
data = "wrong username"
}
else {
var tmpPwd = result[0].password;
if (result[0].activate == 1) {
if (tmpPwd === password) {
code = 200;
status = 'success'; // verify ok;
var userInfo = {
id: result[0].id,
username: username
}
data = JSON.stringify(userInfo);
var expiresTime = new Date();
if(req.body.remembered) {
var persistPeriod = 30*24*60*60*1000;
}
else {
var persistPeriod = 15*60*1000;
}
expiresTime.setTime(expiresTime.getTime() + persistPeriod);
res.cookie('userinfo', JSON.stringify(userInfo), {maxAge: persistPeriod, httpOnly: false, expires: expiresTime.toGMTString()});
}
else {
code = 402; // wrong password;
status = 'error';
data = "密码错误";
}
}
else {
code = 403;
status = 'error';
data = "not activate";
}
}
var response = {
status: status,
data: data,
code: code
};
res.json(response);
数据库的操作
var pool1 = mysql.createPool({
host: "localhost",
user: "user",
password: "password",
database: "database"
});
let query = function (sql, values, pool) {
return new Promise((resolve, reject) => {
pool.getConnection(function(err, connection) {
if (err) reject(err);
else {
connection.query(sql, values, (err, rows) => {
if (err) {
reject(err);
}
else resolve(rows)
connection.release();
})
}
})
})
}
注册部分的后端
- 添加了一个激活步骤,在注册完后,自动发送邮件给用户,激活码随机生成
- 在注册中考虑到存在已经注册过的情况,需要对数据库先查询
- 邮箱设置部分见下
router.post('/register', async(req, res) => {
var reg_email = req.body.email;
var reg_pwd = req.body.password;
var sql = `SELECT * FROM user WHERE username = '${reg_email}'`;
var result = await query(sql, null, pool1);
var reg_date = getMysqlDate();
var response;
if (result.length != 0) {
response = {
status: 'error',
code : 400,
data: 'registed'
};
} else {
var activateCode = generateCode();
console.log(activateCode);
var insert_sql = `INSERT INTO user (email, password, signup_date, activate, activateCode) VALUES ('${reg_email}', '${reg_pwd}', '${reg_date}', 0, '${activateCode}')`;
var inResult = await query(insert_sql, null, pool1);
response = {
status: 'success',
code : 200,
data: 'ok'
};
var mailText = `Please verify your account through the link http://www.rikuki.cn/register.html?username=${reg_email}&activateCode=${activateCode}`;
var mailOptions = {
from: 'your email address',
to: `${reg_email}`,
subject: 'Verify your account on Rikuki',
text: mailText
};
transporter.sendMail(mailOptions, function(error, info) {
if (error) {
console.log(error);
} else {
console.log("Email Sent" + info.response);
}
});
}
console.log(response);
res.json(response);
// res.render('index', {msg: "The activate email has been sent to you, please check your email and verify yourself before next signing in."});
});
对应也要install nodemai的包
var nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'youremail',
pass: 'yourpassword'
}
});
(6)verify的链接需要自己考虑,我考虑的就是传个username和激活码,通过get的方式,然后后端接收到这个url请求后,分析username和激活码,如果激活码和数据库里的相同,便更改激活状态,否则认定为无效的url,如果username不存在也是无效的
req.query.xxx;
get通过req.query的方法读取url里的参数,url的格式严格按照html后?参数名1=值1&参数2=值2的格式- index页面我设置成一个显示提示信息的页面
router.get('/register.html', async(req, res) => {
var userMail = req.query.username;
var activateCode = req.query.activateCode;
if (userMail && activateCode) {
var sql = `SELECT * FROM user WHERE email = '${userMail}'`;
console.log(sql);
var result = await query(sql, null, pool1);
console.log(result);
if (result.length == 0){
res.render('index', {msg: "无效的网址"});
} else {
if (result[0].activateCode != activateCode ) res.end('invalid url');
else {
var nowStatus = result[0].activate;
if (nowStatus==1) {
res.render('index', {msg: "无效的网址"});
} else {
var curId = result[0].id;
var update_sql = `UPDATE user SET activate = 1 WHERE id = ${curId}`;
var updateResult = await query(update_sql, null, pool1);
res.render('index', { msg: `您已经注册成功,以后可以使用${userMail}登陆`});
}
}
}
} else {
res.render('index', {msg: "无效的网址"});
}
});
因为在同域里,不需要解决跨域问题
网友评论