demo

  • 部署服务器
  • 代码解读
    • node的代码
      • 模型
      • 测试
      • api
    • 前端weui代码
      • index.html
      • example.js
      • 实现tab
      • 绑定实践
  • 微信配置说明
  • 总结

部署服务器

阿里云

ubuntu 14.10 LTS 64位

登录远端服务器

ssh root@ip

创建用户

  1. # sudo useradd -m -d /home/sang -s /bin/bash -c "the sang user" -U sang
  2. # passwd sang
  3. Enter new UNIX password:
  4. Retype new UNIX password:
  5. passwd: password updated successfully
  • useradd创建登录用户
  • passwd设置用户登录密码

赋予sudo权限

如果有必要使用sudu权限,请修改

  1. # sudo vi /etc/sudoers

复制root行改为sang即可

  1. # User privilege specification
  2. root ALL=(ALL:ALL) ALL
  3. sang ALL=(ALL:ALL) ALL

切换用户

  1. # su - sang
  2. $ ls
  3. $
  4. $ pwd
  5. /home/sang
  6. $

安装必备软件

安装git

如果上面没有复制给sang账户sudo权限,请切换到root账户操作

  1. sudo apt-get update
  2. sudo apt-get install git

安装nginx

  1. sudo apt-get install nginx

开机启动

http://www.jianshu.com/p/2e03255cfabb)

  1. sudo apt-get install sysv-rc-conf
  2. sudo sysv-rc-conf nginx on

注意:Ubuntu系统中服务的运行级别

  • 0 系统停机状态
  • 1 单用户或系统维护状态
  • 2~5 多用户状态
  • 6 重新启动

准备工作目录

  1. mkdir -p workspace/github
  2. cd workspace/github

安装nodejs

安装nvm

  1. $ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash
  2. % Total % Received % Xferd Average Speed Time Time Time Current
  3. Dload Upload Total Spent Left Speed
  4. 100 7766 100 7766 0 0 28614 0 --:--:-- --:--:-- --:--:-- 28656
  5. => Downloading nvm as script to '/home/sang/.nvm'
  6. => Appending source string to /home/sang/.bashrc
  7. => Close and reopen your terminal to start using nvm
  8. $ source ~/.bashrc
  9. $ nvm
  10. Node Version Manager

安装nodejs lts版本

  1. $ nvm install 4
  2. Downloading https://nodejs.org/dist/v4.3.2/node-v4.3.2-linux-x64.tar.xz...
  3. ######################################################################## 100.0%
  4. Now using node v4.3.2 (npm v2.14.12)
  5. Creating default alias: default -> 4 (-> v4.3.2)
  6. $ node -v
  7. v4.3.2

使之成为默认

  1. $ nvm alias default 4.3
  2. default -> 4.3 (-> v4.3.2)

确认npm版本

  1. $ npm -v
  2. 2.14.12

只要大于2.9.1即可,如不是,请npm i -g npm@2.9.1

安装nrm

  1. $ npm i -g nrm
  2. npm WARN deprecated npmconf@0.1.16: this package has been reintegrated into npm and is now out of date with respect to npm
  3. /home/sang/.nvm/versions/node/v4.3.2/bin/nrm -> /home/sang/.nvm/versions/node/v4.3.2/lib/node_modules/nrm/cli.js
  4. nrm@0.3.0 /home/sang/.nvm/versions/node/v4.3.2/lib/node_modules/nrm
  5. ├── ini@1.3.4
  6. ├── only@0.0.2
  7. ├── extend@1.3.0
  8. ├── async@0.7.0
  9. ├── open@0.0.5
  10. ├── commander@2.9.0 (graceful-readlink@1.0.1)
  11. ├── npmconf@0.1.16 (inherits@2.0.1, osenv@0.0.3, ini@1.1.0, semver@2.3.2, mkdirp@0.3.5, once@1.3.3, nopt@2.2.1, config-chain@1.1.10)
  12. ├── node-echo@0.0.6 (jistype@0.0.3, mkdirp@0.3.5, coffee-script@1.7.1)
  13. └── request@2.69.0 (aws-sign2@0.6.0, forever-agent@0.6.1, tunnel-agent@0.4.2, oauth-sign@0.8.1, is-typedarray@1.0.0, caseless@0.11.0, stringstream@0.0.5, isstream@0.1.2, json-stringify-safe@5.0.1, extend@3.0.0, tough-cookie@2.2.1, node-uuid@1.4.7, qs@6.0.2, combined-stream@1.0.5, form-data@1.0.0-rc3, mime-types@2.1.10, aws4@1.3.2, hawk@3.1.3, bl@1.0.3, http-signature@1.1.1, har-validator@2.0.6)

