forthxu 发布的文章

如何使用Mysql正确的处理财务数据

财务数据相比于普通的互联网应用数据,对数据的一致性有更高的要求。因为涉及到用户金钱的流动,出现问题就意味金钱和声誉上的损失。在用 Mysql 处理财务数据时,我认为应该遵循以下原则:

  1. 使用 DECIMAL 数据类型存储金额。因为浮点数精度是有限的,并且无法精确的表示一些数字。应用程序也应该使用 Decimal 函数库来进行金额的加减乘除的运算,比如 Python 的 decimal 模块,C++ 的 boost Multiprecision 库。
  2. 使用事务来更新数据库。涉及到数据库多个记录更新时,事务能够做到要么全部成功,要么全部失败,这保证了数据的一致性。Mysql 使用事务需要 InnoDB 引擎。
  3. 更新数据库时使用悲观锁。更新数据前使用 SELECT …  FOR UPDATE; 来查询,这样防止并发的请求读到脏数据,导致数据错乱。虽然加锁会影响性能,但为了数据的一致性也是值得的。
  4. 记录资金变化的流水日志。不能简单的只记录用户的金额,还要记录每笔资金的来龙去脉,包括变化的大小、时间、业务、更新前金额、更新后金额、备注等,另外还有记录业务ID防止重复更新金额。这样在对账的时候才能有理有据。

下面以一个完整的示例来说明如何设计一个完备的记账系统用来记录人民币余额,这里精度只需要2位小数就可以了。

假设已经有一个用户表,每个用户有一个唯一ID。我们需要创建两张表,一张余额表,一张流水表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE TABLE `balance` (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` int NOT NULL,
  `item` varchar(10) NOT NULL,
  `balance` decimal(20,2) NOT NULL
) ENGINE=InnoDB;
 
CREATE TABLE `history` (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `user_id` int NOT NULL,
  `item` varchar(10) NOT NULL,
  `amount` decimal(20,2) NOT NULL,
  `befor` decimal(20,2) NOT NULL,
  `after` decimal(20,2) NOT NULL,
  `business` varchar(30) NOT NULL,
  `business_id` varchar(100) NOT NULL,
  `detail` text
) ENGINE=InnoDB;

为了加快查询速度,另外为了有效利用 InnoDB 的行级锁,我们需要给两张表加上联合索引。另外,我们需要保证流水记录中 user_id, item, business, business_id 的组合是唯一的,避免重复更新数据。

1
2
3
4
5
ALTER TABLE balance ADD INDEX `user_item_idx` (`user_id`, `item`);
 
ALTER TABLE history ADD INDEX `user_item_idx` (`user_id`, `item`);
 
ALTER TABLE history ADD UNIQUE update_unique (user_id, item, business, business_id);

假设这时候用户 ID 为 1 的用户充值了 100 元人民币,我们需要把用户的人民币余额加上 100 元,我们要如何处理呢?

这里我们还需要一个充值表用来保存用户的充值记录,同时,我们再创建一个提现表来表示用户提现记录,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE `deposit` (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` int NOT NULL,
  `item` varchar(10) NOT NULL,
  `amount` decimal(20,2) NOT NULL
) ENGINE=InnoDB;
 
