在线等,感谢!!!
来自酷炫的 CNodeMD
在线等,感谢!!!
来自酷炫的 CNodeMD
第一节 课程概述
本课程面向初学者,内容涵盖以太坊开发相关的基本概念,并将手把手地教大家如何构建一个 基于以太坊的完整去中心化应用 —— 区块链投票系统。
通过本课程的学习,你将掌握:
以太坊区块链的基本知识
开发和部署以太坊合约所需的软件环境
使用高级语言(solidity
)编写以太坊合约
使用NodeJS编译、部署合约并与之交互
使用Truffle
框架开发分布式应用
使用控制台或网页与合约进行交互
前序知识要求
为了顺利完成本课程,最好对以下技术已经有一些基本了解:
一种面向对象的开发语言,例如:Python,Ruby,Java…
前端开发语言:HTML/CSS/JavaScript
Linxu命令行的使用
数据库的基本概念
课程的所有代码均已在Ubuntu(Trusty、Xenial)和 macOS 上测试过。
第二节 课程简介
在本课程中,我们将会构建一个去中心化的(Decentralized
)投票应用。利用这个投票应用, 用户可以在不可信(trustless
)的分布环境中对特定候选人投票,每次投票都会被记录在区块 链上:
所谓去中心化应用(DApp
:Dcentralized Application),就是一个不存在中心服务器 的应用。在网络中成百上千的电脑上,都可以运行该应用的副本,这使得它几乎不可能 出现宕机的情况。
基于区块链的投票是完全去中心化的,因此无须任何中心化机构的存在。
第三节 开发迭代
本课程将涵盖应用开发的整个过程,我们将通过三次迭代来渐进地引入区块链应用 开发所涉及的相关概念、语言和工具:
Vanilla:在第一个迭代周期,我们不借助任何开发框架,而仅仅使用NodeJS来进行应用开发, 这有助于我们更好地理解区块链应用的核心理念。
Truffle:在第二个迭代周期,我们将使用最流行的去中心化应用开发框架Truffle
进行开发。 使用开发框架有助于我们提高开发效率。
Token:在第三个迭代周期,我们将为投票应用引入代币(Token
) —— 现在大家都改口 称之为通证了 —— 都是ICO
惹的祸。代币是公链上不可或缺的激励机制,也是区块链 应用区别于传统的中心化应用的另一个显著特征。
为什么选择投票应用作为课程项目?
之所以选择投票作为我们的第一个区块链应用,是因为集体决策 —— 尤其是投票机制 —— 是以太坊的 一个核心的价值主张。
另一个原因在于,投票是很多复杂的去中心化应用的基础构件,所以我们选择了投票应用作为学习区块链 应用开发的第一个项目。
第四节 初识区块链
如果你熟悉关系型数据库,就应该知道一张数据表里可以包含很多行数据记录。例如,下面的数据表中 包含了6条交易记录:
本质上,区块链首先就是一个分布式(Distributed
)数据库,这个数据库维护了一个不断增长的记录列表。 现在,让我们对数据进行批量(batch
)存储,比如每批 100 行,并将各存储批次连接起来,是不是就像一条链?
在区块链里,多个数据记录组成的批次就被称为块(block
),块里的每一行数据记录就被称为交易(transaction
):
最开始的那个块,通常被称为创世块(genesis block
),它不指向任何其他块。
不可篡改性
区块链的一个显著特点是,数据一旦写入链中,就不可篡改重写。
在传统的关系型数据库中,你可以很容易地更新一条数据记录。但是,在区块链中,一旦数据写入就无法 再更新了 —— 因此,区块链是一直增长的。
那么,区块链是如何实现数据的不可篡改特性?
这首先得益于哈希(Hash
)函数 —— 如果你还没接触过哈希函数,不妨将它视为一个数字指纹的计算函数: 输入任意长度的内容,输出定长的码流(指纹)。哈希函数的一个重要特性就是,输入的任何一点微小变化,都会 导致输出的改变。因此可以将哈希值作为内容的指纹来使用。 你可以点击这里进一步了解哈希函数。
由于区块链里的每个块都存储有前一个块内容的哈希值,因此如果有任何块的内容被篡改,被篡改的块之后 所有块的哈希值也会随之改变,这样我们就很容易检测出区块链的各块是否被篡改了。
去中心化的挑战
一旦完全去中心化,在网络上就会存在大量的区块链副本(即:全节点),很多事情都会变得比之前中心化 应用环境复杂的多,例如:
如何保证所有副本都已同步到最新状态?
如何保证所有交易都被广播到所有运行和维护区块链副本的节点计算机上?
如何防止恶意参与者篡改区块链
…
在接下来的课程中,通过与经典的C/S架构的对比,我们将逐步理解去中心化应用的核心思路, 并掌握如何构建以太坊上的去中心化应用。
第五节 C/S架构以服务器为中心
理解去中心化应用架构的最好方法,就是将它与熟悉的Client/Server
架构进行对比。如果你是一个web
开发者, 应该对下图很了解,这是一个典型的Client/Server
架构:
一个典型web应用的服务端通常由 Java,Ruby,Python 等等语言实现。前端代码由 HTML/CSS/JavaScript 实现。 然后将整个应用托管在云端,比如 AWS、Google Cloud Platform、Heroku…,或者放在你租用的一个VPS
主机上。
用户通过客户端(Client
)与 web 应用(Server
)进行交互。典型的客户端包括浏览器、命令行工具(curl
、wget
等)、 或者是API
访问代码。注意在这种架构中,总是存在一个(或一组)中心化的 web 服务器,所有的客户端都需要 与这一(组)服务器进行交互。当一个客户端向服务器发出请求时,服务器处理该请求,与数据库/缓存进行交互, 读/写/更新数据库,然后向客户端返回响应。
这是我们熟悉的中心化架构。在下一节,我们将会看到基于区块链的去中心化架构的一些显著区别。
第六节 去中心化架构——彼此平等的节点
下图给出了基于以太坊的去中心化应用架构:
你应该已经注意到,每个客户端(浏览器)都是与各自的节点应用实例进行交互,而不是向 一个中心化的服务器请求服务。
在一个理想的去中心化环境中,每个想要跟DApp交互的人,都需要在他们的计算机或手机上面运行 一个的完整区块链节点 —— 简言之,每个人都运行一个全节点。这意味着,在能够真正使用一个 去中心化应用之前,用户不得不下载整个区块链。
不过我们并非生活在一个乌托邦里,期待每个用户都先运行一个全节点,然后再使用你的应用是不现实的。 但是去中心化背后的核心思想,就是不依赖于中心化的服务器。所以,区块链社区已经出现了 一些解决方案,例如提供公共区块链节点的Infura
, 以及浏览器插件Metamask
等。通过这些方案, 你就不需要花费大量的硬盘、内存和时间去下载并运行完整的区块链节点,同时也可以利用去中心化 的优点。我们将会以后的课程中对这些解决方案分别进行评测。
第七节 以太坊——世界计算机
以太坊是一种区块链的实现。在以太坊网络中,众多的节点彼此连接,构成了以太坊网络:
以太坊节点软件提供两个核心功能:数据存储、合约代码执行。
在每个以太坊全节点中,都保存有完整的区块链数据。以太坊不仅将交易数据保存在链上,编译后 的合约代码同样也保存在链上。
以太坊全节点中,同时还提供了一个虚拟机来执行合约代码。
交易数据
以太坊中每笔交易都存储在区块链上。当你部署合约时,一次部署就是一笔交易。当你为候选者投票时,一次投票 又是另一笔交易。所有的这些交易都是公开的,每个人都可以看到并进行验证。这个数据永远也无法篡改。
为了确保网络中的所有节点都有着同一份数据拷贝,并且没有向数据库中写入任何无效数据,以太坊 目前使用工作量证明 (POW:Proof Of Work
)算法来保证网络安全,即通过矿工挖矿(Mining
)来达成共识(Consensus
)—— 将数据同步到所有节点。
工作量证明不是达成共识的唯一算法,挖矿也不是区块链的唯一选择。现在,我们只需要了解,共识是指各节点 的数据实现了一致,POW
只是众多用于建立共识的算法中的一种,这种算法需要通过矿工的挖矿来实现非可信环境下的 可信交易。共识是目的,POW是手段。
合约代码
以太坊不仅仅在链上存储交易数据,它还可以在链上存储合约代码。
在数据库层面,区块链的作用就是存储交易数据。那么给候选者投票、或者检索投票结果的逻辑放在哪儿呢? 在以太坊的世界里,你可以使用Solidity
语言来编写业务逻辑/应用代码(也就是合约:Contract
), 然后将合约代码编译为以太坊字节码,并将字节码部署到区块链上:
编写合约代码也可以使用其他的语言,不过 Solidity
是到目前为止最流行的选择。
以太坊虚拟机
以太坊区块链不仅存储数据和代码,每个节点中还包含一个虚拟机(EVM:Ethereum Virtual Machine)来执行 合约代码 —— 听起来就像计算机操作系统。
事实上,这一点是以太坊区别于比特币(Bitcoin
)的最核心的一点:虚拟机的存在使区块链迈入了2.0 时代,也让区块链第一次成为应用开发者友好的平台。
JS开发库
为了便于构建基于web的DApp,以太坊还提供了一个非常方便的JavaScript库web3.js
,它封装了以太坊节点的API 协议,从而让开发者可以轻松地连接到区块链节点而不必编写繁琐的RPC
协议包。所以,我们可以在常用的JS框架 (比如 reactjs、angularjs 等)中直接引入该库来构建去中心化应用:
我的微信:
PC端课程地址: http://xc.hubwiz.com/course/5a952991adb3847553d205d1?affid=cnode
按照cnode-api的顺序来的, 基本只是做了包装, 需要修改的可以看代码,做出修改. 通过 graphql 的 rest-wrapper resolver 包装以后,就可以获得 graphql的一些很好的特征了. 因为没有数据写入的Grpahql 的数据库,所以所有的操作都用的是query. 没有用 mutate. 因为最终还是访问的 REST API.
这的graphql采用的是 graphcool 的服务器, 尽管试, 没有写入操作.
Graphcool-cnode-server graphiQL地址
服务器的初始化可以参考这里Graphcool Server
可以选择部署在本地docker 中, 如果用于测试可以 直接部署在云上.
大致的流程如下在 GraphQL客户端或者是 GraphiQL执行的操作,会经过 resolver 函数的处理, resolver 实际是 express 服务器, 在这里可以执行数据库操作,或者是执行转发任务, 如果是为 API 提供服务, 就使用转发. 我们这里就是转发.
1
get/tpoics 主题首页就是数据列表,tab用于分类, page 用于分页
allTopics.graphql
//⛔️有些字段没有列出,可以做修改
type AllTopicsPayload {
id: String!
tab: String
title: String!
visit: Int!
aurl: String!
author_id: String!
}
input QueryInput {
page: Int!
tab: String!
}
extend type Query {
AllTopics(page: Int!, tab: String!): [AllTopicsPayload!]!
}
allTopics.js
require('isomorphic-fetch');
const R = require('ramda');
const url = 'https://cnodejs.org/api/v1/topics';
module.exports = (event) => {
const { tab, page } = event.data;
urlWithParams = `${url}?tab=${tab}&page=${page}`;
let options = {
method: 'GET'
};
return fetch(urlWithParams, options).then((response) => response.json()).then((responseData) => {
const NodeList = responseData.data;
const allCnode = [];
const selectPropertyX = (x) => ({
id: x.id,
tab: x.tab,
aurl: x.author.avatar_url,
visit: x.visit_count,
title: x.title,
author_id: x.author_id
});
const allTopics = R.map(selectPropertyX, NodeList);
//const getIdcollections=R.curry(R.map(selectPropertyX,data));
//const allCnode=getIdcollections(NodeList);
return { data: allTopics };
});
};
查询示意图
注意事项
graphiQL里执行的操作就是最好的文档, 这里执行的查询结果, 在其他地方可以完全复现,如果复现不了,就是你的客户端代码由问题. 在客户端我们可以使用 graphql-tag 的方法把这段查询的字符串给拼接出来.如果拼接没有问题, 得到的结果是完全一样的
2
get/topics主题详情getOneTopic.graphql
type OneTopicPayload {
id: String!
tab: String
title: String!,
content:String!,
}
extend type Query {
getOneTopic(id:String!): OneTopicPayload!
}
getOneTopic.js
require('isomorphic-fetch')
const R = require('ramda');
module.exports = event => {
const {id} = event.data
const url = `https://cnodejs.org/api/v1/topic/${id}`
return fetch(url)
.then(response => response.json())
.then(responseData => {
return {data: responseData.data}
})
}
查询
3
新建主题 post/topicscreateTopic.graphql
type createTopicPayload {
tab: String!@default(value: "dev") #默认选了 dev
title:String!
accesstoken: String!
content: String!
success: Boolean
}
extend type Query {
createTopic(title: String!,accesstoken:String!,tab:String!,content:String!): createTopicPayload
}
createTopic.js
'use latest'
import fetch from 'node-fetch'
const api = 'https://cnodejs.org/api/v1/topics'
module.exports = async event => {
const {tab, accesstoken, title, content} = event.data
const body = {
accesstoken: accesstoken,
tab: tab,
title: title,
content: content
}
const ress = await fetch(api, {
method: 'POST',
body: JSON.stringify(body),
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
})
.then(res => res.json())
.then(json => json)
const success = {
'success': ress.success
}
console.log(ress);
return {data: success}
}
查询示意
4
编辑主题 post/topics/updateupdateTopic.graphql
type updateTopicPayload {
tab: String!@default(value: "dev")
title:String!
accesstoken: String!
content: String!
success: Boolean
topic_id:String!
}
extend type Query {
updateTopic(title: String!,accesstoken:String!,tab:String!,content:String!,topic_id:String!): updateTopicPayload
}
updateTopic.js
'use latest';
import fetch from 'node-fetch';
const api = 'https://cnodejs.org/api/v1/topics/update';
module.exports = async event => {
const { tab, accesstoken, title, content, topic_id } = event.data;
const body = {
accesstoken: accesstoken,
tab: tab,
title: title,
content: content,
topic_id: topic_id
};
const ress = await fetch(api, {
method: 'POST',
body: JSON.stringify(body),
headers: { Accept: 'application/json', 'Content-Type': 'application/json' }
})
.then(res => res.json())
.then(json => json);
const success = {
success: ress.success
};
console.log(ress);
return { data: ress };
};
查询结果
和创建基本是一样的
1
收藏主题 get topic_collection/collectcreateTopicCollection.graphql
type createTopicCollectionPayload {
topic_id: String
accesstoken: String
success: Boolean
}
extend type Query {
createTopicCollection(topic_id: String!,accesstoken:String!): createTopicCollectionPayload
}
createTopicCollection.js
'use latest'
import fetch from 'node-fetch'
const api = 'https://cnodejs.org/api/v1/topic_collect/collect'
module.exports = async event => {
const {topic_id, accesstoken} = event.data
const body = {
accesstoken: accesstoken,
topic_id: topic_id
}
const ress = await fetch(api, {
method: 'POST',
body: JSON.stringify(body),
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
})
.then(res => res.json())
.then(json => json)
const success = {
'success': ress.success
}
return {data: success}
}
2
取消主题收藏 post/topic_collection/de_collectcancelTopicCollection.graphql
type cancelTopicCollectionPayload {
topic_id: String
accesstoken: String
success: Boolean
}
extend type Query {
cancelTopicCollection(topic_id: String!,accesstoken:String!): cancelTopicCollectionPayload
}
cancelTopicCollection.js
'use latest'
import fetch from 'node-fetch'
const api = 'https://cnodejs.org/api/v1/topic_collect/de_collect'
module.exports = async event => {
const {topic_id, accesstoken} = event.data
const body = {
accesstoken: accesstoken,
topic_id: topic_id
}
const ress = await fetch(api, {
method: 'POST',
body: JSON.stringify(body),
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
})
.then(res => res.json())
.then(json => json)
const success = {
'success': ress.success
}
return {data: success}
}
3
主题首页 get/topic_collection/:loginnameallCollections.graphql
type AllCollectionsPayload {
id: String!
tab: String
title: String!
visit: Int!
}
extend type Query {
AllCollections(name:String!): [AllCollectionsPayload !]!
}
allCollections.js
require('isomorphic-fetch')
const R = require('ramda')
const url = 'https://cnodejs.org/api/v1/topic_collect/'
module.exports = (event) => {
const {name} = event.data
urlWithParams = `${url}${name}`
let options = {
method: 'GET'
}
return fetch(urlWithParams, options).then((response) => response.json()).then((responseData) => {
const Collections = responseData.data
const selectPropertyX = (x) => ({id: x.id, tab: x.tab, title: x.title, visit: x.visit_count, author_id: x.author_id})
const allCollections = R.map(selectPropertyX, Collections)
return {data: allCollections}
})
};
查询结果
1
新建评论 post/ topic/:topic_id/repliescreateReplies.graphql
type createRepliesPayload {
topic_id:String!
accesstoken:String!
content: String!
reply_id: String
success:Boolean
}
extend type Query {
createReplies(topic_id: String!,accesstoken:String!,content:String!,reply_id:String): createRepliesPayload
}
createReplies.js
'use latest'
import fetch from 'node-fetch'
module.exports = async event => {
const {topic_id, accesstoken, content, reply_id} = event.data
const body = {
accesstoken: accesstoken,
content: content,
reply_id: reply_id
}
const api=`https://cnodejs.org/api/v1/topic/${topic_id}/replies`
const ress = await fetch(api, {
method: 'POST',
body: JSON.stringify(body),
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}
})
.then(res => res.json())
.then(json => json)
const success = {
'success': ress.success
}
console.log(ress)
return {data: success}
}
查询结果
2
为评论点赞 post /reply/:reply_id/upscreateUps.graphql
type createUpsPayload {
accesstoken:String!
reply_id: String!
success:Boolean
}
extend type Query {
createUps(accesstoken:String!,reply_id:String): createUpsPayload
}
createUps.js
'use latest'
import fetch from 'node-fetch'
module.exports = async event => {
const { accesstoken, reply_id} = event.data
const body = {
accesstoken: accesstoken
}
const api = `https://cnodejs.org/api/v1/reply/${reply_id}/ups`
const ress = await fetch(api, {
method: 'POST',
body: JSON.stringify(body),
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}
})
.then(res => res.json())
.then(json => json)
const success = {
'success': ress.success
}
console.log(ress)
return {data: success}
}
查询结果
1
用户详情 get /user/:loginnamegetUserInfo.graphql
type UserInfoPayload {
loginname: String!
avatar_url: String!
score: Int!
recent_topics:[Json!]!
recent_replies:[Json!]!
}
extend type Query {
getUserInfo(name:String!): UserInfoPayload!
}
getUserInfo.js
require('isomorphic-fetch')
const url = 'https://cnodejs.org/api/v1/user/'
module.exports =async (event) => {
const {name} = event.data
urlWithParams = `${url}${name}`
let options = {
method: 'GET'
}
return fetch(urlWithParams, options)
.then(response => response.json())
.then(responseData => {
return {data: responseData.data}
})
}
查询结果
getMessageCount.graphql
type MessageCountPayload {
accesstoken:String!
data:Int
success:Boolean
}
extend type Query {
getMessageCount(accesstoken:String!): MessageCountPayload!
}
getMessageCount.js
require('isomorphic-fetch')
const url = 'https://cnodejs.org/api/v1/message/count?accesstoken='
module.exports =async (event) => {
const {accesstoken} = event.data
urlWithParams = `${url}${accesstoken}`
let options = {
method: 'GET'
}
return fetch(urlWithParams, options)
.then(response => response.json())
.then(responseData => {
return {data: responseData}
})
}
2
获取已读和未读消息 get /messagesgetMessages.graphql
type MessagesPayload {
has_read_messages:[Json]
hasnot_read_messages:[Json]
}
extend type Query {
getMessages(accesstoken:String!): MessagesPayload!
}
getMessages.js
require('isomorphic-fetch')
const url = 'https://cnodejs.org/api/v1/messages?accesstoken='
module.exports =async (event) => {
const {accesstoken} = event.data
urlWithParams = `${url}${accesstoken}`
let options = {
method: 'GET'
}
return fetch(urlWithParams, options)
.then(response => response.json())
.then(responseData => {
return {data: responseData.data}
})
}
查询结果
3
全部标记已读 post /message/mark_allmarkAll.graphql
type markAllPayload {
accesstoken: String!
success: Boolean
}
extend type Query {
markAll(accesstoken:String!): markAllPayload
}
markAll.js
'use latest';
import fetch from 'node-fetch';
const api = 'https://cnodejs.org/api/v1/message/mark_all';
module.exports = async event => {
const {accesstoken} = event.data;
const body = {
accesstoken: accesstoken,
};
const ress = await fetch(api, {
method: 'POST',
body: JSON.stringify(body),
headers: { Accept: 'application/json', 'Content-Type': 'application/json' }
})
.then(res => res.json())
.then(json => json);
const success = {
success: ress.success
};
console.log(ress);
return { data: ress };
};
查询结果
我使用的的 Apollo-client的方法
①
在顶层组件导入配置文件import { ApolloProvider } from 'react-apollo'
import { ApolloClient, HttpLink, InMemoryCache} from 'apollo-client-preset'
const httpLink = new HttpLink({ uri: 'https://api.graph.cool/simple/v1/cjaxudkum2ugf0127kok921bc' })
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
connectToDevTools: false
})
export default class App extends React.Component {
render() {
return (
<ApolloProvider client={client}>
<Navigator />
</ApolloProvider>
)
}
}
②
实际是单一数据来源的 store, 在组件中可以直接使用查询了import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
//这里拼接的查询字符串和我们在浏览器中使用的字符串完全相同
//如果在浏览器中正常,这里没有结果,或者结果不一致, 问题就在这里了.
const getCollections = gql`
query ($name: String!){
AllCollections(name:$name) {
id,
tab,
title,
visit
}
}
`;
class Collection extends Component {
componentDidMount() {
}
render() {
const {navigate} = this.props.navigation;
//console.log(this.props.data)
if(this.props.data.loading){
return (
<View style={{flex:1,alignItems:'center',justifyContent:'center'}}>
<Text>Loading...</Text>
</View>
)
}
//console.log(this.props.data.AllCollections);
return (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.props.data.AllCollections}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.title}`}
subtitle={`访问次数:${item.visit}`}
avatar={{ uri: "https://avatars3.githubusercontent.com/u/3118295?v=4&s=120" }}
containerStyle={{ borderBottomWidth: 0.2 }}
onPress={() => navigate('Detail',{id:`${item.id}`})}
/>
)}
keyExtractor={item => item.id}
onEndReachedThreshold={50}
/>
</List>
);
}
}
//注入 graphql 查询
export default graphql(getCollections, {options: ({navigation}) => {
return ({
variables: {
name: 'alsotang',
}
});
}})(Collection)
①
:严格了类型,类型错误,或者缺少都会报错②
:可以根据需求灵活的选择需要返回的数据,例如 列表并不需要 cotent,我们可以选择性的在 graphql 返回数据时省掉这一个字段. 如果 graphQL 服务器和 REST 服务器在同一个主机下,效率会大大提高.③
:为 API生成了一个单一的入口,多个查询可以一次返回. 减少请求次数④
: 提供了强有力的测试和说明工具: graphiql, 统一前后端的操作.⑤
: graphQL自带说明性质, 有很好的代码提示和自动完成功能. 后续的客户端也会提供这个功能, 出错的机会大大降低了摘要:使用Easy-Monitor,可以准确定位Node.js应用的性能瓶颈,帮助我们优化代码性能。
当应用出现性能问题时,最大的问题在于: 如何准确定位造成性能瓶颈的代码呢?对于Node.js开发者,这里推荐一下Easy-Monitor,它应该是阿里巴巴某个90后程序员开发的。这个NPM模块可以帮助我们快速定位性能瓶颈。
当负载较高时,某个后端模块的响应时间慢了很多,甚至出现超时错误"504 Gateway Time"。通过查看监控可知,这个模块在高峰期的CPU使用量是满负荷的,这有可能是问题所在。
接入Easy-Monitor非常简单,在入口js文件中导入即可:
if (process.env.NODE_ENV === "development")
{
const easyMonitor = require("easy-monitor");
easyMonitor("backend");
}
启动应用,访问:http://localhost:12333/index,即可查看Easy-Monitor的UI界面:
**ab**命令可以用于进行压力测试
ab -n 1000 -c 10 -T 'application/json' -p data.json http://localhost:3000/data/
运行ab测试的同时,在easy-monitor界面,选择cpu,然后start。easy-monitor就会默默地采集CPU数据,5分钟之后,就可以看到统计结果:
使用Fundebug,阔以及时发现并修复应用错误,赶紧免费试用吧!!!
由Easy-Monitor的统计结果可知,函数A是性能瓶颈,消耗了最多的CPU时间。
那么,剩下的工作就非常简单了,对函数A进行性能优化即可。经过分析,函数A进行了大量重复计算,增加2行代码就可以大大地优化其性能。具体细节不再赘述,因为不是本文重点。
根据ab命令的测试结果,优化前平均每秒处理5.36个,优化后这个数字变成了48.35,是之前的9倍。将这个模块部署之后,服务器的CPU使用率大幅下降,接口的响应时间也恢复了正常。
使用Easy-Monitor,可以将性能瓶颈准确定位到某些函数,然后进行针对性的优化,这样可以帮助我们快速修复性能问题。
这是wooyun.org镜像的go-server-js版本,修改自 https://github.com/acgpiano/wooyun-node
支持标题,作者,类型,厂商检索。
方便新手小白使用,搭建方法非常简单。
把该项目克隆到本地
git clone https://github.com/zengming00/go-server-js-wooyun.git
下载wooyun的静态资源(十几个G):
链接: 百度网盘密码: mqnp
需要解压到go-server-js-wooyun/public/bugs/
文件夹下面(自行新建bugs文件夹)
到下面的地址下载go-server-js
https://github.com/zengming00/go-server-js/releases
go-server-js自带了sqlite3数据库,不依赖环境,整个服务器只有一个文件,特别适合这种项目使用,因为它是捆绑一个商城发布的,里面的其它文件是不需要的,下载解压后把go-server-js.exe (在linux或mac下是go-server-js) 拿出来放到 go-server-js-wooyun/ 下面运行就可以了
默认port端口是8080,可以在config.json里面修改
打开浏览器 http://127.0.0.1:8080就可以使用了。
仅供自学使用
我可以在本地电脑用mongo命令 登陆服务器的数据库进行访问操作吗?
用mongodb compass程序也无法访问数据库,这是为什么? !!数据库没有设置admin用户,服务器需要adminname adminpassword才能登陆
一直想写一篇关于 Event Loop 的文章,前不久发现 CNode 上有位同学写了一篇原理分析的文章很详细,这里我就不献丑了。本文就拿出六道题来补充一下,放出一张我认为非常直观的图。
绿色小块是 macrotask(宏任务),macrotask 中间的粉红箭头是 microtask(微任务)。
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
运行结果:
setImmediate
setTimeout
或者:
setTimeout
setImmediate
为什么结果不确定呢?
解释:setTimeout/setInterval 的第二个参数取值范围是:[1, 2^31 - 1],如果超过这个范围则会初始化为 1,即 setTimeout(fn, 0) === setTimeout(fn, 1)。我们知道 setTimeout 的回调函数在 timer 阶段执行,setImmediate 的回调函数在 check 阶段执行,event loop 的开始会先检查 timer 阶段,但是在开始之前到 timer 阶段会消耗一定时间,所以就会出现两种情况:
再看个例子:
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
const start = Date.now()
while (Date.now() - start < 10);
运行结果一定是:
setTimeout
setImmediate
const fs = require('fs')
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})
})
运行结果:
setImmediate
setTimeout
解释:fs.readFile 的回调函数执行完后:
所以,在 I/O Callbacks 中注册的 setTimeout 和 setImmediate,永远都是 setImmediate 先执行。
setInterval(() => {
console.log('setInterval')
}, 100)
process.nextTick(function tick () {
process.nextTick(tick)
})
运行结果:setInterval 永远不会打印出来。
解释:process.nextTick 会无限循环,将 event loop 阻塞在 microtask 阶段,导致 event loop 上其他 macrotask 阶段的回调函数没有机会执行。
解决方法通常是用 setImmediate 替代 process.nextTick,如下:
setInterval(() => {
console.log('setInterval')
}, 100)
setImmediate(function immediate () {
setImmediate(immediate)
})
运行结果:每 100ms 打印一次 setInterval。
解释:process.nextTick 内执行 process.nextTick 仍然将 tick 函数注册到当前 microtask 的尾部,所以导致 microtask 永远执行不完; setImmediate 内执行 setImmediate 会将 immediate 函数注册到下一次 event loop 的 check 阶段,而不是当前正在执行的 check 阶段,所以给了 event loop 上其他 macrotask 执行的机会。
再看个例子:
setImmediate(() => {
console.log('setImmediate1')
setImmediate(() => {
console.log('setImmediate2')
})
process.nextTick(() => {
console.log('nextTick')
})
})
setImmediate(() => {
console.log('setImmediate3')
})
运行结果:
setImmediate1
setImmediate3
nextTick
setImmediate2
注意:并不是说 setImmediate 可以完全替代 process.nextTick,process.nextTick 在特定场景下还是无法被替代的,比如我们就想将一些操作放到最近的 microtask 里执行。
const promise = Promise.resolve()
.then(() => {
return promise
})
promise.catch(console.error)
运行结果:
TypeError: Chaining cycle detected for promise #<Promise>
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
at Function.Module.runMain (module.js:667:11)
at startup (bootstrap_node.js:187:16)
at bootstrap_node.js:607:3
解释:promise.then 类似于 process.nextTick,都会将回调函数注册到 microtask 阶段。上面代码会导致死循环,类似前面提到的:
process.nextTick(function tick () {
process.nextTick(tick)
})
再看个例子:
const promise = Promise.resolve()
promise.then(() => {
console.log('promise')
})
process.nextTick(() => {
console.log('nextTick')
})
运行结果:
nextTick
promise
解释:promise.then 虽然和 process.nextTick 一样,都将回调函数注册到 microtask,但优先级不一样。process.nextTick 的 microtask queue 总是优先于 promise 的 microtask queue 执行。
setTimeout(() => {
console.log(1)
}, 0)
new Promise((resolve, reject) => {
console.log(2)
for (let i = 0; i < 10000; i++) {
i === 9999 && resolve()
}
console.log(3)
}).then(() => {
console.log(4)
})
console.log(5)
运行结果:
2
3
5
4
1
解释:Promise 构造函数是同步执行的,所以先打印 2、3,然后打印 5,接下来 event loop 进入执行 microtask 阶段,执行 promise.then 的回调函数打印出 4,然后执行下一个 macrotask,恰好是 timer 阶段的 setTimeout 的回调函数,打印出 1。
setImmediate(() => {
console.log(1)
setTimeout(() => {
console.log(2)
}, 100)
setImmediate(() => {
console.log(3)
})
process.nextTick(() => {
console.log(4)
})
})
process.nextTick(() => {
console.log(5)
setTimeout(() => {
console.log(6)
}, 100)
setImmediate(() => {
console.log(7)
})
process.nextTick(() => {
console.log(8)
})
})
console.log(9)
运行结果:
9
5
8
1
7
4
3
6
2
process.nextTick、setTimeout 和 setImmediate 的组合,请读者自己推理吧。
如题,谢谢了
mongodb数据库文件删除重新新建文件夹打开数据库后,再开启博客,反馈如下信息,localhost:3000也进不去了, (node:11340) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 4): TypeError: Cannot read property ‘isAdmin’ of null 百度了这段报错的代码stackoverflow网站上的同仁弟兄也遇到同样问题,也还没解决 附上博客源代码,这段报错的愿意是指我在调用数据库中创建的的user.isAdmin(因为数据库中没有数据,不存在user,isAdmin也没有办法调用)而报的错嘛? 我上网搜索 如何在数据库中给数据库的表中的user通过代码添加新数据,但是网友给出的结果都是,在mongo命令行中,给数据库添加管理员用户之类的。。 https://github.com/Freen247/Nodejs-Express-Swig-Bookstrap-Mongodb.git
有一个这样的需求: 收到用户请求后,读取第三方服务器的视频文件返回数据流给用户使用 说明:第三方服务器内的视频或其他文件用户不能直接访问,我们可以访问,而我们自己的服务器上也不能保存视频内容,我们有点像鉴权功能 我们想采用这样的方式解决: 读取视频数据到内存后直接转发给用户使用,比如视频链接为sourceUrl,我们使用原生node写了如下原型
const http = require('http')
const request = require('request');
http.createServer(async(req,res)=>{
await request.get(sourceUrl).pipe(res);
}).listen(3000)
这样是样访问localhsot:3000是能展示视频的,也能播放。
但是如果使用koa2 编写如下代码就死活不行
const Koa = require('koa');
const request = require('request');
const app = new Koa();
app.use(async (ctx) =>{
await request.get(sourceUrl).pipe(ctx.res);
//或者 await request.get(sourceUrl).pipe(ctx.response);
})
app.listen(3000)
问题来了,koa2如何返回数据流的,应该把输入传到哪个输出对象上或是用什么方法。初学初学,大神莫怪。
另外,就上面说到的需求(数据不对外公开访问,鉴权服务器和最终使用者者不能存贮内容)有没有更多的方案,好用的工具推荐。
我们是英特吉姆Integem,是总部在美国硅谷的 Startup。Integem 专注于打造新一代的现实增强平台,创办人拥有多项相关专利。目前业务迅速发展,急需优秀人才加入。
职位诱惑:发展前景好, 挑战性工作, 带薪年假, 出国机会, 可以在任何地方工作。 待遇: 15K-25K; 14 月薪水。有原始股票,年终分红和奖励。
是否有些心动?小伙伴们赶快简历丢过来。
职位介绍 由于是远程分布式团队,要求成员有强大的自我驱动力和独立性,不需要持续监督,擅于沟通和配合。
任职要求 本科及以上学历,计算机及相关专业; 具有 Node, Express, HTML5, CSS3 等开发经验; 至少熟练使用 React、Vue 或 Angular 中的一种,有实际项目经验; MySQL 有较强的 Troubleshooting 能力,能独立研究和解决问题;
具备以下资格优先考虑: 你位于上海附近城市; 熟悉 Node.js 之外的一门后端语言,如Java/Python/Golang等; 对前沿的 Web 技术(如 HTML5、Node.js )有强烈兴趣,有良好的学习能力和强烈的进取心;
请把简历寄到:jtech_2013@hotmail.com
还是觉得 time 命令的输出结果太抽象了,
=>> time clj -i a.clj
1
real 0m1.258s
user 0m2.157s
sys 0m0.147s
有没有更好的命令可以用?
项目中需要画一个流程图,各位大神有在vue项目中画过流程图的吗?麻烦告知一下用啥画的呗,如果有demo可以参考就更好了 谢谢
半新人,没有这一块的经验,想问下大家这方面的实践是怎么样的?然后我想达到的效果就是前端传给我的markdown格式我保存后返回给前端,前端还能够正确显示,同时返回给前端时是从数据库原样返回还是需要处理一下,希望大家能够解答一下,谢谢! 我目前想的是直接存前端传过来的内容到文档的一个字段里,然后根据查询返回一样内容给前端,就不知道前端格式会不会炸,目前前端部分还未完成,无法验证我的想法。
本文以 vs code 编辑器为例(因为我也不知道其它的怎么配置),介绍如何配置 launch.json 文件,实现调试时的热加载。第一次在社区发帖,还有点小紧张。
众所周知(管你知不知道),我们可以用 nodemon实现 node.js 项目的的热加载,当你修改文件代码时,node服务会重新启动。但是用debug模式启动时,虽然我们可以在文件代码中直接打断点,但是修改代码后,如果想看到修改效果,只能关闭debug服务,重新启动,这就让人很不爽了。这里说下我的实现方法:
1.选择添加配置。
2.选择 Nodemon 安装程序(从这里我们也可以看到,似乎还可以通过npm 的方式进行设置,原谅我没弄明白怎么用)。
3.将 ‘runtimeExecutable’ 的配置修改为"${workspaceRoot}/nodemodules/.bin/nodemon" ,注意下面的"program",要修改为你的程序启动文件。
4.全部修改完后,选择nodemon,启动程序,就可以实现debug模式的热加载了。
最后,说下这里还存在的问题。当用debug模式启动后,关闭程序时,必须要在 ‘终端’ 面板中,按 ctrl + c 退出程序,点击红色的停止按钮不起作用。第二点,退出程序之后,vs code等一会会有个错误提示。如果想避免这个提示,可以先点击红色的停止按钮,然后在 ‘终端’ 面板中按 ctrl + c 。欢迎大家提供其它办法,或者指出正确的配置方式。
再补充一点,如果你想将配置保存下来,上传至gitHub中的项目,先将 .gitignore 中,忽略 .vscode文件夹的配置行,前面加个‘#’号注释起来,本地提交一次。然后取消注释,在文件的末尾增加一行 !.vscode/launch.json ,就可以了。别问我为什么要说,因为我在这遇到坑了,还查了半天。原因就是 git 本来没有跟踪 .vscode 文件夹下的文件。
(当我想尝试调试时的热加载时,在网上搜了很多,但是都没看明白,所以鼓捣好之后,还是分享出来,让大家节省点时间把)
Nest 是构建高效,可扩展的 Node.js Web 应用程序的框架。 它使用现代的 JavaScript 或 TypeScript(保留与纯 JavaScript 的兼容性),并结合 OOP(面向对象编程),FP(函数式编程)和FRP(函数响应式编程)的元素。在底层,Nest 使用了 Express,可以方便地使用各种可用的第三方插件。
Nest 真正解决了长期以来 Node.js 框架的架构问题,使得开发变得优雅,适合大型项目开发。
common:File(s)Interceptor不填充抛出的异常#437
核心:NestFactory.create()回报any
核心:ApplicationConfig在#434内使用ExternalContextCreator
常见:HttpException延伸Error
核心:使cors中间件可定制(enableCors(),{ cors })#457
微服务:RpcException扩展Error
websockets:WsException扩展Error
github: https://github.com/nestjs/nest
中文文档: https://docs.nestjs.cn
基于 nest 的模块化敏捷开发系统架构:https://github.com/notadd/notadd/tree/next
不太了解它的插件机制。
但是我猜应该是按顺序初始化的的吧,而不是并发。
然后不知道有个插件在请求个地址,半天没响应,就一直阻塞了其他插件的加载,每次打开vscode,都得等很久其他插件才出来。
就报这个错误,鬼知道是哪里错了…
而且在问题里面,也没有相关的错误记录
各位大佬有什么比较好的方法?逐一排除法?
最近新购入了一台在香港的服务器,并且利用godaddy购入了域名,所以决定搭建一个Blog.
目前,主流的搭建博客的方式主要有:
Hexo
Wordpress
不过本次我不打算采取上述的方式来搭建Blog。孙正华老哥之前写的iBlog是我非常喜欢的Blog风格样式(http://skysun.name),界面优雅美观,阅读流畅,带目录,支持响应式。所以我本次决定采用iBlog作为Blog的基础,后面再完善。
iBlog需要 nodejs , redis , mongoDB的环境,对于我这种重度的Docker依赖者,自然需要利用Docker搭建一个这样的环境。
在这里,我们需要利用docker-compose来帮助我们完成多个container的链接,所以需要写一份 docker-compose.yml 作为构建的基础。
直接使用官方的node镜像,并制定版本到8.9.0。
由于在Quick Start中指出,项目的前端依赖由bower提供,所以我们需要对官方的镜像上再安装一个全局的bower,为此,我们写一份 Dockerfile
FROM node:8.9.0
RUN npm install bower -g
接着,我们需要在docker-compose里面写入server部分的代码:
web:
build: ./node_env # 指向Dockerfile存放的位置
ports:
- 80:3000 # 端口映射
volumes:
- ./:/home/src # 文件夹映射
command: sh /home/src/bin/enterpoint.sh # 入口命令
下面是enterpoint.sh的代码,主要做的是安装依赖和启动server
cd /home/src
echo "begin to npm install"
npm install
echo "begin to bower install"
bower install --allow-root # 在docker环境下需要在允许root执行bower
node /home/src/bin/www
数据库主要是mongoDB 和 redis 两个数据库,我们在docker-compose中写入他们两个的配置:
redis:
image: redis:3.2.0
mongoDB:
image: tutum/mongodb:3.2
environment:
AUTH: 'no' # 由于Docker container之间会形成内网环境,所以在不暴露的端口情况,可以不设置验证。
为了避免更新container之后数据库的数据丢失,我们需要将container里面的数据绑定在本地的磁盘上,这就需要用到 docker-compose 语法中的volumes选项。
一般情况下,我们会自主选择一个文件夹进行绑定,就例如我在nodejs环境搭建的 yml 片段中写的那样。
- 本地的文件夹 : container中的文件夹
不过由于数据库的操作会涉及到权限问题,所以我们直接将创建文件夹的事情交给Docker来完成,只需要在 yml 文件中声明:
volumes:
db-volume:
然后选用即可。
version: '2' # volumes是docker-compose version 2 才支持的关键字,所以此处要声明使用version2
services:
web:
build: ./node_env
ports:
- 80:3000
volumes:
- ./:/home/src
command: sh /home/src/bin/enterpoint.sh
links:
- redis
- mongoDB
redis:
image: redis:3.2.0
mongoDB:
image: tutum/mongodb:3.2
volumes:
- db_volume:/data/db
environment:
AUTH: 'no'
volumes:
db_volume:
配置到docker-compose.yml之后,只需要在iBlog的文件夹下输入命令:
sudo docker-compose up -d
即可启动整个服务,iBlog就正常跑起来了。
项目完整地址:https://github.com/cctv1005s/iBlog2
iBlog2项目地址:https://github.com/eshengsky/iBlog2