做为一名姿(这不是错别字)深前端开发工程师,随着区块链热潮的兴起,本人在3年前就开始接触区块链,嗯。。。没错,就是从17年人人暴富的炒币开始。
区块链认知水平:去中心化,pow,pos,区块链,侧链,闪电链,公链,联盟链,私链(有没有这玩意儿?)
嗯。。。。这些关键字我都记住了。
以前没做过区块链的相关项目,大家都一样,从头捋
创建,加入,退出聊天室
聊天室加密
聊天信息加密
未读消息提示
消息上链费用提示
多端适配
用户加入房间信息提示
moji表情
1.新手任务,做一个基于MetaID的聊天室
2.获取在新手村生活需要的资源
Metaid:官网,协议规范(就是约定链上数据数据格式)
MetaID是基于Metanet的二级协议规范,具体去官网查看详细解释
Metaid Protocol:官网,已经创建并公开的基于MetaID的协议都在这
其实就是约定协议规范里部分的数据格式
ShowDB:官网,链上数据服务
提供基于metaid协议链上数据的查询服务(公司做了一份链上数据的拷贝,加快查询速度)
ShowMoney:官网,bsv钱包服务
从以上资源不难看出,MetaID是推广某种事实上的规范,方便链上数据互通,为推动区块链技术的发展做出贡献(伟大的愿景)
3.查阅完成新手任务需要的知识
以上所说的文档之类,看过,心里有数即可,遇到问题随时翻阅
基础知识了解了,现在来看做一个聊天室需要哪些功能:
1.用户登录/注册
2.创建聊天室 协议
3.加入聊天室 协议
4.退出聊天室 协议
5.聊天 协议
这五个操作都需要上链,上链就需要钱包创建交易,自建钱包交易对于我们初学者来说肯定不可行,难度太高,使用支持MetaID的钱包服务是首选,使用MetaID标准发行方的Showmoney创建账号,在创建账号的时候showmoney默认生成了metaID节点,节点包含用户信息。以后的登录就采用showmoney授权的方式进行。
既然有钱包服务,那创建交易之类的逻辑肯定得有配套得sdk啦,ShowMoney提供了metaidjs 来提供创建创建协议内容节点。协议节点之间得关联,sdk内部已经帮你处理好了(屏蔽细节加速开发)
2,3,4,5的操作需要定义协议节点内容,这部分工作的意义就在于约定链上数据的格式,方便应用间数据互通,其它个人或者机构可以方便的获取对应数据,消除信息孤岛。
以发一条聊天信息为例:
// 协议格式
const protocolData = {
groupID, "房间的nodeid"
timestamp,//创建消息的时间戳
nickName,//群聊的昵称,我默认使用创建MetaID对用的名字
content: cryptMessage(content, mataid.substring(0, 16)),//消息内容,
//我这是为了避免明文传输,把消息进行了加密,解密方式要包含在某种信息里,自行约定
contentType: "text/plain", //内容格式
encryption: "aes" //加密方式
};
// 插入节点
const node = {
nodeName: "simpleGroupChat",
metaIdTag: process.env.VUE_APP_IDtags,
brfcId: "80395c27b6eb",
encrypt: 0,
payCurrency: "usd",
path: "/Protocols/simpleGroupChat",
dataType: "application/json",
data: JSON.stringify(protocolData),
accessToken
};
metaIdJs.addProtocolNode(node)
数据上链已经完成,操作得成功与否取决于你得ShowMoney账户上是否有余额可以支付这次交易,根据数据内容大小每次上链所需费用不同,上链有最低消费-_-!,即使你发了条空白消息也有那么多数据不是
创建房间需要一条交易,加入房间需要一条交易,退出房间需要一条交易,聊天也需要一条交易,嗯。。。。。。。万般操作皆交易!!!
现在开始组织数据,感受一下
以获取已加入房间用户为例,上代码:
async getGroupUser(groupID) {
// 根据最新退出房间时间判断是否是有效用户
let cache = {
};
let userlist = [];
const joinRes = await getProtocolData([["SimpleGroupJoin", {
groupID }]], {
}, 0, 1000);
const leaveRes = await getProtocolData([["SimpleGroupLeave", {
groupID }]], {
}, 0, 1000);
const creater = await getProtocolData("SimpleGroupCreate", {
metanetId: groupID });
for (let element of joinRes.data) {
if (cache[element.rootTxId]) {
continue;
}
cache[element.rootTxId] = element.timestamp;
}
if (!cache[creater.data[0].rootTxId]) {
cache[creater.data[0].rootTxId] = creater.data[0].timestamp;
}
// 记录最后一次用户加入房间的时间点
if (!this.groupGather[groupID]) {
this.groupGather[groupID] = {
};
}
if (joinRes.data[0] && joinRes.data[0].timestamp) {
this.groupGather[groupID].lastJoinTime = joinRes.data[0].timestamp;
} else {
this.groupGather[groupID].lastJoinTime = creater.data[0].timestamp;
}
// 记录最后一次用户离开的时间点
if (leaveRes.data[0] && leaveRes.data[0].timestamp) {
this.groupGather[groupID].lastLeaveTime = leaveRes.data[0].timestamp;
}
for (let element of leaveRes.data) {
if (element.timestamp > cache[element.rootTxId]) {
delete cache[element.rootTxId];
}
}
let users = await batchGetUserInfo(Object.keys(cache));
for (let item of users.data) {
if (!this.userGather[item.metaId]) {
if (!item.avatarTxId) {
item.headUrl = process.env.VUE_APP_DEFAULT_AVATOR;
} else {
item.headUrl = process.env.VUE_APP_IMG_API + "metafile/" + item.avatarTxId;
}
this.$store.commit("addNewUser", {
id: item.metaId,
userInfo: item
});
}
if (this.groupGather[groupID].userlist.indexOf(item.metaId) < 0) {
userlist.push(item.metaId);
}
}
this.groupGather[groupID].userlist.push(...userlist);
}
/**
*
* @param {String | Array} protocols 协议列表
* demo:['ShowText',['metanode',{isPrivate:0}],['metanote',{isPrivate:0}]] || 'ShowText'
* @param {Number} skip 偏移量
* @param {NUmber} limit 数据限制量,默认10,负数为无限制
* @param {Number} timestamp 排序,-1为倒序,1为顺序,默认倒序-1
*/
export function getProtocolData(protocols, config, skip = 0, limit = 10, timestamp = -1) {
let protocol_list = [];
let fin = {
};
if (!config) {
config = {
};
}
if (Object.prototype.toString.call(config) !== "[object Object]") {
throw new Error("second param must be object {}");
}
if (typeof protocols !== "string" && !Array.isArray(protocols)) {
throw new Error("first param must be Array or string");
}
if (typeof protocols === "string") {
fin = {
parentNodeName: protocols
};
} else {
protocols.forEach(val => {
if (typeof val !== "string" && !Array.isArray(val)) {
throw new Error("item must be Array or string");
}
if (Array.isArray(val)) {
let [parentNodeName, screen] = val;
let obj = {
};
if (Object.prototype.toString.call(screen) !== "[object Object]") {
throw new Error("if protocol item is array,the second item must be an object");
} else {
obj["parentNodeName"] = parentNodeName;
for (let key in screen) {
obj[`data.${
key}`] = screen[key];
}
protocol_list.push(obj);
}
} else {
protocol_list.push({
parentNodeName: val });
}
});
find = {
$and: [
{
$or: [...protocol_list]
}
]
};
}
const query = {
find: {
...find,
metaId: process.env.VUE_APP_IDtags,
...config
// isValid: true,
},
sort: {
timestamp
},
skip,
limit
};
return Api.ajaxGetUseBase64("/showMANDB/api/v1/query/queryFindMetaData/", query);
}
每次切换房间,需要先获取创建当前聊天室协议节点内容、加入当前聊天室协议节点列表,离开当前聊天室协议节点列表,组织这些数据的逻辑关系,判断是否是有效用户,开始你的表演。。。。。
完成聊天室,修复bug,差不多整体用了一个月时间。
把聊天室代码整理一下就会开源,有问题在公共聊天室发言我看到后会回复
看资料花了一周
知道些概念就可以
vue学习用了两天
主流的前端框架,从应用的角度来说我们只需要关注基础语法,路由,状态管理,数据传递四个主要方面,其它高级特性知道就行,需要或者闲的时候再去研究,我们不需要写出狂拽酷炫吊炸天的代码,大部分代码的生命周期都不长,完成需求,项目死了你的代码价值就不大,项目好了自会有资源来完善更改,唯用主义(手动笑脸)
未读消息提示仅能提示以应用登录为开始时间后续的未读聊天数,你想要存储的每种类型的数据都需要上链,如果加上未读开始时间记录,上链费用会大大增加 ,需要取舍
你可以看到当前房间的所有消息记录,不管你什么时间加入房间
还是要知道点数据库查询语句的,查询条件需要使用mongodb语法构建
关联型数据是常见业务场景,希望MetaID推行方丰富请求数据处理逻辑,sdk提供方便得关联数据查询方法
此限制涉及到共识,安全方面,是bsv本身的限制,未来可能会去掉