10 答疑问题收集
最后更新于:2022-04-01 23:35:29
StuQ翻转课堂|系统掌握 Node.js 开发方法及微信开发实践 答疑问题收集
1.一直是在windows环境下做开发,对linux下进行开发有点找不着北。有没有什么入门的资料推荐一下。
2.Node.Js是在linux下开发比较好是吗?
3.在学习node时总要安装这个安装那个,尤其在wndows平台老不那么顺畅,有没有针对这个问题的最佳实践,mac平台又当如何?
4.准备用node+redis写一个TCP/IP程序,涉及接收、解析、存储GPS数据,可行吗?
5.采用restful的读取方式构造多个http.request()从redis中读取数据,可能要生成10万,甚至100万,1000万个http.request() 请求,总是造成OOM,有没有什么解决办法?
6.有没有很方便生成,管理,错误处理多个http请求的模块推荐?请求的url大同小异。
7.建议老师提前做一个比较具有比较完整功能和组件的实用性产品,比如cms或者blog,作为学习的目标。这样的话在学习起来每个人都可以针对自己需要的部分学习,而且实践性更强。
8.建议老师多讲讲开发的经验、和大型企业的规范要求,比如开发的流程、组件命名的规范,因为这些东西在书本上都学不到,所以才更有价值。
';
9.3 容器和微服务
最后更新于:2022-04-01 23:35:26
# 容器和微服务
## docker
[https://github.com/fundon/fundon.github.io/issues/19](https://github.com/fundon/fundon.github.io/issues/19)
## 微服务
';
9.2 如何展望未来的大前端
最后更新于:2022-04-01 23:35:24
# 为什么前端越来越难?越来越有意思?
## 大前端
从架构上讲,软件从c/s到b/s过度,它的桥梁是浏览器,尤其是ajax促进了web2.0的成功,所以现在我们看到的绝大部分软件的架构都是b/s的,也称为瘦客户端。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/59d1bab5ff79926dffdc41850aa2b703_1576x1150.png)
从prototype.js到yui,到jquery,到extjs等他们只是从用法和ui上演进,还有就是underscore等工具库。
而backbone的出现,把mvc引入前端,于是前端开始了分层。
当angular.js横空出世,又引入了双向绑定,ioc依赖注入,指令等概念,这实际上在java里早有的概念,这又再一次增加了前端的复杂度。
上面说的是架构上得演进,还有一些enhance的提高,比如js方面有coffeescript和typescript,css方面有less/sass/scss/stylus等,这些不是什么新概念,是对web开发的增强。前提是你熟练使用js和css才能用。
最后是MEAN的full stack最新趋势。有望替换LAMP.
这些还只是前端的发展,现在是移动互联网时代,在微信淘宝等带领下,h5正如火如荼的袭来,可以说是当下最火的技术。那么移动端h5开发和上面的前端技术如何结合就成了现下得趋势。
我觉得大前端应该现下web的统称,包含web开发最佳实践,趋势以及h5。 nodejs作为一个兼容js语法的平台,更容易让广大前端开发者接受,在构建,工具等领域辅助大前端的成长。
## 如何学习
我们来想想一般的前端有什么技能?
* html
* css(兼容浏览器)
* js会点(可能更多的是会点jquery)
* ps切图
* firebug和chrome debuger会的人都不太多
* 用过几个框架,大部分人是仅仅会用
* 英语一般
* svn/git会一点
那么他们如果想在前端领域做的更深有哪些难点呢?
* 基础:oo,dp,命令,shell,构建等
* 编程思想上的理解(mvc、ioc,规约等)
* 区分概念
* 外围验收,如h5和hybird等
* 追赶趋势,如何学习新东西
以上皆是痛点。
现在来总结一下学习有2种,1是从头来,2是从某一种框架起
第一种学习下来,没个几年很难学通,第二种只会用框架,补齐概念和基础也比较费劲。总之,无论如何学习曲线都是比较陡峭的,那么如何来让入门的开发者快速学习到这些呢?
软件的精髓在应变,殊途同归,学会如何学习才是我们最重要的道。
* 积极的心态,做好适应变化的准备
* 找到属于自己的学习方式
* 如果有机会,改变或创新,贡献开源社区
# 如何展望未来的前端
我先告诉未来很美好,你先做好准备。然后慢慢的等我聊聊。
## 界面技术4个时期
界面技术从上世纪DOS字符界面到Windows图形界面(或图形用户界面GUI),Browser浏览器界面,移动端应用4个不同的发展时期
![gui_roadmap.png](https://dn-cnode.qbox.me/FgdC1tJOAafmHQ3IhTl5e54yee9M)
1. Terminal终端
2. GUI图形用户界面
3. Browser浏览器
4. Mobile移动端
我们在以web开发来细分
1. Browser(曾经无数时间我们都在兼容ie)
2. Mobile Browser(html5)
不管你承认与否,现在都是移动互联网时代
## 2个变革的桥梁都是浏览器
### 从c/s到b/s架构的演变
直白点讲就是:从GUI到Browser的架构演变。
要想对“C/S”和“B/S”技术发展变化有所了解,首先必须搞清楚三个问题。
第一、什么是C/S结构。
C/S(Client/Server)结构,即大家熟知的客户机和服务器结构。它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销。目前大多数应用软件系 统都是Client/Server形式的两层结构,由于现在的软件应用系统正在向分布式的Web应用发展,Web和Client/Server应用都可以进行同样的业务处理,应用不同的模块共享逻辑组件;因此,内部的和外部的用户都可以访问新的和现有的应用系统,通过现有应用系统中的逻辑可以扩展出新的应用系统。这也就是目前应用系统的发展方向。 传统的C/S体系结构虽然采用的是开放模式,但这只是系统开发一级的开放性,在特定的应用中无论是Client端还是Server端都还需要特定的软件支持。由于没能提供用户真正期望的开放环境,C/S结构的软件需要针对不同的操作系统系统开发不同版本的软件,加之产品的更新换代十分快,已经很难适应百台电脑以上局域网用户同时使用。而且代价高,效率低。
第二、什么是B/S结构。
B/S(Browser/Server)结构即浏览器和服务器结构。它是随着 Internet技术的兴起,对C/S结构的一种变化或者改进的结构。在这种结构下,用户工作界面是通过WWW浏览器来实现,极少部分事务逻辑在前端 (Browser)实现,但是主要事务逻辑在服务器端(Server)实现,形成所谓三层3-tier结构。这样就大大简化了客户端电脑载荷,减轻了系统维护与升级的成本和工作量,降低了用户的总体成本(TCO)。 以目前的技术看,局域网建立B/S结构的网络应 用,并通过Internet/Intranet模式下数据库应用,相对易于把握、成本也是较低的。它是一次性到位的开发,能实现不同的人员,从不同的地 点,以不同的接入方式(比如LAN,WAN,Internet/Intranet等)访问和操作共同的数据库;它能有效地保护数据平台和管理访问权限,服 务器数据库也很安全。特别是在JAVA这样的跨平台语言出现之后,B/S架构管理软件更是方便、快捷、高效。
第三、B/S相比C/S的优势
B/S结构下软件相比C/S结构下软件,有着独特的优势。
节约投资。B/S结构下软件一般只有初期一次性投入成本;而C/S结构下软件则随着应用范围的扩大,要求不断进行资本的投入。比如需要购买更为高级的服务器或者增加相应的管理人员等。
简化工作。B/S结构下软件安装在服务器端即可解决问题,在做更改时,只需调整服务器端即可。C/S结构下软件则需要安装在客户机端,调整的时候需要涉及到局域网内的每一台机器。对于区域级服务器来讲,C/S结构的软件更新则更加复杂。
更好地保障数据安全。在C/S结构软件的解决方案里,对于异地经营的大型集团企业,需要在各地分别安装区域级服务器。一旦某一个区域级服务器出现问题,对数据的安全会造成一定影响,而且总部也不会得到准确的最终数据。对B/S结构下软件来讲,其数据集中存在于企业的中央数据库,可有效地保护数据的安全,而且企业可随时随地掌握自己的经营状况、市场动态,以做出最快决策。
不受网络的限制。C/S结构软件仅适用于局域网内部用户或宽带用户(1兆以上);而B/S结构软件则适合于任何网络结构(包括28.8K拨号入网方式),尤其适合于宽带不能达到的地方。
### 从传统web到html5的hybrid开发演变
传统web开发就不多讲了,相信大部分人都明白。
#### 移动应用开发分类
* native原生开发
* Hybrid混搭开发
native原生开发指的使用移动设备上系统支持的语言开发的app,比如android使用java开发,iOS使用oc开发,最终开发的应用以apk和ipa包得形式上传到应用商店,提供给移动设备进行安装。
Hybrid混搭开发是指使用html5技术开发的跨浏览器应用,并最终可以将html5.js.css等打包成apk和ipa包的开发方式。它也可以上传到应用商店,提供给移动设备进行安装。它最大的好处是通过h5开发一次,就可以在多个平台上安装。
Hybrid App主要以JS+Native两者相互调用为主,从开发层面实现“一次开发,多处运行”的机制,成为真正适合跨平台的开发。目前已经有众多Hybrid App开发成功应用,比如百度、网易、街旁等知名移动应用,都是采用Hybrid App开发模式。 经过众多开发者与成功案例证明Hybrid App兼具了Native App的良好用户体验的优势,也兼具了Web App使用HTML5跨平台开发低成本的优势。现在有更多的开发者在面临移动平台的选择,所以在这里根据开发中各个平台的使用情况,针对现在主流的平台进行分析。
#### 主流移动平台分析
Hybrid App开发,现阶段主流的平台包括PhoneGap,AppCan,appMobi,Titanium等,它们基于webkit开源内核,使用HTML5 标准开发,适配机型简单,支持开发者自定义插件,并能很好的应用于商业,教育,娱乐等行业,成为移动开发者的首选开发平台。
#### 总结
从上面的描述,我们可以知道Hybrid混搭开发是通过移动端浏览器为核心,作为界面和操作系统间交互的媒介。
## h5特点
* 语义化标签
* css3动画
* media query
* canvas
* cache:localstorage,websql ,indexdb
* 其他移动端特性,如陀螺仪,地图等
## 未来的2点
* js一统天下(nodejs做后端,传统web和h5使用javasctipt,更智能的工具如gulp,更简单的写法如coffeescript等)
* h5大行其道(网速变快,硬件内存增长)
## 总结
前端开发人员有更多可以尝试和学习的机会,这是机遇也是挑战。加油吧,前端er们。
我们的时代到了
';
9.1 前后端分离实践?
最后更新于:2022-04-01 23:35:22
9 高级篇
最后更新于:2022-04-01 23:35:20
# 项目实战《付费课程系统MVP》
回到课程主题《Nodejs微信开发》
1. 第0节:nodejs入门 (14/14)
2. 第1节:express和微信开发入门 (6/6)
3. 第2节:微信实例和h5实践 (4/4)
4. 第3节:weui实战和微信支付 (7/7)
* 微信支付(稍等,支付账号还在申请中)
5. 第4节:实战付费课程系统(TODO)
我们花了很多精力把基础知识和单一技能点
* 基本技能(coding ide,git,命令行等)
* nodejs基础
* express
* 微信基础(后台)
* 微信分享
* h5和weui实践
* 微信支付
这些都太零散了,为了能够让大家有一个整体的项目认知,这里面我们再加一个项目实践
《rework》一书里讲,抓自己的痒,是说创业的时候选题,从自身的痛点出发,这样更容易成功
那么,我们(StuQ)的这个课程,如果想要推广,想让更多人参与,怎么办呢?
既然讲的是这课,那我们就写一个吧
';
8.10 demo
最后更新于:2022-04-01 23:35:17
# 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 = " "
+"
';
"
+"
"
+"
"
+" "
+"
"
+" "
item_html += item;
}
all_courses_html = "" + course.name + "
" +"" + course.desc + "
" +" " + item_html + "
查看更多"
// 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
~~~
~~~
留意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在线课程,准备招生
## 写给大家
* 学不学在自己,会不会也在自己
* 少抱怨、多思考、未来更美好
* 闲时要有吃紧的心思
* 一万个小时就能成为专家,难在坚持
* 掌握了学习方法,以后职业不愁
8.9 数据统计
最后更新于:2022-04-01 23:35:15
# 数据统计
flurry,localytics,talkingdatada支持h5平台
## mta 腾讯云统计
[http://mta.qq.com/mta/ctr_index/h5](http://mta.qq.com/mta/ctr_index/h5)
腾讯云分析H5应用提供了以下统计功能。
### 基础功能
实时数据、关键数据、运营商、客户端信息、访客画像、访客深度、地域信息、页面排行、外部链接、入口页面、离开页面等;如果您只需要基础功能,可以通过快速上手进行接入。
### 高级功能
AD-Tag细分、指定按钮统计;如果您要使用高级功能,可以查看下面的高级接入说明。
用法非常简单
### 用法:基本统计
* 登录
* 创建应用,选h5
* 创建成功后,返回app id和Secret key,以及一个script信息,放到你的html里即可
~~~
请安装在标签前;
~~~
### 用法:AD-Tag细分
说白了就是渠道统计
分享H5链接时带上ADTAG参数,例如分享链接为www.qq.com/test.html,添加ADTAG后,为www.qq.com/test.html?ADTAG=a.b.c.d
### 用法:指定按钮统计
手码
~~~
onclick="MtaH5.clickStat('按钮标识')"
~~~
## DataEye统计平台
不好用
[http://h5.dataeye.com/](http://h5.dataeye.com/)
## mixpanel
[http://exp.freesvc.com/public/articles/13?from=timeline&isappinstalled=0](http://exp.freesvc.com/public/articles/13?from=timeline&isappinstalled=0)
[](https://i5ting.github.io/wechat-dev-with-nodejs/vip-lession/monit.html)[](https://i5ting.github.io/wechat-dev-with-nodejs/vip-lession/demo.html)
';
8.8 监控
最后更新于:2022-04-01 23:35:13
# 监控
* 自建
* 云服务
## 自建
* keepalive
* Zabbix (nagios、[https://github.com/XiaoMi/open-falcon](https://github.com/XiaoMi/open-falcon))
* Notifier
## 云服务
alinode、听云或oneapm都还可以
[http://alinode.aliyun.com/](http://alinode.aliyun.com/)
';
8.7 部署
最后更新于:2022-04-01 23:35:10
# 部署
## 采用云服务器
比如阿里云,coding其实也可以凑合着用
* server
* db
* slb
* 监控
推荐ubuntu 14.10 LTS
## 登录远端服务器
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
~~~
### 准备工作目录
~~~
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
~~~
## 阿里云服务器挂载数据硬盘
购买普通阿里云服务器的时候,本机默认自带的系统盘大小为20G,但是这样的大小是不满足部署产品服务器的需求, 所以可以购买阿里云数据盘,一半大小为200G
* 首先使用root用户查看系统版本,本文是在centos中部署使用
在终端中使用下面命令查看系统版本
~~~
$ lsb_release -a
LSB Version: :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description: CentOS Linux release 7.2.1511 (Core)
Release: 7.2.1511
Codename: Core
~~~
确定系统版本之后在终端中确认系统盘情况(注:使用root用户):
~~~
$ fdisk -l
Disk /dev/xvda: 21.5 GB, 21474836480 bytes, 41943040 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x0009e68a
Device Boot Start End Blocks Id System
/dev/xvda1 * 2048 41943039 20970496 83 Linux
Disk /dev/xvdb: 214.7 GB, 214748364800 bytes, 419430400 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0xd0c73cf7
Device Boot Start End Blocks Id System
/dev/xvdb1 2048 419430399 209714176 83 Linux
~~~
首先确认在阿里云中购买了数据盘,上面的Disk /dev/xvda是自带的系统盘,/dev/xvda1表示已经挂载并且在使用中,Disk /dev/xvdb是数据盘, 上面的情况是已经挂载好的,如果没有挂载情况,只显示Disk /dev/xvdb: 214.7 GB, 214748364800 bytes, 419430400 sectors,表示未被 使用
* 将未被分区挂载的数据盘进行分区挂载
~~~
$ fdisk /dev/xvdb
~~~
根据提示,输入"n","p","1",两次回车,"wq",分区开始,很快就会结束
* 查看新的分区:
~~~
$ fdisk -l
~~~
此时应该显示/dev/xvdb已被分区, like this:
~~~
Device Boot Start End Blocks Id System
/dev/xvdb1 2048 419430399 209714176 83 Linux
~~~
* 格式化新的分区
以ext3为例:使用“mkfs.ext3 /dev/xvdb1”命令对新分区进行格式化,格式化的时间根据硬盘大小有所不同。 (也可自主决定选用其它文件格式,如ext4等)
~~~
$ mkfs.ext3 /dev/xvdb1
~~~
需要等一段时间等待格式化完毕
* 添加新的分区信息
~~~
$ echo '/dev/xvdb1 /mnt ext3 defaults 0 0' >> /etc/fstab
~~~
* 查看分区
~~~
$ cat /etc/fstab
~~~
出现/dev/xvdb1 /mnt ext3 defaults 0 0 说明成功
* 挂载新的分区
~~~
$ mount -a
~~~
* 查看分区的情况
~~~
$ df -h
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/xvda1 20510332 2031552 17413872 11% /
devtmpfs 934320 0 934320 0% /dev
tmpfs 942004 0 942004 0% /dev/shm
tmpfs 942004 98724 843280 11% /run
tmpfs 942004 0 942004 0% /sys/fs/cgroup
/dev/xvdb1 206292664 1065720 194724852 1% /mnt
~~~
/dev/xvdb1已经成功启用,挂载成功
## pm2
[https://github.com/Unitech/pm2](https://github.com/Unitech/pm2)
~~~
$ npm install pm2 -g # Install PM2
$ pm2 start app.js # Start, Daemonize and auto restart application
$ pm2 start app.js -i 4 # Start 4 instances of application in cluster mode
# it will load balance network queries to each app
$ pm2 start app.js --name="api" # Start application and name it "api"
$ pm2 start app.js --watch # Restart application on file change
$ pm2 start script.sh # Start bash script
$ pm2 list # List all processes started with PM2
$ pm2 monit # Display memory and cpu usage of each app
$ pm2 show [app-name] # Show all informations about application
$ pm2 logs # Display logs of all apps
$ pm2 logs [app-name] # Display logs for a specific app
$ pm2 flush
$ pm2 stop all # Stop all apps
$ pm2 stop 0 # Stop process with id 0
$ pm2 restart all # Restart all apps
$ pm2 reload all # Reload all apps in cluster mode
$ pm2 gracefulReload all # Graceful reload all apps in cluster mode
$ pm2 delete all # Kill and delete all apps
$ pm2 delete 0 # Delete app with id 0
$ pm2 scale api 10 # Scale app with name api to 10 instances
$ pm2 reset [app-name] # Reset number of restart for [app-name]
$ pm2 startup # Generate a startup script to respawn PM2 on boot
$ pm2 save # Save current process list
$ pm2 resurrect # Restore previously save processes
$ pm2 update # Save processes, kill PM2 and restore processes
$ pm2 generate # Generate a sample json configuration file
$ pm2 deploy app.json prod setup # Setup "prod" remote server
$ pm2 deploy app.json prod # Update "prod" remote server
$ pm2 deploy app.json prod revert 2 # Revert "prod" remove server by 2
$ pm2 module:generate [name] # Generate sample module with name [name]
$ pm2 install pm2-logrotate # Install module (here a log rotation system)
$ pm2 uninstall pm2-logrotate # Uninstall module
$ pm2 publish # Increment version, git push and npm publish
~~~
pm2 是一个带有负载均衡功能的Node应用的进程管理器. 当你要把你的独立代码利用全部的服务器上的所有CPU,并保证进程永远都活着,0秒的重载, PM2是完美的。 可以感受一下[官方的部署文档示例](http://pm2.keymetrics.io/docs/usage/deployment/), [github项目地址](https://github.com/Unitech/pm2)。
主要的特点:
* 内建负载均衡(使用Node cluster 集群模块)
* 后台运行
* 0秒停机重载,我理解大概意思是维护升级的时候不需要停机.
* 具有Ubuntu和CentOS 的启动脚本
* 停止不稳定的进程(避免无限循环)
* 控制台检测
* 提供 HTTP API
* 远程控制和实时的接口API ( Nodejs 模块,允许和PM2进程管理器交互 )
## pm2部署简单应用
### 安装pm2
~~~
npm install -g pm2
~~~
### 使用pm2部署简单的项目
~~~
$ pm2 start app.js --name "heheda" -i 0 --watch
~~~
* pm2 start app.js : 使用pm2启动app.js
* -i 0 : 使用最大进程数启动
* --name : 指定一个你喜欢的名字
* --watch : 开启监视模式,如果代码有变动pm2自动重启
### 查看pm2部署
~~~
pm2 ls
~~~
## pm2自动部署远程服务器
目前我们部署服务器的方式是使用oschina托管项目,然后在服务器中安装git将项目克隆到服务器中,然后 使用pm2部署项目,如果项目有任何的修改,就会需要跑到几个服务器中pull代码,然后pm2 reload项目, 蛋疼的要死。 现在就使用pm2的远程部署方式,解决这个蛋疼的问题!
### 准备工作
#### 将本地机器和线上服务器建立ssh信任,免密码登陆
* 生成git ssh公钥(本地机器和服务器操作一样)
~~~
$ git config --global user.name "heheda"
$ git config --global user.email "heheda@mail.com"
$ ssh-keygen -t rsa -C "heheda@mail.com"
~~~
连续三次回车,这样生成的ssh公钥添加到github
* 查看生成的ssh公钥
~~~
$ ls ~/.ssh/
authorized_keys id_rsa id_rsa.pub known_hosts
~~~
理论上已经生成ssh公钥,在用户主目录下的.ssh中生成的id_rsa.pub就是生成的公钥 authorized_keys文件是通过授权的ssh公钥,在使用ssh协议进行远程访问的时候,如果该机器的ssh公钥在 这个文件中,那么能直接进行访问
* 将ssh公钥拷贝到服务器
~~~
$ scp ~/.ssh/id_rsa.pub username@ip:用户主目录/.ssh/authorized_keys
~~~
执行这个命令是将本地的id_rsa.pub拷贝到服务器的.ssh/目录下并命名为authorized_keys 这样就能不需要密码访问远程服务器了 上一步已经将服务器的ssh公钥添加到 github 中了,这样服务器中clone项目也不需要密码
### pm2配置文件ecosystem.json
* [官方的部署文档示例](http://pm2.keymetrics.io/docs/usage/deployment/)
~~~
{
/**
* Deployment section
* http://pm2.keymetrics.io/docs/usage/deployment/
*/
"deploy" : {
"yourprojectname" : {
"user" : "node",
"host" : ["ip"],
"ref" : "origin/master",
"repo" : "git.oschina.net",
"path" : "/your/deploy/folder/",
"post-deploy" : "npm install ; pm2 start bin/www --name 'hz-frontend' --watch",
"env" : {
"NODE_ENV": "dev"
}
}
}
}
~~~
* user : 你登陆到远程主机的用户名
* host : 服务器的ip地址
* ref : 部署的分支
* repo : github或oschina中托管的地址
* path : 部署到服务器的目录
* post-deploy : 部署时的命令
### 执行部署
* 首次在服务器中部署(服务器中没有需要部署的项目,需要将代码克隆到服务器)
~~~
pm2 deploy ecosystem.json yourprojectname setup
~~~
上面命令是将项目从github或oschina中克隆到指定path中,需要注意一下的是,pm2 将目录结构分为 :
|current | shared |source |
* 克隆好之后执行安装和启动
~~~
pm2 deploy ecosystem.json yourprojectname
~~~
官方推荐在部署的项目中也使用ecosystem.json进行启动项目 :
~~~
{
"apps" : [{
// Application #1
"name" : "hz-mq",
"script" : "index.js",
"args" : "--toto=heya coco -d 1",
"watch" : true,
"node_args" : "--harmony",
"merge_logs" : true,
"cwd" : "/Users/zxy/work/hz-mq",
"env": {
"NODE_ENV": "development",
"AWESOME_SERVICE_API_TOKEN": "xxx"
},
"env_production" : {
"NODE_ENV": "production"
},
"env_staging" : {
"NODE_ENV" : "staging",
"TEST" : true
},
"exec_mode" : "cluster_mode"
}]
}
~~~
这个相对来说就简单了,就不一一说。 没有使用的原因是放在项目中在本地和服务器中使用需要来回修改启动目录。
';
8.6 开发
最后更新于:2022-04-01 23:35:08
# 开发
参照前几节
## git 流程
* 新建issue(feature)
* 新建分支
* 开发
* 提交(git commit -am 'fix #21 xxxx')
* 合并到主分支
## 测试
单元测试需要的各个模块说明
* mocha(Mocha is a feature-rich JavaScript test framework running on node.js and the browser, making asynchronous testing simple and fun.)
* chai(Chai is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework.)
* sinon(Standalone test spies, stubs and mocks for JavaScript.)
* zombie (页面事件模拟Zombie.js is a lightweight framework for testing client-side JavaScript code in a simulated environment. No browser required.)
* supertest(接口测试 Super-agent driven library for testing node.js HTTP servers using a fluent API)
';
8.5 静态api
最后更新于:2022-04-01 23:35:06
# 静态api
## Why?
开发流程
1. 需求(用户故事)
2. 交互
3. 静态api
4. 开发和ui并行
5. 测试、部署等
![](https://i5ting.github.io/wechat-dev-with-nodejs/vip-lession/img/api-before.png)
![](https://i5ting.github.io/wechat-dev-with-nodejs/vip-lession/img/api-after.png)
这里面可以看到交互ue出了,就可以出静态api
静态api好处
* 分析在前,定义好接口规范和字段等
* 有了静态api,开发也可以并行
* 后端java/node/php
* h5/pc
* 移动端
如果再智能点,静态api是可以对线上api进行测试和压测的。
## 实现方法
### json-server
Get a full fake REST API with zero coding in less than 30 seconds (seriously)
[https://github.com/typicode/json-server](https://github.com/typicode/json-server)
Create a db.json file
~~~
{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}
~~~
Start JSON Server
~~~
$ json-server --watch db.json
~~~
Now if you go to [http://localhost:3000/posts/1](http://localhost:3000/posts/1), you'll get
~~~
{ "id": 1, "title": "json-server", "author": "typicode" }
~~~
### apie
[https://github.com/base-n/apie](https://github.com/base-n/apie)
Features
* 代码极简,可配置api目录
* 支持路径和url映射,在routes目录下创建api目录,即可使用/api作为路径
* 支持v1或v2版本
* 使用express-style的路由,写法简单
* 使用res.api约定api返回格式
* 使用req.db(实际是lowdb)模拟数据
Teck Stacks
* use base2 as micro kernel
* use expess-style routes
* use res.api as api convention
* use nodemon for livereload
[https://github.com/base-n/apie/blob/master/routes/api/index.js](https://github.com/base-n/apie/blob/master/routes/api/index.js)
~~~
var express = require('express');
var router = express.Router();
// console.dir(router)
/* GET home page. */
router.get('/', function(req, res, next) {
res.api({a:1});
});
router.get('/error', function(req, res, next) {
res.api_error({a:1});
});
module.exports = router;
~~~
## tpl
* url : [http://127.0.0.1:3019/api/](http://127.0.0.1:3019/api/)
* method : GET
* type : JSON
* params : 无
* 返回
* 返回状态码
* 返回字段说明
示例
获取课程列表
* url : [http://127.0.0.1:3019/api/v1/lessions](http://127.0.0.1:3019/api/v1/lessions)
* method : GET
* type : JSON
* params : 无
* 返回
* 返回状态码
* 0 成功
* 返回字段说明
* lessions 对象数组
## 课程api
* 获取课程列表
* 获取课程详情
* 创建订单
* 我的课程
大家试着补出一下
';
8.4 模型
最后更新于:2022-04-01 23:35:03
# 建模
## ER模型
关系数据库或者ER图,这其实是以实体(个体,类)为基础的物理语言,因为关系是实体之间的关系,是由实体来(联合)定义的,所以是实体在先,关系在后的. 当然,也有所谓的纯关系项,比如学生成绩(数学分数),既不属于学生,也不属于课程,而是它们的关系存在.
* 三范式
* 笛卡尔积
* 关系:一对一,一对多,多对多
* SQL
* 工具:powerdesigner、erwin
说明:常见的关系型数据库来描述er模型比较多。我们用mongodb也是一样的,处理查询不太一样以外,其他都是一样的。
## 实体
* 用户(微信)
* 课程
* 订单
## 关系
* 我的课程:1对多 -
## 微信用户
~~~
/**
* Created by alfred on August 25th 2015, 6:11:46 pm.
*/
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MongooseDao = require('mongoosedao');
var wechatSchema = new Schema({
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
}
});
wechatSchema.statics.find_by_openid = function(openid, cb) {
return this.findOne({
openid: openid
}, cb);
};
wechatSchema.statics.find_by_unionid = function(unionid, cb) {
return this.findOne({
unionid: unionid
}, cb);
};
var Wechat = mongoose.model('Wechat', wechatSchema);
var WechatDao = new MongooseDao(Wechat);
module.exports = WechatDao;
~~~
## 课程列表
## 订单
';
8.3 技术栈
最后更新于:2022-04-01 23:35:01
# 技术选型
* nodejs
* mongodb
* h5
* 前后端分离
## 前后端分离
* 前端
* public下面的采用nginx做反向代理
* 其他的采用express+jade精简代码(ajax与后端交互)
* 后端json api
实例[https://github.com/moajs/moa-frontend/blob/master/config/nginx.example.conf](https://github.com/moajs/moa-frontend/blob/master/config/nginx.example.conf)
~~~
server {
listen 8000;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location / {
proxy_redirect off;
proxy_set_header Host $host;
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_pass http://127.0.0.1:3010;
proxy_redirect off;
}
# Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location /api {
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_pass http://127.0.0.1:3005;
proxy_redirect off;
}
}
~~~
注意
* listen 8000; (nginx端口8000)
* location / (前端端口3010)
* location /api (后端端口3005)
## 后端api
* express(koa、或base2也可以)
* res.api
* mount-routes
* mongoose
* 微信授权[https://github.com/node-webot/wechat-oauth](https://github.com/node-webot/wechat-oauth)
* 微信支付[https://github.com/tvrcgo/weixin-pay](https://github.com/tvrcgo/weixin-pay)
## 前端h5
* zepto
* weui
* iscroll
* fastclick
* swiper
## 高级h5
* vux
* jmui
';
8.2 ui/ue
最后更新于:2022-04-01 23:34:59
# ui/ue
当用户故事和场景确定了,此时就可以开始设计了
* 第一步是ue:出低保真原型图,确认交互
* 第二步是ui:出高保真设计图,切图交付给开发
一般我们是出了交互,就让【产品经理和交互设计师】给【设计和开发】讲解需求和交互,【设计和开发】基本可以并行,在设计未定稿前,开发可以技术调研、选型、建模,编写api等
## ue
* Balsamiq Mockups
![](https://i5ting.github.io/wechat-dev-with-nodejs/vip-lession/img/Mockups.png)
下面是我设计的交互
![](https://i5ting.github.io/wechat-dev-with-nodejs/vip-lession/img/kecheng.png)
* axure
![](https://i5ting.github.io/wechat-dev-with-nodejs/vip-lession/img/axture.jpg)
## ui
* skech
Sketch中文网 [http://www.sketchcn.com/](http://www.sketchcn.com/)
匡雪婷 [http://www.aegeank.com/](http://www.aegeank.com/)
叶老师的 [https://github.com/hayeah/30-days-of-design](https://github.com/hayeah/30-days-of-design)
目前我的朋友在帮我弄,设计还没有出来
';
8.1 需求分析
最后更新于:2022-04-01 23:34:57
# 需求分析
项目实战《付费课程MVP系统》
## 什么是用户故事?
用户故事是从用户的角度来描述用户渴望得到的功能。一个好的用户故事包括三个要素:
1. 角色:谁要使用这个功能。
2. 活动:需要完成什么样的功能。
3. 商业价值:为什么需要这个功能,这个功能带来什么样的价值。
用户故事通常按照如下的格式来表达:
* 英文:As a , I want to , so that .
* 中文:作为一个, 我想要, 以便于
* 举例:作为一个“网站管理员”,我想要“统计每天有多少人访问了我的网站”,以便于“我的赞助商了解我的网站会给他们带来什么收益。”
需要注意的是用户故事不能够使用技术语言来描述,要使用用户可以理解的业务语言来描述
## 场景
cucumber里的步骤定义
~~~
Given /^当前是(.*)/ do |action|
@action= action
end
When /^输入是(.*)/ do |subject|
@subject = subject
end
Then /^能看到(.*)/ do |greeting|
if greeting != "#{@action}#{@subject}"
raise "期望看到<" + greeting + ">,实际看到<" + "#{@action}#{@subject}>"
end
end
~~~
场景:
1. 假如当前是中文
2. 当输入是测试
3. 那么能看到中文测试
场景1:
1. 假如当前是入库
2. 当输入是选择某个货品
3. 那么能完成入库
场景2:
1. 假如当前是入库
2. 当输入是无法选择任何产品
3. 那么能跳转到新建产品界面
## 精简后的需求文档
我们探索的办法
* 使用用户故事描述,需求
* 使用场景来验证并测试
严格执行agile很难,没有scrum master搞不定
## 具体需求
先实现一个MVP(最小可用原型)
### 查看所有课程
* 作为一个,
* 我想要,
* 以便于
场景1
1. 假如当前没有课程
2. 当输入是点击了查看所有课程连接
3. 那么能看到错误提示页面,提示请稍后再来
### 查看课程详情
* 作为一个,
* 我想要,
* 以便于
### 参加免费课程
* 作为一个,
* 我想要,
* 以便于
### 参加付费课程
* 作为一个,
* 我想要,
* 以便于
场景1:支付成功
1. 假如当前支付成功
2. 当输入是钱数>0,并且微信支付成功
3. 那么能看到我的课程里的课程支付信息
场景2:支付失败
1. 假如当前支付失败
2. 当输入钱数《=0,或微信支付失败
3. 那么能看到错误提示页面,提示请返回重试
### 查看我的个人信息
* 作为一个,
* 我想要,
* 以便于
### 使用微信用户登录
* 作为一个,
* 我想要,
* 以便于
### 查看我的课程
* 作为一个,
* 我想要,
* 以便于
### 分享课程详情
* 作为一个,
* 我想要,
* 以便于
';
8 项目实战《付费课程系统MVP》
最后更新于:2022-04-01 23:34:54
# 项目实战《付费课程系统MVP》
回到课程主题《Nodejs微信开发》
1. 第0节:nodejs入门 (14/14)
2. 第1节:express和微信开发入门 (6/6)
3. 第2节:微信实例和h5实践 (4/4)
4. 第3节:weui实战和微信支付 (7/7)
* 微信支付(稍等,支付账号还在申请中)
5. 第4节:实战付费课程系统(TODO)
我们花了很多精力把基础知识和单一技能点
* 基本技能(coding ide,git,命令行等)
* nodejs基础
* express
* 微信基础(后台)
* 微信分享
* h5和weui实践
* 微信支付
这些都太零散了,为了能够让大家有一个整体的项目认知,这里面我们再加一个项目实践
《rework》一书里讲,抓自己的痒,是说创业的时候选题,从自身的痛点出发,这样更容易成功
那么,我们(StuQ)的这个课程,如果想要推广,想让更多人参与,怎么办呢?
既然讲的是这课,那我们就写一个吧
';
7.9 随堂练习
最后更新于:2022-04-01 23:34:52
# 随堂练习:写一个通用h5支付接口
如何写一个通用h5支付接口?
* 想想我们的支付接口需要什么参数?
* 如何能够做到可配置? -
';
7.8 数据处理
最后更新于:2022-04-01 23:34:50
# 数据处理
## csv处理
* i-csv
* import-csv
* export-csv
* json2csv
* fastcsv
### json2csv
json to csv converter
[https://github.com/i5ting/json2csv](https://github.com/i5ting/json2csv)
## import-csv
[https://github.com/i5ting/import-csv](https://github.com/i5ting/import-csv)
默认使用gbk编码
~~~
var import_csv = require('import-csv')
import_csv('test.csv',function(err, data){
console.log(data);
})
~~~
指定字符编码机
~~~
var import_csv = require('import-csv')
import_csv('test.csv',function(err, data){
console.log(data);
}, 'gbk')
~~~
## export-csv
[https://github.com/i5ting/export-csv](https://github.com/i5ting/export-csv)
默认使用gbk编码
~~~
var export_csv = require('.')
var data = [
{a:1,b:2},
{a:2,b:2},
{a:3,b:2}
]
export_csv(data, 'test1.csv')
~~~
过滤item,重写数据
~~~
var export_csv = require('.')
var data = [
{ a: 1, b: 2 },
{ a: 2, b: 2 },
{ a: 3, b: 2 }
]
export_csv(data, 'test3.csv', function (item) {
for (var key in item) {
item[key] = 'yy +' + item[key];
}
return item;
})
~~~
指定回调函数
~~~
var export_csv = require('.')
var data = [
{a:1,b:2},
{a:2,b:2},
{a:3,b:2}
]
export_csv(data, 'test2.csv', function (item) {
return item;
}, function () {
console.log('end...');
})
~~~
指定回调函数,并生成header
~~~
var export_csv = require('.')
var data = [
{a:1,b:2},
{a:2,b:2},
{a:3,b:2}
]
export_csv(data, 'test2.csv', function (item) {
return item;
}, function () {
console.log('end...');
}, true)
~~~
## mongodb如何统计
~~~
var json2csv = require('json2csv');
require('../db.js');
var fs = require('fs');
var Order = require('../app/models/order');
var Activity = require('../app/models/activity');
var Wechat = require('../app/models/wechat');
var Contact = require('../app/models/contact');
function main(){
Order.model.aggregate([
{$match : {status2 : {$exists : true} }},
{$group : {_id : "$activity" , count : {$sum : "$product_count"}}}
]).exec(function (err, data) {
var obj = [];
for (var i = data.length - 1; i >= 0; i--) {
var json = {
"url" : "http://shop.mengxiaoban.cn/iscroll.html?id=" + String(data[i]._id),
"count" : String(data[i].count)
}
obj.push(json);
console.log(json.url);
}
var opts = {
data: obj,
fields: ['url','count'],
fieldNames: ['url','count'],
quotes: ''
};
json2csv(opts, function(err, csv) {
if (err) console.log(err);
fs.writeFile('./1119todayurl.csv', csv, function(err) {
if (err) throw err;
console.log('file saved');
});
});
})
}
}
main();
~~~
AGGREGATION 关联
《SQL to Aggregation Mapping Chart》
[http://docs.mongodb.org/manual/reference/sql-aggregation-comparison/](http://docs.mongodb.org/manual/reference/sql-aggregation-comparison/)
## 优化查询速度
了解索引优化
~~~
ContactSchema = new Schema({
...
owner: {
type: Schema.ObjectId,
required: true,
index: true
}
});
~~~
也可以这样的
~~~
ContactSchema.ensureIndexes(owner);
~~~
## 了解explain
~~~
db.usermodels.find({
'_id' :{
"$gt" :ObjectId("55940ae59c39572851075bfd")
}
}).explain()
~~~
关注点
* stage:查询策略
* nReturned:返回的文档行数
* needTime:耗时(毫秒)
* indexBounds:所用的索引
[http://docs.mongodb.org/v3.0/reference/explain-results/](http://docs.mongodb.org/v3.0/reference/explain-results/)
## 了解profile
profile级别有三种:
* 0:不开启
* 1:记录慢命令,默认为大于100ms
* 2:记录所有命令
* 3、查询profiling记录 开启
~~~
db.setProfilingLevel(2, 20)
~~~
默认记录在system.profile中
~~~
db['system.profile'].find()
~~~
## MONGODB的部署
* replset
* shard
我写的《 mongodb运维之副本集实践》
[https://cnodejs.org/topic/5590adbbebf9c92d17e734de](https://cnodejs.org/topic/5590adbbebf9c92d17e734de)
';
7.7 对账单
最后更新于:2022-04-01 23:34:48
# 对账单
防漏单
## 下载对账单
[https://pay.weixin.qq.com/index.php/settle/state_account](https://pay.weixin.qq.com/index.php/settle/state_account)
![](https://i5ting.github.io/wechat-dev-with-nodejs/pay/8.png)
## 对账
~~~
var csv = require('csv');
var import_csv = require('import-csv');
var parse = csv.parse;
var fs = require('fs');
var Promise = require('bluebird');
var iconv = require('iconv-lite');
var Order = require('../app/models/order');
var Activity = require('../app/models/activity');
var Wechat = require('../app/models/wechat');
var Contact = require('../app/models/contact');
var DELIVERY = require('../config/delivery_company');
require("../db");
var import_csv = require('import-csv')
var array = [];
import_csv('./1229.csv',function(err, data){
array.push(data);
array[0].shift();
Promise.all(array).then(_for);
}, 'utf-8')
function _for () {
if(array[0].length > 0) {
var data = array[0].shift();
var pay_num = data[0].split('#')[0];
var type = data[24];
var deliver_number = data[2].split('`')[0];
var _company = data[1];
var company;
if (_company.indexOf('圆通速递') !=-1){
company = DELIVERY.Y_T_S_D;
}if (_company.indexOf('百世汇通') !=-1){
company = DELIVERY.B_S_H_T;
}
if(pay_num&&type&&deliver_number){
var obj = {
pay_num : pay_num,
type : type ,
deliver_number : deliver_number,
company : company
}
Promise.resolve(obj).then(function (obj) {
find_order(obj);
});
}
}
}
function find_order (obj) {
console.log(obj.pay_num + obj.type + obj.deliver_number + 'zzzzzzzzzzzzs');
Order.oneAsync({pay_num : obj.pay_num}
).then(function (order) {
var d_num = order.delivery_num;
console.log(d_num + 'dddddddddddddd');
if (d_num) {
if (d_num.indexOf(obj.deliver_number) != -1) {
var d = obj.type + ':' + obj.deliver_number + ';';
console.log("这个单已经有了,不处理 " + d_num);
}else {
var d = d_num + obj.type + ':' + obj.deliver_number + ';';
console.log("这个单还没有,拼接插入" + d);
update_order(obj.pay_num, d, obj.company)
}
}else{
console.log("运单号是空的,直接插入");
var d = obj.type + ':' + obj.deliver_number + ';';
update_order(obj.pay_num, d, obj.company)
}
}).then(function () {
_for();
});
}
function update_order (pay_num, d, company) {
console.log(company);
return Order.updateAsync({pay_num : pay_num}, {status : "1", delivery_num : d, delivery_company : company}, function (err, result) {
console.log('-ok=' +result.ok + '-nModified=' +result.nModified + '- n=' +result.n + '原有单号+拼接');
})
}
~~~
';
7.6 公众号支付(JSAPI)
最后更新于:2022-04-01 23:34:45
# 公众号支付(JSAPI)
## out_trade_no生成
~~~
function _get_date_string () {
var date = moment().format('YYYY MM DD HH mm ss');
return date.split(' ').join('_');
}
function _get_out_trade_no () {
return _get_date_string () + "" + Math.random().toString().substr(2, 10);
}
~~~
测试方法
~~~
function pay_h5(){
var ordor_id = _get_out_trade_no ();
alert(ordor_id)
$.get('/wechats/pay_h5?id=o12hcuKXjejDFUwxMgToaGtjtqf4&order_id=' + ordor_id + '&body=1111&detail=222222&fee=1&cb_url=/wechats/pay_calllback/'+ ordor_id, function(data){
var r = data.data;
WeixinJSBridge.invoke('getBrandWCPayRequest', r, function(res){
if(res.err_msg == "get_brand_wcpay_request:ok"){
alert("支付成功");
// 这里可以跳转到订单完成页面向用户展示
}else{
alert("支付失败,请重试");
}
});
});
}
~~~
## 限制频率
~~~
npm i -S moa-middleware-rate-cache
~~~
way 1:
~~~
var rate_cache = require('moa-middleware-rate-cache');
var r = new rate_cache(redis, 'xxxxx_key', 40);
~~~
参数
* redis, 传入redis对象
* 'xxxxx_key', 在redis里缓存的key
* 40(秒)缓存时间
way 2:
~~~
var rate_cache = require('moa-middleware-rate-cache');
var r = new rate_cache(redis, 'xxxxx_key2222');
~~~
参数
* redis, 传入redis对象
* 'xxxxx_key', 在redis里缓存的key
* 默认缓存时间是30秒 -
## 对账单接口
### 支付成功的回调
~~~
wxpay.createUnifiedOrder({
body: '扫码支付测试',
out_trade_no: '20140703'+Math.random().toString().substr(2, 10),
total_fee: 1,
spbill_create_ip: '192.168.2.210',
notify_url: 'http://wxpay_notify_url',
trade_type: 'NATIVE',
product_id: '1234567890'
}, function(err, result){
console.log(result);
});
~~~
#### 支付结果异步通知
~~~
商户服务端处理微信的回调(express为例)
// 原生支付回调
router.use('/wxpay/native/callback', wxpay.useWXCallback(function(msg, req, res, next){
// msg: 微信回调发送的数据
}));
// 支付结果异步通知
router.use('/wxpay/notify', wxpay.useWXCallback(function(msg, req, res, next){
// 处理商户业务逻辑
// res.success() 向微信返回处理成功信息,res.fail()返回失败信息。
res.success();
}));
~~~
#### 手动对账单
查询订单
~~~
// 通过微信订单号查
wxpay.queryOrder({ transaction_id:"xxxxxx" }, function(err, order){
console.log(order);
});
// 通过商户订单号查
wxpay.queryOrder({ out_trade_no:"xxxxxx" }, function(err, order){
console.log(order);
});
~~~
';