centos web应用 购物模版 全栈

标题

  • 这是一篇写给自己的总结web应用的笔记
  • 内容包括nodejs的express框架,前端html的设计,css盒子模型的思考和css3新属性的试水
  • angularjs在写购物页面时的便利
  • django在做server时遇到的一堆关于post,get的问题

核心科技

  • nodejs
  • express
  • bootstrap
  • django
  • selenium
  • angular.js

一点说明

  1. vps来自vultr,domain买自某鹅,centos7.4
  2. 安装nodejs和python3这种事情就不写了
  3. 原本使用apache提供web服务,后面使用nginx反向代理
  4. database用mysql(原谅我知识匮乏),安装的phpmyadmin给显示操作数据库数据
  5. phpmyadmin在apache下,所以重置了httpd的端口

Chapter 1

基于express的简单展示页面(注册+登陆)

  • 成果

主页.png

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就能看到

express hello world.png

(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: "无效的网址"});
  }
});

因为在同域里,不需要解决跨域问题

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354

推荐阅读更多精彩内容

  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,182评论 0 3
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,082评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,714评论 2 59
  • 练字有很多好处,可以静心,修性。 说说练毛笔字吧。 最开始的想练习毛笔,是因为在广场看到有位老大爷以地为纸,手执大...
    从0721开始日更阅读 197评论 0 0
  • 东莞装修#玄关隔断, 【家庭装修】玄关装潢隔绝在咱们平常装修中,玄关的装修也是一个异常重要的部门,那有些家庭为了隔...
    东莞康之源装饰小唐阅读 130评论 0 0