测速

  1. $ nrm test
  2. * npm ---- 274ms
  3. cnpm --- 6868ms
  4. taobao - 716ms
  5. edunpm - 5598ms
  6. eu ----- Fetch Error
  7. au ----- Fetch Error
  8. sl ----- 1234ms
  9. nj ----- 2228ms
  10. pt ----- Fetch Error

切换源

  1. $ nrm use npm
  2. Registry has been set to: https://registry.npmjs.org/

部署nodejs应用

基础

  • git clone
  • npm i
  • pm2 start

修改nginx

  1. cat /etc/nginx/sites-enabled/default
  2. upstream backend_nodejs {
  3. server 127.0.0.1:3019 max_fails=0 fail_timeout=10s;
  4. #server 127.0.0.1:3001;
  5. keepalive 512;
  6. }
  7. server {
  8. listen 80 default_server;
  9. listen [::]:80 default_server ipv6only=on;
  10. #root /usr/share/nginx/html;
  11. root /home/sang/workspace/oschina/base2-wechat-jssdk/public;
  12. index index.html index.htm;
  13. # Make site accessible from http://localhost/
  14. server_name nodeonly.mengxiaoban.cn at35.com;
  15. client_max_body_size 16M;
  16. keepalive_timeout 10;
  17. location / {
  18. # First attempt to serve request as file, then
  19. # as directory, then fall back to displaying a 404.
  20. #try_files $uri $uri/ =404;
  21. # Uncomment to enable naxsi on this location
  22. # include /etc/nginx/naxsi.rules
  23. proxy_set_header X-Real-IP $remote_addr;
  24. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  25. proxy_set_header Host $http_host;
  26. proxy_set_header X-NginX-Proxy true;
  27. proxy_redirect off;
  28. proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
  29. proxy_set_header Connection "";
  30. proxy_http_version 1.1;
  31. proxy_pass http://backend_nodejs;
  32. }
  33. }

注意

  • upstream backend_nodejs定义的代理转发的api地址
  • location /下面的proxy_pass,从upstream里取
  • root下面放的是静态资源,比如express下的public目录

然后重启nginx即可

  1. sudo nginx -s reload

了解MONGODB的部署

  • replset
  • shard

我写的《 mongodb运维之副本集实践》

https://cnodejs.org/topic/5590adbbebf9c92d17e734de

on ubuntu

https://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/

代码部署

ssh免登陆

  1. git clone git@git.oschina.net:i5ting/wechat-dev-with-nodejs.git
  • 调整nginx
  • pm2

node的代码

模型

  • 目录结构
  • 表间关系

User用户

  1. username: {// 真实姓名
  2. type: String
  3. },
  4. password : String,
  5. unionid : String,
  6. openid: {// from weixin openid
  7. type: String,
  8. required: true,
  9. index: {
  10. unique: true
  11. }
  12. },
  13. nickname : String,// from weixin 昵称
  14. sex : String,// from weixin 性别 0->女 1->男
  15. language : String,// from weixin 语言
  16. city : String,// from weixin 城市
  17. province : String,// from weixin
  18. country : String,// from weixin
  19. headimgurl : String,// from weixin 头像路径
  20. privilege : [], // from weixin
  21. created_at : {
  22. type: Date,
  23. "default": Date.now
  24. }

Course课程

  1. name: {// 课程名
  2. type: String
  3. },
  4. pic: {// 课程图片
  5. type: String,
  6. "default": "/images/logo.png"
  7. },
  8. desc: {// 描述
  9. type: String,
  10. "default": "这是StuQ的一门在线课程"
  11. },
  12. price: {// 价格
  13. type: Number
  14. },
  15. docent: {// 讲师
  16. type: String
  17. },
  18. content: {// 大纲
  19. type: String
  20. },
  21. owner_id: { //user_id
  22. type: Schema.ObjectId,
  23. index: true,
  24. required: true,
  25. ref: 'User'
  26. },
  27. created_at: {
  28. type: Date,
  29. "default": Date.now
  30. }

