# demo
* 部署服務器
* 代碼解讀
* node的代碼
* 模型
* 測試
* api
* 前端weui代碼
* index.html
* example.js
* 實現tab
* 綁定實踐
* 微信配置說明
* 總結
## 部署服務器
阿里云
ubuntu 14.10 LTS 64位
## 登錄遠端服務器
ssh root@ip
## 創建用戶
~~~
# sudo useradd -m -d /home/sang -s /bin/bash -c "the sang user" -U sang
# passwd sang
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
~~~
* useradd創建登錄用戶
* passwd設置用戶登錄密碼
## 賦予sudo權限
如果有必要使用sudu權限,請修改
~~~
# sudo vi /etc/sudoers
~~~
復制root行改為sang即可
~~~
# User privilege specification
root ALL=(ALL:ALL) ALL
sang ALL=(ALL:ALL) ALL
~~~
## 切換用戶
~~~
# su - sang
$ ls
$
$ pwd
/home/sang
$
~~~
## 安裝必備軟件
### 安裝git
如果上面沒有復制給sang賬戶sudo權限,請切換到root賬戶操作
~~~
sudo apt-get update
sudo apt-get install git
~~~
### 安裝nginx
~~~
sudo apt-get install nginx
~~~
### 開機啟動
([http://www.jianshu.com/p/2e03255cfabb)](http://www.jianshu.com/p/2e03255cfabb%EF%BC%89)
~~~
sudo apt-get install sysv-rc-conf
sudo sysv-rc-conf nginx on
~~~
注意:Ubuntu系統中服務的運行級別
* 0 系統停機狀態
* 1 單用戶或系統維護狀態
* 2~5 多用戶狀態
* 6 重新啟動
### 準備工作目錄
~~~
mkdir -p workspace/github
cd workspace/github
~~~
## 安裝nodejs
### 安裝nvm
~~~
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 7766 100 7766 0 0 28614 0 --:--:-- --:--:-- --:--:-- 28656
=> Downloading nvm as script to '/home/sang/.nvm'
=> Appending source string to /home/sang/.bashrc
=> Close and reopen your terminal to start using nvm
$ source ~/.bashrc
$ nvm
Node Version Manager
~~~
安裝nodejs lts版本
~~~
$ nvm install 4
Downloading https://nodejs.org/dist/v4.3.2/node-v4.3.2-linux-x64.tar.xz...
######################################################################## 100.0%
Now using node v4.3.2 (npm v2.14.12)
Creating default alias: default -> 4 (-> v4.3.2)
$ node -v
v4.3.2
~~~
使之成為默認
~~~
$ nvm alias default 4.3
default -> 4.3 (-> v4.3.2)
~~~
### 確認npm版本
~~~
$ npm -v
2.14.12
~~~
只要大于2.9.1即可,如不是,請`npm i -g npm@2.9.1`
### 安裝nrm
~~~
$ npm i -g nrm
npm WARN deprecated npmconf@0.1.16: this package has been reintegrated into npm and is now out of date with respect to npm
/home/sang/.nvm/versions/node/v4.3.2/bin/nrm -> /home/sang/.nvm/versions/node/v4.3.2/lib/node_modules/nrm/cli.js
nrm@0.3.0 /home/sang/.nvm/versions/node/v4.3.2/lib/node_modules/nrm
├── ini@1.3.4
├── only@0.0.2
├── extend@1.3.0
├── async@0.7.0
├── open@0.0.5
├── commander@2.9.0 (graceful-readlink@1.0.1)
├── 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)
├── node-echo@0.0.6 (jistype@0.0.3, mkdirp@0.3.5, coffee-script@1.7.1)
└── 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)
~~~
測速
~~~
$ nrm test
* npm ---- 274ms
cnpm --- 6868ms
taobao - 716ms
edunpm - 5598ms
eu ----- Fetch Error
au ----- Fetch Error
sl ----- 1234ms
nj ----- 2228ms
pt ----- Fetch Error
~~~
切換源
~~~
$ nrm use npm
Registry has been set to: https://registry.npmjs.org/
~~~
## 部署nodejs應用
### 基礎
* git clone
* npm i
* pm2 start
### 修改nginx
~~~
cat /etc/nginx/sites-enabled/default
upstream backend_nodejs {
server 127.0.0.1:3019 max_fails=0 fail_timeout=10s;
#server 127.0.0.1:3001;
keepalive 512;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
#root /usr/share/nginx/html;
root /home/sang/workspace/oschina/base2-wechat-jssdk/public;
index index.html index.htm;
# Make site accessible from http://localhost/
server_name nodeonly.mengxiaoban.cn at35.com;
client_max_body_size 16M;
keepalive_timeout 10;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
#try_files $uri $uri/ =404;
# Uncomment to enable naxsi on this location
# include /etc/nginx/naxsi.rules
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_redirect off;
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_pass http://backend_nodejs;
}
}
~~~
注意
* upstream backend_nodejs定義的代理轉發的api地址
* location /下面的proxy_pass,從upstream里取
* root下面放的是靜態資源,比如express下的public目錄
然后重啟nginx即可
~~~
sudo nginx -s reload
~~~
### 了解MONGODB的部署
* replset
* shard
我寫的《 mongodb運維之副本集實踐》
[https://cnodejs.org/topic/5590adbbebf9c92d17e734de](https://cnodejs.org/topic/5590adbbebf9c92d17e734de)
on ubuntu
[https://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/](https://docs.mongodb.org/manual/tutorial/install-mongodb-on-ubuntu/)
## 代碼部署
ssh免登陸
~~~
git clone git@git.oschina.net:i5ting/wechat-dev-with-nodejs.git
~~~
* 調整nginx
* pm2
## node的代碼
### 模型
* 目錄結構
* 表間關系
User用戶
~~~
username: {// 真實姓名
type: String
},
password : String,
unionid : String,
openid: {// from weixin openid
type: String,
required: true,
index: {
unique: true
}
},
nickname : String,// from weixin 昵稱
sex : String,// from weixin 性別 0->女 1->男
language : String,// from weixin 語言
city : String,// from weixin 城市
province : String,// from weixin
country : String,// from weixin
headimgurl : String,// from weixin 頭像路徑
privilege : [], // from weixin
created_at : {
type: Date,
"default": Date.now
}
~~~
Course課程
~~~
name: {// 課程名
type: String
},
pic: {// 課程圖片
type: String,
"default": "/images/logo.png"
},
desc: {// 描述
type: String,
"default": "這是StuQ的一門在線課程"
},
price: {// 價格
type: Number
},
docent: {// 講師
type: String
},
content: {// 大綱
type: String
},
owner_id: { //user_id
type: Schema.ObjectId,
index: true,
required: true,
ref: 'User'
},
created_at: {
type: Date,
"default": Date.now
}
~~~
訂單
~~~
desc: {// 訂單說明
type: String
},
"user_id": { //user_id
type: Schema.ObjectId,
index: true,
required: true,
ref: 'User'
},
user_name: {// 用戶名
type: String
},
"course_id": { //user_id
type: Schema.ObjectId,
index: true,
required: true,
ref: 'Course'
},
course_name: {// 課程名
type: String
},
created_at : {
type: Date,
"default": Date.now
}
~~~
問題
* 如何查看我的課程?
* 如果查看我購買的課程?
源碼介紹的時候,穿插robo和dash用法
### 測試
user
~~~
var request = require('supertest');
var assert = require('chai').assert;
var expect = require('chai').expect;
require('chai').should();
require('../db')
var User = require('../app/models/user')
// 測試代碼基本結構
describe('用戶User', function(){
before(function(d) {
// runs before all tests in this block
User.remove({"openid":"ss"},function(){
d()
})
})
after(function(){
// runs after all tests in this block
// User.remove({},function(err, user){
// });
})
beforeEach(function(){
// runs before each test in this block
})
afterEach(function(){
// runs after each test in this block
})
describe('#save()', function(){
this.timeout(30000);
it('should return stuq when user save', function(done){
User.create({"username":"stuq","password":"password", "openid":"ss"},function(err, user){
if(err){
console.log(err)
expect(err).to.be.not.null;
done();
}
expect(user.username).to.be.a('string');
expect(user.username).to.equal('stuq');
done();
});
})
})
})
~~~
用戶與課程
~~~
var request = require('supertest');
var assert = require('chai').assert;
var expect = require('chai').expect;
require('chai').should();
require('../db')
var User = require('../app/models/user')
var Course = require('../app/models/course')
var Order = require('../app/models/order')
var _user;
// 測試代碼基本結構
describe('課程Course', function(){
before(function(done) {
// runs before all tests in this block
User.removeAsync({"username":"stuq","password":"password", "openid":"ss"}).then(function(){
return User.createAsync({"username":"stuq","password":"password", "openid":"ss"})
}).then(function(user){
_user = user;
return Course.removeAsync({"name":"Node.js微信開發"});
}).then(function(){
done();
});
})
after(function(){
// runs after all tests in this block
// User.remove({},function(err, user){
// });
})
beforeEach(function(){
// runs before each test in this block
})
afterEach(function(){
// runs after each test in this block
})
describe('#save()', function(){
it('should return Node.js微信開發 when Course save', function(done){
Course.create({
"name":"Node.js微信開發","desc":"stuq在線課程", "docent":"桑世龍", owner_id: _user._id,
desc:"通過學習Node.js基礎和express,微信開發常用庫,h5,最后達到學會Node.js開發的目的,該課程以實戰為主,深入淺出"
},function(err, c){
if(err){
console.log(err)
expect(err).to.be.not.null;
done();
}
expect(c.name).to.be.a('string');
expect(c.name).to.equal('Node.js微信開發');
done();
});
})
})
})
~~~
三個表相關的訂單
~~~
var request = require('supertest');
var assert = require('chai').assert;
var expect = require('chai').expect;
require('chai').should();
require('../db')
var User = require('../app/models/user')
var Order = require('../app/models/order')
var Course = require('../app/models/course')
var _user, _course;
// 測試代碼基本結構
describe('訂單Order', function(){
before(function(done) {
// runs before all tests in this block
User.removeAsync({"openid":"ss1"}).then(function(){
return Course.removeAsync({"name":"Node.js微信開發1"});
}).then(function(){
User.create({"username":"stuq1","password":"password", "openid":"ss1"},function(err, user){
_user = user;
// console.log(err)
// console.log(_user)
return Course.create({"name":"Node.js微信開發1","desc":"stuq在線課程", "docent":"桑世龍", owner_id: _user._id},function(err1, c){
// console.log(c)
_course = c;
done();
});
});
})
})
after(function(){
})
beforeEach(function(){
// runs before each test in this block
})
afterEach(function(){
// runs after each test in this block
})
describe('#save()', function(){
it('should return order when order save', function(done){
Order.create({
"desc":"a order"
,"user_id":_user._id
, "user_name": _user.username
,course_id : _course._id
,course_name : _course.name
},function(err, order){
if(err){
console.log(err)
expect(err).to.be.not.null;
done();
}
expect(order.desc).to.be.a('string');
expect(order.desc).to.equal('a order');
done();
});
})
})
})
~~~
另外舉例runkoa沒有ci引發的血案
### api
自動掛載路由
~~~
var mount = require('mount-routes');
// simple
mount(app, __dirname + '/app/routes');
~~~
示例
~~~
var express = require('express');
var router = express.Router();
var User = require('../../models/user')
var Course = require('../../models/course')
var Order = require('../../models/order')
router.get('/', function(req, res, next) {
Course.find({},function(err, courses){
res.json({
status:{
code:0,
msg:'sucess'
},
data:courses
});
});
})
module.exports = router;
~~~
測試
~~~
curl http://127.0.0.1:3019/api/courses
~~~
或者`postman`
## 前端weui代碼
weui v0.4.x新增了路由和tab等組件,問題還是挺多的
frontend目錄隨便配,目的就是為了讓大家理解前后端分離
### 路由
定義骨架
~~~
var router = new Router({
container: '#container',
enterTimeout: 250,
leaveTimeout: 250
});
~~~
然后
~~~
// course
var course = {
url: '/course',
className: 'panel',
render: function () {
// alert(getQueryStringByName('id'));
return $('#tpl_course').html();
},
bind: function () {
$('.pay_btn').on('click', function(){
var id = getQueryStringByName('id');
pay_h5(id);
})
}
};
// tabbar
var tabbar = {
url: '/home',
className: 'tabbar',
render: function () {
var _t = this;
setTimeout(function(){
_t.bind()
},100)
return $('#tpl_tabbar').html();
},
bind: function () {
$('.course_list').html(all_courses_html);
$('.weui_tabbar_content').eq(0).show()
$('.weui_tabbar_item').on('click', function () {
$('.weui_tabbar_item').eq($('.weui_tabbar_item').index(this)).addClass('weui_bar_item_on').siblings().removeClass('weui_bar_item_on')
$('.weui_tabbar_content').eq($('.weui_tabbar_item').index(this)).show().siblings().hide();
});
}
};
router.push(tabbar)
.push(course)
.setDefault('/home')
.init();
~~~
### example.js
根據query的參數名,取值
~~~
function getQueryStringByName(name){
var result = location.hash.match(new RegExp("[\?\&]" + name+ "=([^\&]+)","i"));
if(result == null || result.length < 1){
return "";
}
return result[1];
}
~~~
ajax
~~~
var all_courses_html;
$.getJSON('/api/courses',function(res){
// alert(res)
var item_html = ""
for(var i in res.data){
console.log(i);
var course = res.data[i];
var item = " <a href='#/course?id=" + course._id + "' class='weui_media_box weui_media_appmsg'>"
+" <div class='weui_media_hd'>"
+" <img class='weui_media_appmsg_thumb' src='" + course.pic + "' alt=''>"
+" </div>"
+" <div class='weui_media_bd'>"
+" <h4 class='weui_media_title'>" + course.name + "</h4>"
+" <p class='weui_media_desc'>" + course.desc + "</p>"
+" </div>"
+" </a>"
item_html += item;
}
all_courses_html = "<div class='weui_panel_bd'> " + item_html + " </div><a class='weui_panel_ft' href='javascript:void(0);'>查看更多</a>"
// alert(all);
$('.course_list').html(all_courses_html);
})
~~~
這樣首頁是ok了,但是里面呢?
~~~
// tabbar
var tabbar = {
url: '/home',
className: 'tabbar',
render: function () {
var _t = this;
setTimeout(function(){
_t.bind()
},100)
return $('#tpl_tabbar').html();
},
bind: function () {
$('.course_list').html(all_courses_html);
$('.weui_tabbar_content').eq(0).show()
$('.weui_tabbar_item').on('click', function () {
$('.weui_tabbar_item').eq($('.weui_tabbar_item').index(this)).addClass('weui_bar_item_on').siblings().removeClass('weui_bar_item_on')
$('.weui_tabbar_content').eq($('.weui_tabbar_item').index(this)).show().siblings().hide();
});
}
};
~~~
* setTimeout
* $('.course_list').html(all_courses_html);
### 實現tab
~~~
<script type="text/html" id="tpl_tabbar">
<div class="weui_tab">
<div class="weui_tab_bd">
<div class="weui_tabbar_content">
<div class="hd">
<h1 class="page_title">StuQ課程</h1>
<p class="page_desc"> 提升你的IT職業技能最好的在線學習平臺</p>
</div>
<div class="weui_panel weui_panel_access">
<div class="weui_panel_hd">課程列表</div>
<div class='course_list'></div>
</div>
</div>
<div class="weui_tabbar_content">
</div>
</div>
<div class="weui_tabbar">
<a href="javascript:;" class="weui_tabbar_item">
<div class="weui_tabbar_icon">
<img src="./images/icon_nav_article.png" alt="">
</div>
<p class="weui_tabbar_label">課程</p>
</a>
<a href="javascript:;" class="weui_tabbar_item">
<div class="weui_tabbar_icon">
<img src="./images/icon_nav_cell.png" alt="">
</div>
<p class="weui_tabbar_label">我</p>
</a>
</div>
</div>
</script>
~~~
留意weui_tabbar_content
~~~
$('.weui_tabbar_content').eq(0).show()
$('.weui_tabbar_item').on('click', function () {
$('.weui_tabbar_item').eq($('.weui_tabbar_item').index(this)).addClass('weui_bar_item_on').siblings().removeClass('weui_bar_item_on')
$('.weui_tabbar_content').eq($('.weui_tabbar_item').index(this)).show().siblings().hide();
});
~~~
參見[https://github.com/i5ting/i5ting.jquery.tab](https://github.com/i5ting/i5ting.jquery.tab)
### 綁定事件
* on
* live
* bind
## 微信配置說明
Stuq微信開發測試賬號信息
### 公眾平臺
[https://mp.weixin.qq.com/](https://mp.weixin.qq.com/)
* 微信名:StuQ課課程測試賬號
* 微信賬戶:zhuohang111@163.com
* 密碼:duanmu5061656
~~~
{
"app_id": "wx1207227ce79d76c3",
"app_secret": "b1693148b1b26318c9d8224a17ff0ee1"
}
~~~
### 微信支付
[https://pay.weixin.qq.com](https://pay.weixin.qq.com/)
微信支付商戶號 1299809901 商戶平臺登錄帳號 1299809901@1299809901 商戶平臺登錄密碼 000090
安裝操作證書
* 安裝安全控件
* 安裝操作證書(請事先聯系海角,獲取對應的短信驗證碼,輸入短信驗證碼和驗證碼就自動安裝完成了)
然后點擊api安全
* 有手機號授權找海角
* 有域名ip地址指定找i5ting
## 總結
### 關于StuQ

軟件公司招聘需要巨大,但入門難,技術發展過快(指數),而人的曲線成長較慢,現在的慕客形式又過于老舊,呆板,少互動,所以社群時代的在線教育,一定是專業的、互動的、深入淺出、共同成長,這些正是StuQ最擅長的方面,我個人特別看好StuQ這個品牌,真心推薦,如果不是股份綁定,我一定會加入StuQ
### 我的近況
* 新書《更了不起的 Node 4:將下一代 Web 框架 Koa 進行到底》,預計2到3個月就能和大家見面
* StuQ-Koa在線課程,準備招生
## 寫給大家
* 學不學在自己,會不會也在自己
* 少抱怨、多思考、未來更美好
* 閑時要有吃緊的心思
* 一萬個小時就能成為專家,難在堅持
* 掌握了學習方法,以后職業不愁
- 前言
- 1 skill
- 1.1 Coding WebIDE
- 1.2 git
- 1.3 extra practice
- 1.4 預習
- 2 nodejs入門
- 2.1 入門
- 2.2 安裝
- 2.3 helloworld
- 2.4 commonJS規范
- 2.5 模塊導出
- 2.6 Nodejs代碼調試
- 2.7 編寫Nodejs模塊
- 2.8 最小化問題
- 2.9 隨堂練習
- 3 異步流程控制
- 3.1 什么時候會用到異步流程控制
- 3.2 簡單做法async模塊
- 3.3 Promise/a+規范
- 3.4 Node.js Promise/a+實現
- 3.5 生成器Generators/yield
- 3.6 Async函數/Await
- 3.7 神奇的co
- 3.8 5種 yieldable
- 3.9 學習重點
- 3.10 隨堂練習
- 4 express和微信開發入門
- 4.1 入門
- 4.2 connect
- 4.3 靜態Http服務器
- 4.4 那些預處理器
- 4.5 路由
- 4.6 視圖與模塊引擎
- 4.7 中間件
- 4.8 更多實踐
- 4.9 微信入門
- 4.10 隨堂練習:完成登錄、注冊功能
- 5 微信實例與H5實踐
- 5.1 微信基礎和sandbox
- 5.2 公眾號菜單和自動回復
- 5.3 微信OAuth用戶授權
- 5.4 微信分享
- 5.5 wechat-api
- 5.6 H5-上篇
- 5.7 H5-下篇
- 5.8 隨堂練習
- 6 weui實戰
- 6.1 使用bower
- 6.2 移動端抽象
- 6.3 優化滑動列表
- 6.4 weui
- 6.5 讓weui和iscroll結婚
- 6.6 優化事件
- 6.7 how-to-write-h5
- 6.8 優化無止境
- 6.9 隨堂練習
- 7 微信支付
- 7.1 吹個牛
- 7.2 支付概述
- 7.3 科普幾個概念
- 7.4 準備
- 7.5 調試
- 7.6 公眾號支付(JSAPI)
- 7.7 對賬單
- 7.8 數據處理
- 7.9 隨堂練習
- 8 項目實戰《付費課程系統MVP》
- 8.1 需求分析
- 8.2 ui/ue
- 8.3 技術棧
- 8.4 模型
- 8.5 靜態api
- 8.6 開發
- 8.7 部署
- 8.8 監控
- 8.9 數據統計
- 8.10 demo
- 9 高級篇
- 9.1 前后端分離實踐?
- 9.2 如何展望未來的大前端
- 9.3 容器和微服務
- 10 答疑問題收集