CREATE TABLE `withdraw` (
  `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `user_id` int NOT NULL,
  `item` varchar(10) NOT NULL,
  `amount` decimal(20,2) NOT NULL
) ENGINE=InnoDB;

首先,开始一个事务,创建充值记录:

1
2
3
START TRANSACTION;
 
INSERT INTO deposit VALUES (NULL, 1, 'CNY', '100')

插入成功后,我们可以使用 MYSQL 的 API 获取到上次插入后生成的自增 ID 的值,假设这里也为 1. 然后我们需要从数据库查询当前余额。查询的时候注意要使用 FOR UPDATE 来锁住该记录,避免其它并发的请求读到脏数据。即使当前该记录不存在,在事务提交之前,其它读请求仍然被阻塞读不到数据的。

1
SELECT id, balance FROM balance where user_id = 1 and item = 'CNY' FOR UPDATE;

由于用户是新注册的,查询到的数据为空,所以我们创建新的记录:

1
INSERT INTO balance VALUES (NULL, 1, 'CNY', '100');

然后我们要在流水表中记录下这次的变更操作:

1
INSERT INTO history VALUES (NULL, NULL, 1, 'CNY', '100', '0', '100', 'deposit', '1', '');

最后提交事务:

1
COMMIT;

事务提交后,所有的数据都写入到数据库中了,中间如果有异常发生,则执行

1
ROLLBACK;

来放弃所有的变更。

假设过了一段时间用户要求提现 50 元,操作流程如下:

1
2
3
START TRANSACTION;
 
SELECT id, balance FROM balance where user_id = 1 and item = 'CNY' FOR UPDATE;

假设读出的 id 也为 1. 由于用户之前充值了 100 元还没有使用,所以余额是 100,大于 50 满足提现条件。如果不满足的话需要执行 ROLLBACK. 然后:

1
2
3
4
5
6
7
INSERT INTO withdraw VALUES (NULL, 1, 'CNY', '50');
 
UPDATE balance set balance = '50' WHERE id = 1;
 
INSERT INTO history VALUES (NULL, NULL, 1, 'CNY', '-50', '100', '50', 'withdraw', '1', '');
 
COMMIT;

这就完整的实现了整个充值和提现的流程。

当然,这里示例的充值和提现是最简化的流程,实际业务中,充值和提现往往涉及到多种状态的流转。并且在提现中,用户发起提现和实际进行转账不可能是同时进行的,中间可能会取消操作,直接扣除资金不太妥当,更妥当的做法是把待提现的资金冻结起来,冻结操作其实就是创建一个新的 item: CNY_FREEZE, 在 CNY 上扣减,然后在 CNY_FREEZE 上增加来实现。转账后再从 CNY_FREEZE 中扣除。如果取消操作则取消冻结,从 CNY_FREEZE 扣减,增加到 CNY 上面,这样整个流程在流水日志上都有体现。

https://web.archive.org/web/20170617125020/http://blog.haipo.me/?p=1266

https://zhuanlan.zhihu.com/p/143866444

投屏技术

AirPlay:应用于苹果设备,如:iPhone、iPad、Mac,使用 AirPlay 协议传输,无须其他软件辅助即可连接投屏;
DLNA:应用于 Windows 设备,一般还需要借助第三方软件完成投屏,也可应用于支持 DLNA 协议的 Android 设备,一般只能投放视频和图片;
Miracast:应用于 Android 设备,设备如果支持该功能,通常在设置中显示为 “无线显示” 或 “无线投屏”、“Miracast” 等标识。

协议
实现
项目

mysql根据逗号隔开的字段数据把一行数据拆分成多行数据

# 查找出被逗号分隔字段需要拆分的最大数量
select max((LENGTH(逗号分隔的字段)-LENGTH(REPLACE(逗号分隔的字段, ',', ''))+1)) from 处理表 where 条件;

# 创建一张临时表用于联合查询,方便把处理表单行记录分隔为多行
CREATE TEMPORARY TABLE incre_table (
    `id` int NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (`id`)
);
insert into incre_table values (1);
insert into incre_table values (2);
insert into incre_table values (3);
insert into incre_table values (4);
insert into incre_table values (5);
insert into incre_table values (6);
insert into incre_table values (7);
insert into incre_table values (8);
insert into incre_table values (9);
insert into incre_table values (10);
# ... 大于 需要拆分的最大数量

# 关键在于连表查询 ON b.id <= 逗号分隔的数量
SELECT
    a.id,
    substring_index(
        substring_index(a.逗号分隔的字段, ',', b.id),
        ',', - 1
    )
FROM
    处理表 a
RIGHT JOIN incre_table b ON b.id <= (
    LENGTH(a.逗号分隔的字段) - LENGTH(REPLACE (a.逗号分隔的字段, ',', '')) + 1
)
WHERE
    a.条件;

php strtotime相对时间-1month的使用问题

历史代码的一个问题

➜  ~ date
Tue Mar 31 17:37:20 CST 2020
➜  ~ php -r "echo date('Y-m', strtotime('-1 month'));"
2020-03                                                                                                                                                                                                        
➜  ~ php -r "echo date('Y-m', strtotime('+1 month'));"
2020-05

当前日期是2020年03月31日,直观感觉减一个月应该是输出2020-02,加一个月应该是输出2020-04。

➜  ~ cal 2 2020
   February 2020
Su Mo Tu We Th Fr Sa
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29

➜  ~ cal 3 2020
     March 2020
Su Mo Tu We Th Fr Sa
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31


➜  ~ cal 4 2020
     April 2020
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

其实是程序跟人理解不同造成的:
2020年03月31日,减一个月等于 2020年02月31日,系统没有2020年02月31日,往前加就等于2020年03月02日
2020年03月31日,加一个月等于 2020年04月31日,系统没有2020年04月31日,往前加就等于2020年05月01日

#php5.3之前可以采用:

➜  ~ php -r "echo date('Y-m-d', strtotime(date('Ym01').' -1 month'));"
2020-02-01  

     

#php5.3之后可以采用:

    ➜  ~ php -r "echo date('Y-m-d', strtotime('first day of -1 month'));"
2020-02-01                                                                                                                                                                                               ➜  ~ php -r "echo date('Y-m-d', strtotime('last day of -1 month'));"
2020-02-29

团队协作的原则

原则

本文档规范作为团队的基本原则,在无法做出决定,或不知道某件事情是否该做时,可以以本文档作为参考指导。

零成本质疑

任何人、任何方案都是可以被质疑的,包括本文档,我们提倡任何人,不管能力高低,经验多寡都可以对自己不理解的事物提出质疑,被质疑者应客观沟通,以解决问题为目标,不得给质疑者形成任何心理压力。任何非恶意的疑问,质疑都是被鼓励的。

演进迭代

我们相信任何事物都可以通过演进迭代不断优化,包括本文档。这意味着我们在开始着手做某件事时,不需要一下子做到完美,可以先开始然后通过演进迭代一步步趋向目标;这也意味着,我们不应该对已经做过的事情轻易满足,而是要持续不断的思考其演进方向,迭代优化。

科学思维

我们提倡任何知识、结论都要至少经过“理论猜想”、“实验验证”、“推理预测”三个过程。不盲目接受网络、书本上的信息,更不奉为金科玉律。提出观点时,首先通过自己的理论基础提出猜想,并设计实验进行验证,根据验证过的观点做出推论和预测,并时刻以后续发展结果检验预测是否正确,观点是否完善。

等价交换

我们不允许损害他人的利益来换取自己的利益,同样我们也不提倡损伤自己的利益来满足他人的利益。我们提倡从长远时间来看,每个人的付出和收益是不断趋于等价的。

不做评价

跟做评价相比,我们更倾向讲事实讲道理,我们不说某个事情“很容易”,我们只说经过1、2、3等步骤就可以做好。我们不说一个问题“很傻”,我们只说这个问题的答案是什么以及如何得到这个答案。我们不说一个人“不负责任”,我们只说一件事原定要做到1、2、3,而现在只做到了1、3,那么我们该如何一起解决2的问题。

BTC和USDT(OMNI)转账协议分析

对照 《精通比特币》的交易章节分析转账协议

参考:
比特币脚本
在比特币上发代币的基本原理——omni协议发代币的通俗解释
官方的开发文档 Bitcoin的WIKI建议这两份英文文档

USDT只是是OMNI协议上面的一种代币,编号31,而OMNI协议是基于BTC网络保证数据真实可靠,OMNI节点保存了一份账号表,OMNI协议通过OP_RETURN来操作账号表,要修改账号表需要证明操作权限,因而一般附带了BTC转账的过程,也会产生BTC手续费。

BTC转账协议分析

--------------------
交易:a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d
raw: https://btc.com/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d.rawhex

01 加锁数量

输出脚本:
0010a5d4e8000000 小头,八字节,转出数量 10000.00000000
19 脚本长度

76 OP_DUP
a9 OP_HASH160
14 20字节
46af3fb481837fadbb421727f9959c2d32a36829 允许解锁这笔交易的公钥对应的公钥hash
88 OP_EQUALVERIFY
ac OP_CHECKSIG

00000000 LockTime


--------------------
--------------------
交易:cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79
raw:https://btc.com/cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79.rawhex
01000000018dd4f5fbd5e980fc02f35c6ce145935b11e284605bf599a13c6d415db55d07a1000000008b4830450221009908144ca6539e09512b9295c8a27050d478fbb96f8addbc3d075544dc41328702201aa528be2b907d316d2da068dd9eb1e23243d97e444d59290d2fddf25269ee0e0141042e930f39ba62c6534ee98ed20ca98959d34aa9e057cda01cfd422c6bab3667b76426529382c23f42b9b08d7832d4fee1d6b437a8526e59667ce9c4e9dcebcabbffffffff0200719a81860000001976a914df1bd49a6c9e34dfa8631f2c54cf39986027501b88ac009f0a5362000000434104cd5e9726e6afeae357b1806be25a4c3d3811775835d235417ea746b7db9eeab33cf01674b944c64561ce3388fa1abd0fa88b06c44ce81e2234aa70fe578d455dac00000000

--------------------
01000000 版本

--------------------
01 输入脚本数量

--------------------
解锁输入脚本一:P2PKH(Pay-to-Public-Key-Hash)

8dd4f5fbd5e980fc02f35c6ce145935b11e284605bf599a13c6d415db55d07a1 引用的交易哈希,32字节,小头
00 引用的交易输出索引
0000008b 脚本长度139=1长度+71签名+1类型+1长度+1版本+64公钥

签名:
48 签名和签名类型的长度
30450221009908144ca6539e09512b9295c8a27050d478fbb96f8addbc3d075544dc41328702201aa528be2b907d316d2da068dd9eb1e23243d97e444d59290d2fddf25269ee0e 签名
01 签名类型

公钥:Hash160(46af3fb481837fadbb421727f9959c2d32a36829) Address(17SkEw2md5avVNyYgj6RiXuQKNwkXaxFyQ)
41 公钥长度
04 公钥版本
2e930f39ba62c6534ee98ed20ca98959d34aa9e057cda01cfd422c6bab3667b7 公钥X
6426529382c23f42b9b08d7832d4fee1d6b437a8526e59667ce9c4e9dcebcabb 公钥Y

ffffffff 序列号

--------------------
02 输出脚本数量

--------------------
加锁输出脚本一:P2PKH(Pay-to-Public-Key-Hash)
00719a8186000000 小头,八字节,转出数量 5777.00000000
19 脚本长度
76 DUP
a9 HASH160
14 PUSHDATA(20)
df1bd49a6c9e34dfa8631f2c54cf39986027501b
88 EQUALVERIFY
ac CHECKSIG

--------------------
加锁输出脚本二:P2PK(Pay-to-Public-Key)
009f0a5362000000 小头,八字节,转出数量 4223.00000000
43 脚本长度
41 PUSHDATA(65)
04 cd5e9726e6afeae357b1806be25a4c3d3811775835d235417ea746b7db9eeab3 3cf01674b944c64561ce3388fa1abd0fa88b06c44ce81e2234aa70fe578d455d 版本+公钥XY
ac CHECKSIG

--------------------
00000000 LockTime

OMNI协议转账分析一

--------------------------------------------------------------------------
交易:
https://btc.com/9bc1e10acf8f166613500d8a210dc4d2be018dc47730f268b351f4cb89fda86e.rawhex
01000000016749664960e8cfa57abd3dce29e73c0b6b79626c0b6dd703309d7497da8939d0010000006b483045022100ad0ece657f9320045be9740abadf7cf8948ba9fd22c1ec3d48adf13a0753105102204d588f0cdf7f5262df3cfb3fb18635114c044e062d2f898a08c02171b390debb012103da5bac7b36d5aa38f531c6b9601e21bb598a4b6716ebed38b009a55dabde9440feffffff030000000000000000166a146f6d6e69000000000000001f0000048c27395000f52e7a00000000001976a914a25dec4d0011064ef106a983c39c7a540699f22088ac1c0200000000000017a914ebc6513b2e2aa73207663f613cd89a479e5f24d787f9480800

01 版本
00000001 解锁脚本数量

--------------------
解锁输入脚本一:
6749664960e8cfa57abd3dce29e73c0b6b79626c0b6dd703309d7497da8939d0 引用的交易哈希,32字节,小头
01 引用的交易输出索引
0000006b 脚本长度
48 签名长度72
3045022100ad0ece657f9320045be9740abadf7cf8948ba9fd22c1ec3d48adf13a0753105102204d588f0cdf7f5262df3cfb3fb18635114c044e062d2f898a08c02171b390debb01
21 公钥长度33
03da5bac7b36d5aa38f531c6b9601e21bb598a4b6716ebed38b009a55dabde9440
feffffff

03 输出脚本数量
--------------------
加锁输出脚本一:
0000000000000000 小头,八字节,转出数量 0
16 锁定脚本长度22
6a OP_RETURN 标记交易无效
14 长度20
6f6d6e69
000000000000001f 31USDT
0000048c27395000 50000.00000000
--------------------
加锁输出脚本二:
f52e7a0000000000 小头,八字节,转出数量 8007413
19 锁定脚本长度25
76 OP_DUP
a9 OP_HASH160
14 20字节
a25dec4d0011064ef106a983c39c7a540699f220 地址 1FoWyxwPXuj4C6abqwhjDWdz6D4PZgYRjA
88 OP_EQUALVERIFY
ac OP_CHECKSIG
--------------------
加锁输出脚本三:
1c02000000000000 小头,八字节,转出数量 540
17 锁定脚本长度23
a9 OP_HASH160
14 20字节
ebc6513b2e2aa73207663f613cd89a479e5f24d7
87 EQUAL
--------------------
f9480800 LockTime




--------------------------------------------------------------------------
交易:5bf6c71747fdfd725acc9216c1a2d633d8ae47d6a842c8f83e3b0c9e600410f2
https://btc.com/5bf6c71747fdfd725acc9216c1a2d633d8ae47d6a842c8f83e3b0c9e600410f2.rawhex
01000000016ea8fd89cbf451b368f23077c48d01bed2c40d218a0d501366168fcf0ae1c19b010000006a4730440220301a98b76c2e16640b152aba20b396c03ef3dbd13873b492489add044e2e9c58022048cfea2d818d4a632396795f1cec12a3e470da17b9d1ea6c8d7342411682721e012103da5bac7b36d5aa38f531c6b9601e21bb598a4b6716ebed38b009a55dabde9440feffffff030000000000000000166a146f6d6e69000000000000001f0000000ba43b7400291c7a00000000001976a914a25dec4d0011064ef106a983c39c7a540699f22088ac22020000000000001976a91404a94b25d962e443e95304d2de5a1bf957b9807a88acf9480800

输入8007413=8003147(输出一8002601+输出二546)+手续费4266 

01 版本
00000001 解锁脚本数量

--------------------
解锁输入脚本一:
6ea8fd89cbf451b368f23077c48d01bed2c40d218a0d501366168fcf0ae1c19b 引用的交易哈希,32字节,小头 9bc1e10acf8f166613500d8a210dc4d2be018dc47730f268b351f4cb89fda86e
01 引用的交易输出索引
0000006a 脚本长度
47 签名长度71
30440220301a98b76c2e16640b152aba20b396c03ef3dbd13873b492489add044e2e9c58022048cfea2d818d4a632396795f1cec12a3e470da17b9d1ea6c8d7342411682721e01
21 公钥长度33
03da5bac7b36d5aa38f531c6b9601e21bb598a4b6716ebed38b009a55dabde9440 地址 1FoWyxwPXuj4C6abqwhjDWdz6D4PZgYRjA
feffffff


03 输出脚本数量
--------------------
加锁输出脚本一:
0000000000000000 小头,八字节,转出数量 0
16 锁定脚本长度22
6a OP_RETURN 标记交易无效
14 长度20
6f6d6e69 omni的16进制表示
000000000000001f 31USDT
0000000ba43b7400 500.00000000
--------------------
加锁输出脚本二:
291c7a0000000000 小头,八字节,转出数量 8002601
19 锁定脚本长度25
76 OP_DUP
a9 OP_HASH160
14 20字节
a25dec4d0011064ef106a983c39c7a540699f220
88 OP_EQUALVERIFY
ac OP_CHECKSIG
--------------------
加锁输出脚本三:
2202000000000000 小头,八字节,转出数量 546
19 锁定脚本长度25
76 OP_DUP
a9 OP_HASH160
1404a94b25d962e443e95304d2de5a1bf957b9807a
88 OP_EQUALVERIFY
ac OP_CHECKSIG
--------------------
f9480800 LockTime

OMNI协议转账分析二

https://www.omniexplorer.info/tx/f404e033d9a8ef815db75d5056eab9f1e09d3865c53afe5ce02884bfb4247047

BTC 0.0093566(0.00100000+0.00835660)=0.0087566(875114+546)+0.0006


01000000

02 输入数量

https://btc.com/c23495f6e7ba24705d43583edd69ff25a354c18e69fd8514c07ec6f47cb995de from 1K6JtSvrHtyFmxdtGZyZEF7ydytTGqasNc
de95b97cf4c67ec01485fd698ec154a325ff69dd3e58435d7024bae7f69534c2 大头 c23495f6e7ba24705d43583edd69ff25a354c18e69fd8514c07ec6f47cb995de
00
0000006a
47
304402200a6fef16882db2f3e07356b619121d74cf0bd42872cba57430e901b4252f7c8102202edcaa90b278d568faa55a6a9c523ff0cd09e0c916e75ea7baf4690d5747789c01
21
0382df61bad93a1211ceac5c78fd273d65e405a7e148e068ced3e40bf87cf71721
ffffffff

https://btc.com/ee1673b09b0edaf7aaf8eb0bfd53a5a2757eb3e342e731bfc960b869aa0ab6b3 from 1K6JtSvrHtyFmxdtGZyZEF7ydytTGqasNc
b3b60aaa69b860c9bf31e742e3b37e75a2a553fd0bebf8aaf7da0e9bb07316ee 大头 ee1673b09b0edaf7aaf8eb0bfd53a5a2757eb3e342e731bfc960b869aa0ab6b3
02
0000006b
48
3045022100ae11d3c92a501496381aa7eaf10ef458f4aabdd3075233ae70d9d32f6b83d812022053aa4171e3d2b58465dde42d07ba4e5f74948b2cdb01a67dfdac4e4eb24b684901
21
0382df61bad93a1211ceac5c78fd273d65e405a7e148e068ced3e40bf87cf71721
ffffffff

03 输出数量

6a5a0d0000000000 875114
19
76
a9
14
c6734676a08e3c6438bd95fa62c57939c988a17b 1K6JtSvrHtyFmxdtGZyZEF7ydytTGqasNc
88
ac

0000000000000000
16
6a
14
6f6d6e69
0000000000000002
0000000000989680

2202000000000000 546
19
76
a9
14
ee692ea81da1b12d3dd8f53fd504865c9d843f52 1Njbpr7EkLA1R8ag8bjRN7oks7nv5wUn3o
88
ac

00000000

区块链学习知识模块

1.Bitcoin,Ethereum,HyperLedger Fabric的区块链相关机制与原理以及理念;
2.共识算法,主要有PoW,PoS,DPoS,PBFT,Paxos,Raft等;
3.分布式架构,包含网络编程、分布式算法、加密签名、数据存储、p2p并发;
4.HTTP/2协议,gRPC框架,protobuf的开发等;
5.NoSQL数据库的原理与使用,尤其是KV型数据库:LevelDB,RocksDB等,monogodb用到了区块链浏览器;
6.golang,C++,python等主流区块链系统开发语言;
7.钱包管理公钥私钥、代币信息获取、冷热钱包处理;
8.交易所系统(市场行情、钱包账号、交易撮合)( http://w.lianmishu.com );
9.数据采集处理( http://www.lianzhuli.com http://www.lianmishu.com http://www.0123456789.com http://www.0123456789.biz https://www.feixiaohao.com/ )
10.区块链浏览器( https://www.yitaifang.com )
11.linux操作系统,Docker容器技术的原理,部署和使用优化。

js代码特殊的加载机制

利用图片加载js代码
https://imququ.com/post/code2png-encoder.html

利用零宽度字符隐藏代码
http://ucren.com/blog/archives/549
https://www.cnblogs.com/luanshixia/archive/2015/05/07/ghost_zwsp.html

利用图片加载js代码引用非https链接
https://imququ.com/post/use-image-to-transfer-data.html

mysql innoDB使用聚族索引

https://dev.mysql.com/doc/refman/5.6/en/innodb-index-types.html
http://umumble.com/blogs/mysql/mysql-(innodb)-clustered-and-non_clustered-indexes-/
http://blog.csdn.net/jhgdike/article/details/60579883
http://blog.csdn.net/caomiao2006/article/details/52140953
http://www.cnblogs.com/xxmysql/p/5874803.html

使用swoole和redis做股票和区块链服务

PHP 的redis扩展是阻塞式 IO ,使用订阅/发布模式时,会导致整个进程进入阻塞。因此必须使用Swoole\Redis异步客户端来实现。

$server = new swoole_websocket_server("0.0.0.0", 9501);

$server->on('workerStart', function ($server, $workerId) {
    $client = new swoole_redis;
    $client->on('message', function (swoole_redis $client, $result) use ($server) {
        if ($result[0] == 'message') {
            foreach($server->connections as $fd) {
                $server->push($fd, $result[1]);
            }
        }
    });
    $client->connect('127.0.0.1', 6379, function (swoole_redis $client, $result) {
        $client->subscribe('kline1min');
    });
});

$server->on('open', function ($server, $request) {

});

$server->on('message', function (swoole_websocket_server $server, $frame) {
    $server->push($frame->fd, "hello");
});

$server->on('close', function ($serv, $fd) {

});

$server->start();
  • 在进程启动(onWorkerStart)时创建了Swoole\Redis客户端,连接到Redis服务器
  • 连接成功后,订阅msg_0主题的消息
  • 当有新的message时,Swoole\Redis会触发onMessage事件回调
  • 在这个回调函数中使用$server->connections遍历服务器所有的连接,发送消息