订单

  1. desc: {// 订单说明
  2. type: String
  3. },
  4. "user_id": { //user_id
  5. type: Schema.ObjectId,
  6. index: true,
  7. required: true,
  8. ref: 'User'
  9. },
  10. user_name: {// 用户名
  11. type: String
  12. },
  13. "course_id": { //user_id
  14. type: Schema.ObjectId,
  15. index: true,
  16. required: true,
  17. ref: 'Course'
  18. },
  19. course_name: {// 课程名
  20. type: String
  21. },
  22. created_at : {
  23. type: Date,
  24. "default": Date.now
  25. }

问题

  • 如何查看我的课程?
  • 如果查看我购买的课程?

源码介绍的时候,穿插robo和dash用法

测试

user

  1. var request = require('supertest');
  2. var assert = require('chai').assert;
  3. var expect = require('chai').expect;
  4. require('chai').should();
  5. require('../db')
  6. var User = require('../app/models/user')
  7. // 测试代码基本结构
  8. describe('用户User', function(){
  9. before(function(d) {
  10. // runs before all tests in this block
  11. User.remove({"openid":"ss"},function(){
  12. d()
  13. })
  14. })
  15. after(function(){
  16. // runs after all tests in this block
  17. // User.remove({},function(err, user){
  18. // });
  19. })
  20. beforeEach(function(){
  21. // runs before each test in this block
  22. })
  23. afterEach(function(){
  24. // runs after each test in this block
  25. })
  26. describe('#save()', function(){
  27. this.timeout(30000);
  28. it('should return stuq when user save', function(done){
  29. User.create({"username":"stuq","password":"password", "openid":"ss"},function(err, user){
  30. if(err){
  31. console.log(err)
  32. expect(err).to.be.not.null;
  33. done();
  34. }
  35. expect(user.username).to.be.a('string');
  36. expect(user.username).to.equal('stuq');
  37. done();
  38. });
  39. })
  40. })
  41. })

用户与课程

  1. var request = require('supertest');
  2. var assert = require('chai').assert;
  3. var expect = require('chai').expect;
  4. require('chai').should();
  5. require('../db')
  6. var User = require('../app/models/user')
  7. var Course = require('../app/models/course')
  8. var Order = require('../app/models/order')
  9. var _user;
  10. // 测试代码基本结构
  11. describe('课程Course', function(){
  12. before(function(done) {
  13. // runs before all tests in this block
  14. User.removeAsync({"username":"stuq","password":"password", "openid":"ss"}).then(function(){
  15. return User.createAsync({"username":"stuq","password":"password", "openid":"ss"})
  16. }).then(function(user){
  17. _user = user;
  18. return Course.removeAsync({"name":"Node.js微信开发"});
  19. }).then(function(){
  20. done();
  21. });
  22. })
  23. after(function(){
  24. // runs after all tests in this block
  25. // User.remove({},function(err, user){
  26. // });
  27. })
  28. beforeEach(function(){
  29. // runs before each test in this block
  30. })
  31. afterEach(function(){
  32. // runs after each test in this block
  33. })
  34. describe('#save()', function(){
  35. it('should return Node.js微信开发 when Course save', function(done){
  36. Course.create({
  37. "name":"Node.js微信开发","desc":"stuq在线课程", "docent":"桑世龙", owner_id: _user._id,
  38. desc:"通过学习Node.js基础和express,微信开发常用库,h5,最后达到学会Node.js开发的目的,该课程以实战为主,深入浅出"
  39. },function(err, c){
  40. if(err){
  41. console.log(err)
  42. expect(err).to.be.not.null;
  43. done();
  44. }
  45. expect(c.name).to.be.a('string');
  46. expect(c.name).to.equal('Node.js微信开发');
  47. done();
  48. });
  49. })
  50. })
  51. })

三个表相关的订单

  1. var request = require('supertest');
  2. var assert = require('chai').assert;
  3. var expect = require('chai').expect;
  4. require('chai').should();
  5. require('../db')
  6. var User = require('../app/models/user')
  7. var Order = require('../app/models/order')
  8. var Course = require('../app/models/course')
  9. var _user, _course;
  10. // 测试代码基本结构
  11. describe('订单Order', function(){
  12. before(function(done) {
  13. // runs before all tests in this block
  14. User.removeAsync({"openid":"ss1"}).then(function(){
  15. return Course.removeAsync({"name":"Node.js微信开发1"});
  16. }).then(function(){
  17. User.create({"username":"stuq1","password":"password", "openid":"ss1"},function(err, user){
  18. _user = user;
  19. // console.log(err)
  20. // console.log(_user)
  21. return Course.create({"name":"Node.js微信开发1","desc":"stuq在线课程", "docent":"桑世龙", owner_id: _user._id},function(err1, c){
  22. // console.log(c)
  23. _course = c;
  24. done();
  25. });
  26. });
  27. })
  28. })
  29. after(function(){
  30. })
  31. beforeEach(function(){
  32. // runs before each test in this block
  33. })
  34. afterEach(function(){
  35. // runs after each test in this block
  36. })
  37. describe('#save()', function(){
  38. it('should return order when order save', function(done){
  39. Order.create({
  40. "desc":"a order"
  41. ,"user_id":_user._id
  42. , "user_name": _user.username
  43. ,course_id : _course._id
  44. ,course_name : _course.name
  45. },function(err, order){
  46. if(err){
  47. console.log(err)
  48. expect(err).to.be.not.null;
  49. done();
  50. }
  51. expect(order.desc).to.be.a('string');
  52. expect(order.desc).to.equal('a order');
  53. done();
  54. });
  55. })
  56. })
  57. })

