美文网首页
centos web应用 购物模版 全栈

centos web应用 购物模版 全栈

作者: Gantowell | 来源:发表于2018-09-26 12:14 被阅读0次

    标题

    • 这是一篇写给自己的总结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: "无效的网址"});
      }
    });
    
    

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

    相关文章

      网友评论

          本文标题:centos web应用 购物模版 全栈

          本文链接:https://www.haomeiwen.com/subject/cuetiftx.html