# 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
![](https://i5ting.github.io/wechat-dev-with-nodejs/vip-lession/img/1.png)
软件公司招聘需要巨大,但入门难,技术发展过快(指数),而人的曲线成长较慢,现在的慕客形式又过于老旧,呆板,少互动,所以社群时代的在线教育,一定是专业的、互动的、深入浅出、共同成长,这些正是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 答疑问题收集