另外举例runkoa没有ci引发的血案

api

自动挂载路由

  1. var mount = require('mount-routes');
  2. // simple
  3. mount(app, __dirname + '/app/routes');

示例

  1. var express = require('express');
  2. var router = express.Router();
  3. var User = require('../../models/user')
  4. var Course = require('../../models/course')
  5. var Order = require('../../models/order')
  6. router.get('/', function(req, res, next) {
  7. Course.find({},function(err, courses){
  8. res.json({
  9. status:{
  10. code:0,
  11. msg:'sucess'
  12. },
  13. data:courses
  14. });
  15. });
  16. })
  17. module.exports = router;

测试

  1. curl http://127.0.0.1:3019/api/courses

或者postman

前端weui代码

weui v0.4.x新增了路由和tab等组件,问题还是挺多的

frontend目录随便配,目的就是为了让大家理解前后端分离

路由

定义骨架

  1. var router = new Router({
  2. container: '#container',
  3. enterTimeout: 250,
  4. leaveTimeout: 250
  5. });

然后

  1. // course
  2. var course = {
  3. url: '/course',
  4. className: 'panel',
  5. render: function () {
  6. // alert(getQueryStringByName('id'));
  7. return $('#tpl_course').html();
  8. },
  9. bind: function () {
  10. $('.pay_btn').on('click', function(){
  11. var id = getQueryStringByName('id');
  12. pay_h5(id);
  13. })
  14. }
  15. };
  16. // tabbar
  17. var tabbar = {
  18. url: '/home',
  19. className: 'tabbar',
  20. render: function () {
  21. var _t = this;
  22. setTimeout(function(){
  23. _t.bind()
  24. },100)
  25. return $('#tpl_tabbar').html();
  26. },
  27. bind: function () {
  28. $('.course_list').html(all_courses_html);
  29. $('.weui_tabbar_content').eq(0).show()
  30. $('.weui_tabbar_item').on('click', function () {
  31. $('.weui_tabbar_item').eq($('.weui_tabbar_item').index(this)).addClass('weui_bar_item_on').siblings().removeClass('weui_bar_item_on')
  32. $('.weui_tabbar_content').eq($('.weui_tabbar_item').index(this)).show().siblings().hide();
  33. });
  34. }
  35. };
  36. router.push(tabbar)
  37. .push(course)
  38. .setDefault('/home')
  39. .init();

example.js

根据query的参数名,取值

  1. function getQueryStringByName(name){
  2. var result = location.hash.match(new RegExp("[\?\&]" + name+ "=([^\&]+)","i"));
  3. if(result == null || result.length < 1){
  4. return "";
  5. }
  6. return result[1];
  7. }

ajax

  1. var all_courses_html;
  2. $.getJSON('/api/courses',function(res){
  3. // alert(res)
  4. var item_html = ""
  5. for(var i in res.data){
  6. console.log(i);
  7. var course = res.data[i];
  8. var item = " <a href='#/course?id=" + course._id + "' class='weui_media_box weui_media_appmsg'>"
  9. +" <div class='weui_media_hd'>"
  10. +" <img class='weui_media_appmsg_thumb' src='" + course.pic + "' alt=''>"
  11. +" </div>"
  12. +" <div class='weui_media_bd'>"
  13. +" <h4 class='weui_media_title'>" + course.name + "</h4>"
  14. +" <p class='weui_media_desc'>" + course.desc + "</p>"
  15. +" </div>"
  16. +" </a>"
  17. item_html += item;
  18. }
  19. all_courses_html = "<div class='weui_panel_bd'> " + item_html + " </div><a class='weui_panel_ft' href='javascript:void(0);'>查看更多</a>"
  20. // alert(all);
  21. $('.course_list').html(all_courses_html);
  22. })

这样首页是ok了,但是里面呢?

  1. // tabbar
  2. var tabbar = {
  3. url: '/home',
  4. className: 'tabbar',
  5. render: function () {
  6. var _t = this;
  7. setTimeout(function(){
  8. _t.bind()
  9. },100)
  10. return $('#tpl_tabbar').html();
  11. },
  12. bind: function () {
  13. $('.course_list').html(all_courses_html);
  14. $('.weui_tabbar_content').eq(0).show()
  15. $('.weui_tabbar_item').on('click', function () {
  16. $('.weui_tabbar_item').eq($('.weui_tabbar_item').index(this)).addClass('weui_bar_item_on').siblings().removeClass('weui_bar_item_on')
  17. $('.weui_tabbar_content').eq($('.weui_tabbar_item').index(this)).show().siblings().hide();
  18. });
  19. }
  20. };
  • setTimeout
  • $(‘.course_list’).html(all_courses_html);

实现tab

  1. <script type="text/html" id="tpl_tabbar">
  2. <div class="weui_tab">
  3. <div class="weui_tab_bd">
  4. <div class="weui_tabbar_content">
  5. <div class="hd">
  6. <h1 class="page_title">StuQ课程</h1>
  7. <p class="page_desc"> 提升你的IT职业技能最好的在线学习平台</p>
  8. </div>
  9. <div class="weui_panel weui_panel_access">
  10. <div class="weui_panel_hd">课程列表</div>
  11. <div class='course_list'></div>
  12. </div>
  13. </div>
  14. <div class="weui_tabbar_content">
  15. </div>
  16. </div>
  17. <div class="weui_tabbar">
  18. <a href="javascript:;" class="weui_tabbar_item">
  19. <div class="weui_tabbar_icon">
  20. <img src="./images/icon_nav_article.png" alt="">
  21. </div>
  22. <p class="weui_tabbar_label">课程</p>
  23. </a>
  24. <a href="javascript:;" class="weui_tabbar_item">
  25. <div class="weui_tabbar_icon">
  26. <img src="./images/icon_nav_cell.png" alt="">
  27. </div>
  28. <p class="weui_tabbar_label">我</p>
  29. </a>
  30. </div>
  31. </div>
  32. </script>

留意weui_tabbar_content

  1. $('.weui_tabbar_content').eq(0).show()
  2. $('.weui_tabbar_item').on('click', function () {
  3. $('.weui_tabbar_item').eq($('.weui_tabbar_item').index(this)).addClass('weui_bar_item_on').siblings().removeClass('weui_bar_item_on')
  4. $('.weui_tabbar_content').eq($('.weui_tabbar_item').index(this)).show().siblings().hide();
  5. });

参见https://github.com/i5ting/i5ting.jquery.tab

绑定事件

  • on
  • live
  • bind

微信配置说明

Stuq微信开发测试账号信息

公众平台

https://mp.weixin.qq.com/

  1. {
  2. "app_id": "wx1207227ce79d76c3",
  3. "app_secret": "b1693148b1b26318c9d8224a17ff0ee1"
  4. }

微信支付

https://pay.weixin.qq.com

微信支付商户号 1299809901
商户平台登录帐号 1299809901@1299809901
商户平台登录密码 000090

安装操作证书

  • 安装安全控件
  • 安装操作证书(请事先联系海角,获取对应的短信验证码,输入短信验证码和验证码就自动安装完成了)

然后点击api安全

  • 有手机号授权找海角
  • 有域名ip地址指定找i5ting

总结

关于StuQ

demo - 图1

软件公司招聘需要巨大,但入门难,技术发展过快(指数),而人的曲线成长较慢,现在的慕客形式又过于老旧,呆板,少互动,所以社群时代的在线教育,一定是专业的、互动的、深入浅出、共同成长,这些正是StuQ最擅长的方面,我个人特别看好StuQ这个品牌,真心推荐,如果不是股份绑定,我一定会加入StuQ

我的近况

  • 新书《更了不起的 Node 4:将下一代 Web 框架 Koa 进行到底》,预计2到3个月就能和大家见面
  • StuQ-Koa在线课程,准备招生

写给大家

  • 学不学在自己,会不会也在自己
  • 少抱怨、多思考、未来更美好
  • 闲时要有吃紧的心思
  • 一万个小时就能成为专家,难在坚持
  • 掌握了学习方法,以后职业不愁