Compare commits
899 Commits
2.2.0
...
3.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f33956f585 | ||
|
|
5f0de0d513 | ||
|
|
9c97d4a41c | ||
|
|
cd4b7df1d7 | ||
|
|
926b77a429 | ||
|
|
7c30ba6b03 | ||
|
|
cd85bd087c | ||
|
|
53ae4b1ed7 | ||
|
|
e526a94520 | ||
|
|
d708485918 | ||
|
|
c73a3e85aa | ||
|
|
61ddf9515c | ||
|
|
5b98480950 | ||
|
|
25fa833495 | ||
|
|
4b2af374f5 | ||
|
|
6238224e8c | ||
|
|
fe890025c5 | ||
|
|
78612b6e58 | ||
|
|
4957731760 | ||
|
|
fba39219cf | ||
|
|
b5329aef7c | ||
|
|
4d6fc097c2 | ||
|
|
863c2d8c7a | ||
|
|
3596d6077d | ||
|
|
ed40799fe3 | ||
|
|
32afd38ad4 | ||
|
|
e0049fc7da | ||
|
|
f8c4bc01bc | ||
|
|
0c13cded89 | ||
|
|
690f8aed10 | ||
|
|
5d473eddcf | ||
|
|
2d6896adde | ||
|
|
8f8002608e | ||
|
|
9c3e6ce8bd | ||
|
|
3a04c98f62 | ||
|
|
e8c8bd0ba7 | ||
|
|
939b02fdbc | ||
|
|
cd52bfbfa5 | ||
|
|
c78b7fa2b0 | ||
|
|
e5b3509ebf | ||
|
|
d45d34430d | ||
|
|
26e172680d | ||
|
|
429eae64c6 | ||
|
|
19aab968ae | ||
|
|
7d9a8a5ee1 | ||
|
|
1abdfc761d | ||
|
|
43634974fc | ||
|
|
5893d18149 | ||
|
|
a145814923 | ||
|
|
366b49ca82 | ||
|
|
dbbc6f75d8 | ||
|
|
316c29628a | ||
|
|
11cd4ef56e | ||
|
|
fe8b38f968 | ||
|
|
8477bf153f | ||
|
|
2f94f5258d | ||
|
|
eb05cea443 | ||
|
|
af26086019 | ||
|
|
a0950a4be3 | ||
|
|
9f970857ae | ||
|
|
1a1fc8c558 | ||
|
|
fd7b9928ba | ||
|
|
da1bbee24c | ||
|
|
3086bcdf6d | ||
|
|
50d6e75e71 | ||
|
|
413d783d53 | ||
|
|
85240c753b | ||
|
|
c2c5433220 | ||
|
|
5cd28964e6 | ||
|
|
d87b7dc0f7 | ||
|
|
1d27091558 | ||
|
|
3dafbf4fbd | ||
|
|
3a78f5e2b1 | ||
|
|
3f26648a3c | ||
|
|
a62e950870 | ||
|
|
aee7fa332a | ||
|
|
c1a0fae6e6 | ||
|
|
ed31edcc5c | ||
|
|
4aea90cb39 | ||
|
|
a58c3aadab | ||
|
|
f2fb40b67c | ||
|
|
18df76f650 | ||
|
|
dce126136b | ||
|
|
d32f7b0ff8 | ||
|
|
d55362e190 | ||
|
|
6bcedea720 | ||
|
|
ccc801e6cb | ||
|
|
a2404482a3 | ||
|
|
4c41dd09d2 | ||
|
|
ab83194bbe | ||
|
|
6013571267 | ||
|
|
2636bc2e35 | ||
|
|
a2b013402b | ||
|
|
206f041d29 | ||
|
|
ce885a7a61 | ||
|
|
b0d0d5eba9 | ||
|
|
d45c4e24fd | ||
|
|
49fffcc464 | ||
|
|
e89d5ad1ac | ||
|
|
11aed8c6be | ||
|
|
46d2f895e2 | ||
|
|
ed6b65eb88 | ||
|
|
8efb63a334 | ||
|
|
13b5c44627 | ||
|
|
2a3c953c36 | ||
|
|
fd8b3721ae | ||
|
|
3734f5d476 | ||
|
|
088f963ad1 | ||
|
|
c8a6bc69d3 | ||
|
|
8584cc647c | ||
|
|
d8ac604592 | ||
|
|
9acb5e760b | ||
|
|
96879bf415 | ||
|
|
d7e815d670 | ||
|
|
dfcb8a4550 | ||
|
|
96fa6b105c | ||
|
|
b86f51ab46 | ||
|
|
8473a1152d | ||
|
|
65acfaa0bd | ||
|
|
c58e08998a | ||
|
|
9cf905421e | ||
|
|
c65694402f | ||
|
|
0d24ae6192 | ||
|
|
383e0e22af | ||
|
|
87840930e0 | ||
|
|
05b3321af7 | ||
|
|
19e380d1fb | ||
|
|
d1f69556b7 | ||
|
|
80ba34351c | ||
|
|
81a5e48b26 | ||
|
|
aa28675016 | ||
|
|
f8ecfa2745 | ||
|
|
efda3f4408 | ||
|
|
a5b29ac398 | ||
|
|
73151db726 | ||
|
|
aab57ce216 | ||
|
|
f140784a50 | ||
|
|
579269cb5b | ||
|
|
ed5a9c6c12 | ||
|
|
ca430123c9 | ||
|
|
bc68aff654 | ||
|
|
19e3280e29 | ||
|
|
a813e80b32 | ||
|
|
05b3446f59 | ||
|
|
60ce722010 | ||
|
|
1fc329c670 | ||
|
|
4035ffcffb | ||
|
|
b41871a2a2 | ||
|
|
a44a7e86d7 | ||
|
|
716734d26f | ||
|
|
b3e97febe1 | ||
|
|
520db4b550 | ||
|
|
c6c3efade4 | ||
|
|
0b7f5f9f05 | ||
|
|
33a14dee8b | ||
|
|
bddc54b93d | ||
|
|
2e0f5e2c51 | ||
|
|
2c6f6067ca | ||
|
|
dcf41091ef | ||
|
|
8040a329cb | ||
|
|
6e43d4092f | ||
|
|
0ac2985fc1 | ||
|
|
1e037b9610 | ||
|
|
38b70db2dc | ||
|
|
b35d546555 | ||
|
|
5e3d07abe6 | ||
|
|
1d131871f1 | ||
|
|
5e8f80b626 | ||
|
|
9128efbaa1 | ||
|
|
3545e1173e | ||
|
|
9bf95223a1 | ||
|
|
3f2bd69002 | ||
|
|
4554bbc85b | ||
|
|
8901c31631 | ||
|
|
61ece52edc | ||
|
|
fb936e97ac | ||
|
|
8ca4f7400a | ||
|
|
61b48676ae | ||
|
|
257e59b79f | ||
|
|
c19791c31d | ||
|
|
c99747c1f6 | ||
|
|
a9b6989966 | ||
|
|
c9bf0fb13c | ||
|
|
04247048d4 | ||
|
|
b2a6c2f5e0 | ||
|
|
865c306e14 | ||
|
|
588fc919d9 | ||
|
|
8ff7da4d23 | ||
|
|
da516b487d | ||
|
|
06615cbb29 | ||
|
|
463ee0cced | ||
|
|
208ccd6642 | ||
|
|
cd2bb1b570 | ||
|
|
52a195aca2 | ||
|
|
fbc3cf00a2 | ||
|
|
ede5f456a8 | ||
|
|
2633e91d4d | ||
|
|
39b3a74d34 | ||
|
|
3ecd224ce6 | ||
|
|
49c7033eaa | ||
|
|
20ca848df9 | ||
|
|
5b0e3ed636 | ||
|
|
14840715bc | ||
|
|
ed4537e5d5 | ||
|
|
58d4b4427d | ||
|
|
8c6aba9361 | ||
|
|
1173949bba | ||
|
|
279956456c | ||
|
|
3f8c557a96 | ||
|
|
ff93201ab1 | ||
|
|
cd94539e99 | ||
|
|
adc1d176e3 | ||
|
|
44c25f7fdd | ||
|
|
3806983bc2 | ||
|
|
c6f877700e | ||
|
|
c2fb90dfc7 | ||
|
|
ca61e6a238 | ||
|
|
9399e54da8 | ||
|
|
385f217b96 | ||
|
|
65bb6a2473 | ||
|
|
a430cf59c4 | ||
|
|
4bf328ef67 | ||
|
|
ebb724415d | ||
|
|
daa07dcb2b | ||
|
|
635f081437 | ||
|
|
c7b4ca2a82 | ||
|
|
b5c91531fa | ||
|
|
8199e91ce0 | ||
|
|
44c32c1711 | ||
|
|
5a3da43640 | ||
|
|
352f3e920e | ||
|
|
5f4a00c77e | ||
|
|
aeea6f5dc1 | ||
|
|
ea74a531b4 | ||
|
|
02003b2bd3 | ||
|
|
723dfd30a2 | ||
|
|
aa29dc99f3 | ||
|
|
74ba4d0145 | ||
|
|
5705f5227f | ||
|
|
3bb34b46ee | ||
|
|
4d0ea99f47 | ||
|
|
fe70c07113 | ||
|
|
27c6bf1864 | ||
|
|
f4ded4c6d7 | ||
|
|
4a2c7f5ecb | ||
|
|
7a051380e2 | ||
|
|
35dec947df | ||
|
|
6b34427855 | ||
|
|
59a9f30089 | ||
|
|
779564ebb6 | ||
|
|
a4f992b9e5 | ||
|
|
cf3f09600b | ||
|
|
83c1d63408 | ||
|
|
43337c34c8 | ||
|
|
bb431ccf53 | ||
|
|
ba69cc93ee | ||
|
|
406de8f4df | ||
|
|
fd05fe6e25 | ||
|
|
10a0ee9142 | ||
|
|
836d4dc1ef | ||
|
|
a18fcdb21e | ||
|
|
6c9cd06e4a | ||
|
|
44c1ac054c | ||
|
|
9d77d60887 | ||
|
|
8f7fd97ebe | ||
|
|
87e3b2249c | ||
|
|
5e12af77a4 | ||
|
|
47244373d7 | ||
|
|
a72530598a | ||
|
|
2679facf24 | ||
|
|
cf9db4b0d4 | ||
|
|
f341c8585e | ||
|
|
28f7b47034 | ||
|
|
99954f9140 | ||
|
|
85b21d63ed | ||
|
|
ef443d9912 | ||
|
|
db2b22c822 | ||
|
|
24156884e4 | ||
|
|
6413567353 | ||
|
|
84f84dad40 | ||
|
|
dd01653da9 | ||
|
|
085472a12c | ||
|
|
e9b6965678 | ||
|
|
1a9680c09a | ||
|
|
40f8fe22ee | ||
|
|
be6002dd64 | ||
|
|
96ebb770b6 | ||
|
|
9bcdb254c9 | ||
|
|
87e71f1eb8 | ||
|
|
451ce104c5 | ||
|
|
7920711988 | ||
|
|
6e78f2caf2 | ||
|
|
2727b056eb | ||
|
|
975d0e3540 | ||
|
|
eb7e700e7c | ||
|
|
18892a14c2 | ||
|
|
6cbde80a1c | ||
|
|
6fc875aa72 | ||
|
|
29f5c3c758 | ||
|
|
afa6411004 | ||
|
|
67526c30da | ||
|
|
dc67dbc489 | ||
|
|
05ac2feebd | ||
|
|
6f30c04899 | ||
|
|
dfce8e9749 | ||
|
|
aae326133b | ||
|
|
15383a6b92 | ||
|
|
5d8a1deb35 | ||
|
|
b09d12aad5 | ||
|
|
488acc3e69 | ||
|
|
32534c870d | ||
|
|
74af1516ac | ||
|
|
07b2f175f3 | ||
|
|
1a1c65afce | ||
|
|
90282b858e | ||
|
|
f819922d8a | ||
|
|
aa8fdf557e | ||
|
|
4896cf0ad3 | ||
|
|
2e61e2a366 | ||
|
|
172a7444c9 | ||
|
|
3ea5b16487 | ||
|
|
e83a169fd6 | ||
|
|
53c1c0103c | ||
|
|
e467402a15 | ||
|
|
d5fc16dccb | ||
|
|
3be84edb5a | ||
|
|
fe3bb22311 | ||
|
|
86c2cf96a9 | ||
|
|
67ba8340cc | ||
|
|
ac69640253 | ||
|
|
64965bcc93 | ||
|
|
2eb73f3746 | ||
|
|
8f761c31e3 | ||
|
|
528b61862a | ||
|
|
c7003a7139 | ||
|
|
75e65b082e | ||
|
|
22d38b783d | ||
|
|
ebdada57a6 | ||
|
|
28f8850013 | ||
|
|
540977c637 | ||
|
|
4ba74e9f3e | ||
|
|
cecdb1681c | ||
|
|
2947da2a00 | ||
|
|
da6000b4da | ||
|
|
8be7207c3c | ||
|
|
0a806a74b1 | ||
|
|
d56f9d6e6e | ||
|
|
dc742763a8 | ||
|
|
0b9b87ba23 | ||
|
|
346be22ea9 | ||
|
|
b2c95d96b1 | ||
|
|
1c801bb205 | ||
|
|
163d240a9c | ||
|
|
19fc3a6d40 | ||
|
|
4c9bc002f4 | ||
|
|
b3a5d50ba8 | ||
|
|
f7919129b6 | ||
|
|
dccb1d91e2 | ||
|
|
41b058aeaf | ||
|
|
f2a12634a4 | ||
|
|
8d33f45388 | ||
|
|
cb7e230214 | ||
|
|
badd05a014 | ||
|
|
47ffe9a2ec | ||
|
|
4fe74eb5fe | ||
|
|
1c40896dc0 | ||
|
|
872b4ce1c6 | ||
|
|
c0fed1a391 | ||
|
|
d1f584e490 | ||
|
|
79f5011d58 | ||
|
|
e350edd41f | ||
|
|
23a6e56478 | ||
|
|
80050f11f2 | ||
|
|
25c46cc978 | ||
|
|
458c42ae7c | ||
|
|
febeeadc7b | ||
|
|
044643559d | ||
|
|
782e668abf | ||
|
|
fa6af88566 | ||
|
|
c106c4fc2a | ||
|
|
09780f64de | ||
|
|
81d4558b2f | ||
|
|
3de0e37fd5 | ||
|
|
fcfd2f8ba5 | ||
|
|
7e7e461237 | ||
|
|
2afab030bd | ||
|
|
5cbeee6e86 | ||
|
|
87bbffefc3 | ||
|
|
a2877cab70 | ||
|
|
4f10b6dbc3 | ||
|
|
24a213a8ff | ||
|
|
cf21044cf7 | ||
|
|
289a5d7051 | ||
|
|
b5ae76d298 | ||
|
|
598cf60e10 | ||
|
|
c4ca2103e4 | ||
|
|
4b55a3e525 | ||
|
|
376b81f841 | ||
|
|
ee6035c59a | ||
|
|
a757330788 | ||
|
|
bf141202f8 | ||
|
|
1be3127177 | ||
|
|
c74a43cd74 | ||
|
|
1b520d3c96 | ||
|
|
b7418a2a3a | ||
|
|
eee67cdb53 | ||
|
|
1d70bc4ed7 | ||
|
|
d06b0dd6d6 | ||
|
|
cbbac0554e | ||
|
|
5f16766997 | ||
|
|
dcd6cae0f6 | ||
|
|
cc43993d5b | ||
|
|
2478ffe331 | ||
|
|
18ae960f86 | ||
|
|
fcb226e357 | ||
|
|
58c04b7a7b | ||
|
|
a6401fa9ef | ||
|
|
2481124ada | ||
|
|
488fde47e5 | ||
|
|
e7e77c99d0 | ||
|
|
6fbd35ed33 | ||
|
|
fba2339280 | ||
|
|
bd8270a4ae | ||
|
|
5299061c87 | ||
|
|
e843aeac55 | ||
|
|
19c792209b | ||
|
|
277d442634 | ||
|
|
51bec043e3 | ||
|
|
b7595f6f22 | ||
|
|
55181d583f | ||
|
|
b5a5a4f32e | ||
|
|
a746779efa | ||
|
|
736b6ea0a5 | ||
|
|
884f07baa6 | ||
|
|
b496c3136e | ||
|
|
bbfb40f973 | ||
|
|
a73e1fc164 | ||
|
|
ef7acb7fa0 | ||
|
|
fcd108adbe | ||
|
|
c9c76bfd10 | ||
|
|
d9302a3120 | ||
|
|
0cb3308dd1 | ||
|
|
583b9d7cf7 | ||
|
|
d7728ebc25 | ||
|
|
b6742526aa | ||
|
|
4defd8ac1b | ||
|
|
d28672e2ad | ||
|
|
94d6a51358 | ||
|
|
54eabc948f | ||
|
|
6c9e26ce83 | ||
|
|
a934037179 | ||
|
|
396444a089 | ||
|
|
699444abdd | ||
|
|
088031f807 | ||
|
|
8ce6e2c111 | ||
|
|
e7f36b9fcf | ||
|
|
dab91f526e | ||
|
|
35e23a7cbc | ||
|
|
7df05df48f | ||
|
|
707a1ae628 | ||
|
|
a65fc4076b | ||
|
|
f487897cf2 | ||
|
|
869d9ed543 | ||
|
|
8f61ed7561 | ||
|
|
8cb6a32341 | ||
|
|
687260e53c | ||
|
|
a919ce4554 | ||
|
|
d19d7acedd | ||
|
|
a393b3aff5 | ||
|
|
8c2036b6b4 | ||
|
|
dc081861ad | ||
|
|
ec76d2ba7d | ||
|
|
47ce0a8e9c | ||
|
|
f1c730962b | ||
|
|
d95a5131f6 | ||
|
|
69637cf482 | ||
|
|
25d6ec3a92 | ||
|
|
8bf5ff4e7f | ||
|
|
fd4e4be3a8 | ||
|
|
e0a01ea2ca | ||
|
|
256ccb96a6 | ||
|
|
bfffdc7d21 | ||
|
|
3bbe21268b | ||
|
|
1537e88a74 | ||
|
|
0d9711e62d | ||
|
|
0a7c5f06c6 | ||
|
|
e7a8a32b73 | ||
|
|
62b703f2e4 | ||
|
|
f44921d136 | ||
|
|
cd42545c7d | ||
|
|
0df3fd7bed | ||
|
|
56241da281 | ||
|
|
05a04104dd | ||
|
|
50d27ee186 | ||
|
|
371926b5a6 | ||
|
|
97face2406 | ||
|
|
ea64b40011 | ||
|
|
6d23e489aa | ||
|
|
f4d75e211f | ||
|
|
48f6cc644d | ||
|
|
33f517333f | ||
|
|
7253a309c7 | ||
|
|
f2aff5882d | ||
|
|
0dd61a7270 | ||
|
|
420b8e6d67 | ||
|
|
9e08919ce0 | ||
|
|
a7324f6c49 | ||
|
|
721c0834db | ||
|
|
e37629aaea | ||
|
|
a43a4a429e | ||
|
|
f5d34198e7 | ||
|
|
46604293e9 | ||
|
|
58e405cccd | ||
|
|
1deea5bebc | ||
|
|
cad5636f92 | ||
|
|
90290bee94 | ||
|
|
9f7ebceeb9 | ||
|
|
60ea2c7e12 | ||
|
|
441c866160 | ||
|
|
33d3341bb3 | ||
|
|
1d778fd78e | ||
|
|
0cd73a6304 | ||
|
|
2fedfe5b18 | ||
|
|
77c140bee6 | ||
|
|
c06e38d71f | ||
|
|
9b59cec22a | ||
|
|
306e992789 | ||
|
|
8335cfed69 | ||
|
|
4702aa1987 | ||
|
|
145e1b31a0 | ||
|
|
f2e28de20c | ||
|
|
bcaaf88edb | ||
|
|
120698fb43 | ||
|
|
dd88585ffa | ||
|
|
93a8c450ad | ||
|
|
3451434997 | ||
|
|
0bab2e74b0 | ||
|
|
c1a877489b | ||
|
|
520099aaba | ||
|
|
9bd02b3dd2 | ||
|
|
ba64f2c189 | ||
|
|
fbb194a6c5 | ||
|
|
30b2d2134b | ||
|
|
7eea71e282 | ||
|
|
e40b3029e0 | ||
|
|
17492c78fa | ||
|
|
a9276d3f72 | ||
|
|
eecbe49955 | ||
|
|
be9874a9ad | ||
|
|
b4fcc057c5 | ||
|
|
d9499dc56b | ||
|
|
f6806c88ab | ||
|
|
2caca545ad | ||
|
|
ff0f11da7f | ||
|
|
daa1822bc5 | ||
|
|
d490892092 | ||
|
|
182a4e7b06 | ||
|
|
e9beed03ea | ||
|
|
8351527aea | ||
|
|
ddff663b0b | ||
|
|
31b4fb9201 | ||
|
|
1f7f0da383 | ||
|
|
9fa1079248 | ||
|
|
ae0cf5e3d7 | ||
|
|
672369c598 | ||
|
|
73687f248b | ||
|
|
65cc2a948a | ||
|
|
299f36dcc0 | ||
|
|
f5cbce5195 | ||
|
|
a3780667b2 | ||
|
|
a21742a326 | ||
|
|
40aacb2e61 | ||
|
|
ba18869c85 | ||
|
|
a454f2c523 | ||
|
|
7caebaa6ba | ||
|
|
580abd7317 | ||
|
|
3187431611 | ||
|
|
81c37c9a0a | ||
|
|
ba5bca20da | ||
|
|
533fe187bd | ||
|
|
c4da26c19d | ||
|
|
ca7bfcc8c6 | ||
|
|
6c7b582faf | ||
|
|
6f2e6135f1 | ||
|
|
37a92e03c1 | ||
|
|
35385f8916 | ||
|
|
a2fbdd8e0a | ||
|
|
bab610722f | ||
|
|
4cabc8d132 | ||
|
|
e8bd381907 | ||
|
|
42aac802dd | ||
|
|
d748a20445 | ||
|
|
5db3ff934d | ||
|
|
9bfa3ede20 | ||
|
|
127466e546 | ||
|
|
16aa30cac7 | ||
|
|
05a3d0111b | ||
|
|
9f4b5fb14a | ||
|
|
a716e68f87 | ||
|
|
2e127771ed | ||
|
|
c6bbba6051 | ||
|
|
443ed115b0 | ||
|
|
f450b8d863 | ||
|
|
acf570e652 | ||
|
|
4e9199c68e | ||
|
|
6f5575b155 | ||
|
|
700b854434 | ||
|
|
37b5d954e9 | ||
|
|
ac50e96568 | ||
|
|
86174c2e9f | ||
|
|
c1ae1dee06 | ||
|
|
c4aedf2958 | ||
|
|
736c8f2fc0 | ||
|
|
294084299e | ||
|
|
b1d30da4bc | ||
|
|
506f2e7c0c | ||
|
|
e13660dcdc | ||
|
|
4fbe2aa5af | ||
|
|
5ddc514545 | ||
|
|
3cfcbaec16 | ||
|
|
50f06e6a4f | ||
|
|
c7e2b15629 | ||
|
|
8e81f72584 | ||
|
|
0fb5ed00f6 | ||
|
|
bf7920cc15 | ||
|
|
971f03ae0f | ||
|
|
24b9d93920 | ||
|
|
8d81f4d5df | ||
|
|
b6e135a642 | ||
|
|
d3f4ade215 | ||
|
|
56d2a52706 | ||
|
|
b40dc1e05e | ||
|
|
bed3337866 | ||
|
|
28dc73d060 | ||
|
|
5ea1226ad0 | ||
|
|
e43f2a4158 | ||
|
|
d73e771ef3 | ||
|
|
c077e9418c | ||
|
|
2ce888c84c | ||
|
|
b4d175ff98 | ||
|
|
35e18d0481 | ||
|
|
f6cccb00e3 | ||
|
|
2a82c82039 | ||
|
|
a2e8a1b582 | ||
|
|
dbd78d4b86 | ||
|
|
8105892b6e | ||
|
|
bc7d5871e4 | ||
|
|
680e6a8c5f | ||
|
|
c364084cb2 | ||
|
|
63f8042746 | ||
|
|
48e6b2cea0 | ||
|
|
228309c571 | ||
|
|
ca3a3df3ba | ||
|
|
c7df37b17c | ||
|
|
41f03fbba4 | ||
|
|
b3089c1bba | ||
|
|
c5a6f1fea4 | ||
|
|
ba2777137b | ||
|
|
6d90be164a | ||
|
|
4da6f5859a | ||
|
|
15d4ea710a | ||
|
|
f0541c1f32 | ||
|
|
2b8cab1824 | ||
|
|
61c7972915 | ||
|
|
44a0eec74c | ||
|
|
e57753e44b | ||
|
|
74e91a2950 | ||
|
|
7ce3ef41df | ||
|
|
444a77933a | ||
|
|
78f78c607d | ||
|
|
69155002dc | ||
|
|
475d14fab7 | ||
|
|
f4d7e63358 | ||
|
|
f222d2b45b | ||
|
|
d0155fe1da | ||
|
|
4737d0b507 | ||
|
|
09bd0197bb | ||
|
|
f0f120bd32 | ||
|
|
c897da29c6 | ||
|
|
e347e254e8 | ||
|
|
12363aebf0 | ||
|
|
ff0b925313 | ||
|
|
a6b4bd9b80 | ||
|
|
485fa5476c | ||
|
|
689076d97c | ||
|
|
cca6102e91 | ||
|
|
095855162b | ||
|
|
326f934013 | ||
|
|
35b0c258fe | ||
|
|
6650846b15 | ||
|
|
a33d320f4c | ||
|
|
0bcfea6aa4 | ||
|
|
db6e63e91c | ||
|
|
a7f84fb53a | ||
|
|
ce7f2b1765 | ||
|
|
abbfb59eff | ||
|
|
b57fef16f9 | ||
|
|
1706afbcd0 | ||
|
|
d01bd69aa5 | ||
|
|
e6b9ae3ee1 | ||
|
|
73b6b8045d | ||
|
|
3c87abc6e8 | ||
|
|
e95925c129 | ||
|
|
82c44d6c40 | ||
|
|
e0a268e05e | ||
|
|
487892e1d9 | ||
|
|
7ab4e88359 | ||
|
|
4702b6ee75 | ||
|
|
20cd3aa66d | ||
|
|
391114bdef | ||
|
|
ffe1052ecc | ||
|
|
b4159152a7 | ||
|
|
8fc6e4b0f7 | ||
|
|
c8938b7a4b | ||
|
|
34db1626a5 | ||
|
|
7f0c97c5b9 | ||
|
|
74050c46e7 | ||
| 3ed1cb665a | |||
|
|
a6f33ba69d | ||
|
|
176b690417 | ||
|
|
e22b1b90ec | ||
|
|
a3c560790c | ||
| 570e2108dd | |||
|
|
59c0d95e5d | ||
|
|
9ceaecdc02 | ||
|
|
19d50898ef | ||
|
|
5b62ca62ae | ||
| fb528d30ce | |||
| 5f2d5ed334 | |||
| c20e459900 | |||
| 09220825cf | |||
|
|
3d4db23d27 | ||
|
|
4496b67dcc | ||
|
|
2a13298384 | ||
|
|
d6ec404d76 | ||
|
|
3235fd4dc1 | ||
|
|
293740fee2 | ||
|
|
d3c420ec84 | ||
|
|
85ef09d43c | ||
|
|
e4561d69c4 | ||
|
|
6b4d206099 | ||
|
|
50843edf6a | ||
|
|
3a1686f8da | ||
|
|
66dd91bb97 | ||
|
|
e020e5d593 | ||
|
|
3d62663281 | ||
|
|
8d9485c02e | ||
|
|
9fb45dd683 | ||
|
|
8a4924dba9 | ||
|
|
beaf7be606 | ||
|
|
7dbd21bdf4 | ||
|
|
9ce3056203 | ||
|
|
880b4e847c | ||
|
|
71e83d5bc8 | ||
|
|
432fd92cca | ||
|
|
907a9cea25 | ||
| 07391810ff | |||
| 5063421364 | |||
| 2b4d308783 | |||
| e2f49968b3 | |||
| 229778ebf9 | |||
|
|
d2c0972c93 | ||
|
|
d300b6e518 | ||
|
|
56cb7b2223 | ||
|
|
2fc42d5d60 | ||
|
|
999e90f709 | ||
|
|
45c6cd7d2a | ||
|
|
5aa0858021 | ||
|
|
e981da3932 | ||
|
|
2685be7306 | ||
|
|
a13c4628f5 | ||
|
|
b36417454c | ||
|
|
a6f5952dee | ||
|
|
d67dfe46f6 | ||
|
|
e57cc43500 | ||
|
|
481063285b | ||
|
|
d805523dbd | ||
|
|
58267f66fc | ||
|
|
48215f2e5e | ||
|
|
7e0fc1528a | ||
|
|
c185d20a93 | ||
|
|
7ec847e576 | ||
|
|
4ee16d4fc6 | ||
|
|
59d614a24e | ||
|
|
40037b3f4b | ||
|
|
f91c8b6205 | ||
|
|
8f73a99ff7 | ||
|
|
e0f07cb396 | ||
|
|
2950ab7472 | ||
|
|
0ab4053dfb | ||
|
|
745aa0f268 | ||
|
|
12bb93c2f0 | ||
| 95ca175901 | |||
| 71585ed29d | |||
|
|
231a377718 | ||
|
|
a80ee902a9 | ||
|
|
c2d3b5f92a | ||
|
|
64365af124 | ||
|
|
60619dbffc | ||
|
|
77e77e9cc3 | ||
|
|
d72b41a902 | ||
|
|
dfddaaea94 | ||
|
|
6b872c6f74 | ||
|
|
202c8aee77 | ||
|
|
ba397a1744 | ||
|
|
d699a152d5 | ||
|
|
beef44ea50 | ||
|
|
b991a2da7b | ||
|
|
bbe4addd83 | ||
|
|
600829645d | ||
|
|
68280cfe7e | ||
|
|
c5523aa95d | ||
|
|
93a68a5582 | ||
|
|
6155236d3c | ||
| 28f7f20728 | |||
| 235256d679 | |||
| 626d569858 | |||
| 0492179bdd | |||
| 1dfd1de5c1 | |||
| d15d01c32b | |||
|
|
c9f4278d9b | ||
|
|
6aa0540c9e | ||
|
|
9689dc9db1 | ||
|
|
c20e3324d4 | ||
| 303f44cd2b | |||
| 66b73973b4 | |||
|
|
0ff4e52ed3 | ||
| b6d1f724e9 | |||
| e77b9d4970 | |||
|
|
456b102c15 | ||
|
|
cc57997abc | ||
|
|
19e61c7cc3 | ||
|
|
f908513dca | ||
|
|
7dc39e6ada | ||
|
|
b0be53554d | ||
|
|
b98048bd39 | ||
|
|
fffd3fdc95 | ||
|
|
dea9ed2ccd | ||
|
|
de5744c9e4 | ||
|
|
a23f3d8f16 | ||
|
|
0c24bfdedd | ||
|
|
c0b95c6840 | ||
|
|
e977b09e20 | ||
|
|
4ff75cf199 | ||
|
|
24e70c70ce | ||
|
|
275a7bf00b | ||
|
|
455fc79818 | ||
|
|
8740c3c255 | ||
|
|
98bfca5bb9 | ||
|
|
fc8d01ad5f | ||
|
|
d9b8df1725 | ||
|
|
9b7802ac04 | ||
|
|
6e1f3e0406 | ||
|
|
a2d4bab062 | ||
|
|
f1cefad910 | ||
|
|
957c69bd1e | ||
|
|
2902c5e805 | ||
|
|
faf9f5d988 | ||
|
|
874f061468 | ||
|
|
69521a1f1f | ||
|
|
fb9dbed306 | ||
|
|
d42158ac90 | ||
|
|
ff3ebec562 | ||
|
|
ea79de617e | ||
|
|
9e1ad6a983 | ||
|
|
c17248df31 | ||
|
|
4c116ebd86 | ||
|
|
c490fe0c1c | ||
|
|
cefdf23799 | ||
|
|
7f70642606 | ||
|
|
1d5b2609f9 | ||
|
|
a206fe8b87 | ||
|
|
fb4f6c45ce | ||
|
|
c50ae245bd | ||
|
|
f6c2131ebf | ||
|
|
543d1d2922 | ||
|
|
bb61e6f6a2 | ||
|
|
2d1bbf6b48 | ||
|
|
67e42cfe3e | ||
|
|
429a2cf230 | ||
|
|
9ace85e604 | ||
|
|
f677b0e132 | ||
|
|
f137f044d0 | ||
|
|
77c12db31a | ||
|
|
b670cb29fe | ||
|
|
95d7bb071d | ||
|
|
eadb4c1dee | ||
|
|
6672a6c852 | ||
|
|
094feddda4 | ||
|
|
f86eddb298 | ||
|
|
a93b4917cd | ||
|
|
0f9767aa16 | ||
|
|
0c9f246690 | ||
|
|
517d258d61 | ||
|
|
61e3818563 | ||
|
|
776ec98a3e | ||
|
|
f3e844bb0a | ||
|
|
a55cd4ed05 | ||
|
|
8a985620f9 |
15
.editorconfig
Normal file
@@ -0,0 +1,15 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
if [ ! -d "/app/zhamao-framework/bin" ]; then
|
||||
cp -r /app/zhamao-framework-bak/* /app/zhamao-framework/
|
||||
fi
|
||||
php /app/zhamao-framework/bin/start
|
||||
21
.gitattributes
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Exclude build/test files from archive (reduce package size)
|
||||
/.github export-ignore
|
||||
/.phive export-ignore
|
||||
/build export-ignore
|
||||
/docs export-ignore
|
||||
/ext export-ignore
|
||||
/resources export-ignore
|
||||
/tests export-ignore
|
||||
/tests_old export-ignore
|
||||
/.editorconfig export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.php-cs-fixer.php export-ignore
|
||||
/captainhook.json export-ignore
|
||||
/install-runtime.sh export-ignore
|
||||
/package.json export-ignore
|
||||
/phpstan.neon export-ignore
|
||||
/phpunit.xml.dist export-ignore
|
||||
|
||||
# Configure diff output
|
||||
*.php diff=php
|
||||
38
.github/ISSUE_TEMPLATE/1_Bug_report.yaml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: 🐛 漏洞(BUG)报告
|
||||
description: ⚠️ 请不要直接在此提交安全漏洞
|
||||
labels: bug
|
||||
|
||||
body:
|
||||
- type: input
|
||||
id: affected-versions
|
||||
attributes:
|
||||
label: 受影响版本
|
||||
placeholder: x.y.z
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 描述
|
||||
description: 请详细地描述您的问题
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce-steps
|
||||
attributes:
|
||||
label: 复现步骤
|
||||
description: |
|
||||
请尽可能地提供可以复现此步骤的漏洞。
|
||||
如果步骤过长或难以描述,您可以自行建立一个用于复现漏洞的仓库。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: possible-solution
|
||||
attributes:
|
||||
label: 解决方案
|
||||
description: 如果您对这个漏洞的成因或修复有任何意见的话,请在此提出
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: 附加信息
|
||||
description: 其他可能有帮助的信息,如日志、截图等
|
||||
19
.github/ISSUE_TEMPLATE/2_Feature_request.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: 🚀 功能建议
|
||||
description: 新功能、改进的意见、草案
|
||||
labels: enhancement
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 描述
|
||||
description: 请提供简洁清楚的描述
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: example
|
||||
attributes:
|
||||
label: 例子
|
||||
description: |
|
||||
一个简单的例子,展示该功能将如何被使用(包括代码、配置文件等)
|
||||
如果这是针对已有功能的改进,请展示改进前后使用方式(或效能)的对比
|
||||
43
.github/workflows/coding-style.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Code Style
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
concurrent_skipping: same_content_newer
|
||||
skip_after_successful_duplicate: true
|
||||
paths: '["src/**", "tests/**"]'
|
||||
do_not_skip: '["workflow_dispatch", "schedule"]'
|
||||
|
||||
cs-fix:
|
||||
needs: pre_job
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||
name: Code Style
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PHP
|
||||
uses: sunxyw/workflows/setup-environment@main
|
||||
with:
|
||||
php-version: 8.0
|
||||
php-extensions: swoole, posix, json
|
||||
operating-system: ubuntu-latest
|
||||
use-cache: true
|
||||
|
||||
- name: Code Style
|
||||
uses: sunxyw/workflows/coding-style@main
|
||||
61
.github/workflows/increment-build-number.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Increment Build Number
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
types:
|
||||
- closed
|
||||
paths:
|
||||
- 'src/**.php'
|
||||
|
||||
jobs:
|
||||
incremental-build-number:
|
||||
if: github.event.pull_request.merged == true
|
||||
name: Incremental Build Number
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: sunxyw/workflows/setup-environment@main
|
||||
with:
|
||||
php-version: 8.1
|
||||
php-extensions: swoole, posix, json
|
||||
operating-system: ubuntu-latest
|
||||
use-cache: true
|
||||
|
||||
- name: Prepare Doxygen
|
||||
id: prepare-doxygen
|
||||
continue-on-error: true
|
||||
run: bin/prepare-doxygen before
|
||||
|
||||
- name: Generate Doxygen
|
||||
if: steps.prepare-doxygen.outcome == 'success'
|
||||
uses: mattnotmitt/doxygen-action@v1.9.5
|
||||
|
||||
- name: Finishing Doxygen
|
||||
if: steps.prepare-doxygen.outcome == 'success'
|
||||
run: bin/prepare-doxygen after
|
||||
|
||||
- name: Commit API Docs
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: update api docs
|
||||
branch: ${{ github.base_ref }}
|
||||
|
||||
- name: Increment build number
|
||||
id: increment-build-number
|
||||
run: |
|
||||
sed -i -r 's/(.*)(\VERSION_ID\s=\s)([0-9]+)(.*)/echo "\1\2$((\3+1))\4"/ge' src/ZM/Framework.php
|
||||
BUILD_ID=$(cat src/ZM/Framework.php | grep "VERSION_ID = " | sed 's/[^0-9]//g')
|
||||
echo "::set-output name=build_id::$BUILD_ID"
|
||||
|
||||
- name: Commit build number
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: increment build number (build ${{ steps.increment-build-number.outputs.build_id }})
|
||||
branch: ${{ github.base_ref }}
|
||||
43
.github/workflows/static-analysis.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Static Analysis
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
concurrent_skipping: same_content_newer
|
||||
skip_after_successful_duplicate: true
|
||||
paths: '["src/**", "tests/**"]'
|
||||
do_not_skip: '["workflow_dispatch", "schedule"]'
|
||||
|
||||
analyse:
|
||||
needs: pre_job
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||
name: Static Analysis
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PHP
|
||||
uses: sunxyw/workflows/setup-environment@main
|
||||
with:
|
||||
php-version: 8.0
|
||||
php-extensions: swoole, posix, json
|
||||
operating-system: ubuntu-latest
|
||||
use-cache: true
|
||||
|
||||
- name: Static Analysis
|
||||
uses: sunxyw/workflows/static-analysis@main
|
||||
55
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- '*-dev*'
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- review_requested
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
steps:
|
||||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
concurrent_skipping: same_content_newer
|
||||
skip_after_successful_duplicate: true
|
||||
paths: '["src/**", "tests/**", "bin/phpunit-zm"]'
|
||||
do_not_skip: '["workflow_dispatch", "schedule"]'
|
||||
|
||||
test:
|
||||
needs: pre_job
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||
strategy:
|
||||
matrix:
|
||||
operating-system: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
php-version: [ 8.0, 8.1, 8.2 ]
|
||||
name: PHP ${{ matrix.php-version }} Test (${{ matrix.operating-system }})
|
||||
runs-on: ${{ matrix.operating-system }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup PHP
|
||||
uses: sunxyw/workflows/setup-environment@main
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
php-extensions: swoole, posix, json
|
||||
operating-system: ${{ matrix.operating-system }}
|
||||
use-cache: true
|
||||
|
||||
- name: Test
|
||||
uses: sunxyw/workflows/test@main
|
||||
51
.github/workflows/vuepress-deploy.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Docs and Script Auto Deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- 'ext/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Deploy docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Deploy docs to GitHub Pages
|
||||
uses: jenkey2011/vuepress-deploy@master
|
||||
env:
|
||||
ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_SCRIPT: yarn && yarn docs:build
|
||||
BUILD_DIR: docs/.vuepress/dist/
|
||||
- name: Copy deployment to current folder
|
||||
run: |
|
||||
cp -r "${GITHUB_WORKSPACE}/docs/.vuepress/dist" "./deploy"
|
||||
- name: Deploy to Zhamao Server
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.ZHAMAO_XIN_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "deploy/"
|
||||
REMOTE_HOST: ${{ secrets.ZHAMAO_XIN_HOST }}
|
||||
REMOTE_USER: ${{ secrets.ZHAMAO_XIN_USER }}
|
||||
TARGET: ${{ secrets.FRAMEWORK_ZHAMAO_XIN_TARGET }}
|
||||
- name: deploy script file
|
||||
uses: wlixcc/SFTP-Deploy-Action@v1.2
|
||||
with:
|
||||
username: ${{ secrets.ZHAMAO_XIN_USER }}
|
||||
server: ${{ secrets.ZHAMAO_XIN_HOST }}
|
||||
ssh_private_key: ${{ secrets.ZHAMAO_XIN_PRIVATE_KEY }}
|
||||
local_path: './ext/go.sh'
|
||||
remote_path: ${{ secrets.ZHAMAO_XIN_MAIN_TARGET }}
|
||||
- name: deploy script file
|
||||
uses: wlixcc/SFTP-Deploy-Action@v1.2
|
||||
with:
|
||||
username: ${{ secrets.ZHAMAO_XIN_USER }}
|
||||
server: ${{ secrets.ZHAMAO_XIN_HOST }}
|
||||
ssh_private_key: ${{ secrets.ZHAMAO_XIN_PRIVATE_KEY }}
|
||||
local_path: './ext/v3.sh'
|
||||
remote_path: ${{ secrets.ZHAMAO_XIN_MAIN_TARGET }}
|
||||
90
.gitignore
vendored
@@ -1,13 +1,87 @@
|
||||
.idea/
|
||||
/src/test/
|
||||
/src/webconsole/config/
|
||||
/vendor/
|
||||
zm.json
|
||||
### Zhamao Framework ###
|
||||
/zm_data/
|
||||
composer.lock
|
||||
/resources/server.phar
|
||||
/distribute/
|
||||
/bin/.phpunit.result.cache
|
||||
/resources/zhamao.service
|
||||
/runtime/
|
||||
/tmp/
|
||||
/temp/
|
||||
/site/
|
||||
/plugins/
|
||||
/doxy/
|
||||
/walle/
|
||||
# 框架审计文件
|
||||
audit.log
|
||||
|
||||
# 进程锁文件
|
||||
.daemon_pid
|
||||
.zm_worker_*.pid
|
||||
|
||||
### Composer ###
|
||||
composer.phar
|
||||
/vendor/
|
||||
composer.lock
|
||||
|
||||
### Go CQHTTP ###
|
||||
/ext/go-cqhttp/data/
|
||||
/ext/go-cqhttp/logs/
|
||||
/ext/go-cqhttp/config.hjson
|
||||
/ext/go-cqhttp/device.json
|
||||
/ext/go-cqhttp/go-cqhttp
|
||||
/ext/go-cqhttp/session.token
|
||||
|
||||
### Git ###
|
||||
# 备份文件
|
||||
*.orig
|
||||
|
||||
# 解决 Merge 冲突时可能会自动生成的文件
|
||||
*.BACKUP.*
|
||||
*.BASE.*
|
||||
*.LOCAL.*
|
||||
*.REMOTE.*
|
||||
*_BACKUP_*.txt
|
||||
*_BASE_*.txt
|
||||
*_LOCAL_*.txt
|
||||
*_REMOTE_*.txt
|
||||
|
||||
# Git Hooks
|
||||
cghooks.lock
|
||||
|
||||
### VuePress ###
|
||||
/node_modules/
|
||||
/docs/.vuepress/dist/
|
||||
package-lock.json
|
||||
|
||||
### PHPUnit ###
|
||||
|
||||
# 生成的文件
|
||||
/bin/.phpunit.result.cache
|
||||
.phpunit.result.cache
|
||||
.daemon_pid
|
||||
.phpunit.cache
|
||||
|
||||
# 本地配置
|
||||
/phpunit.xml
|
||||
|
||||
# 构建目录
|
||||
/build/
|
||||
|
||||
### PhpStorm ###
|
||||
# 兼容 PHPStorm 及其他 Jetbrains IDE
|
||||
|
||||
# 除了代码格式配置和运行配置之外的所有文件
|
||||
.idea/*
|
||||
|
||||
!.idea/codeStyles
|
||||
!.idea/runConfigurations
|
||||
|
||||
### Phive ###
|
||||
/.phive/
|
||||
/tools/
|
||||
|
||||
### ASDF ###
|
||||
/.tool-version
|
||||
|
||||
.DS_Store
|
||||
|
||||
### PHP CS Fixer ###
|
||||
.php-cs-fixer.cache
|
||||
|
||||
76
.php-cs-fixer.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @since 2.7.0
|
||||
*/
|
||||
|
||||
return (new PhpCsFixer\Config())
|
||||
->setRiskyAllowed(true)
|
||||
->setRules([
|
||||
'@PSR12' => true,
|
||||
'@Symfony' => true,
|
||||
'@PhpCsFixer' => true,
|
||||
'array_syntax' => [
|
||||
'syntax' => 'short',
|
||||
],
|
||||
'list_syntax' => [
|
||||
'syntax' => 'short',
|
||||
],
|
||||
'concat_space' => [
|
||||
'spacing' => 'one',
|
||||
],
|
||||
'blank_line_before_statement' => [
|
||||
'statements' => [
|
||||
'declare',
|
||||
],
|
||||
],
|
||||
'ordered_imports' => [
|
||||
'imports_order' => [
|
||||
'class',
|
||||
'function',
|
||||
'const',
|
||||
],
|
||||
'sort_algorithm' => 'alpha',
|
||||
],
|
||||
'single_line_comment_style' => [
|
||||
'comment_types' => [
|
||||
],
|
||||
],
|
||||
'yoda_style' => [
|
||||
'always_move_variable' => false,
|
||||
'equal' => false,
|
||||
'identical' => false,
|
||||
],
|
||||
'multiline_whitespace_before_semicolons' => [
|
||||
'strategy' => 'no_multi_line',
|
||||
],
|
||||
'constant_case' => [
|
||||
'case' => 'lower',
|
||||
],
|
||||
'class_attributes_separation' => true,
|
||||
'combine_consecutive_unsets' => true,
|
||||
'declare_strict_types' => true,
|
||||
'linebreak_after_opening_tag' => true,
|
||||
'lowercase_static_reference' => true,
|
||||
'no_useless_else' => true,
|
||||
'no_unused_imports' => true,
|
||||
'not_operator_with_successor_space' => false,
|
||||
'not_operator_with_space' => false,
|
||||
'ordered_class_elements' => true,
|
||||
'php_unit_strict' => false,
|
||||
'phpdoc_separation' => false,
|
||||
'single_quote' => true,
|
||||
'standardize_not_equals' => true,
|
||||
'multiline_comment_opening_closing' => true,
|
||||
'phpdoc_summary' => false,
|
||||
'php_unit_test_class_requires_covers' => false,
|
||||
])
|
||||
->setFinder(
|
||||
PhpCsFixer\Finder::create()
|
||||
->in(__DIR__ . '/src')
|
||||
->in(__DIR__ . '/tests')
|
||||
->in(__DIR__ . '/config')
|
||||
->in(__DIR__ . '/bin')
|
||||
);
|
||||
11
.phpstorm.meta.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSTORM_META {
|
||||
|
||||
use OneBot\V12\Object\Event\OneBotEvent;
|
||||
use ZM\Context\Context;
|
||||
|
||||
override(Context::__call(0), map([
|
||||
'getBotEvent' => OneBotEvent::class,
|
||||
]));
|
||||
}
|
||||
68
CONTRIBUTING.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 炸毛框架贡献指南
|
||||
|
||||
首先很高兴你愿意读这篇文章,因为我们需要更多的开发者来帮助我们开发和维护。
|
||||
|
||||
如果你还没加入我们的 [QQ 群(670821194)][qq-group],我们建议你加入一下,以便和其他开发者实时交流和协作。
|
||||
|
||||
## 测试
|
||||
|
||||
我们目前采用 PHPUnit 作为测试框架,如果你编写了新的功能或改变了现有的行为,我们希望你可以编写相应的测试代码。
|
||||
|
||||
## 提交变更
|
||||
|
||||
请使用 [GitHub Pull Request][new-pr] 提交你的更改,并附上恰当的说明(例如一个简明的列表列出所作的改动)。
|
||||
|
||||
同时,请不要忘记在提交前执行代码格式检查 `composer cs-fix` 和静态分析检查 `composer analyse`
|
||||
。虽然我们的自动化流程会在你推送到仓库后自动运行检查,但我仍然建议你提前在本地运行,以节省可能的修改成本。
|
||||
|
||||
对了,请尽量为你的每个函数、方法加上 PHPDoc 注释。
|
||||
|
||||
## 具体贡献流程
|
||||
|
||||
#### **你发现了个 BUG?**
|
||||
|
||||
- **如果你发现的是安全漏洞,请不要在 GitHub 上回报!** 请直接联系 [@crazywhalecc](https://github.com/crazywhalecc)。
|
||||
|
||||
- **确保该问题还没被其他人回报。** 你可以在 [Issue 列表][issues] 中搜索相关关键词来检查。
|
||||
|
||||
- 如果你没能找到一个已有的 Issue 来描述你遇到的问题,[请新建一个 Issue][new-issue]。请务必包含**标题和清晰的描述**
|
||||
,尽可能多的相关信息,以及一个**代码示例**或者一个**可执行的测试用例**,来描述你遇到的问题。
|
||||
|
||||
- 如果可以的话,请使用相关的 Issue 模板来创建 Issue。
|
||||
|
||||
#### **你修复了个 BUG?**
|
||||
|
||||
- 在 GitHub 上提交一个 Pull Request。
|
||||
|
||||
- 确保 PR 的描述清楚地描述了问题和解决方案。如果有相关的 Issue,请在 PR 描述中包含 Issue 编号。
|
||||
|
||||
#### **你想添加新功能或改变现有功能?**
|
||||
|
||||
- 虽然你可以直接提交 PR,但我们建议你先与我们的开发者讨论你的想法,以避免你的努力白费。你可以在 [QQ 群][qq-group]
|
||||
中与我们的开发者交流,或者在 [GitHub Issue][issues] 中提出你的想法。
|
||||
|
||||
- 如果你的想法被采纳,你可以在 GitHub 上[提交一个 Pull Request][new-pr]。
|
||||
|
||||
#### **你对源码有疑问?**
|
||||
|
||||
- 你可以在 [QQ 群][qq-group] 中与我们的开发者讨论,或者你也可以在 [GitHub Issue][issues] 中提出你的疑问。
|
||||
|
||||
#### **你想为文档做贡献?**
|
||||
|
||||
- 非常欢迎^^
|
||||
|
||||
- 你可以直接[提交一个 Pull Request][new-pr]。
|
||||
|
||||
炸毛框架是由志愿开发者共同维护的项目。我们欢迎你加入我们的开发。
|
||||
|
||||
感谢~ :heart: :heart: :heart:
|
||||
|
||||
炸毛框架开发团队
|
||||
|
||||
[issues]: https://github.com/zhamao-robot/zhamao-framework/issues
|
||||
|
||||
[new-pr]: https://github.com/zhamao-robot/zhamao-framework/pull/new/master
|
||||
|
||||
[new-issue]: https://github.com/zhamao-robot/zhamao-framework/issues/new
|
||||
|
||||
[qq-group]: https://jq.qq.com/?_wv=1027&k=KdIGy0UK
|
||||
@@ -1,3 +0,0 @@
|
||||
FROM zmbot/swoole:latest
|
||||
|
||||
# TODO: auto-setup entrypoint
|
||||
2
LICENSE
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
Copyright 2022 CrazyBot Developer Team
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
151
README.md
@@ -1,97 +1,146 @@
|
||||
<div align="center">
|
||||
<img src="/resources/images/logo_trans.png" height = "150" alt="炸毛框架"><br>
|
||||
<img src="https://cdn.jsdelivr.net/gh/zhamao-robot/zhamao-framework/resources/images/logo_trans.png" width = "150" height = "150" alt="炸毛框架"><br>
|
||||
<h2>炸毛框架</h2>
|
||||
炸毛框架 (zhamao-framework) 是一个协程高性能的聊天机器人 + Web 服务器开发框架<br><br>
|
||||
炸毛框架 (zhamao-framework) 是一个高性能的聊天机器人 + Web 服务器开发框架<br><br>
|
||||
|
||||
[]()
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
|
||||
[](https://packagist.org/packages/zhamao/framework)
|
||||
[]()
|
||||
<p align="center">
|
||||
<a href="https://onebot.dev/">
|
||||
<img src="https://img.shields.io/badge/OneBot-12-black?style=flat-square" alt="OneBot">
|
||||
</a>
|
||||
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid)
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
|
||||
<a href="https://github.com/zhamao-robot/zhamao-framework/actions">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/zhamao-robot/zhamao-framework/test.yml?branch=v3-develop&label=Test&style=flat-square" alt="Integration Test">
|
||||
</a>
|
||||
|
||||
<a href="https://packagist.org/packages/zhamao/framework">
|
||||
<img src="https://img.shields.io/packagist/dt/zhamao/framework?label=Downloads&style=flat-square" alt="下载数">
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/zhamao-robot/zhamao-framework/releases">
|
||||
<img src="https://img.shields.io/packagist/v/zhamao/framework?include_prereleases&label=Release&style=flat-square" alt="最新版本">
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/zhamao-robot/zhamao-framework?label=License&style=flat-square" alt="开源协议">
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/zhamao-robot/zhamao-framework/search?q=TODO">
|
||||
<img src="https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO?label=TODO%20Counter&style=flat-square" alt="TODO">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
## 开发者注意
|
||||
**开发者 QQ 群:670821194**
|
||||
开发者 QQ 群:**670821194** [点击加入群聊](https://jq.qq.com/?_wv=1027&k=YkNI3AIr)
|
||||
|
||||
**当前 v2 版本已正式发布,此 master 分支为 2.0 版本,如需查看 v1 版本,请移步 `v1-legacy` 分支!**
|
||||
**如果有愿意一起开发框架本身的开发者,请提出 PR 或 Issue 参与开发!如果对框架本身的核心设计有更好的想法,可与作者成立开发组(目前仅 2 人),参与 OneBot V12 生态和框架本身的开发。**
|
||||
|
||||
**2.0 版本如果有问题请第一时间加群反馈!**
|
||||
|
||||
有关 3.0 版本的最新情况,请看这里:[Issue #22](https://github.com/zhamao-robot/zhamao-framework/issues/22)
|
||||
**相关正在进行的版本任务见 Projects 一栏!**
|
||||
|
||||
## 简介
|
||||
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(OneBot 兼容的 QQ 机器人对接),包含 Websocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
|
||||
|
||||
框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
|
||||
炸毛框架使用 PHP 编写,主要面向 API 服务,聊天机器人,包含 Websocket、HTTP
|
||||
等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
|
||||
|
||||
框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的插件。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("你好")
|
||||
*/
|
||||
public function hello() {
|
||||
ctx()->reply("你好,我是炸毛!"); // 简单的命令式回复
|
||||
#[\BotCommand('你好')]
|
||||
public function hello(\BotContext $ctx) {
|
||||
$ctx->reply("你好,我是炸毛!"); // 简单的命令式回复
|
||||
}
|
||||
/**
|
||||
* @RequestMapping("/index")
|
||||
*/
|
||||
#[\Route('/index')]
|
||||
public function index() {
|
||||
return "<h1>hello!</h1>"; // 快速的 HTTP 服务开发
|
||||
}
|
||||
```
|
||||
|
||||
## 开始
|
||||
框架首先需要部署环境,可以参考下方文档中部署环境和框架的方法进行。
|
||||
|
||||
## 文档(v2 版本)
|
||||
查看文档:[https://docs-v2.zhamao.xin/](https://docs-v2.zhamao.xin/)
|
||||
框架目前支持 Linux、WSL、macOS、Windows 环境直接运行,其中 Linux、macOS 环境可直接使用下方一键安装脚本。
|
||||
|
||||
备用链接:[https://docs-v2.zhamao.me/](https://docs-v2.zhamao.me/)
|
||||
> 如果你想在其他环境安装部署,可使用 Docker 快速部署或手动安装环境后安装框架,详见文档。
|
||||
|
||||
自行构建文档:`mkdocs build -d distribute`
|
||||
```bash
|
||||
# Linux、macOS 下一键检测 PHP 环境、安装框架
|
||||
bash <(curl -fsSL https://zhamao.xin/v3.sh)
|
||||
|
||||
# 启动框架
|
||||
cd zhamao-v3
|
||||
./zhamao server
|
||||
```
|
||||
|
||||
一键安装脚本还有可以自定义参数的方式,比如:
|
||||
|
||||
```bash
|
||||
# 脚本默认会检测系统的PHP,如果想直接跳过检测,安装独立的PHP版本,则添加此环境变量
|
||||
export ZM_NO_LOCAL_PHP="yes"
|
||||
# 脚本如果安装独立版本PHP,默认版本为8.1,如果想使用其他版本,则添加此环境变量指定版本
|
||||
export ZM_DOWN_PHP_VERSION="8.2"
|
||||
# 脚本默认会将框架在当前目录下的 `zhamao-app` 目录进行安装,如果想使用其他目录,则添加此环境变量
|
||||
export ZM_CUSTOM_DIR="my-custom-app"
|
||||
# 脚本默认会对本项目使用阿里云国内加速镜像,如果想使用packagist源,则添加此环境变量
|
||||
export ZM_COMPOSER_PACKAGIST="yes"
|
||||
# 执行完前面的环境变量再执行一键安装脚本,就可以实现自定义参数!
|
||||
bash <(curl -fsSL https://zhamao.xin/v3.sh)
|
||||
```
|
||||
|
||||
关于其他安装方式,请参阅 [文档](https://framework.zhamao.xin/guide/installation.html) 。
|
||||
|
||||
## 文档
|
||||
|
||||
查看文档(国内自建):<https://framework.zhamao.xin/>
|
||||
|
||||
备用链接(国外托管):<https://framework.zhamao.me/>
|
||||
|
||||
## 特点
|
||||
- 支持多账号
|
||||
- 使用 Swoole 多工作进程机制和协程加持,尽可能简单的情况下提升了性能
|
||||
- 灵活的注解事件绑定机制
|
||||
- 支持下断点调试(Psysh)
|
||||
- 易用的上下文,模块内随处可用
|
||||
- 采用模块化编写,可单独拆装功能
|
||||
- 常驻内存,全局缓存变量随处使用
|
||||
- 自带 MySQL、Redis 等数据库连接池等数据库连接方案
|
||||
- 自带 HTTP 服务器、WebSocket 服务器可复用,可以构建属于自己的 HTTP API 接口
|
||||
- 静态文件服务器
|
||||
|
||||
## 从 v1 升级
|
||||
炸毛框架 v2 相对 v1 版本改动了不少内容,其中包括框架底层机制、注解事件分发、调试、命名空间等变化,详情可查看上方文档。
|
||||
|
||||
如果旧版框架使用过程中无问题且对新功能暂无需求,可以继续使用 v1 版本,后续也将维护安全类更新和修复致命 bug。
|
||||
- 原生支持多个机器人客户端同时连接
|
||||
- 灵活的注解事件绑定机制,可同时使用 Annotation 和原生 Attribute 注解
|
||||
- 完善的插件系统,可编写插件后打包或分发,供他人使用
|
||||
- 采用插件化编写,可自由搭配其他 Composer 组件,也可单文件面向过程编写
|
||||
- 支持模块打包、热加载,分享模块更方便
|
||||
- 常驻内存,全局缓存变量随处使用,提供多种缓存方案
|
||||
- 自带 MySQL、SQLite、Redis 等数据库连接池
|
||||
- 本身为 HTTP 服务器、WebSocket 服务器,可以构建属于自己的 HTTP API 接口
|
||||
- 可选自带 PHP 环境,无需手动编译安装,by [crazywhalecc/static-php-cli](https://github.com/crazywhalecc/static-php-cli)
|
||||
|
||||
## 贡献和捐赠
|
||||
如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。目前项目仅一人维护,耗费精力较大,所以非常欢迎对框架的贡献。
|
||||
|
||||
如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。
|
||||
|
||||
目前项目仅两人维护,耗费精力较大,所以非常欢迎对框架的贡献。
|
||||
|
||||
本项目为作者闲暇时间开发,如果觉得好用,不妨进行捐助~你的捐助会让我更加有动力完善插件,感谢你的支持!
|
||||
|
||||
我们会将捐赠的资金用于本项目驱动的炸毛机器人和框架文档的服务器开销上。
|
||||
我们会将捐赠的资金用于本项目驱动的炸毛机器人和框架文档的服务器开销上。[捐赠列表](https://github.com/zhamao-robot/thanks)
|
||||
|
||||
如果您不想直接参与框架的开发,也可以分享你编写的模块,帮助完善框架生态。
|
||||
|
||||
### 支付宝
|
||||

|
||||
|
||||
如果你对我们的周边感兴趣,我们还有炸毛机器人定制 logo 的雨伞,详情咨询作者 QQ,我们会作为您捐助了本项目!
|
||||

|
||||
|
||||
## 关于
|
||||
|
||||
框架和 SDK 是 炸毛机器人 项目的核心框架开源部分。炸毛机器人是作者写的一个高性能机器人,曾获全国计算机设计大赛一等奖。
|
||||
|
||||
作者的炸毛机器人已从2018年初起稳定运行了**三年**,并且持续迭代。
|
||||
作者的炸毛机器人已从2018年初起稳定运行了**五年**,并且持续迭代。
|
||||
|
||||
欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ(627577391)或提交 Issue 进行疑难解答。
|
||||
可以提交 [Issue](https://github.com/zhamao-robot/zhamao-framework/issues/new/choose) 或 [加群(670821194)](https://jq.qq.com/?_wv=1027&k=YkNI3AIr) 进行疑难解答。
|
||||
|
||||
本项目在更新内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。
|
||||
本项目在更新内容时,请及时关注 GitHub 动态,更新前请将自己的插件或项目代码做好备份。
|
||||
|
||||
项目框架采用 Apache-2.0 协议开源,在分发或重写修改等操作时需遵守协议。项目模块部分(`Module` 文件夹) 在非借鉴框架内代码时可不遵守 Apache-2.0 协议进行分发和修改(声明版权)。
|
||||
项目框架采用 Apache-2.0 协议开源,在分发或重写修改等操作时需遵守协议。项目插件部分(除 `src/Globals`、`src/ZM` 文件夹外的其他文件夹) 在非借鉴框架内代码时可不遵守 Apache-2.0 协议进行分发和修改(声明版权)。
|
||||
|
||||
**注意**:在你使用 mirai 等 `AGPL-3.0` 协议的机器人软件与框架连接时,使用本框架需要将你编写或修改的部分使用 `AGPL-3.0` 协议重新分发。
|
||||
|
||||
在贡献代码时,请保管好自己的全局配置文件中的敏感信息,请勿将带有个人信息的配置文件上传 GitHub 等网站。
|
||||
|
||||
感谢 JetBrains 为此开源项目提供 PhpStorm 开发工具支持:
|
||||
|
||||
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/PhpStorm.svg" width="300">
|
||||
|
||||
感谢开发者 @sunxyw 中为项目开发规范化提出的一些建议。
|
||||
|
||||

|
||||
|
||||
14
SECURITY.md
@@ -1,14 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.0 | :white_check_mark: |
|
||||
| 1.6.x | :white_check_mark: |
|
||||
| 1.1.x | :x: |
|
||||
| 1.0.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a bug which is safety related, you should post a new issue named **Security Issue**, and I will check it as soon as possible.
|
||||
@@ -1,65 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Copyright: Swlib
|
||||
* Author: Twosee <twose@qq.com>
|
||||
* Date: 2018/4/14 下午10:58
|
||||
*/
|
||||
|
||||
Co::set([
|
||||
'log_level' => SWOOLE_LOG_INFO,
|
||||
'trace_flags' => 0
|
||||
]);
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
foreach ([
|
||||
__DIR__ . '/../../../autoload.php',
|
||||
__DIR__ . '/../../autoload.php',
|
||||
__DIR__ . '/../vendor/autoload.php',
|
||||
__DIR__ . '/vendor/autoload.php'
|
||||
] as $file
|
||||
) {
|
||||
if (file_exists($file)) {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', $file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
|
||||
fwrite(
|
||||
STDERR,
|
||||
'You need to set up the project dependencies using Composer:' . PHP_EOL . PHP_EOL .
|
||||
' composer install' . PHP_EOL . PHP_EOL .
|
||||
'You can learn all about Composer on https://getcomposer.org/.' . PHP_EOL
|
||||
);
|
||||
|
||||
die(1);
|
||||
} else {
|
||||
if (array_reverse(explode('/', __DIR__))[0] ?? '' === 'test') {
|
||||
$vendor_dir = dirname(PHPUNIT_COMPOSER_INSTALL);
|
||||
$bin_unit = "{$vendor_dir}/bin/phpunit";
|
||||
$unit_uint = "{$vendor_dir}/phpunit/phpunit/phpunit";
|
||||
if (file_exists($bin_unit)) {
|
||||
@unlink($bin_unit);
|
||||
@symlink(__FILE__, $bin_unit);
|
||||
}
|
||||
if (file_exists($unit_uint)) {
|
||||
@unlink($unit_uint);
|
||||
@symlink(__FILE__, $unit_uint);
|
||||
}
|
||||
}
|
||||
}
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
$starttime = microtime(true);
|
||||
go(function (){
|
||||
try{
|
||||
PHPUnit\TextUI\Command::main(false);
|
||||
} catch(Exception $e) {
|
||||
echo $e->getMessage().PHP_EOL;
|
||||
}
|
||||
});
|
||||
Swoole\Event::wait();
|
||||
echo "Took ".round(microtime(true) - $starttime, 4). "s\n";
|
||||
65
bin/phpunit-zm
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/** For Swoole coroutine tests */
|
||||
|
||||
// 如果改成 true,则会在终端显示所有炸毛框架的 Log
|
||||
const ZM_TEST_LOG_DEBUG = false;
|
||||
|
||||
use OneBot\Driver\Event\Process\WorkerStartEvent;
|
||||
use PHPUnit\TextUI\Command;
|
||||
use Swoole\Atomic;
|
||||
use Tests\ZMResultPrinter;
|
||||
use ZM\Command\Server\ServerStartCommand;
|
||||
use ZM\Event\EventProvider;
|
||||
use ZM\Framework;
|
||||
use ZM\Logger\ConsoleLogger;
|
||||
use ZM\Store\MockAtomic;
|
||||
|
||||
// 引入自动加载
|
||||
require $_composer_autoload_path ?? dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
// 声明一个全局的原子计数,用于保存整个进程的退出状态码
|
||||
global $_swoole_atomic;
|
||||
// Windows 没有 Swoole,所以就使用了一个假的原子计数器,反正 Windows 只有单进程
|
||||
$_swoole_atomic = class_exists('\\Swoole\\Atomic') ? new Atomic() : new MockAtomic();
|
||||
|
||||
// 注册 Logger 等级
|
||||
ob_logger_register(new ConsoleLogger(ZM_TEST_LOG_DEBUG ? 'debug' : 'error'));
|
||||
|
||||
// 使用框架自身的 EventProvider
|
||||
global $ob_event_provider;
|
||||
$ob_event_provider = new EventProvider();
|
||||
|
||||
// 注册一个最低级别的 WorkerStart 事件,用于在框架的事件初始化之后开始运行 PHPUnit
|
||||
ob_event_provider()->addEventListener(WorkerStartEvent::getName(), function () {
|
||||
register_shutdown_function(function () {
|
||||
$error = error_get_last();
|
||||
// 下面这段代码的作用就是,不是错误引发的退出时照常退出即可
|
||||
if (($error['type'] ?? 0) != 0) {
|
||||
logger()->emergency(zm_internal_errcode('E00027') . 'Internal fatal error: ' . $error['message'] . ' at ' . $error['file'] . "({$error['line']})");
|
||||
}
|
||||
Framework::getInstance()->stop();
|
||||
});
|
||||
$_SERVER['argv'][] = '--printer';
|
||||
$_SERVER['argv'][] = ZMResultPrinter::class;
|
||||
try {
|
||||
// 不退出,而是返回 code
|
||||
$retcode = Command::main(false);
|
||||
} finally {
|
||||
global $_swoole_atomic;
|
||||
$_swoole_atomic->set($retcode ?? 0);
|
||||
Framework::getInstance()->stop($retcode ?? 0);
|
||||
}
|
||||
}, 1);
|
||||
|
||||
try {
|
||||
$options = ServerStartCommand::exportOptionArray();
|
||||
$options['driver'] = 'workerman';
|
||||
$options['worker-num'] = 1;
|
||||
$options['private-mode'] = true;
|
||||
(new Framework($options))->init()->start();
|
||||
exit($_swoole_atomic->get());
|
||||
} catch (Throwable $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
3
bin/phpunit-zm.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
php %~dp0phpunit-zm %*
|
||||
76
bin/prepare-doxygen
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
function find_valid_root(string $current_dir, int $max_loop): string
|
||||
{
|
||||
if ($max_loop <= 0) {
|
||||
return '';
|
||||
}
|
||||
if (!file_exists($current_dir . '/composer.json')) {
|
||||
return find_valid_root(dirname($current_dir), $max_loop - 1);
|
||||
}
|
||||
return $current_dir;
|
||||
}
|
||||
|
||||
$root = find_valid_root(getcwd(), 3);
|
||||
if (empty($root)) {
|
||||
echo '找不到有效的根目录';
|
||||
exit(1);
|
||||
}
|
||||
|
||||
chdir($root);
|
||||
|
||||
require_once $root . '/vendor/autoload.php';
|
||||
|
||||
// 获取参数,可为 before 或 after
|
||||
$param = $argv[1] ?? 'before';
|
||||
|
||||
if ($param === 'before') {
|
||||
// Doxyfile file source: https://pastebin.com/raw/N8tJ9kWE
|
||||
// Posted by: sunxyw
|
||||
|
||||
// 从剪贴板获取 Doxyfile 内容并保存到临时文件
|
||||
logger()->info('正在获取 Doxyfile 内容');
|
||||
$doxyfile = file_get_contents('https://pastebin.com/raw/N8tJ9kWE');
|
||||
$doxyfile = str_replace('<zm_version>', \ZM\Framework::VERSION, $doxyfile);
|
||||
file_put_contents('Doxyfile', $doxyfile);
|
||||
|
||||
// 应用 Awesome 样式
|
||||
// 来源:https://github.com/jothepro/doxygen-awesome-css.git
|
||||
// 优先使用本地文件
|
||||
logger()->info('正在应用 Awesome 样式');
|
||||
\ZM\Store\FileSystem::createDir('doxy/css');
|
||||
if (
|
||||
file_exists('doxy/css/doxygen-awesome.css') &&
|
||||
(md5_file('doxy/css/doxygen-awesome.css') === '326a1447f9693d1b3876f59de837a7c0')
|
||||
) {
|
||||
logger()->info('本地 Awesome 样式文件已存在,跳过下载');
|
||||
} else {
|
||||
logger()->info('正在下载 Awesome 样式文件');
|
||||
$cmd = [
|
||||
'git clone https://github.com/jothepro/doxygen-awesome-css.git doxy/css',
|
||||
'cd doxy/css',
|
||||
'git checkout v2.1.0',
|
||||
'cd ../..',
|
||||
];
|
||||
$cmd = implode(' && ', $cmd);
|
||||
exec($cmd);
|
||||
}
|
||||
} elseif ($param === 'after') {
|
||||
// 删除临时文件
|
||||
unlink('Doxyfile');
|
||||
|
||||
// 清除旧文档
|
||||
exec('rm -rf docs/.vuepress/public/doxy');
|
||||
|
||||
// 授予目录权限:doxy/html
|
||||
exec('sudo chmod -R 644 doxy/html');
|
||||
|
||||
// 移动新文档到 docs 目录
|
||||
exec('mv doxy/html docs/.vuepress/public/doxy');
|
||||
|
||||
logger()->info('文档生成完成');
|
||||
} else {
|
||||
logger()->error('参数错误');
|
||||
exit(1);
|
||||
}
|
||||
14
bin/start
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (!is_dir(__DIR__ . '/../vendor')) {
|
||||
define("LOAD_MODE", 1); //composer项目模式
|
||||
define("LOAD_MODE_COMPOSER_PATH", getcwd());
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require_once LOAD_MODE_COMPOSER_PATH . "/vendor/autoload.php";
|
||||
} else {
|
||||
define("LOAD_MODE", 0); //源码模式
|
||||
require_once __DIR__ . "/../vendor/autoload.php";
|
||||
}
|
||||
|
||||
(new ZM\ConsoleApplication("zhamao-framework"))->initEnv()->run();
|
||||
31
bin/systemd
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php /** @since 1.2 */
|
||||
switch ($argv[1] ?? '') {
|
||||
case '--generate':
|
||||
case '':
|
||||
generate($argv);
|
||||
break;
|
||||
case '--help':
|
||||
case '-h':
|
||||
default:
|
||||
echo "\nUsage: " . $argv[0] . " [OPTION]\n";
|
||||
echo "\nzhamao-framework systemd generator.";
|
||||
echo "\n\n -h, --help\t\tShow this help menu";
|
||||
echo "\n --generate\tGenerate a systemd service file\n\n";
|
||||
break;
|
||||
}
|
||||
|
||||
function generate($argv) {
|
||||
$s = "[Unit]\nDescription=zhamao-framework Daemon\nAfter=rc-local.service\n\n[Service]\nType=simple";
|
||||
$s .= "\nUser=" . exec("whoami");
|
||||
$s .= "\nGroup=" . exec("groups | awk '{print $1}'");
|
||||
$s .= "\nWorkingDirectory=" . getcwd();
|
||||
if ($argv[0] == "systemd" && !file_exists(getcwd() . '/systemd'))
|
||||
$s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server --disable-console-input";
|
||||
else
|
||||
$s .= "\nExecStart=" . getcwd() . "/bin/start server --disable-console-input";
|
||||
$s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n";
|
||||
@mkdir(getcwd() . "/resources/");
|
||||
file_put_contents(getcwd() . "/resources/zhamao.service", $s);
|
||||
echo "File successfully generated. Path: " . getcwd() . "/resources/zhamao.service\n";
|
||||
}
|
||||
118
bin/watcher
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_red="\e[33m"
|
||||
_green="\e[32m"
|
||||
_reset="\e[0m"
|
||||
|
||||
unix_s=$(uname -s)
|
||||
|
||||
unix_release=$(
|
||||
marked_release=""
|
||||
if [ "$unix_s" = "Linux" ]; then
|
||||
echo $HOME | grep com.termux >/dev/null
|
||||
if [ $? == 0 ]; then
|
||||
marked_release="termux"
|
||||
elif [ -f "/etc/redhat-release" ]; then
|
||||
if [ "$(cat /etc/redhat-release | awk '{print $1}' | grep -v '^$')" = "CentOS" ]; then
|
||||
marked_release="CentOS"
|
||||
else
|
||||
marked_release="unknown"
|
||||
fi
|
||||
elif [ -f "/etc/os-release" ]; then
|
||||
cat /etc/os-release | grep Alpine > /dev/null
|
||||
if [ $? == 0 ]; then
|
||||
marked_release="Alpine"
|
||||
fi
|
||||
fi
|
||||
if [ "$marked_release" = "" ]; then
|
||||
if [ -f "/etc/issue" ]; then
|
||||
marked_release=$(cat /etc/issue | grep -v '^$' | awk '{print $1}')
|
||||
else
|
||||
marked_release="unknown"
|
||||
fi
|
||||
fi
|
||||
elif [ "$unix_s" = "Darwin" ]; then
|
||||
marked_release=$(sw_vers | grep ProductName | awk '{print $2" "$3" "$4}')
|
||||
fi
|
||||
echo $marked_release
|
||||
)
|
||||
unix_release=$(echo $unix_release | xargs)
|
||||
|
||||
function echo_error() {
|
||||
echo -e "${_red}$1${_reset}"
|
||||
}
|
||||
|
||||
function echo_info() {
|
||||
echo -e "${_green}$1${_reset}"
|
||||
}
|
||||
|
||||
function install_test() {
|
||||
which fswatch >/dev/null
|
||||
if [ $? != 0 ]; then
|
||||
operate_confirm "fswatch还没有安装,是否确认安装?" && install_fswatch
|
||||
fi
|
||||
}
|
||||
function install_fswatch() {
|
||||
if [ "$unix_s" = "Linux" ]; then
|
||||
case $unix_release in
|
||||
"Kali" | "Ubuntu" | "Debian" | "Raspbian" | 'Pop!_OS')
|
||||
sudo apt-get install fswatch -y ;;
|
||||
#"termux") pkg install $1 -y ;;
|
||||
"CentOS")
|
||||
curl -o fswatch.tgz https://download.fastgit.org/emcrisostomo/fswatch/releases/download/1.16.0/fswatch-1.16.0.tar.gz && \
|
||||
tar -xzf fswatch.tgz && \
|
||||
cd fswatch-1.16.0 && \
|
||||
./configure && \
|
||||
make && \
|
||||
sudo make install && \
|
||||
cd .. && \
|
||||
rm -rf fswatch.tgz fswatch-1.16.0
|
||||
;;
|
||||
#"Alpine") apk add $1 ;;
|
||||
*)
|
||||
echo_error "不支持的 Linux 发行版:$unix_release"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
elif [ "$unix_s" = "Darwin" ]; then
|
||||
brew install fswatch
|
||||
else
|
||||
echo_error "不支持的操作系统:$unix_s"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
function operate_confirm() {
|
||||
echo -n $(echo_info "$1 [Y/n] ")
|
||||
read operate
|
||||
operate=$(echo $operate | tr A-Z a-z)
|
||||
if [[ "$operate" = "y" || "$operate" = "" ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo_info "当前系统:$unix_release"
|
||||
install_test
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
else
|
||||
echo_info "程序路径:$(which fswatch)"
|
||||
fi
|
||||
watch_dir="./src"
|
||||
if [ ! -d "$watch_dir" ]; then
|
||||
echo_error "src目录不存在!"
|
||||
exit 1
|
||||
else
|
||||
echo_info "监听目录:$watch_dir"
|
||||
fi
|
||||
_pid=$(cat .daemon_pid | awk -F"\"pid\": " '{print $2}' | grep -v ^$ | sed 's/,//g')
|
||||
if [ "$_pid" = "" ]; then
|
||||
echo_error "未检测到框架进程"
|
||||
exit 1
|
||||
fi
|
||||
fswatch $watch_dir | while read file
|
||||
do
|
||||
echo "Detect file change: $file"
|
||||
kill -USR1 $_pid
|
||||
done
|
||||
31
bin/zhamao
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
|
||||
# shellcheck disable=SC2068
|
||||
# shellcheck disable=SC2181
|
||||
# author: crazywhalecc
|
||||
# since: 2.5.0
|
||||
|
||||
if [ "$ZM_CUSTOM_PHP_PATH" != "" ]; then
|
||||
echo "* Using PHP executable: ""$ZM_CUSTOM_PHP_PATH"
|
||||
executable="$ZM_CUSTOM_PHP_PATH"
|
||||
elif [ -f "$(pwd)/runtime/php" ]; then
|
||||
executable="$(pwd)/runtime/php"
|
||||
echo "* Framework started with built-in php."
|
||||
else
|
||||
which php >/dev/null 2>&1
|
||||
if [ $? -eq 0 ]; then
|
||||
executable=$(which php)
|
||||
else
|
||||
echo '[ErrCode:E00014] Cannot find any PHP runtime, please use command "./install-runtime.sh" or install PHP manually!'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$(pwd)/src/entry.php" ]; then
|
||||
$executable "$(pwd)/src/entry.php" $@
|
||||
elif [ -f "$(pwd)/vendor/zhamao/framework/src/entry.php" ]; then
|
||||
$executable "$(pwd)/vendor/zhamao/framework/src/entry.php" $@
|
||||
else
|
||||
echo "[ErrCode:E00015] Cannot find zhamao-framework entry file!"
|
||||
exit 1
|
||||
fi
|
||||
27
bin/zhamao.bat
Normal file
@@ -0,0 +1,27 @@
|
||||
@echo off
|
||||
@REM Check if ZM_CUSTOM_PHP_PATH is set
|
||||
IF /i "%ZM_CUSTOM_PHP_PATH%" neq "" (
|
||||
@REM Set the path to the custom PHP
|
||||
echo "* Using custom PHP executable: %ZM_CUSTOM_PHP_PATH%"
|
||||
SET executable=%ZM_CUSTOM_PHP_PATH%
|
||||
) ELSE IF exist ./runtime/php.exe (
|
||||
@REM Set the path to the built-in PHP
|
||||
echo "* Using built-in PHP executable"
|
||||
SET executable=.\runtime\php.exe
|
||||
) ELSE (
|
||||
@REM Set the path to the system PHP
|
||||
echo "* Using system PHP executable"
|
||||
SET executable=php
|
||||
)
|
||||
|
||||
IF exist src/entry.php (
|
||||
@REM Run the PHP entry point
|
||||
%executable% src/entry.php %*
|
||||
) ELSE IF exist vendor/zhamao/framework/src/entry.php (
|
||||
@REM Run the PHP entry point
|
||||
%executable% vendor/zhamao/framework/src/entry.php %*
|
||||
) ELSE (
|
||||
@REM No entry point found
|
||||
echo "[ErrCode:E00015] Cannot find zhamao-framework entry file!"
|
||||
exit /b 1
|
||||
)
|
||||
47
captainhook.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"pre-push": {
|
||||
"enabled": true,
|
||||
"actions": [
|
||||
{
|
||||
"action": "composer analyse"
|
||||
},
|
||||
{
|
||||
"action": "composer test"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pre-commit": {
|
||||
"enabled": true,
|
||||
"actions": [
|
||||
{
|
||||
"action": "composer cs-fix -- --config=.php-cs-fixer.php --dry-run --diff {$STAGED_FILES|of-type:php}",
|
||||
"conditions": [
|
||||
{
|
||||
"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileStaged\\OfType",
|
||||
"args": ["php"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"post-change": {
|
||||
"enabled": true,
|
||||
"actions": [
|
||||
{
|
||||
"action": "composer install",
|
||||
"options": [],
|
||||
"conditions": [
|
||||
{
|
||||
"exec": "\\CaptainHook\\App\\Hook\\Condition\\FileChanged\\Any",
|
||||
"args": [
|
||||
[
|
||||
"composer.json",
|
||||
"composer.lock"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
117
composer.json
@@ -1,58 +1,103 @@
|
||||
{
|
||||
"name": "zhamao/framework",
|
||||
"description": "High performance QQ robot and web server development framework",
|
||||
"minimum-stability": "stable",
|
||||
"description": "High performance chat robot and web server development framework",
|
||||
"license": "Apache-2.0",
|
||||
"version": "2.2.0",
|
||||
"extra": {
|
||||
"exclude_annotate": [
|
||||
"src/ZM"
|
||||
]
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "whale",
|
||||
"email": "crazysnowcc@gmail.com"
|
||||
"name": "jerry",
|
||||
"email": "admin@zhamao.me"
|
||||
},
|
||||
{
|
||||
"name": "swift",
|
||||
"email": "hugo_swift@yahoo.com"
|
||||
"name": "sunxyw",
|
||||
"email": "dev@sunxyw.xyz"
|
||||
}
|
||||
],
|
||||
"prefer-stable": true,
|
||||
"bin": [
|
||||
"bin/start",
|
||||
"bin/phpunit-swoole"
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"doctrine/annotations": "~1.10",
|
||||
"php": "^8.0 || ^8.1 || ^8.2",
|
||||
"ext-json": "*",
|
||||
"psy/psysh": "@stable",
|
||||
"symfony/polyfill-ctype": "^1.20",
|
||||
"symfony/polyfill-mbstring": "^1.20",
|
||||
"symfony/console": "^5.1",
|
||||
"zhamao/connection-manager": "*@dev",
|
||||
"zhamao/console": "^1.0",
|
||||
"zhamao/config": "^1.0",
|
||||
"zhamao/request": "*@dev",
|
||||
"symfony/routing": "^5.1",
|
||||
"symfony/polyfill-php80": "^1.20",
|
||||
"ext-posix": "*"
|
||||
"ext-tokenizer": "*",
|
||||
"doctrine/dbal": "^2.13.1",
|
||||
"dragonmantank/cron-expression": "^3.3",
|
||||
"jelix/version": "^2.0",
|
||||
"koriym/attributes": "^1.0",
|
||||
"nunomaduro/collision": "^6.3",
|
||||
"onebot/libonebot": "^0.5",
|
||||
"php-di/php-di": "^7",
|
||||
"psr/container": "^2.0",
|
||||
"psr/simple-cache": "^3.0",
|
||||
"psy/psysh": "^0.11.8",
|
||||
"symfony/console": "^6.0",
|
||||
"symfony/polyfill-ctype": "^1.19",
|
||||
"symfony/polyfill-mbstring": "^1.19",
|
||||
"symfony/routing": "~6.0 || ~5.0 || ~4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"captainhook/captainhook": "^5.12",
|
||||
"friendsofphp/php-cs-fixer": "^3.2 != 3.7.0",
|
||||
"jangregor/phpstan-prophecy": "^1.0",
|
||||
"jetbrains/phpstorm-attributes": "^1.0",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "^1.1",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.1",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
"swoole/ide-helper": "^4.5"
|
||||
},
|
||||
"replace": {
|
||||
"symfony/polyfill-php80": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "*",
|
||||
"ext-mbstring": "*"
|
||||
"ext-ctype": "Use C/C++ extension instead of polyfill will be more efficient",
|
||||
"ext-mbstring": "Use C/C++ extension instead of polyfill will be more efficient",
|
||||
"ext-pdo_mysql": "If you use mysql in framework, you will need this extension",
|
||||
"ext-redis": "If you use Redis in framework, you will need this extension",
|
||||
"league/climate": "Display columns and status in terminal"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ZM\\": "src/ZM"
|
||||
},
|
||||
"files": [
|
||||
"src/ZM/global_functions.php"
|
||||
"src/Globals/global_functions.php",
|
||||
"src/Globals/global_defines_app.php",
|
||||
"src/Globals/global_class_alias.php"
|
||||
]
|
||||
},
|
||||
"require-dev": {
|
||||
"swoole/ide-helper": "@dev"
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Module\\": "src/Module",
|
||||
"Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"bin/phpunit-zm",
|
||||
"bin/zhamao",
|
||||
"bin/zhamao.bat"
|
||||
],
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"phpstan/extension-installer": true
|
||||
},
|
||||
"optimize-autoloader": true,
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"zm": {
|
||||
"exclude-annotation-path": [
|
||||
"src/ZM",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": "vendor/bin/captainhook install -f -s",
|
||||
"analyse": "phpstan analyse --memory-limit 300M",
|
||||
"cs-fix": "PHP_CS_FIXER_FUTURE_MODE=1 php-cs-fixer fix",
|
||||
"test": "bin/phpunit-zm --no-coverage"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
config/config.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Config 配置类的配置文件
|
||||
* 由于 Config 类是第一批被加载的类,因此本文件存在以下限制:
|
||||
* 1. 只能使用 PHP 格式
|
||||
* 2. 无法利用容器及依赖注入
|
||||
* 3. 必须存在于本地,无法使用远程配置(后续版本可能会支持)
|
||||
*/
|
||||
return [
|
||||
'repository' => [
|
||||
\OneBot\Config\Repository::class, // 配置仓库,须实现 \OneBot\Config\RepositoryInterface 接口
|
||||
[], // 传入的参数,依序传入构造函数
|
||||
],
|
||||
'loader' => [
|
||||
\OneBot\Config\Loader\DelegateLoader::class, // 配置加载器,须实现 \OneBot\Config\LoaderInterface 接口
|
||||
[], // 传入的参数,依序传入构造函数
|
||||
],
|
||||
'source' => [
|
||||
'extensions' => ['php', 'yaml', 'yml', 'json', 'toml'], // 配置文件扩展名
|
||||
'paths' => [
|
||||
SOURCE_ROOT_DIR . '/config', // 配置文件所在目录
|
||||
// 可以添加多个配置文件目录
|
||||
],
|
||||
],
|
||||
];
|
||||
28
config/container.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use OneBot\Driver\Driver;
|
||||
use OneBot\Driver\Process\ProcessManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZM\Framework;
|
||||
|
||||
/*
|
||||
* 这里是容器的配置文件,你可以在这里配置容器的绑定和其他一些参数。
|
||||
* 选用的容器是 PHP-DI,你可以在这里查看文档:https://php-di.org/doc/
|
||||
* 我们建议你在使用容器前先阅读以下章节:
|
||||
* - 基本使用方式:https://php-di.org/doc/container.html
|
||||
* - 注册绑定方式:https://php-di.org/doc/php-definitions.html#definition-types
|
||||
* - 最佳使用实践:https://php-di.org/doc/best-practices.html
|
||||
* 同时,你也可以选择查阅我们(炸毛框架)的相关文档,其会包含一些简单的使用方法和示例。
|
||||
* 关于框架在不同事件(或周期中)默认注册的绑定,请参阅我们的文档,或直接查看 {@see \ZM\Container\ContainerRegistrant}
|
||||
*/
|
||||
return [
|
||||
// 这里定义的是全局容器的绑定,不建议在此处直接调用框架、应用内部的类或方法,因为这些类可能还没有被加载或初始化
|
||||
// 你可以使用匿名函数来延迟加载
|
||||
'definitions' => [
|
||||
'worker_id' => fn () => ProcessManager::getProcessId(),
|
||||
Driver::class => fn () => Framework::getInstance()->getDriver(),
|
||||
LoggerInterface::class => fn () => logger(),
|
||||
],
|
||||
];
|
||||
@@ -1,123 +1,125 @@
|
||||
<?php
|
||||
/** @noinspection PhpFullyQualifiedNameUsageInspection */
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
global $config;
|
||||
|
||||
/** bind host */
|
||||
$config['host'] = '0.0.0.0';
|
||||
declare(strict_types=1);
|
||||
|
||||
/** bind port */
|
||||
$config['port'] = 20001;
|
||||
/* 启动框架的底层驱动(原生支持 swoole、workerman 两种) */
|
||||
$config['driver'] = 'workerman';
|
||||
|
||||
/** 框架开到公网或外部的HTTP访问链接,通过 DataProvider::getFrameworkLink() 获取 */
|
||||
$config['http_reverse_link'] = "http://127.0.0.1:" . $config['port'];
|
||||
|
||||
/** 框架是否启动debug模式 */
|
||||
$config['debug_mode'] = false;
|
||||
|
||||
/** 存放框架内文件数据的目录 */
|
||||
$config['zm_data'] = realpath(__DIR__ . "/../") . '/zm_data/';
|
||||
|
||||
/** 存放各个模块配置文件的目录 */
|
||||
$config['config_dir'] = $config['zm_data'] . 'config/';
|
||||
|
||||
/** 存放崩溃和运行日志的目录 */
|
||||
$config['crash_dir'] = $config['zm_data'] . 'crash/';
|
||||
|
||||
/** 对应swoole的server->set参数 */
|
||||
$config['swoole'] = [
|
||||
'log_file' => $config['crash_dir'] . 'swoole_error.log',
|
||||
'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量
|
||||
'dispatch_mode' => 2, //包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
|
||||
'max_coroutine' => 300000,
|
||||
//'task_worker_num' => 4,
|
||||
//'task_enable_coroutine' => true
|
||||
];
|
||||
|
||||
/** 轻量字符串缓存,默认开启 */
|
||||
$config['light_cache'] = [
|
||||
'size' => 512, //最多允许储存的条数(需要2的倍数)
|
||||
'max_strlen' => 32768, //单行字符串最大长度(需要2的倍数)
|
||||
'hash_conflict_proportion' => 0.6, //Hash冲突率(越大越好,但是需要的内存更多)
|
||||
'persistence_path' => $config['zm_data'].'_cache.json',
|
||||
'auto_save_interval' => 900
|
||||
];
|
||||
|
||||
/** 大容量跨进程变量存储(2.2.0可用) */
|
||||
$config["worker_cache"] = [
|
||||
"worker" => 0,
|
||||
"transaction_timeout" => 30000
|
||||
];
|
||||
|
||||
/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
|
||||
$config['sql_config'] = [
|
||||
'sql_host' => '',
|
||||
'sql_port' => 3306,
|
||||
'sql_username' => 'name',
|
||||
'sql_database' => 'db_name',
|
||||
'sql_password' => '',
|
||||
'sql_options' => [
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_EMULATE_PREPARES => false
|
||||
/* 要启动的服务器监听端口及协议 */
|
||||
$config['servers'] = [
|
||||
[
|
||||
'host' => '0.0.0.0',
|
||||
'port' => 20001,
|
||||
'type' => 'websocket',
|
||||
],
|
||||
[
|
||||
'host' => '0.0.0.0',
|
||||
'port' => 20002,
|
||||
'type' => 'http',
|
||||
'flag' => 20002,
|
||||
],
|
||||
'sql_no_exception' => false,
|
||||
'sql_default_fetch_mode' => PDO::FETCH_ASSOC // added in 1.5.6
|
||||
];
|
||||
|
||||
/** Redis连接信息,host留空则启动时不创建Redis连接池 */
|
||||
$config['redis_config'] = [
|
||||
'host' => '',
|
||||
'port' => 6379,
|
||||
'timeout' => 1,
|
||||
'db_index' => 0,
|
||||
'auth' => ''
|
||||
/* Workerman 驱动相关配置 */
|
||||
$config['workerman_options'] = [
|
||||
'worker_num' => 1, // 如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量
|
||||
];
|
||||
|
||||
/** onebot连接约定的token */
|
||||
$config["access_token"] = '';
|
||||
|
||||
/** HTTP服务器固定请求头的返回 */
|
||||
$config['http_header'] = [
|
||||
'Server' => 'zhamao-framework',
|
||||
'Content-Type' => 'text/html; charset=utf-8'
|
||||
/* Swoole 驱动相关配置 */
|
||||
$config['swoole_options'] = [
|
||||
'coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL), // 协程 Hook 内容
|
||||
'swoole_set' => [
|
||||
'worker_num' => 1, // 如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量
|
||||
'dispatch_mode' => 2, // 包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
|
||||
'max_coroutine' => 300000, // 允许最大的协程数
|
||||
'max_wait_time' => 5, // 安全退出模式下允许等待 Worker 的最长秒数
|
||||
// 'task_worker_num' => 4, // 启动 TaskWorker 进程的数量(默认不启动)
|
||||
// 'task_enable_coroutine' => true // TaskWorker 是否开启协程
|
||||
],
|
||||
'swoole_server_mode' => SWOOLE_PROCESS, // Swoole Server 启动模式,默认为 SWOOLE_PROCESS
|
||||
];
|
||||
|
||||
/** HTTP服务器在指定状态码下回复的页面(默认) */
|
||||
$config['http_default_code_page'] = [
|
||||
'404' => '404.html'
|
||||
/* 默认存取炸毛数据的目录(相对目录时,代表WORKING_DIR下的目录,绝对目录按照绝对目录来) */
|
||||
$config['data_dir'] = WORKING_DIR . '/zm_data';
|
||||
|
||||
/* 框架本体运行时的一些可调配置 */
|
||||
$config['runtime'] = [
|
||||
'reload_delay_time' => 800,
|
||||
'annotation_reader_ignore' => [ // 设置注解解析器忽略的注解名或命名空间,防止解析到不该解析的
|
||||
'name' => [
|
||||
'mixin',
|
||||
],
|
||||
'namespace' => [],
|
||||
],
|
||||
'timezone' => 'Asia/Shanghai',
|
||||
];
|
||||
|
||||
/** zhamao-framework在框架启动时初始化的atomic们 */
|
||||
$config['init_atomics'] = [
|
||||
//'custom_atomic_name' => 0, //自定义添加的Atomic
|
||||
];
|
||||
|
||||
/** 终端日志显示等级(0-4) */
|
||||
$config["info_level"] = 2;
|
||||
|
||||
/** 上下文接口类 implemented from ContextInterface */
|
||||
/* 上下文接口类 implemented from ContextInterface */
|
||||
$config['context_class'] = \ZM\Context\Context::class;
|
||||
|
||||
/** 静态文件访问 */
|
||||
$config['static_file_server'] = [
|
||||
'status' => false,
|
||||
'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
|
||||
'document_index' => [
|
||||
'index.html'
|
||||
]
|
||||
/* 允许加载插件形式 */
|
||||
$config['plugin'] = [
|
||||
'enable' => true,
|
||||
'load_dir' => 'plugins',
|
||||
];
|
||||
|
||||
/** 注册 Swoole Server 事件注解的类列表 */
|
||||
$config['server_event_handler_class'] = [
|
||||
\ZM\Event\ServerEventHandler::class,
|
||||
/* 内部默认启用的插件 */
|
||||
$config['native_plugin'] = [
|
||||
'onebot12' => true, // OneBot v12 协议支持
|
||||
'onebot12-ban-other-ws' => true, // OneBot v12 协议支持,禁止其他 WebSocket 连接
|
||||
'command-manual' => true,
|
||||
];
|
||||
|
||||
/** 服务器启用的外部第三方和内部插件 */
|
||||
$config['modules'] = [
|
||||
'onebot' => [
|
||||
'status' => true,
|
||||
'single_bot_mode' => false
|
||||
], // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭
|
||||
/* 静态文件读取器 */
|
||||
$config['file_server'] = [
|
||||
'enable' => true,
|
||||
'document_root' => $config['data_dir'] . '/public/',
|
||||
'document_index' => 'index.html',
|
||||
'document_code_page' => [
|
||||
'404' => '404.html',
|
||||
'500' => '500.html',
|
||||
],
|
||||
];
|
||||
|
||||
/* MySQL 和 SQLite3 数据库连接配置,框架将自动生成连接池,支持多个连接池 */
|
||||
$config['database'] = [
|
||||
'sqlite_db1' => [
|
||||
'enable' => false,
|
||||
'type' => 'sqlite',
|
||||
'dbname' => 'a.db',
|
||||
'pool_size' => 10,
|
||||
],
|
||||
'default' => [
|
||||
'enable' => false,
|
||||
'type' => 'mysql',
|
||||
'host' => '127.0.0.1', // 填写数据库服务器地址后才会创建数据库连接
|
||||
'port' => 3306,
|
||||
'username' => 'root',
|
||||
'password' => 'ZhamaoTEST',
|
||||
'dbname' => 'zm',
|
||||
'charset' => 'utf8mb4',
|
||||
'pool_size' => 64,
|
||||
],
|
||||
];
|
||||
|
||||
/* Redis 连接配置,框架将自动生成连接池,支持多个连接池 */
|
||||
$config['redis'] = [
|
||||
'default' => [
|
||||
'enable' => false,
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'index' => 0,
|
||||
'auth' => '',
|
||||
'pool_size' => 10,
|
||||
],
|
||||
];
|
||||
|
||||
/* KV 数据库的配置 */
|
||||
$config['kv'] = [
|
||||
'use' => \LightCache::class, // 默认在单进程模式下使用 LightCache,多进程需要使用 ZMRedis
|
||||
'light_cache_dir' => $config['data_dir'] . '/lc', // 默认的 LightCache 保存持久化数据的位置
|
||||
'light_cache_autosave_time' => 600, // LightCache 自动保存时间(秒)
|
||||
'redis_config' => 'default',
|
||||
];
|
||||
|
||||
return $config;
|
||||
|
||||
152
config/global_old.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/* bind host */
|
||||
$config['host'] = '0.0.0.0';
|
||||
|
||||
/* bind port */
|
||||
$config['port'] = 20001;
|
||||
|
||||
/* 框架开到公网或外部的HTTP访问链接,通过 DataProvider::getFrameworkLink() 获取 */
|
||||
$config['http_reverse_link'] = 'http://127.0.0.1:' . $config['port'];
|
||||
|
||||
/* 框架是否启动debug模式,当debug模式为true时,启用热更新(需要安装inotify扩展) */
|
||||
$config['debug_mode'] = false;
|
||||
|
||||
/* 存放框架内文件数据的目录 */
|
||||
$config['zm_data'] = realpath(WORKING_DIR) . '/zm_data/';
|
||||
|
||||
/* 存放各个模块配置文件的目录 */
|
||||
$config['config_dir'] = $config['zm_data'] . 'config/';
|
||||
|
||||
/* 存放崩溃和运行日志的目录 */
|
||||
$config['crash_dir'] = $config['zm_data'] . 'crash/';
|
||||
|
||||
/* 对应swoole的server->set参数 */
|
||||
$config['swoole'] = [
|
||||
'log_file' => $config['crash_dir'] . 'swoole_error.log',
|
||||
// 'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量
|
||||
'dispatch_mode' => 2, // 包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
|
||||
'max_coroutine' => 300000,
|
||||
'max_wait_time' => 5,
|
||||
// 'task_worker_num' => 4,
|
||||
// 'task_enable_coroutine' => true
|
||||
];
|
||||
|
||||
/* 一些框架与框架运行时设置的调整 */
|
||||
$config['runtime'] = [
|
||||
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL),
|
||||
'swoole_server_mode' => SWOOLE_PROCESS,
|
||||
'middleware_error_policy' => 1,
|
||||
'reload_delay_time' => 800,
|
||||
'global_middleware_binding' => [],
|
||||
'save_console_log_file' => false, // 改为目标路径,则将 Console 输出的日志保存到文件
|
||||
'annotation_reader_ignore' => [ // 设置注解解析器忽略的注解名或命名空间,防止解析到不该解析的
|
||||
'name' => [
|
||||
'mixin',
|
||||
],
|
||||
'namespace' => [],
|
||||
],
|
||||
];
|
||||
|
||||
/* 轻量字符串缓存,默认开启 */
|
||||
$config['light_cache'] = [
|
||||
'size' => 512, // 最多允许储存的条数(需要2的倍数)
|
||||
'max_strlen' => 32768, // 单行字符串最大长度(需要2的倍数)
|
||||
'hash_conflict_proportion' => 0.6, // Hash冲突率(越大越好,但是需要的内存更多)
|
||||
'persistence_path' => $config['zm_data'] . '_cache.json',
|
||||
'auto_save_interval' => 900,
|
||||
];
|
||||
|
||||
/* 大容量跨进程变量存储(2.2.0可用) */
|
||||
$config['worker_cache'] = [
|
||||
'worker' => 0,
|
||||
'transaction_timeout' => 30000,
|
||||
];
|
||||
|
||||
/* MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
|
||||
$config['mysql_config'] = [
|
||||
'host' => '',
|
||||
'port' => 3306,
|
||||
'unix_socket' => null,
|
||||
'username' => 'root',
|
||||
'password' => '123456',
|
||||
'dbname' => '',
|
||||
'charset' => 'utf8mb4',
|
||||
'pool_size' => 64,
|
||||
'options' => [
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
],
|
||||
];
|
||||
|
||||
/* Redis连接信息,host留空则启动时不创建Redis连接池 */
|
||||
$config['redis_config'] = [
|
||||
'host' => '',
|
||||
'port' => 6379,
|
||||
'timeout' => 1,
|
||||
'db_index' => 0,
|
||||
'auth' => '',
|
||||
];
|
||||
|
||||
/* onebot连接约定的token */
|
||||
$config['access_token'] = '';
|
||||
|
||||
/* HTTP服务器固定请求头的返回 */
|
||||
$config['http_header'] = [
|
||||
'Server' => 'zhamao-framework',
|
||||
'Content-Type' => 'text/html; charset=utf-8',
|
||||
];
|
||||
|
||||
/* HTTP服务器在指定状态码下回复的页面(默认) */
|
||||
$config['http_default_code_page'] = [
|
||||
'404' => '404.html',
|
||||
];
|
||||
|
||||
/* zhamao-framework在框架启动时初始化的atomic们 */
|
||||
$config['init_atomics'] = [
|
||||
// 'custom_atomic_name' => 0, //自定义添加的Atomic
|
||||
];
|
||||
|
||||
/* 终端日志显示等级(0-4) */
|
||||
$config['info_level'] = 2;
|
||||
|
||||
/* 上下文接口类 implemented from ContextInterface */
|
||||
$config['context_class'] = \ZM\Context\Context::class;
|
||||
|
||||
/* 静态文件访问 */
|
||||
$config['static_file_server'] = [
|
||||
'status' => false,
|
||||
'document_root' => realpath(__DIR__ . '/../') . '/resources/html',
|
||||
'document_index' => [
|
||||
'index.html',
|
||||
],
|
||||
];
|
||||
|
||||
/* 机器人解析模块,关闭后无法使用如CQCommand等注解(上面的modules即将废弃) */
|
||||
$config['onebot'] = [
|
||||
'status' => true,
|
||||
'single_bot_mode' => false,
|
||||
'message_level' => 99,
|
||||
'message_convert_string' => true,
|
||||
'message_command_policy' => 'interrupt',
|
||||
];
|
||||
|
||||
/* 一个远程简易终端,使用nc直接连接即可,但是不建议开放host为0.0.0.0(远程连接) */
|
||||
$config['remote_terminal'] = [
|
||||
'status' => false,
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 20002,
|
||||
'token' => '',
|
||||
];
|
||||
|
||||
/* 模块(插件)加载器的相关设置 */
|
||||
$config['module_loader'] = [
|
||||
'enable_hotload' => false,
|
||||
'load_path' => $config['zm_data'] . 'modules',
|
||||
];
|
||||
|
||||
return $config;
|
||||
@@ -1,6 +1,6 @@
|
||||
______
|
||||
|__ / |__ __ _ _ __ ___ __ _ ___
|
||||
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
|
||||
/ /_| | | | (_| | | | | | | (_| | (_) |
|
||||
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/
|
||||
______
|
||||
|__ / |__ __ _ _ __ ___ __ _ ___
|
||||
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
|
||||
/ /_| | | | (_| | | | | | | (_| | (_) |
|
||||
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/
|
||||
|
||||
|
||||
96
docker-compose.yml
Normal file
@@ -0,0 +1,96 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
bot:
|
||||
build:
|
||||
context: docker/zhamao
|
||||
dockerfile: ./Dockerfile
|
||||
container_name: zhamao-bot
|
||||
depends_on:
|
||||
- database-postgres
|
||||
- database-redis
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "20001-20005:20001-20005/tcp"
|
||||
volumes:
|
||||
- ".:/bot"
|
||||
|
||||
walle-q:
|
||||
build:
|
||||
context: docker/walle-q
|
||||
dockerfile: ./Dockerfile
|
||||
container_name: zhamao-bot-walle-q
|
||||
depends_on:
|
||||
- bot
|
||||
networks:
|
||||
- bot-net
|
||||
volumes:
|
||||
- "./docker/volumes/walle-q-data:/bot"
|
||||
|
||||
database-postgres:
|
||||
container_name: zhamao-bot-db-postgres
|
||||
env_file:
|
||||
- docker/environment.env
|
||||
image: postgres:14.4-alpine
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "5432:5432/tcp"
|
||||
restart: always
|
||||
volumes:
|
||||
- "./docker/volumes/postgres-data:/var/lib/postgresql/data"
|
||||
- "./docker/volumes/postgres-init:/docker-entrypoint-initdb.d"
|
||||
|
||||
database-redis:
|
||||
command:
|
||||
- redis-server
|
||||
- --requirepass
|
||||
- "$${REDIS_PASSWORD}"
|
||||
container_name: zhamao-bot-db-redis
|
||||
env_file:
|
||||
- docker/environment.env
|
||||
image: redis:7.0-alpine
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "6379:6379/tcp"
|
||||
restart: always
|
||||
volumes:
|
||||
- "./docker/volumes/redis-data:/data"
|
||||
|
||||
db-admin-adminer:
|
||||
container_name: zhamao-bot-dbadmin-adminer
|
||||
depends_on:
|
||||
database-postgres:
|
||||
condition: service_started
|
||||
database-redis:
|
||||
condition: service_started
|
||||
env_file:
|
||||
- docker/environment.env
|
||||
image: adminer:latest
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "15432:8080/tcp"
|
||||
profiles:
|
||||
- with-adminer
|
||||
|
||||
db-admin-redis-insight:
|
||||
container_name: zhamao-bot-dbadmin-redis-insight
|
||||
depends_on:
|
||||
database-postgres:
|
||||
condition: service_started
|
||||
database-redis:
|
||||
condition: service_started
|
||||
image: redislabs/redisinsight:latest
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "16379:8001/tcp"
|
||||
profiles:
|
||||
- with-adminer
|
||||
|
||||
networks:
|
||||
bot-net:
|
||||
name: bot-net
|
||||
2
docker/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/volumes
|
||||
environment.env
|
||||
7
docker/environment.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
REDIS_PASSWORD={{$REDIS_PASSWORD}}
|
||||
|
||||
POSTGRES_USER={{$DB_ROOT_USER}}
|
||||
POSTGRES_PASSWORD={{$DB_ROOT_PASSWORD}}
|
||||
POSTGRES_APPLICATION_USER={{$DB_USER}}
|
||||
POSTGRES_APPLICATION_USER_PASSWORD={{$DB_PASSWORD}}
|
||||
POSTGRES_APPLICATION_DATABASE={{$DB_NAME}}
|
||||
14
docker/walle-q/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM alpine:latest
|
||||
|
||||
MAINTAINER sunxyw <me@sunxyw.xyz>
|
||||
|
||||
RUN apk add --no-cache curl
|
||||
RUN mkdir -p /bot && \
|
||||
curl -fsSL https://github.com/onebot-walle/walle-q/releases/latest/download/walle-q-i686-linux-musl.tar.gz -O && \
|
||||
tar -zxvf walle-q-i686-linux-musl.tar.gz -C /bot && \
|
||||
rm -rf walle-q-i686-linux-musl.tar.gz && \
|
||||
chmod +x /bot/walle-q-i686-linux-musl
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
ENTRYPOINT [ "/bot/walle-q-i686-linux-musl" ]
|
||||
14
docker/zhamao/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM phpswoole/swoole:5.0-php8.1
|
||||
|
||||
MAINTAINER sunxyw <me@sunxyw.xyz>
|
||||
|
||||
WORKDIR "/bot"
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
EXPOSE 20001-20005
|
||||
|
||||
RUN docker-php-ext-install pcntl
|
||||
|
||||
ENTRYPOINT [ "./zhamao" ]
|
||||
CMD [ "server" ]
|
||||
141
docs/.vuepress/components/ChatBox.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<template>
|
||||
<div class="doc-chat-container">
|
||||
<div class="doc-chat-content">
|
||||
<div v-for="i in chat" v-bind="i">
|
||||
<div class="doc-chat-row" v-if="i.type === 0">
|
||||
<div class="doc-chat-box">{{ i.content }}</div>
|
||||
<img class="doc-chat-avatar" src="https://api.btstu.cn/sjtx/api.php" alt=""/>
|
||||
</div>
|
||||
<div class="doc-chat-row doc-chat-row-robot" v-else-if="i.type === 1">
|
||||
<img class="doc-chat-avatar" src="https://docs-v1.zhamao.xin/logo.png" alt=""/>
|
||||
<div class="doc-chat-box doc-chat-box-robot">
|
||||
<span v-for="(p,index) in i.content.split('\n')">{{p}}<br v-if="index !== i.content.length - 1"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="doc-chat-row doc-chat-banner" v-else-if="i.type === 2">
|
||||
{{ i.content }}
|
||||
</div>
|
||||
<div class="doc-chat-row doc-chat-row-robot" v-else-if="i.type === 3">
|
||||
<img class="doc-chat-avatar" src="https://docs-v1.zhamao.xin/logo.png" alt=""/>
|
||||
<div class="doc-chat-box doc-chat-box-robot">
|
||||
<img :src="i.content" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
type:
|
||||
0: 我方发送消息
|
||||
1: 机器人回复消息
|
||||
2: 显示一个横幅系统消息
|
||||
3: 机器人回复一个图片
|
||||
-->
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ChatBox",
|
||||
props: ['myChats'],
|
||||
data() {
|
||||
return {
|
||||
chat: this.myChats,
|
||||
multiline: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.doc-chat-content {
|
||||
padding: 12px;
|
||||
}
|
||||
.doc-chat-container {
|
||||
border-radius: 6px;
|
||||
max-width: 550px;
|
||||
min-height: 30px;
|
||||
/*noinspection CssUnresolvedCustomProperty*/
|
||||
background-color: #f2f4f5;
|
||||
|
||||
margin-bottom: 1em;
|
||||
box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
|
||||
}
|
||||
|
||||
.doc-chat-row {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 1 1 auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.doc-chat-banner {
|
||||
justify-content: center;
|
||||
background: rgba(0,0,0,0.1);
|
||||
width: max-content;
|
||||
margin: 8px auto;
|
||||
padding: 4px 14px;
|
||||
border-radius: 8px;
|
||||
color: gray;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.doc-chat-row-robot {
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
.doc-chat-box {
|
||||
color: #000000de;
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
max-width: 55%;
|
||||
border-radius: .5rem;
|
||||
padding: .4rem .6rem;
|
||||
margin: .4rem .8rem;
|
||||
background-color: #fff;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
.doc-chat-box:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: auto;
|
||||
top: 0;
|
||||
width: 8px;
|
||||
height: 12px;
|
||||
color: #fff;
|
||||
border: 0 solid transparent;
|
||||
border-bottom: 7px solid;
|
||||
border-radius: 0 0 8px 0;
|
||||
left: calc(100% - 4px);
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.doc-chat-box-robot:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: calc(100% - 4px);
|
||||
top: 0;
|
||||
width: 8px;
|
||||
height: 12px;
|
||||
color: #fff;
|
||||
border: 0 solid transparent;
|
||||
border-bottom: 7px solid;
|
||||
border-radius: 0 0 0 8px;
|
||||
left: auto;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.doc-chat-avatar {
|
||||
background-color: aquamarine;
|
||||
width: 36px !important;
|
||||
height: 36px !important;
|
||||
border-radius: 18px;
|
||||
}
|
||||
</style>
|
||||
65
docs/.vuepress/config.js
Normal file
@@ -0,0 +1,65 @@
|
||||
module.exports = {
|
||||
title: '炸毛框架 v3',
|
||||
description: '一个高性能聊天机器人 + Web 框架',
|
||||
theme: 'antdocs',
|
||||
markdown: {
|
||||
lineNumbers: true
|
||||
},
|
||||
head: [
|
||||
['link', { rel: 'icon', href: '/logo_trans.png' }],
|
||||
['script', {}, `
|
||||
var _hmt = _hmt || [];
|
||||
(function () {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "https://hm.baidu.com/hm.js?f0f276cefa10aa31a20ae3815a50b795";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
`]
|
||||
],
|
||||
themeConfig: {
|
||||
repo: 'zhamao-robot/zhamao-framework',
|
||||
logo: '/logo_trans.png',
|
||||
docsDir: 'docs',
|
||||
editLinks: true,
|
||||
lastUpdated: '上次更新',
|
||||
activeHeaderLinks: false,
|
||||
nav: [
|
||||
{ text: '指南', link: '/guide/' },
|
||||
{ text: '事件', link: '/event/' },
|
||||
{ text: 'API 文档', link: '/doxy/', target: '_blank' },
|
||||
{ text: '炸毛框架 v2', link: 'https://docs-v2.zhamao.xin/' }
|
||||
],
|
||||
sidebar: {
|
||||
'/guide/': [
|
||||
{
|
||||
title: '指南',
|
||||
collapsable: false,
|
||||
sidebarDepth: 1,
|
||||
children: [
|
||||
'',
|
||||
'installation',
|
||||
'configuration',
|
||||
'structure',
|
||||
'get_started',
|
||||
]
|
||||
}
|
||||
],
|
||||
'/event/': [
|
||||
{
|
||||
title: '事件',
|
||||
collapsable: false,
|
||||
sidebarDepth: 1,
|
||||
children: [
|
||||
'',
|
||||
'bot',
|
||||
'http',
|
||||
'middleware',
|
||||
'framework',
|
||||
'extend',
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
1
docs/.vuepress/public/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
framework.zhamao.me
|
||||
BIN
docs/.vuepress/public/index_demo.png
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
docs/.vuepress/public/logo_trans.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
1
docs/.vuepress/public/robots.txt
Normal file
@@ -0,0 +1 @@
|
||||
User-agent: *
|
||||
1
docs/.vuepress/styles/palette.less
Normal file
@@ -0,0 +1 @@
|
||||
@accentColor: #3665aa;
|
||||
@@ -1 +0,0 @@
|
||||
# FAQ
|
||||
44
docs/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
home: true
|
||||
heroImage: ./logo_trans.png
|
||||
actions:
|
||||
- text: 快速上手
|
||||
link: /guide/
|
||||
type: primary
|
||||
size: large
|
||||
- text: 快速上手(v2 旧版)
|
||||
link: https://docs-v2.zhamao.xin/
|
||||
type: primary
|
||||
ghost: true
|
||||
size: large
|
||||
features:
|
||||
- title: 高性能
|
||||
details: 基于 PHP 的 Swoole 高性能扩展,利用 WebSocket 进行与 OneBot 协议兼容的聊天机器人软件的通信,还有数据库连接池、内存缓存、多任务进程等特色,大幅增强性能。
|
||||
- title: 易于开发
|
||||
details: 所有功能采用模块化设计,除特殊情况外几乎所有功能都不需要修改框架内任意代码,框架采用灵活的注解进行各类事件绑定,同时支持下断点调试。
|
||||
- title: 接口直观
|
||||
details: 支持命令、普通文本、正则匹配、自然语言处理等多种对话解析方式,利用协程巧妙实现了直观的交互式会话模式,同时支持多种富文本的处理。
|
||||
footer: |
|
||||
Apache-2.0 Licensed | Copyright © 2019-2022 Zhamao Developer Team | <a href="http://beian.miit.gov.cn">沪ICP备2021010446号-1</a>
|
||||
---
|
||||
|
||||
# 快速上手
|
||||
|
||||
## 安装框架和环境
|
||||
|
||||
此命令可一键以模板安装框架!(仅限 Linux 和 macOS)
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://zhamao.xin/v3.sh)
|
||||
```
|
||||
|
||||
## 运行框架
|
||||
|
||||
```bash
|
||||
cd zhamao-app/
|
||||
./zhamao server
|
||||
```
|
||||
|
||||
## 效果图
|
||||
|
||||

|
||||
@@ -1,143 +0,0 @@
|
||||
# 接入 WebSocket 客户端
|
||||
|
||||
炸毛框架其实从本质上讲,就是一个 HTTP + WebSocket 服务器,所以框架也支持对接其他任何 HTTP 客户端和 WebSocket 客户端,实际上炸毛框架非常适合用 WebSocket 做在线的 IM 聊天通讯,也可以方便地进行 WS 通信。这里主要说明如何对接一个自定义的 WebSocket 客户端。
|
||||
|
||||
## 类型指定
|
||||
|
||||
由于 WebSocket 连接都具有同样的性质,没有状态,所以在建立 WebSocket 连接的时候,需要客户端表明自己的身份和类型。指定客户端连接类型的方式有两种:
|
||||
|
||||
- `GET` 参数传递,在连接的时候,加上 GET 参数 `type` 即可。比如 js 中 WebSocket 建立时地址写:`ws://127.0.0.1:20001/?type=foo`,这时传入的连接就是 `foo` 类型。
|
||||
- `Header` 传递,用户需要在建立连接时指定 HTTP 的头部信息 `X-Client-Role`,例如 `X-Client-Role: foo`,这时传入的连接就是 `foo` 类型。
|
||||
|
||||
以上两种方式,`Header` 方式比 `GET` 方式优先级要高,如果两者均没有指定,框架会将此连接当作 `default` 类型接入。
|
||||
|
||||
!!! note "提示"
|
||||
|
||||
对于对接 OneBot 标准的机器人客户端,只要符合 OneBot 标准,即 `X-Client-Role` 会自动带上 `universal`、`qq` 等字样,就会自动标记为 `qq` 类型。
|
||||
|
||||
## 逻辑编写
|
||||
|
||||
传入连接后,我们就能通过注解事件绑定来做我们自己想做的事情了!比如下方是传入类型为 foo 连接要做的事情
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnOpenEvent;
|
||||
use ZM\Console\Console;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
class Hello {
|
||||
/**
|
||||
* @OnOpenEvent("foo")
|
||||
*/
|
||||
public function onFooConnect(ConnectionObject $conn) {
|
||||
Console::info($conn->getName()." 已连接!");
|
||||
}
|
||||
```
|
||||
|
||||
以上作用就是在终端输出 `foo 已连接!` 这个提示的。关于 `ConnectionObject` 对象,见下方。
|
||||
|
||||
## WS 连接对象
|
||||
|
||||
对于每一个 WebSocket 连接,框架内都有一个专属的操作类,有获取类型名称、保存链接参数和属性以及获取文件标识符等功能。
|
||||
|
||||
### getFd()
|
||||
|
||||
获取文件标示符,用于发送消息、接收消息等。这个参数获取的 `fd` 是 Swoole 指定的,用于发送信息等。
|
||||
|
||||
```php
|
||||
$fd = $conn->getFd();
|
||||
server()->send($fd, "hello world");
|
||||
```
|
||||
|
||||
> WebSocket 是全双工的,所以发送和接收其实是互不干扰的,你可以不仅仅在 WebSocket 相关的上下文中,还可以比如在 HTTP 或者机器人上下文中给别的 WebSocket 客户端发请求。
|
||||
|
||||
### getName()
|
||||
|
||||
获取连接对象绑定的连接类型,例如上方提到的 `foo`、`default` 等。
|
||||
|
||||
```php
|
||||
Console::info("当前连接类型:".$conn->getName()); //当前连接类型:foo
|
||||
```
|
||||
|
||||
### setName()
|
||||
|
||||
改变连接对象绑定的连接类型,例如从 `foo` 改为 `bar`。
|
||||
|
||||
```php
|
||||
$s = $conn->getName(); // foo
|
||||
$conn->setName("bar");
|
||||
$s = $conn->getName(); // bar
|
||||
```
|
||||
|
||||
### getOptions()
|
||||
|
||||
获取此连接存储的所有参数,以数组形式。存储内容见下方 `setOption()`。
|
||||
|
||||
格式:`["参数1" => {参数1的值}, "参数2" => {参数2的值}]`
|
||||
|
||||
### getOption()
|
||||
|
||||
获取此连接存储的参数,获取指定名称的,此方法拥有一个参数 `$key`,指定即可获取。
|
||||
|
||||
如果没有对应参数,则返回 `null`。
|
||||
|
||||
我们在前面的机器人部分知道,框架主要是用于机器人的连接,那么机器人客户端在连接后,比如我们想知道这个机器人的 WS 连接对应的是哪个 QQ 号的机器人,我们就可以用 `getOption("connect_id")` 来获取。这个 `connect_id` 是 OneBot 标准的客户端接入后自动填入的一个参数。例如,我们想在机器人接入后打出接入机器人的 QQ 号:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnOpenEvent("qq")
|
||||
*/
|
||||
public function onQQConnect($conn) {
|
||||
Console::success("机器人 ".$conn->getOption("connect_id")." 已连接!"); // 机器人 123456 已连接!
|
||||
}
|
||||
```
|
||||
|
||||
### setOption()
|
||||
|
||||
设置连接存储的参数。参数:`setOption($key, $value)`。`$key` 限定为 `connect_id` 一种。(因为目前有了 LightCache,所以这里暂时不提供别的 key 设定)
|
||||
|
||||
```php
|
||||
$conn->setOption("connect_id", "asdasdasd"); // $value 最长长度为 29
|
||||
```
|
||||
|
||||
## 发送到 WebSocket 客户端
|
||||
|
||||
很简单,从上面获取到 `fd` 后使用下面的方式就可以了~
|
||||
|
||||
```php
|
||||
server()->push($conn->getFd(), "hello"); // 第二个为 string 类型的参数
|
||||
```
|
||||
|
||||
## 从客户端接收
|
||||
|
||||
接收消息必须从 `@OnMessageEvent` 注解事件下接收,使用上下文 `ctx()->getFrame()` 获取消息帧。
|
||||
|
||||
从这里获取的 `Frame` 对象,见 [Swoole 文档 - Frame](https://wiki.swoole.com/#/websocket_server?id=swoolewebsocketframe)。
|
||||
|
||||
Frame 对象有四个参数:
|
||||
|
||||
- `$frame->fd`:获取发来帧的 fd
|
||||
- `$frame->data`:数据本体
|
||||
- `$frame->opcode`:数据类型 int 值,见 [Swoole 文档 - 数据帧类型](https://wiki.swoole.com/#/websocket_server?id=%e6%95%b0%e6%8d%ae%e5%b8%a7%e7%b1%bb%e5%9e%8b)
|
||||
- `$frame->finish`:是否发送完毕,bool
|
||||
|
||||
下面以接收一个 json 字符串为例,并进行后续的解析:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnMessageEvent("foo")
|
||||
*/
|
||||
public function onMessage() {
|
||||
$frame = ctx()->getFrame();
|
||||
$json_str = $frame->data; // 假设传入的是 {"key1":"value1","k2":"v2"}
|
||||
$json = json_decode($json_str, true);
|
||||
Console::info("key1 的值是:" . $json["key1"]);
|
||||
}
|
||||
```
|
||||
|
||||
## 关闭连接
|
||||
|
||||
```php
|
||||
server()->close($conn->getFd());
|
||||
```
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
# 框架高级启动
|
||||
|
||||
## 框架下载方式
|
||||
|
||||
从前面的几章中,我们了解到框架有多种下载到本地的方式。
|
||||
|
||||
- Composer 依赖模式
|
||||
- Starter 从模板创建模式
|
||||
- 源码模式
|
||||
|
||||
### Composer 依赖模式
|
||||
|
||||
从 Composer 依赖加载框架是一种拉取框架的方式,这种方式的优点在于,你可以直观地感受到是如何使用框架从零开始一个完整的项目的过程。
|
||||
|
||||
从 Composer 依赖的启动步骤:
|
||||
|
||||
```bash
|
||||
mkdir my-bot # 新建一个空的文件夹
|
||||
cd my-bot/
|
||||
composer require zhamao/framework # 从 composer 拉取后会自动部署 autoload 和 composer.json 等内容
|
||||
|
||||
# 使用命令初始化框架
|
||||
vendor/bin/start init
|
||||
|
||||
# 启动框架
|
||||
vendor/bin/start server
|
||||
```
|
||||
|
||||
注意:使用 `init` 命令时,会给当前目录解压以下文件:
|
||||
|
||||
```php
|
||||
$extract_files = [
|
||||
"/config/global.php", // 全局配置文件
|
||||
"/.gitignore", // git 排除文件
|
||||
"/config/file_header.json", // HTTP 文件头
|
||||
"/config/console_color.json", // 终端颜色主题文件
|
||||
"/config/motd.txt", // 框架启动时自定义的 motd
|
||||
"/src/Module/Example/Hello.php", // 框架自带的示例模块
|
||||
"/src/Module/Middleware/TimerMiddleware.php", // 框架自带的函数运行时间监控中间件
|
||||
"/src/Custom/global_function.php" // 用户可在这里自定义编写自己的全局函数
|
||||
];
|
||||
```
|
||||
|
||||
经过 init 解压这些文件后,你的框架就能正常运行且开始编写代码了!
|
||||
|
||||
### Starter 模板模式
|
||||
|
||||
从模板新建其实原理和 Composer 依赖模式完全一样,只不过,这个过程是使用模板仓库新建的项目,使用 Composer 自带的 `create-project` 方式创建的。starter 也是一个 GitHub 项目,见 [地址](https://github.com/zhamao-robot/zhamao-framework-starter)。
|
||||
|
||||
```bash
|
||||
composer create-project zhamao/framework-starter my-bot/ # my-bot 是你自定义的文件夹名称,和上方相同
|
||||
cd my-bot
|
||||
vendor/bin/start server # 启动框架
|
||||
```
|
||||
|
||||
Starter 模式相当于直接从 GitHub 拉取 `zhamao-framework-starter` 项目,然后执行 `composer update`。
|
||||
|
||||
那和 Composer 依赖模式有什么区别呢?没区别!构建出来的框架和文件是一模一样的!使用 Composer 依赖模式,使用 `init` 命令后,文件会和 `zhamao-framework-starter` 仓库拉取回来的模板一模一样!(或者换句话说,这个仓库就是使用 `init` 命令生成的文件的)
|
||||
|
||||
那使用哪种好呢?看你自己!如果你想给你自己的已有项目套上炸毛框架,那么就推荐使用 Composer 依赖模式,如果是从 0 开始编写框架模块,则推荐使用模板模式。
|
||||
|
||||
### 源码模式
|
||||
|
||||
源码模式和以上两种方案都不一样,源码模式允许你对框架本身进行一系列修改,框架本体就可以直接运行。
|
||||
|
||||
Composer 依赖模式(以及模板模式)和源码模式的区别是:
|
||||
|
||||
- 依赖模式和模板模式是通过 library 方式引入框架的,框架本身会放在 composer 的 `vendor/` 目录下,从 composer 引入的 library 相当于子集,vendor 目录下的文件最好不要手动修改(应该都知道吧),所以框架本身也只是加载了进来。
|
||||
- 源码模式相当于直接从框架源码目录运行框架和模块,框架源码都在 `src/ZM` 目录下,默认的示例模块都在 `src/Module` 下,是同级目录。而此时的 `vendor/` 目录只包含了框架依赖的外部组件,例如注解解析器和 psysh 等。
|
||||
|
||||
源码模式可以方便地调试和修改框架本身,拉取方式很简单,用 `git clone` 或从 GitHub 下载最新版的源码包解压即可。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zhamao-robot/zhamao-framework.git
|
||||
cd zhamao-framework/
|
||||
bin/start server # 第一次运行时会提示一个“框架源码模式需要在autoload文件中添加Module目录为自动加载”
|
||||
composer update # 更新 autoload 文件,应用刚才上一步添加的 `src/Module` 文件夹下的模块自动加载
|
||||
bin/start server # 通过源码模式启动框架
|
||||
```
|
||||
|
||||
## 框架启动参数
|
||||
|
||||
框架启动时可以根据实际情况指定启动参数。
|
||||
|
||||
- `--debug-mode`:启用调试模式,调试模式的作用是关闭一键协程化和终端交互,减少 Swoole 本身对代码逻辑的干扰(比如执行 `shell_exec()` 报错的话可以开启这个进行调试)。
|
||||
- `--log-{mode}`:设置 log 等级。支持 `--log-debug`,`--log-verbose`,`--log-info`,`--log-warning`,`--log-error`。
|
||||
- `--log-theme`:设置终端信息的主题。这个选项适用于多种终端信息显示的兼容,例如白色终端和不支持颜色的终端。详见 [Console - 主题设置](/component/console/#_2)。
|
||||
- `--disable-console-input`:关闭终端交互,如果你使用的不是 tmux、screen 而是直接将进程使用 systemd 等方式运行到 init 守护进程下,则需要关闭终端交互输入,关闭后不可以使用 `stop, reload, logtest` 等交互命令。
|
||||
- `--disable-coroutine`:关闭一键协程化。
|
||||
- `--daemon`:以守护进程方式运行框架,此参数将直接在输出 motd 后将进程挂到 init 下运行,后台常驻。
|
||||
- `--watch`:监控 `src/` 目录下的文件变化,有变化则自动重新载入代码。开启监控需要安装 PHP 扩展:inotify。使用 pecl 就可以安装:`pecl install inotify`。
|
||||
- `--env`:设置运行环境,设置运行环境后将优先加载指定环境的配置文件,支持 `--env=production`,`--env=staging`,`--env=development`,见 [基本配置](/guide/basic-config/#_2)。
|
||||
|
||||
## 守护进程操作命令
|
||||
|
||||
守护进程在 2.2.0 版本开始,可以使用命令行快速操作,如重启、停止、查看状态等。
|
||||
|
||||
注意,这里的守护进程操作命令是指 **使用 `--daemon` 方式启动的框架**,如使用 Docker、screen、tmux 等方式挂后台跑则此命令不可用!
|
||||
|
||||
```bash
|
||||
vendor/bin/start daemon:status # 查看守护进程的状态
|
||||
vendor/bin/start daemon:reload # 重载框架
|
||||
vendor/bin/start daemon:stop # 停止运行守护进程的框架
|
||||
```
|
||||
|
||||
## 独立启动其他组件
|
||||
|
||||
框架默认不止启动框架的 `server` 命令,还有 `init` 命令和 `simple-http-server` 命令。`init` 命令在上方 Composer 依赖模式中提到过,就是初始化各个文件的。
|
||||
|
||||
### 独立 HTTP 文件服务器
|
||||
|
||||
如果你只需要一个静态文件服务器,类似 Nginx,那么框架也支持。
|
||||
|
||||
```bash
|
||||
vendor/bin/start simple-http-server your-web-dir/ --host=0.0.0.0 --port=8080
|
||||
```
|
||||
|
||||
- `your-web-dir` 是必填的参数。
|
||||
- `--host` 和 `--port` 是可选参数,如果不填,则默认使用 `global.php` 配置文件中的配置。
|
||||
@@ -1,6 +0,0 @@
|
||||
# 框架剖析
|
||||
|
||||
## 框架运行总结构图
|
||||
|
||||

|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# 进阶开发
|
||||
在本章,下面的部分将详细说明一些具体的案例和自定义框架的操作。
|
||||
|
||||
- 如何自定义修改框架本身?- [框架启动方式](/advanced/custom-start/)
|
||||
- 如何接入一个自己的 WebSocket 客户端?- [接入 WebSocket 客户端](/advanced/connect-ws-client/)
|
||||
- 框架到底是怎么工作的?- [框架结构剖析](/advanced/framework-structure/)
|
||||
|
||||
> 更多进阶教程敬请期待....(或者你可以选择提 Issue 到框架 GitHub,有需求就写入文档)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# 内部类文件手册
|
||||
|
||||
这个章节写明了在框架使用过程中可能涉及到的框架内部或 Swoole、其他 composer 依赖组件的内部类,这里会根据类的命名空间一一说明。
|
||||
|
||||
## Swoole\Http\Request
|
||||
|
||||
此类是 Swoole 内部的一个类,一般在收到 HTTP 请求时,在 `@RequestMapping` 或 `@OnRequestEvent()` 两个注解下可用,用作获取 GET、POST参数,上传到后端的文件、Cookies 等。详见 [Swoole 文档 - Request](http://wiki.swoole.com/#/http_server?id=httprequest) 。
|
||||
|
||||
### 属性
|
||||
|
||||
- `$fd`:获取当前连接的文件描述符 ID。
|
||||
- `$header`:`HTTP` 请求的头部信息。类型为数组,所有 `key` 均为小写。
|
||||
- `$server`:`HTTP` 请求相关的服务器信息。
|
||||
- `$cookie`:获取 Cookies。
|
||||
- `$get`:获取 GET 参数。
|
||||
- `$post`:获取 POST 参数。
|
||||
- `$files`:获取上传的文件信息
|
||||
|
||||
### 方法
|
||||
|
||||
- `rawContent()`:获取 POST 包原始二进制内容,相当于原生 PHP 的 ` file_get_contents("php://input");` 。
|
||||
- `getData()`:获取完整的原始 `Http` 请求报文。包括 `Http Header` 和 `Http Body`
|
||||
|
||||
### 示例
|
||||
|
||||
```php
|
||||
TODO:先放一放。
|
||||
```
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# 从炸毛框架 V1 升级
|
||||
|
||||
> 这里只写明可能在升级过程中会影响原先代码执行的部分,不包含新增的特性等。
|
||||
|
||||
### 需要改变命名空间的类
|
||||
|
||||
- `Framework\Console` -> `ZM\Console\Console`
|
||||
- `Swlib\Util\SingletonTrait` -> `ZM\Utils\SingletonTrait`
|
||||
- `ZM\Annotation\Http\Before` -> `ZM\Annotation\Http\HandleBefore`
|
||||
- `ZM\Annotation\Http\After` -> `ZM\Annotation\Http\HandleAfter`
|
||||
- `@SwooleEventAt` -> `@OnSwooleEvent`
|
||||
- 删除 `@SwooleEventAfter`
|
||||
- 删除 `ModBase`
|
||||
- `@HandleEvent` -> `@SwooleHandler`
|
||||
- `ZM\Utils\ZMRobot` -> `\ZM\API\ZMRobot`
|
||||
|
||||
### 方法名称变更
|
||||
|
||||
- `ZM\Console::stackTrace()` -> `ZM\Console::trace()`
|
||||
|
||||
### 注解的变化
|
||||
|
||||
`@OnSwooleEvent`(原 `@SwooleEventAt`)中,`rule` 参数不再是自定义语法的东西了(比如之前的 `connectType:qq` 之类的鸡肋语法),直接是可执行的 PHP 代码,比如 `3 == 4`,`connectIsQQ()` 之类的。
|
||||
|
||||
去除 `@CQAPISend`,因为目前没什么意义。
|
||||
|
||||
`@CQCommand` 中,`regexMatch` 变成 `pattern`,`fullMatch` 变成 `regex`,消除歧义(第一个是 * 号匹配符进行匹配的,第二个是标准的正则表达式匹配)。同时新增 `start_with`,`end_with`,`keyword` 平行选项。
|
||||
|
||||
`@OnTick` 注解新增第二个参数 `worker_id`,其中默认是 0,代表只在 `#0` 号工作进程上运行计时器。
|
||||
|
||||
### 中间件编写的改变
|
||||
|
||||
原先的 Middleware 是需要含有 `getName()` 方法才合法,现在不需要了,但是对 `@MiddlewareClass` 注解需要增加参数,也就是说原先 `getName()` 返回的名称现在需要写到 `@MiddlewareClass("xxx")` 这样的形式。
|
||||
|
||||
### ZMBuf 的变化
|
||||
|
||||
由于 2.0 框架使用了多进程模型,所以不能使用原先适用于单进程下全局变量的方式(ZMBuf)进行存取变量,所以 ZMBuf 下的所有方法都需要更改,其中 `get, set` 等对缓存操作的模型请根据 2.0 的文档变更使用 `Redis` 或内置的多进程共享内存可用的 `LightCache` 轻量缓存。
|
||||
|
||||
而获取全局配置文件,如 `global.php` 文件,也发生了变化,新框架引入了 `ZMConfig` 对象,可以快速地区分各类环境变量从而读取不同的配置文件。比如我们获取原先的 global 配置文件中的一项:`ZMBuf::globals("port")`,在 2.0 中需要使用 `ZMConfig::get("global", "port")` 方式。以此类推,`ZMBuf::config("xxx")` 也直接变为 `ZMConfig::get("xxx")` 了。
|
||||
@@ -1,103 +0,0 @@
|
||||
|
||||
.md-header-nav__button.md-logo {
|
||||
padding: .2rem;
|
||||
margin: .2rem;
|
||||
}
|
||||
|
||||
.md-header-nav__button.md-logo img, .md-header-nav__button.md-logo svg {
|
||||
width: 1.6rem;
|
||||
height: 1.6rem;
|
||||
}
|
||||
|
||||
.doc-chat-container {
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
min-height: 30px;
|
||||
/*noinspection CssUnresolvedCustomProperty*/
|
||||
background-color: var(--md-code-bg-color);
|
||||
padding: 12px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);
|
||||
}
|
||||
|
||||
.doc-chat-row {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 1 1 auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.doc-chat-banner {
|
||||
justify-content: center;
|
||||
background: rgba(0,0,0,0.1);
|
||||
width: max-content;
|
||||
margin: 8px auto;
|
||||
padding: 4px 14px;
|
||||
border-radius: 8px;
|
||||
color: gray;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.doc-chat-row-robot {
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
|
||||
.doc-chat-box {
|
||||
color: #000000de;
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
max-width: 55%;
|
||||
border-radius: .5rem;
|
||||
padding: .4rem .6rem;
|
||||
margin: .4rem .8rem;
|
||||
background-color: #fff;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
overflow-wrap: break-word;
|
||||
white-space: normal;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
.doc-chat-box:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: auto;
|
||||
top: 0;
|
||||
width: 8px;
|
||||
height: 12px;
|
||||
color: #fff;
|
||||
border: 0 solid transparent;
|
||||
border-bottom: 7px solid;
|
||||
border-radius: 0 0 8px 0;
|
||||
left: calc(100% - 4px);
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.doc-chat-box-robot:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: calc(100% - 4px);
|
||||
top: 0;
|
||||
width: 8px;
|
||||
height: 12px;
|
||||
color: #fff;
|
||||
border: 0 solid transparent;
|
||||
border-bottom: 7px solid;
|
||||
border-radius: 0 0 0 8px;
|
||||
left: auto;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
.doc-chat-avatar {
|
||||
background-color: aquamarine;
|
||||
width: 36px !important;
|
||||
height: 36px !important;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.md-typeset .admonition, .md-typeset details {
|
||||
font-size: .72rem;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 94 KiB |
@@ -1,65 +0,0 @@
|
||||
# ZMAtomic 原子计数器
|
||||
|
||||
原子计数器是用于多进程间跨进程使用的原子计数使用的,比如统计入站请求数量等。此功能基于 Swoole 的 Atomic,详情见 [Swoole - 文档]([进程间无锁计数器(Atomic) (swoole.com)](http://wiki.swoole.com/#/memory/atomic))。
|
||||
|
||||
## 配置和初始化
|
||||
|
||||
见配置文件:`config/global.php` 中的 `init_atomics` 字段:
|
||||
|
||||
```php
|
||||
/** zhamao-framework在框架启动时初始化的atomic们 */
|
||||
$config['init_atomics'] = [
|
||||
'foo' => 0,
|
||||
'bar' => 4,
|
||||
];
|
||||
```
|
||||
|
||||
这时我们就成功初始化两个原子计数器,名字分别为 `foo` 和 `bar`。
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
初始化的值必须是不小于 0 的 int32 值!
|
||||
|
||||
|
||||
## 使用
|
||||
|
||||
定义和命名空间:`ZM\Store\ZMAtomic`
|
||||
|
||||
连接计数示例:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnRequestEvent;
|
||||
use ZM\Store\ZMAtomic;
|
||||
class Hello {
|
||||
/**
|
||||
* @OnRequestEvent()
|
||||
*/
|
||||
public function onRequest() {
|
||||
$cnt = ZMAtomic::get("foo")->add(1);
|
||||
ctx()->getResponse()->end("当前已访问:".$cnt."次");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ZMAtomic::get()->get()
|
||||
|
||||
获取计数的数字:`dump(ZMAtomic::get("bar")->get());` 返回 4。
|
||||
|
||||
### ZMAtomic::get()->add($num)
|
||||
|
||||
加上一定的数并返回结果:`dump(ZMAtomic::get("bar")->add(5));` 返回 9。
|
||||
|
||||
### ZMAtomic::get()->sub($num)
|
||||
|
||||
要减少的数值(必须为正整数):`dump(ZMAtomic::get("bar")->sub(5));` 返回 5。
|
||||
|
||||
### ZMAtomic::get()->set($num)
|
||||
|
||||
设置计数的数字:`ZMAtomic::get("bar")->set(77);`
|
||||
|
||||
!!! note "提示"
|
||||
|
||||
还有一些不常用的方法,可以看 Swoole 官方的文档,这里就不一一列举了。
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
# Console 控制台
|
||||
|
||||
Console 类所在命名空间:`\ZM\Console\Console`
|
||||
|
||||
Console 类为框架的终端输出管理类。
|
||||
|
||||
## 设置 Log 输出等级
|
||||
|
||||
**输出等级** 控制了输出到命令行的内容的重要性。在框架的输出中,消息有以下几种不同等级的类别
|
||||
|
||||
- **error** / **log**: 0
|
||||
- **warning**: 1
|
||||
- **info** / **success**: 2
|
||||
- **verbose**: 3
|
||||
- **debug**: 4
|
||||
|
||||
输出等级设置后显示的消息类别为小于等于当前 log 的。假设你将 log 等级设置为 3,你可以看到除 debug 外的所有 log 内容。
|
||||
|
||||
通过配置文件 `global.php` 中的 `init_atomics -> info_level` 的数值你可以更改框架的默认 log 等级(默认为 2)。
|
||||
|
||||
你也可以在启动框架的命令行中添加参数来切换 log 等级:
|
||||
|
||||
```bash
|
||||
vendor/bin/start server --log-error # 以 error 等级启动框架
|
||||
vendor/bin/start server --log-warning # 以 warning 等级启动框架
|
||||
vendor/bin/start server --log-info # 以 info 等级启动框架
|
||||
vendor/bin/start server --log-verbose # 以 verbose 等级启动框架
|
||||
vendor/bin/start server --log-debug # 以 debug 等级 启动框架
|
||||
```
|
||||
|
||||
## 使用 Log 输出内容
|
||||
|
||||
作为模块开发者的你,你可以主动调用框架内的 Console 类输出信息到终端。
|
||||
|
||||
### Console::log()
|
||||
|
||||
输出 0 级别的普通 log。
|
||||
|
||||
- 参数:`$msg, $color`,分别为内容和字体颜色。
|
||||
|
||||
> 此 log 不会被 info_level 所限制,无论如何也会输出到终端。
|
||||
|
||||
### Console::error()
|
||||
|
||||
输出 error 级别的红色醒目 log。一般此 log 为框架内部出现不可忍受的错误,比如内存不足、PHP fatal error 等错误。
|
||||
|
||||
- 参数:`$msg`
|
||||
|
||||
> 此 log 不会被 info_level 所限制,无论如何也会输出到终端。
|
||||
|
||||
### Console::warning()
|
||||
|
||||
输出 warning 级别的 log。
|
||||
|
||||
!!! warning 注意
|
||||
|
||||
框架内出现的用户态异常,比如无法发送 API、无法连接数据库等错误,都是 warning 错误,不会导致框架崩溃或功能错误的异常情况建议都使用 warning 输出而不是 error。
|
||||
|
||||
|
||||
### Console::info()
|
||||
|
||||
输出 info 级别的 log。
|
||||
|
||||
### Console::success()
|
||||
|
||||
输出 success 级别的log。
|
||||
|
||||
### Console::verbose()
|
||||
|
||||
输出 verbose 级别的 log。
|
||||
|
||||
### Console::debug()
|
||||
|
||||
输出 debug 级别的 log。
|
||||
|
||||
### Console::stackTrace()
|
||||
|
||||
输出栈追踪信息。
|
||||
|
||||
### Console::setColor()
|
||||
|
||||
返回:彩色的字符串。
|
||||
|
||||
- **string**: 要变颜色的字符串
|
||||
- **color**: 要变的颜色。支持 `red`,`green`,`yellow`,`reset`,`blue`,`gray`,`gold`,`pink`,`lightblue`,`lightlightblue`
|
||||
|
||||
```php
|
||||
Console::log("This is normal msg. (0)");
|
||||
Console::error("This is error msg. (0)");
|
||||
Console::warning("This is warning msg. (1)");
|
||||
Console::info("This is info msg. (2)");
|
||||
Console::success("This is success msg. (2)");
|
||||
Console::verbose("This is verbose msg. (3)");
|
||||
Console::debug("This is debug msg. (4)");
|
||||
Console::stackTrace();
|
||||
$str = Console::setColor("I am gold color.", "gold");
|
||||
```
|
||||
|
||||
## 终端交互命令
|
||||
|
||||
炸毛框架支持从终端输入命令来进行一些操作,例如重启框架、停止框架、执行函数等。
|
||||
|
||||
::: warning 注意
|
||||
|
||||
在 Docker、systemd、daemon 状态下启动的框架会自动关闭终端等待输入,交互不可用。
|
||||
|
||||
:::
|
||||
|
||||
### reload
|
||||
|
||||
重新加载除 `src/Framework/` 下的所有模块。
|
||||
|
||||
- 别名:`r`
|
||||
|
||||
### stop
|
||||
|
||||
停止框架。
|
||||
|
||||
### logtest
|
||||
|
||||
输出各种等级的 log 示例文本。
|
||||
|
||||
### call
|
||||
|
||||
执行对应类的成员方法。下面是例子:
|
||||
|
||||
```bash
|
||||
call \ZM\Utils\ZMUtil reload
|
||||
```
|
||||
|
||||
### bc
|
||||
|
||||
直接执行 PHP 代码,输入格式为 base64。
|
||||
|
||||
```bash
|
||||
bc XEZyYW1ld29ya1xDb25zb2xlOjp3YXJuaW5nKCJoZWxsbyB3YXJuaW5nISIpOw==
|
||||
# 代码内容:\ZM\Console\Console::warning("hello warning!");
|
||||
# 终端输出:[19:14:32] [W] hello warning!
|
||||
```
|
||||
|
||||
### echo
|
||||
|
||||
输出文本
|
||||
|
||||
```bash
|
||||
echo hello
|
||||
```
|
||||
|
||||
### color
|
||||
|
||||
按照颜色输出文本
|
||||
|
||||
```bash
|
||||
color green 我是绿色的字
|
||||
```
|
||||
|
||||
## MOTD
|
||||
|
||||
在 1.4 版本开始,框架支持启动时的 motd 内容修改。
|
||||
|
||||
文件位置:`config/motd.txt`
|
||||
|
||||
## 设置输出主题
|
||||
|
||||
Console 组件支持为多种不同的终端设置不同的主题,比如有些人喜欢使用白色的终端,但是白色终端下 info 的颜色很浅,看不到,还有人使用不能显示颜色的黑白终端.....
|
||||
|
||||
```bash
|
||||
vendor/bin/start server --log-theme={主题名}
|
||||
```
|
||||
|
||||
现有支持的主题有:`default`,`white-term`,`no-color`
|
||||
|
||||
```bash
|
||||
vendor/bin/start server --log-theme=white-term # 如果用的是白色终端,这个主题更友好
|
||||
vendor/bin/start server --log-theme=no-color # 如果不想让 log 带有任何颜色,使用无色主题
|
||||
```
|
||||
|
||||
@@ -1,424 +0,0 @@
|
||||
# 上下文
|
||||
|
||||
上下文作为整个框架中最重要的内容之一,请务必理解和完整地阅读此部分!
|
||||
|
||||
一个上下文描述了一个事件和所关联的对象的环境。例如:你在处理 HTTP 请求的 `@RequestMapping` 绑定的事件中,你需要获取请求的 HTTP 头和 Cookie,再比如你在处理 QQ 机器人发来的命令 `@CQCommand("随机数")` 的时候,在这个方法内,你需要获取发来的人的 QQ 号码。以上我们将处理以上运行环境的对象叫做上下文。
|
||||
|
||||
由于 Swoole 的协程加持,我们利用了协程 ID 绑定对象来进行构造上下文。
|
||||
|
||||
以默认的机器人收发消息为例,通过对默认模块的了解,我们可以知道,在绑定 `@CQCommand` 等类似事件后,你可以用上下文获取发来这条消息的人的 QQ 号码:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("你好")
|
||||
*/
|
||||
public function hello() {
|
||||
$user_id = ctx()->getUserId();
|
||||
ctx()->reply("你好啊,".$user_id.",很高兴认识你!");
|
||||
}
|
||||
```
|
||||
|
||||
`context()` 就是获取上下文对象的全局函数,它还有简写:`ctx()`。
|
||||
|
||||
当然,上下文中的方法不是每个都能在任何时候使用的。例如 `getUserId()` 你不能在 `@RequestMapping` 注解的函数中使用,因为它不是机器人消息的上下文。下面说明上下文对象的方法中,每个都会说明每个方法可以在哪些事件中使用:
|
||||
|
||||
## getServer() - 获取 Server 对象
|
||||
|
||||
获取 Swoole WebSocker Server 对象。此对象是 Swoole 的对象,详情见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server)。
|
||||
|
||||
可以使用的事件:`@OnMessageEvent()`,`@OnOpenEvent()`,`@OnCloseEvent()`,`@OnStart()` 以及所有 HTTP API 发来的事件:`@CQCommand()`,`@CQMessage()` 等。
|
||||
|
||||
## getFrame() - 获取 WS 数据帧
|
||||
|
||||
获取 `\Swoole\Websocket\Frame` 对象,此对象是 Swoole 的对象,详情见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server?id=swoolewebsocketframe)。
|
||||
|
||||
可以使用的事件:`@OnMessageEvent()` 以及所有 HTTP API 发来的事件:`@CQCommand()`,`@CQMessage()` 等,
|
||||
|
||||
## getFd() - 返回 fd 值
|
||||
|
||||
获取当前连入 Swoole 服务器的连接文件描述符 ID。返回 int。一般代表连接号,可用来绑定对应链接。
|
||||
|
||||
可以使用的事件:所有 **getFrame()** 可以使用的,`@OnOpenEvent()`,`@OnCloseEvent()`
|
||||
|
||||
!!! tip "提示"
|
||||
|
||||
值得注意的是,由于机器人客户端和炸毛框架的连接是通过 WebSocket 进行的,而 WebSocket 是长连接,所以同一个机器人一次连接下收发消息所用的连接是同一个,所以 Fd 也是相同的。同理,炸毛框架的内部来区分多个机器人也是通过这一 Fd 进行判定的。
|
||||
|
||||
=== "代码"
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("测试fd")
|
||||
*/
|
||||
public function testfd() {
|
||||
ctx()->reply("当前机器人连接的fd是:".ctx()->getFd()",机器人QQ是:".ctx()->getRobotId());
|
||||
}
|
||||
```
|
||||
|
||||
=== "效果"
|
||||
|
||||
<chat-box>
|
||||
^ 假设我们和连接55555的机器人的私聊
|
||||
) 测试fd
|
||||
( 当前机器人连接的fd是:1,机器人QQ是:55555
|
||||
^ 假设切到了另一个机器人(66666)的私聊
|
||||
) 测试fd
|
||||
( 当前机器人连接的fd是:2,机器人QQ是:66666
|
||||
</chat-box>
|
||||
|
||||
## getData() - 获取事件完整数据
|
||||
|
||||
返回 CQHTTP 事件上报的原始数据包,已经被解析成数组,可以直接操作。
|
||||
|
||||
可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()`,`@CQMessage()` 等。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQMessage(user_id=123456)
|
||||
*/
|
||||
public function onMessage() {
|
||||
$data = ctx()->getData();
|
||||
ctx()->reply("消息类型是:" . $data["message_type"]);
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
^ 假设我是QQ为123456的用户,私聊发消息
|
||||
) 哈咯!!
|
||||
( 消息类型是:private
|
||||
</chat-box>
|
||||
|
||||
## getRequest() - HTTP 请求对象
|
||||
|
||||
返回 `\Swoole\Http\Request` 对象,可在 `@RequestMapping` 中使用,获取 Cookie,请求头,GET 参数什么的。[Swoole 文档](https://wiki.swoole.com/#/http_server?id=httprequest)。
|
||||
|
||||
可以使用的事件:`@RequestMapping()`,`@OnRequestEvent()`,`@OnOpenEvent()`。
|
||||
|
||||
## getResponse() - HTTP 响应对象
|
||||
|
||||
返回 `\Swoole\Http\Response` 对象的增强版,可在 HTTP 请求相关的事件中使用,返回内容和设置 Cookie 什么的。[Swoole 文档](https://wiki.swoole.com/#/http_server?id=httpresponse)。
|
||||
|
||||
可以使用的事件:`@RequestMapping()`,`@OnRequestEvent()`。
|
||||
|
||||
下面是使用以上两个功能的组合示例:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/ping")
|
||||
*/
|
||||
public function ping() {
|
||||
$name = ctx()->getRequest()->get["name"] ?? "unknown";
|
||||
ctx()->getResponse()->end("Hello ".$name."!");
|
||||
}
|
||||
```
|
||||
|
||||
## getConnection() - WS 连接对象
|
||||
|
||||
返回此上下文相关联的 WebSocket 连接对象。详见 [进阶 - 接入 WebSocket 客户端](/advanced/connect-ws-client)。
|
||||
|
||||
可以使用的事件:所有 **getFrame()** 可以使用的都可以使用。
|
||||
|
||||
## getCid() - 上下文 ID
|
||||
|
||||
返回当前上下文所绑定的协程 ID,此 ID 和 `\Co::getCid()` 返回值一样。
|
||||
|
||||
## getRobot() - 获取机器人 API 对象
|
||||
|
||||
返回当前上下文关联的机器人 API 调用对象 [ZMRobot](robot-api.md)。
|
||||
|
||||
可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()`,`@CQMessage()` 等。
|
||||
|
||||
```php
|
||||
ctx()->getRobot()->sendPrivateMsg(123456, "发送私聊消息");
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
^ 正在和机器人聊天
|
||||
( 发送私聊消息
|
||||
</chat-box>
|
||||
|
||||
## getMessage() - 获取消息
|
||||
|
||||
获取 data 数据中的 `message` 消息,用于快速获取用户消息事件的消息内容。
|
||||
|
||||
可以使用的事件:`@CQCommand()`,`@CQMessage`,`@CQBefore("message")`,`@CQAfter("message")`
|
||||
|
||||
=== "代码"
|
||||
```php
|
||||
/**
|
||||
* @CQMessage(group_id=33333)
|
||||
*/
|
||||
public function groupRepeat() {
|
||||
ctx()->reply(ctx()->getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
=== "效果"
|
||||
<chat-box>
|
||||
^ 现在在群33333内,机器人已经成了复读机
|
||||
) 来世还做复读机!!!
|
||||
( 来世还做复读机!!!
|
||||
) 你不许复读!
|
||||
( 你不许复读!
|
||||
</chat-box>
|
||||
|
||||
## getUserId() - 获取用户 QQ 号
|
||||
|
||||
获取发消息的用户的 QQ 号码。
|
||||
|
||||
可以使用的事件:所有 **含有** `user_id` 上报参数的 OneBot 事件。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("whoami")
|
||||
*/
|
||||
public function whoami() {
|
||||
ctx()->reply("你是".ctx()->getUserId()); //返回:你是123456
|
||||
}
|
||||
```
|
||||
|
||||
## getGroupId() - 获取 QQ 群号
|
||||
|
||||
获取发消息来自的 QQ 群号。
|
||||
|
||||
可以使用的事件:所有含有 `group_id` 上报参数的 OneBot 事件。
|
||||
|
||||
## getMessageType() - 消息类型
|
||||
|
||||
获取消息类型,同参数 `message_type`。
|
||||
|
||||
可以使用的事件:所有 `post_type` 为 `message` 的响应事件,如 `@CQMessage`,`@CQCommand`。
|
||||
|
||||
## getRobotId() - 机器人 QQ 号
|
||||
|
||||
获取事件上报的机器人自己的 QQ 号码。
|
||||
|
||||
可以使用的事件:所有 OneBot 发来的事件:`@CQCommand()`,`@CQNotice()` 等。
|
||||
|
||||
## setMessage() - 设置消息
|
||||
|
||||
与 `getMessage()` 对应,用于更改上下文中保存的事件信息,可以用于消息变更和过滤。
|
||||
|
||||
## setUserId() - 设置用户 ID
|
||||
|
||||
与上同理,更改 `user_id`。
|
||||
|
||||
## setGroupId() - 设置群号
|
||||
|
||||
与上同理。
|
||||
|
||||
## setMessageType() - 设置类型
|
||||
|
||||
与上同理,修改消息类型。
|
||||
|
||||
## setData() - 设置数据包
|
||||
|
||||
与上同理,与 `getData()` 对应,用于更改上下文中的 `data`。
|
||||
|
||||
## getCache() - 上下文缓存
|
||||
|
||||
获取保存在上下文中的临时缓存变量。当相关联的事件结束后,数据会从内存中被释放。用于同一事件的多个函数中的信息传递。
|
||||
|
||||
- 参数:`$key`,缓存变量的键名
|
||||
- 返回:`mixed`,存入缓存的变量值。
|
||||
|
||||
```php
|
||||
$a = ctx()->getCache("block_continue");
|
||||
// 如果变量不存在,则返回 null
|
||||
```
|
||||
|
||||
## setCache() - 上下文缓存
|
||||
|
||||
与 `getCache()` 对应,是设置内容的。
|
||||
|
||||
```php
|
||||
ctx()->setCache("abc", "asdasd");
|
||||
$result = ctx()->getCache("abc"); // asdasd
|
||||
```
|
||||
|
||||
## reply() - 快速回复
|
||||
|
||||
快速回复当前用户消息内容。
|
||||
|
||||
- 参数1:`$msg`,字符串,你要回复的消息内容
|
||||
- 参数2:`$yield = false`,可选,当为 `true` 时,会协程等待后返回 **消息回复** 的结果,包括 API 状态码、消息 `message_id` 等。
|
||||
|
||||
```php
|
||||
$r = ctx()->reply("我又好了。");
|
||||
if($r["retcode"] == 0) Console::success("消息发送成功!");
|
||||
```
|
||||
|
||||
## finalReply() - 快速回复
|
||||
|
||||
快速回复用户消息,并阻止其他模块接下来继续处理此事件。
|
||||
|
||||
参数同 `reply()`。
|
||||
|
||||
## waitMessage() - 等待用户消息
|
||||
|
||||
- 参数:`waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "")`
|
||||
- 用途:等待用户输入消息
|
||||
|
||||
`$prompt` 参数为回复用户的文本内容,`$timeout` 是等待用户回复的超时时间(秒),`$timeout_prompt` 是超时后回复用户的文本。
|
||||
|
||||
这个功能可以让开发机器人的代码逻辑和实际贴合,避免回调地狱、拼接参数、上下文脱节等问题,比如下方的示例,可以仅仅用两行代码实现一个问答式的对话过程。
|
||||
|
||||
用法示例:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("自我介绍")
|
||||
*/
|
||||
function yourName(){
|
||||
$r = ctx()->waitMessage("你叫啥名字呀?", 600, "你都10分钟不理我了,嘤嘤嘤");
|
||||
ctx()->finalReply("好的,可爱的机器人记住你叫 ".$r." 啦!以后多聊天哦!");
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) 自我介绍
|
||||
( 你叫啥名字呀?
|
||||
) jerry
|
||||
( 好的,可爱的机器人记住你叫 jerry 啦!以后多聊天哦!
|
||||
) 自我介绍
|
||||
( 你叫啥名字呀?
|
||||
^ 10分钟没理机器人
|
||||
( 你都10分钟不理我了,嘤嘤嘤
|
||||
</chat-box>
|
||||
|
||||
## getArgs() - 自动获取参数
|
||||
|
||||
为 `waitMessage()` 的封装,目的是让机器人的回复更加智能化。最好的例子就是在框架自带的默认示例中“随机数”的例子,我们假设要写一个随机数功能,但是用户从来都是不思考就使用机器人的。抛开人工智能,我们能做的就是“专家系统”,同时让我们写的代码尽可能适配用户所说的每一句话:
|
||||
|
||||
- 随机数 1 100
|
||||
- 随机数(一般不知道怎么用这个功能的人都会只说一个关键词)
|
||||
- 从2到9的随机数
|
||||
|
||||
所以,在匹配第一和第二种情况时候,我们不需要重复写代码,而第一种的话用户已经将参数给你的时候,你不需要再次使用 `waitMessage()` 方式进行等待询问,只需要取到使用就好了。`getArgs()` 就是做这个的。
|
||||
|
||||
定义:`getArgs($mode, $prompt_msg)`
|
||||
|
||||
`$mode`:获取模式,有三种:
|
||||
|
||||
- `ZM_MATCH_ALL`:效果等同于 `getFullArg()`,获取全部的内容,把空格也当作一部分
|
||||
- `ZM_MATCH_NUMBER`:效果等同于 `getNumArg()`,获取下一个数字参数
|
||||
- `ZM_MATCH_FIRST`:效果等同于 `getNextArg()`,获取下一个参数
|
||||
|
||||
`$prompt_msg`:字符串,指定如果参数缺失时询问用户的内容。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("test")
|
||||
*/
|
||||
public function argTest1() {
|
||||
$s = ctx()->getArgs(ZM_MATCH_FIRST, "请输入你要传入的参数内容");
|
||||
return "参数内容:".$s;
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) test
|
||||
( 请输入你要传入的参数内容
|
||||
) test2
|
||||
( 参数内容:test2
|
||||
</chat-box>
|
||||
|
||||
`getArgs()` 也有三层封装,在使用过程中避免麻烦的话,推荐使用下面这几种 `get*Arg()` 方式。
|
||||
|
||||
## getFullArg()
|
||||
|
||||
获取关键词后的整个字符串参数,包括空格,如果不存在则询问。
|
||||
|
||||
典型例子:`复读机 你好 你好`,获取参数时会将 `你好 你好` 当作一个参数来获取。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("test")
|
||||
*/
|
||||
public function argTest1() {
|
||||
$s = ctx()->getFullArg("请输入你要传入的参数内容");
|
||||
return "参数内容:".$s;
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) test abc def argtest
|
||||
( 参数内容:abc def argtest
|
||||
) test
|
||||
( 请输入你要传入的参数内容
|
||||
) abc def
|
||||
( 参数内容:abc def
|
||||
</chat-box>
|
||||
|
||||
## getNextArg()
|
||||
|
||||
获取下一个参数,分隔符可以是空格,tab。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("test")
|
||||
*/
|
||||
public function argTest1() {
|
||||
$s = ctx()->getNextArg("请输入你要传入的参数内容");
|
||||
return "参数内容:".$s;
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) test abc def argtest
|
||||
( 参数内容:abc
|
||||
) test
|
||||
( 请输入你要传入的参数内容
|
||||
) abc
|
||||
( 参数内容:abc
|
||||
</chat-box>
|
||||
|
||||
## getNumArg()
|
||||
|
||||
> 2.1.5 版本起可用。
|
||||
|
||||
获取下一个数字型参数,如果 `is_numeric()` 为 true 则获取成功,如果没有符合的则询问用户。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("test")
|
||||
*/
|
||||
public function argTest1() {
|
||||
$s = ctx()->getNextArg("请输入你要传入的数字内容");
|
||||
return "数字参数内容:".$s;
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) test abc 334 argtest
|
||||
( 数字参数内容:334
|
||||
) test abc
|
||||
( 请输入你要传入的数字内容
|
||||
) 998
|
||||
( 参数内容:998
|
||||
</chat-box>
|
||||
|
||||
## copy()
|
||||
|
||||
获取整个上下文的所有内容的数组形式。
|
||||
|
||||
```php
|
||||
$arr = ctx()->copy();
|
||||
dump($arr);
|
||||
```
|
||||
|
||||
## getOption() - 获取匹配参数内容
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("test")
|
||||
*/
|
||||
public function argTest1() {
|
||||
return "参数内容:".implode(", ", ctx()->getOption());
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) test abc 334 argtest
|
||||
( 参数内容:abc, 334, argtest
|
||||
</chat-box>
|
||||
@@ -1,63 +0,0 @@
|
||||
# 协程池
|
||||
|
||||
首先要声明的一点是,协程池这个概念是我自己编的。
|
||||
|
||||
因为协程的特点,它是单线程下运行的,所以在一个进程内同时实际上只会有一个协程的代码在执行逻辑,但是后面的 IO 操作、协程挂起等待的操作都是同时去做的,比如数据库的大数据读取、写入需要耗时几秒甚至几十秒,这时用基于协程的 MySQL 连接池就完全不是问题。
|
||||
|
||||
但是就拿 MySQL 举例,我们 MySQL 使用的是 TCP 连接,而无论是 MySQL 还是 TCP 连接,最大数量都是有限的。我们即使设置了允许最大协程数量非常大,比如上百万,但是也不能让数据库连接池一个池支持上百万的连接。
|
||||
|
||||
这时假设高并发进来了怎么办呢?这时就需要框架提出的一个折中方案:协程池了。
|
||||
|
||||
顾名思义,协程池是一个容纳协程的区域,而协程里又容纳着各种各样需要阻塞调用被协程调用的 IO 操作,协程池用作限制协程的数量。
|
||||
|
||||
```php
|
||||
use ZM\Utils\CoroutinePool;
|
||||
use ZM\DB\DB;
|
||||
|
||||
// 传统写法,一旦高并发则可能导致 Too many connections
|
||||
go(funuction(){
|
||||
DB::rawQuery("INSERT INTO users VALUES(?,?)", ["admin", "password"]);
|
||||
});
|
||||
// 协程池写法
|
||||
CoroutinePool::go(function(){
|
||||
DB::rawQuery("INSERT INTO users VALUES(?,?)", ["admin", "password"]);
|
||||
}, "foo");
|
||||
```
|
||||
|
||||
参数:`go(callable $func, $name = "default")`
|
||||
|
||||
`$name` 为协程池对应的名字,你可以设置多个协程池,用来支持不同的需要限制并发 IO 数量的地方,例如 Redis 和 MySQL 设置不同的名字。`$func` 可为闭包或可调用的方法名称或数组。
|
||||
|
||||
## 配置
|
||||
|
||||
默认情况下,直接调用 `CoroutinePool::go()` 时,协程池大小为 30,也就是如果有 30 个协程进入了挂起状态(比如数据库在执行查询语句),那么更多的协程执行时就会阻塞并以协程等待的方式等待,直到现有的 30 个协程中的一部分完成了它的工作。
|
||||
|
||||
## 方法
|
||||
|
||||
### CoroutinePool::go()
|
||||
|
||||
将协程放入协程池运行。
|
||||
|
||||
如果不写 `$name` 参数,则使用的是默认协程池。
|
||||
|
||||
### CoroutinePool::defaultSize()
|
||||
|
||||
设置默认协程池的大小(默认 30)
|
||||
|
||||
```php
|
||||
CoroutinePool::defaultSize(64);
|
||||
for($i = 0; $i < 1000; ++$i) {
|
||||
CoroutinePool::go(function(){
|
||||
DB::rawQuery("SELECT * FROM users");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### CoroutinePool::setSize()
|
||||
|
||||
定义:`setSize($name, int $size)`
|
||||
|
||||
`$name` 为字符串,是你要用的协程池的名称。
|
||||
|
||||
`$size` 为大小,最大不可超过 Swoole 配置文件中指定的最大协程数量。
|
||||
|
||||
@@ -1,459 +0,0 @@
|
||||
# CQ 码(多媒体消息)
|
||||
|
||||
消息中的多媒体内容使用 CQ 码来表示,形如 `[CQ:face,id=178]`。其中,`[CQ:]` 是固定格式;`face` 是「功能名」,除了 `face` 还有许多不同的功能名;`id=178` 是「参数」,某些功能不需要参数,而另一些需要多个参数,当有多个参数时,参数间使用逗号分隔。
|
||||
|
||||
## 格式
|
||||
|
||||
一些 CQ 码的例子如下:
|
||||
|
||||
```
|
||||
[CQ:shake]
|
||||
[CQ:face,id=178]
|
||||
[CQ:share,title=标题,url=http://baidu.com]
|
||||
```
|
||||
|
||||
更多 CQ 码功能请参考 [消息段类型](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md)。
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
CQ 码中不应有多余的空格,例如不应该使用 `[CQ:face, id=178]`。
|
||||
|
||||
CQ 码的参数值可以包含空格、换行、除 `[],&` 之外的特殊符号等。在解析时,应直接取 `[CQ:` 后、第一个 `,` 或 `]` 前的部分为功能名,第一个 `,` 之后到 `]` 之间的部分为参数,按 `,` 分割后,每个部分第一个 `=` 前的内容为参数名,之后的部分为参数值。例如 `[CQ:share,title=标题中有=等号,url=http://baidu.com]` 中,功能名为 `share`,`title` 参数值为 `标题中有=等号`,`url` 参数值为 `http://baidu.com`。
|
||||
|
||||
## 转义
|
||||
|
||||
CQ 码中包含一些特殊字符:`[`、`]`、`,` 等,而 CQ 码又是可能混杂在纯文本内容之中的,因此消息中的纯文本内容需要对特殊字符进行转义,以避免歧义。具体的转义规则如下:
|
||||
|
||||
| 转义前 | 转义后 |
|
||||
| ------ | ------- |
|
||||
| `&` | `&` |
|
||||
| `[` | `[` |
|
||||
| `]` | `]` |
|
||||
|
||||
另一方面,CQ 码内部的参数值也可能出现特殊字符,也是需要转义的。由于 `,`(半角逗号)在 CQ 码中用于分隔参数,因此除了上面的转义规则,还需要对 `,` 进行转义,如下:
|
||||
|
||||
| 转义前 | 转义后 |
|
||||
| ------ | ------- |
|
||||
| `&` | `&` |
|
||||
| `[` | `[` |
|
||||
| `]` | `]` |
|
||||
| `,` | `,` |
|
||||
|
||||
例如,一个链接分享消息的 CQ 码可能如下:
|
||||
|
||||
```
|
||||
[CQ:share,title=震惊,小伙睡觉前居然...,url=http://baidu.com/?a=1&b=2]
|
||||
```
|
||||
|
||||
## 封装调用
|
||||
|
||||
框架提供了 CQ 码的封装,你可以在任何位置使用封装好的 CQ 码生成器。
|
||||
|
||||
生成器是一个静态类,里面的方法全部是静态调用,命名空间是:`ZM\API\CQ`。
|
||||
|
||||
例如,给用户发送图片这样写就好啦!只需要将添加图片的地方拼到回复用户的字符串里。如果只发图片,整个字符串里只能有 CQ 码。
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\API\CQ;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
class Hello {
|
||||
/**
|
||||
* @CQCommand("发送图片")
|
||||
*/
|
||||
public function msgRecv() {
|
||||
return CQ::image("https://zhamao.xin/file/hello.jpg");
|
||||
// 相当于返回:"[CQ:image,file=https://zhamao.xin/file/hello.jpg]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
效果
|
||||
|
||||
<chat-box>
|
||||
) 发送图片
|
||||
[ https://zhamao.xin/file/hello.jpg
|
||||
</chat-box>
|
||||
|
||||
## CQ 码操作
|
||||
|
||||
### CQ::decode()
|
||||
|
||||
CQ 码字符反转义。
|
||||
|
||||
| 反转义前 | 反转义后 |
|
||||
| -------- | -------- |
|
||||
| `&` | `&` |
|
||||
| `[` | `[` |
|
||||
| `]` | `]` |
|
||||
|
||||
```php
|
||||
$str = CQ::decode("[我只是一条普通的文本]");
|
||||
// 转换为 "[我只是一条普通的文本]"
|
||||
```
|
||||
|
||||
### CQ::encode()
|
||||
|
||||
转义 CQ 码的敏感符号,防止 酷Q 把不该解析为 CQ 码的消息内容当作 CQ 码处理。
|
||||
|
||||
```php
|
||||
$str = CQ::encode("[CQ:我只是一条普通的文本]");
|
||||
// $str: "[CQ:我只是一条普通的文本]"
|
||||
```
|
||||
|
||||
### CQ::removeCQ()
|
||||
|
||||
去除字符串中所有的 CQ 码。
|
||||
|
||||
```php
|
||||
$str = CQ::removeCQ("[CQ:at,qq=all]这是带表情的全体消息[CQ:face,id=8]");
|
||||
// $str: "这是带表情的全体消息"
|
||||
```
|
||||
|
||||
## CQ 码列表
|
||||
|
||||
### CQ::face() - 发送 QQ 表情
|
||||
|
||||
发送 QQ 原生表情。
|
||||
|
||||
定义:`CQ::face($id)`
|
||||
|
||||
参数:`$id` 为 QQ 表情对应的 ID 号,一些常见的表情 ID 对应的表情样式见 [炸毛框架 1.x 版本文档](https://docs-v1.zhamao.xin/face_list.html)。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("打盹")
|
||||
*/
|
||||
public function faceTest() {
|
||||
ctx()->reply("正在打盹...");
|
||||
ctx()->reply(CQ::face(8));
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) 打盹
|
||||
( 正在打盹...
|
||||
[ https://docs-v1.zhamao.xin/face/8.gif
|
||||
</chat-box>
|
||||
|
||||
!!! note "提示"
|
||||
对于不断更新的 QQ 版本下,可能会持续扩充新的 QQ 表情,如果上表没有新的表情的话,也可以使用消息接收的方式,让机器人收到表情后解析出来对应的 id 然后再发送。
|
||||
|
||||
|
||||
### CQ::image() - 发送图片
|
||||
|
||||
发送图片。
|
||||
|
||||
定义:`image($file, $cache = true, $flash = false, $proxy = true, $timeout = -1)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 默认值 | 说明 |
|
||||
| --------- | ---- | ---- | ------- | ------------------------------------------------------------ |
|
||||
| `file` | ✓ | ✓ | 必填 | 图片文件名 |
|
||||
| `flash` | ✓ | ✓ | `false` | 图片类型,当参数为 true 时代表发送闪照 |
|
||||
| `cache` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否使用已缓存的文件,默认 `true` |
|
||||
| `proxy` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否通过代理下载文件(需通过环境变量或配置文件配置代理),默认 `true` |
|
||||
| `timeout` | | ✓ | `-1` | 只在通过网络 URL 发送时有效,单位秒,表示下载网络文件的超时时间,默认 -1 不超时 |
|
||||
|
||||
发送时,`file` 参数除了支持使用收到的图片文件名直接发送外,还支持:
|
||||
|
||||
- 绝对路径,例如 `file:///root/imagetest/1.png`,格式使用 [`file` URI](https://tools.ietf.org/html/rfc8089)
|
||||
- 网络 URL,例如 `http://i1.piimg.com/567571/fdd6e7b6d93f1ef0.jpg`
|
||||
- Base64 编码,例如 `base64://iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAIAAADJt1n/AAAAKElEQVQ4EWPk5+RmIBcwkasRpG9UM4mhNxpgowFGMARGEwnBIEJVAAAdBgBNAZf+QAAAAABJRU5ErkJggg==`
|
||||
|
||||
### CQ::record() - 发送语音
|
||||
|
||||
发送语音消息。
|
||||
|
||||
定义:`CQ::record($file, $magic = false, $cache = true, $proxy = true, $timeout = -1)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 默认值 | 说明 |
|
||||
| --------- | ---- | ---- | ------- | ------------------------------------------------------------ |
|
||||
| `file` | ✓ | ✓ | 必填 | 音频文件名 |
|
||||
| `flash` | ✓ | ✓ | `false` | 图片类型,当参数为 true 时代表发送闪照 |
|
||||
| `cache` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否使用已缓存的文件,默认 `true` |
|
||||
| `proxy` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否通过代理下载文件(需通过环境变量或配置文件配置代理),默认 `true` |
|
||||
| `timeout` | | ✓ | `-1` | 只在通过网络 URL 发送时有效,单位秒,表示下载网络文件的超时时间,默认 -1 不超时 |
|
||||
|
||||
发送时,`file` 参数除了支持使用收到的语音文件名直接发送外,还支持其它形式,参考上方发送图片。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("说你好")
|
||||
*/
|
||||
public function say() {
|
||||
ctx()->reply(CQ::record("https://zhamao.xin/file/hello.mp3"));
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) 说你好
|
||||
( [语音消息,点击收听] 2'' )))
|
||||
</chat-box>
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::at() - 群里@某人或全体
|
||||
|
||||
在群里 at 某个人或全体成员(全体成员需要有管理员权限)。
|
||||
|
||||
定义:`CQ::at($qq)`
|
||||
|
||||
参数:`$qq` 参数必填,如果填的是 QQ 号,则是单独 at 某人,如果是 `all`,则是 at 全体成员。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("at测试")
|
||||
*/
|
||||
public function atTest() {
|
||||
ctx()->reply(CQ::at(627577391)." 你好啊!");
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) at测试
|
||||
( @鲸鱼 你好啊!
|
||||
</chat-box>
|
||||
|
||||
### CQ::video() - 发送短视频
|
||||
|
||||
发送短视频。
|
||||
|
||||
定义:`CQ::video($file, $cache = true, $proxy = true, $timeout = -1)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 默认值 | 说明 |
|
||||
| --------- | ---- | ---- | ------ | ------------------------------------------------------------ |
|
||||
| `file` | ✓ | ✓ | 必填 | 短视频文件名 |
|
||||
| `cache` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否使用已缓存的文件,默认 `true` |
|
||||
| `proxy` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否通过代理下载文件(需通过环境变量或配置文件配置代理),默认 `true` |
|
||||
| `timeout` | | ✓ | `-1` | 只在通过网络 URL 发送时有效,单位秒,表示下载网络文件的超时时间,默认 -1 不超时 |
|
||||
|
||||
发送时,`file` 参数除了支持使用收到的视频文件名直接发送外,还支持其它形式,参考上方发送图片。
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::rps() - 猜拳
|
||||
|
||||
定义:`CQ::rps()`
|
||||
|
||||
用法:`CQ::rps()`
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::dice() - 掷骰子
|
||||
|
||||
定义:`CQ::dice()`
|
||||
|
||||
用法:`CQ::dice()`
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::shake() - 窗口抖动
|
||||
|
||||
定义:`CQ::shake()`
|
||||
|
||||
用法:`CQ::shake()`
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::poke() - 戳一戳
|
||||
|
||||
发送戳一戳。
|
||||
|
||||
定义:`CQ::poke($type, $id, $name = "")`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| ------ | ---- | ---- | ------------------------------------------------------------ | ------ |
|
||||
| `type` | ✓ | ✓ | 见 [Mirai 的 PokeMessage 类](https://github.com/mamoe/mirai/blob/f5eefae7ecee84d18a66afce3f89b89fe1584b78/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt#L49) | 类型 |
|
||||
| `id` | ✓ | ✓ | 同上 | ID |
|
||||
| `name` | ✓ | | 同上 | 表情名 |
|
||||
|
||||
例子:`CQ::poke(6,-1)`
|
||||
|
||||
效果:放大招
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::anonymous() - 匿名发消息
|
||||
|
||||
匿名发消息。需要在允许匿名发消息的群里发。
|
||||
|
||||
!!! tip "提示"
|
||||
|
||||
当收到匿名消息时,需要通过 [消息事件的群消息](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md#群消息) 的 `anonymous` 字段判断。
|
||||
|
||||
定义:`CQ::anonymous($ignore = 1)`
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("匿名测试")
|
||||
*/
|
||||
public function anonymousTest() {
|
||||
ctx()->reply(CQ::anonymous()."匿名测试");
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::share() - 链接分享
|
||||
|
||||
发送链接分享卡片,可自定义内容。
|
||||
|
||||
定义:`CQ::share($url, $title, $content = null, $image = null)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| --------- | ---- | ---- | -------- | -------------- |
|
||||
| `url` | ✓ | ✓ | - | URL |
|
||||
| `title` | ✓ | ✓ | - | 标题 |
|
||||
| `content` | ✓ | ✓ | - | 可选,内容描述 |
|
||||
| `image` | ✓ | ✓ | - | 可选,图片 URL |
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("链接分享测试")
|
||||
*/
|
||||
public function shareTest() {
|
||||
ctx()->reply(CQ::share("https://baidu.com", "UC忽悠部", "震惊!我市一男子在光天化日之下..."));
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::contact() - 推荐好友
|
||||
|
||||
发送推荐好友的卡片。
|
||||
|
||||
定义:`CQ::contact($type, $id)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| ------ | ---- | ---- | ------------- | ---------------------- |
|
||||
| `type` | ✓ | ✓ | `qq`,`group` | 推荐好友或群 |
|
||||
| `id` | ✓ | ✓ | - | 被推荐人的 QQ 号或群号 |
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("我的名片")
|
||||
*/
|
||||
public function myCard() {
|
||||
ctx()->reply(CQ::contact("qq", ctx()->getUserId()));
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::location() - 发送位置
|
||||
|
||||
发送位置,基于经纬度坐标发的。
|
||||
|
||||
定义:`CQ::location($lat, $lon, $title = "", $content = "")`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| --------- | ---- | ---- | -------- | -------------- |
|
||||
| `lat` | ✓ | ✓ | - | 纬度 |
|
||||
| `lon` | ✓ | ✓ | - | 经度 |
|
||||
| `title` | ✓ | ✓ | - | 可选,标题 |
|
||||
| `content` | ✓ | ✓ | - | 可选,内容描述 |
|
||||
|
||||
### CQ::music() - 音乐分享
|
||||
|
||||
分享音乐,通过卡片。
|
||||
|
||||
发送音乐分享卡片。此 CQ 码如果伴随着其他文字,则文字内容会被丢弃。
|
||||
|
||||
定义:`CQ::music($type, $id_or_url, $audio = null, $title = null, $content = null, $image = null)`
|
||||
|
||||
- `$type`: 发送类型
|
||||
- `$id_or_url`: 音乐的 id 或 音乐卡片点进去打开的链接
|
||||
- `$audio`: 音频文件的 HTTP 地址
|
||||
- `$title`: 音乐卡片的标题,建议 12 字以内
|
||||
- `$content`: 音乐卡片的简介内容(可选)
|
||||
- `$image`: 音乐卡片的图片的链接地址(可选)
|
||||
|
||||
如果 `$type` 参数为 `qq` 或 `163` 或 `xiami`,则必须且只和第二个参数 `$id_or_url` 配合使用。这三个为内置分享,需要先通过搜索功能获取对应平台歌曲的 id 后使用。
|
||||
|
||||
如果 `$type` 参数为 `custom`,则表明此音乐卡片为用户自定义,你可以根据自己的需要自定义卡片内容和音频。此时必须填写 `$id_or_url`, `$audio`, `$title` 三个参数。
|
||||
|
||||
```php
|
||||
ctx()->reply(CQ::music("163", "730806")); //一首我喜欢的歌
|
||||
// 以内置的发送类型发送音乐卡片,我这里挑了网易云音乐的一首歌。
|
||||
|
||||
ctx()->reply("custom", "https://baidu.com/", "https://zhamao.xin/file/hello.mp3", "我是Siri说出来的Hello", "不服来打我呀!", "https://zhamao.xin/file/hello.jpg");
|
||||
// 自定义整个卡片的每个内容
|
||||
```
|
||||
|
||||
### CQ::forward() - 合并转发
|
||||
|
||||
合并转发消息。
|
||||
|
||||
定义:`CQ::forward($id)`
|
||||
|
||||
参数
|
||||
|
||||
```
|
||||
[CQ:forward,id=123456]
|
||||
```
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| ------ | ---- | ---- | -------- | ------------------------------------------------------------ |
|
||||
| `id` | ✓ | | 必填 | 合并转发 ID,需通过 [`get_forward_msg` API](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-获取合并转发消息) 获取具体内容 |
|
||||
|
||||
### CQ::node() - 合并转发自定义节点
|
||||
|
||||
接收时,此消息段不会直接出现在消息事件的 `message` 中,需通过 [`get_forward_msg` API](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-获取合并转发消息) 获取。发送时,通过获取回来的 API 节点信息进行发送。
|
||||
|
||||
定义:`CQ::node($user_id, $nickname, $content)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| ---------- | ---- | ---- | -------- | ------------------------------------------------------------ |
|
||||
| `user_id` | ✓ | ✓ | - | 发送者 QQ 号 |
|
||||
| `nickname` | ✓ | ✓ | - | 发送者昵称 |
|
||||
| `content` | ✓ | ✓ | - | 消息内容,支持发送消息时的 `message` 数据类型,见 [API 的参数](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/#参数) |
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("node测试")
|
||||
*/
|
||||
public function nodeTest() {
|
||||
ctx()->reply(CQ::node(123456, "Jack", "[CQ:face,id=123]哈喽~"));
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::xml() - XML 消息
|
||||
|
||||
发送 QQ 兼容的 XML 多媒体消息。
|
||||
|
||||
定义:`CQ::xml($data)`
|
||||
|
||||
参数:`$data` 为 xml 字符串
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("xml测试")
|
||||
*/
|
||||
public function xmlTest() {
|
||||
ctx()->reply(CQ::xml("<?xml ..."));
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::json() - JSON 消息
|
||||
|
||||
发送 QQ 兼容的 JSON 多媒体消息。
|
||||
|
||||
定义:`CQ::json($data)`
|
||||
|
||||
参数同上,内含 JSON 字符串即可。
|
||||
|
||||
!!! tip "提示"
|
||||
|
||||
因为某些众所周知的原因,XML 和 JSON 的返回不提供实例,有兴趣的可以自行研究如何编写,文档不含任何相关教程。
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
# 全局方法
|
||||
|
||||
全局方法就是 PHP 的全局函数,任意位置都可以调用,无需使用 use 字样。
|
||||
|
||||
## getClassPath()
|
||||
|
||||
根据加载的用户编写的代码类名来获取类所在的文件路径。
|
||||
|
||||
=== "src/Module/Example/Hello.php"
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
class Hello { ... }
|
||||
```
|
||||
|
||||
=== "src/Module/Example/Start.php"
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
class Start {
|
||||
/**
|
||||
* @OnStart()
|
||||
*/
|
||||
public function onStart() {
|
||||
Console::info("Path: ".getClassPath(Hello::class));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "输出结果"
|
||||
|
||||
```
|
||||
[11:12:02] [I] [#0] Path: /mnt/d/project/zhamao-framework/src/Module/Example/Hello.ph
|
||||
```
|
||||
|
||||
## explodeMsg()
|
||||
|
||||
切割字符串的函数,支持多空格,换行,tab。
|
||||
|
||||
定义:`explodeMsg($msg, $ban_comma = false)`
|
||||
|
||||
```php
|
||||
$s = explodeMsg("你好啊 你好你好\n我还有多个空格 哈哈哈");
|
||||
echo json_encode($s, 128|256); // ["你好啊","你好你好","我还有多个空格","哈哈哈"]
|
||||
```
|
||||
|
||||
## unicode_decode()
|
||||
|
||||
Unicode 解码,一般用于被转义的 Unicode 转回来。
|
||||
|
||||
```php
|
||||
echo unicode_decode("\u4f60\u597d"); // 你好
|
||||
```
|
||||
|
||||
## matchPattern()
|
||||
|
||||
根据星号匹配字符串(非正则表达式)。
|
||||
|
||||
匹配示例:
|
||||
|
||||
- `你今天*了吗` -> 你今天喝水了吗
|
||||
- `*的天气怎么样` -> 德州的天气怎么样
|
||||
- `把*翻译成*` -> 把茶翻译成英语
|
||||
|
||||
定义:`matchPattern($pattern, $context)`
|
||||
|
||||
`$pattern` 为匹配模式,例如 `你今天*了吗`。
|
||||
|
||||
`$context` 为要判断是否匹配的内容。
|
||||
|
||||
返回值:`bool`,当为 true 时代表规则是匹配的,false 代表不匹配。
|
||||
|
||||
```php
|
||||
matchPattern("*是个啥?", "996是个啥?"); // true
|
||||
matchPattern("我想听*唱歌", "你想听谁唱歌"); // false
|
||||
matchPattern("*把*翻译成*", "请把你好翻译成阿拉伯语"); // true
|
||||
```
|
||||
|
||||
## split_explode()
|
||||
|
||||
和 `explodeMsg()` 类似,用作分割字符串,不过此函数加入了对 `中文|数字` 两者的分割,也就是说中文和数字之间也会被分割。
|
||||
|
||||
定义:`split_explode($del, $str, $divide_en = false)`
|
||||
|
||||
```php
|
||||
split_explode(" ", "前进20 急啊急啊"); // ["前进","20","急啊急啊"]
|
||||
```
|
||||
|
||||
`$del` 和 `explode()` 的第一个参数作用相同,作为初期分割的标志。
|
||||
|
||||
`$str` 表示待分割的内容。
|
||||
|
||||
`$divide_en` 表示是否分割中文和英文,如果为是,则中文和英文之间也会被分割开。
|
||||
|
||||
## matchArgs()
|
||||
|
||||
`matchPattern()` 的扩展,如果 `matchPattern()` 格式的字符串和模式匹配成功,则通过星号位置来提取星号匹配到的内容,参数同 `matchPattern()`。
|
||||
|
||||
```php
|
||||
$r = matchArgs("把*翻译成*", "把日语翻译成英语"); // ["日语","英语"]
|
||||
```
|
||||
|
||||
## connectIsQQ()
|
||||
|
||||
判断当前 WebSocket 连接是否为 OneBot 标准的机器人客户端。
|
||||
|
||||
## connectIsDefault()
|
||||
|
||||
判断连接是否是未定义类型的 WebSocket 连接。
|
||||
|
||||
## connectIs()
|
||||
|
||||
判断连接是否是对应类型的 WebSocket 连接。
|
||||
|
||||
```php
|
||||
connectIs("your_another_type_connect");
|
||||
```
|
||||
|
||||
## set_coroutine_params()
|
||||
|
||||
设置当前上下文中的一些变量。
|
||||
|
||||
```php
|
||||
set_coroutine_params(["data" => [
|
||||
"post_type" => "message",
|
||||
...
|
||||
]]);
|
||||
```
|
||||
|
||||
## ctx()
|
||||
|
||||
别名:`context()`,获取当前协程的上下文,见 [上下文](/component/context/)。
|
||||
|
||||
## zm_debug()
|
||||
|
||||
同 `Console::debug($msg)`。
|
||||
|
||||
## zm_sleep()
|
||||
|
||||
协程版 `sleep()` 函数。
|
||||
|
||||
定义:`zm_sleep($s = 1)`
|
||||
|
||||
`$s`:睡眠的时间:秒,可支持小数。(例如:0.001 代表 1 毫秒)
|
||||
|
||||
为什么不用 PHP 自带的 sleep 呢?因为炸毛框架是基于协程的,协程版 sleep 需要使用 Swoole 自带的 sleep。此函数做了一个简单的封装。
|
||||
|
||||
```php
|
||||
zm_sleep(5);
|
||||
zm_sleep(0.05);
|
||||
```
|
||||
|
||||
## zm_exec()
|
||||
|
||||
执行系统命令,替代 PHP 的 `exec()`。
|
||||
|
||||
定义:`zm_exec($cmd)`
|
||||
|
||||
返回值:
|
||||
|
||||
```php
|
||||
array(
|
||||
'code' => 0, // 进程退出的状态码
|
||||
'signal' => 0, // 信号
|
||||
'output' => 'hello world', // 输出内容
|
||||
);
|
||||
```
|
||||
|
||||
```php
|
||||
$result = zm_exec("echo 'hello world'")["output"];
|
||||
```
|
||||
|
||||
## zm_cid()
|
||||
|
||||
获取当前协程的 ID,效果等同于 `\Swoole\Coroutine::getCid()`。
|
||||
|
||||
## zm_yield()
|
||||
|
||||
挂起当前协程,直到手动恢复,效果等同于 `\Swoole\Coroutine::yield()`。
|
||||
|
||||
## zm_resume()
|
||||
|
||||
恢复继续执行协程,效果等同于 `\Swoole\Coroutine::resume()`。
|
||||
|
||||
```php
|
||||
$r = 0;
|
||||
function test() {
|
||||
echo "hello-1\n";
|
||||
global $r;
|
||||
$r = zm_cid();
|
||||
zm_yield();
|
||||
echo "hello-2\n";
|
||||
}
|
||||
|
||||
go("test");
|
||||
echo "hello-3\n";
|
||||
zm_resume($r);
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
```
|
||||
hello-1
|
||||
hello-3
|
||||
hello-2
|
||||
```
|
||||
|
||||
## server()
|
||||
|
||||
获取 Swoole Server 对象进行操作,效果等同于 `\ZM\Framework::$server`。
|
||||
|
||||
```php
|
||||
echo server()->worker_id.PHP_EOL; // 0
|
||||
```
|
||||
|
||||
## bot()
|
||||
|
||||
返回 ZMRobot 操作机器人 API 的对象。
|
||||
|
||||
对于默认的模式,如果框架连接了多个机器人实例,则会随机返回一个机器人的 API 实例。如果使用了单例模式,则返回单例模式的机器人 API 实例。
|
||||
|
||||
```php
|
||||
bot()->sendPrivateMsg(123456, "你好啊!!");
|
||||
// 等同于 ZMRobot::getRandom()->sendPrivateMsg(123456, "你好啊!!");
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# 框架组件
|
||||
|
||||
这里列举了框架内的你可能会用到的常用组件。
|
||||
@@ -1,320 +0,0 @@
|
||||
# LightCache 轻量缓存
|
||||
|
||||
在炸毛框架 1.x 时代,框架里有非常方便使用的 ZMBuf 缓存,但是由于 2.x 版本框架加入了多进程模式,所以不能再以传统的存到全局变量的方式来构建和管理缓存了,LightCache 就是替代方案。LightCache 依旧是 key-value 键值对形式的存储,支持多种类型的变量。
|
||||
|
||||
定义:`ZM\Store\LightCache`。
|
||||
|
||||
## 与 ZMBuf 的不同
|
||||
|
||||
从存储内容角度,LightCache 存入的是 Swoole 初始化的共享内存,基于 Swoole/Table 编写。优势在于多进程下的性能极佳,而且没有数据同步问题;劣势在于它需要在启动框架前就声明总大小,不能根据存储数据的大小来划定,需提前指定最大能存储的容量。而 ZMBuf 基于直接把变量存到静态成员中 `public static $data` 类似这样,且 1.x 框架基于单进程单线程,无任何数据同步的问题。
|
||||
|
||||
总之来说,LightCache 是让用户在涉及多进程编程时,一个折中的解决方案,提出和解决了很多多进程开发时存储数据遇到的问题:数据同步、进程间通信效率、数据是否需要上锁等。
|
||||
|
||||
- 数据同步:多进程下因为是固定的内存大小区域,所以每个进程读取和写入都是只有一份数据的,不存在数据不同步的问题。
|
||||
- 进程间通信:因为多个进程共享一片区域的内存,所以不需要进程间通信,无协程切换。
|
||||
- 镀锡是否需要上锁:看情况。一般情况下 Swoole/Table 模块自带一个行锁,只有两个进程在两个 CPU 上同时读取一行数据时才会发生抢锁,作为框架的使用者,如果只写或只读,是无需手动上任何锁的。只有在先 `get()` 再 `set()` 这样的情况才需要上自旋锁。后面的段会详细讲述。
|
||||
|
||||
使用体验上,基本和 ZMBuf 无差,如果没有用过 1.x 的版本,可无视此段话。
|
||||
|
||||
## 使用
|
||||
|
||||
### 配置和初始化
|
||||
|
||||
配置文件还是在 `config/global.php` 文件里,字段是 `light_cache`。
|
||||
|
||||
```php
|
||||
/** 轻量字符串缓存,默认开启 */
|
||||
$config['light_cache'] = [
|
||||
'size' => 1024, //最多允许储存的条数(需要2的倍数)
|
||||
'max_strlen' => 16384, //单行字符串最大长度(需要2的倍数)
|
||||
'hash_conflict_proportion' => 0.6, //Hash冲突率(越大越好,但是需要的内存更多)
|
||||
'persistence_path' => $config['zm_data'].'_cache.json',
|
||||
'auto_save_interval' => 900
|
||||
];
|
||||
```
|
||||
|
||||
其中 `$size` 是最多保存的键值对数目,填写非 2 的倍数时底层会自动修正为 2 的倍数值。
|
||||
|
||||
`$max_strlen` 为单条值最长保存的长度。因为 Swoole/Table 只能存数字、字符串,所以在存取数组等变量时会先将其序列化为字符串形式保存,get 时自动反序列化回来。在存数组等非字符串变量时,请先自行计算你要存取的内容序列化后的最大长度。如果长度超出最大长度,则无法保存,`set()` 将返回 false。
|
||||
|
||||
`hash_conflict_proportion`:Table 模块底层使用 hash 表,会存在 hash 冲突,调大 Hash 冲突率会提升 `size` 指定条目数的准确性,但也将增加物理内存的使用。这里单位是百分比,`0.6` 为 `60%`。
|
||||
|
||||
`persistence_path` 是持久化保存变量的文件保存位置,默认在 `zm_data/_cache.json` 文件。
|
||||
|
||||
`auto_save_interval` 是持久化保存变量的自动保存时间,单位秒。
|
||||
|
||||
### LightCache::set()
|
||||
|
||||
设置内容。
|
||||
|
||||
定义:`LightCache::set($key, $value, $expire = -1)`
|
||||
|
||||
返回值:`bool`。当 value 超出了最大长度或内存不足时,返回 false,其余 true。
|
||||
|
||||
参数:
|
||||
|
||||
`$key` 的长度不能超过 64 字节,且不能存入二进制内容。
|
||||
|
||||
`$value` 可存入 `bool`、`string`、`int`、`array` 等可被 `json_encode()` 的变量,闭包函数和对象不可存入。
|
||||
|
||||
`$expire` 是 `int`,超时时间(秒)。如果设定了大于 0 的值,则表明是在 `$expire` 秒后自动删除。如果为 -1 则什么都不做,如果框架使用了 `stop` 或 Ctrl+C 或意外退出时数据会丢失。如果为 -2,则会将此数据持久化保存,保存在上方配置文件指定的 json 文件中,待关闭后再次启动框架会自动加载回来,不会丢失。
|
||||
|
||||
```php
|
||||
// use ZM\Store\LightCache;
|
||||
/**
|
||||
* @CQCommand("store")
|
||||
*/
|
||||
public function store() {
|
||||
LightCache::set("key1", ["value1" => "strOrInt", "value2" => 123]);
|
||||
return "OK!";
|
||||
}
|
||||
/**
|
||||
* @CQCommand("storeAfterRemove")
|
||||
*/
|
||||
public function storeAfterRemove() {
|
||||
LightCache::set("store1", "remove1", 30);
|
||||
ctx()->reply(LightCache::get("store1") !== null ? "内容存在!" : "内容不存在!");
|
||||
zm_sleep(30);
|
||||
ctx()->reply(LightCache::get("store1") !== null ? "内容存在!" : "内容不存在!");
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) store
|
||||
( OK!
|
||||
) storeAfterRemove
|
||||
( 内容存在!
|
||||
^ 等待 30 秒
|
||||
( 内容不存在!
|
||||
</chat-box>
|
||||
|
||||
### LightCache::get()
|
||||
|
||||
获取内容。
|
||||
|
||||
返回值:`mixed|null`。当无内容或过期时返回 null,剩余情况返回原数据。
|
||||
|
||||
### LightCache::getExpire()
|
||||
|
||||
获取存储项剩余过期时间(秒)。
|
||||
|
||||
定义:`LightCache::getExpire(string $key)`
|
||||
|
||||
```php
|
||||
$s = LightCache::set("test", "hello", 20);
|
||||
zm_sleep(10);
|
||||
dump(LightCache::getExpire("test")); // 返回 10
|
||||
```
|
||||
|
||||
### LightCache::getMemoryUsage()
|
||||
|
||||
获取轻量缓存使用的总空间大小(字节)
|
||||
|
||||
```php
|
||||
LightCache::getMemoryUsage());
|
||||
```
|
||||
|
||||
轻量缓存的内存手工计算方式:(Table 结构体长度` + `KEY 长度 64 字节 + `$size`) * (1 + `$conflict_proportion`) * 列尺寸。
|
||||
|
||||
Table 结构体长度根据你所设定的 `max_strlen` 会变化。
|
||||
|
||||
> 框架默认配置下的轻量缓存启动后大约占用内存 25MB 左右。
|
||||
|
||||
### LightCache::isset()
|
||||
|
||||
判断某项是否存在。
|
||||
|
||||
```php
|
||||
LightCache::set("foo", "bar");
|
||||
dump(LightCache::isset("foo")); // true
|
||||
```
|
||||
|
||||
### LightCache::unset()
|
||||
|
||||
删除某项。
|
||||
|
||||
```php
|
||||
LightCache::set("foo", "bar");
|
||||
LightCache::unset("foo");
|
||||
dump(LightCache::isset("foo")); // false
|
||||
```
|
||||
|
||||
### LightCache::getAll()
|
||||
|
||||
获取所有项。
|
||||
|
||||
```php
|
||||
LightCache::set("k1", ["I", "am", "array"]);
|
||||
LightCache::set("k2", "v2");
|
||||
LightCache::set("k3", 20001);
|
||||
dump(LightCache::getAll());
|
||||
/*
|
||||
{
|
||||
"k1": ["I", "am", "array"],
|
||||
"k2": "v2",
|
||||
"k3": 20001
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
### LightCache::savePersistence()
|
||||
|
||||
立刻保存所有被标记为持久化的缓存项到磁盘。
|
||||
|
||||
!!! note "提示"
|
||||
|
||||
在一般情况下,框架定时执行此方法来保存,在停止框架、reload 框架和 Ctrl+C 停止框架的时候,均会执行保存。
|
||||
|
||||
### 持久化
|
||||
|
||||
将 `set()` 的 expire 设置为 -2 即可。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("store")
|
||||
*/
|
||||
public function store() {
|
||||
LightCache::set("msg_time", time(), -2);
|
||||
return "OK!";
|
||||
}
|
||||
/**
|
||||
* @CQCommand("getStore")
|
||||
*/
|
||||
public function getStore() {
|
||||
return "存储时间:".date("Y-m-d H:i:s", LightCache::get("msg_time"));
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
^ 我在 2021-01-05 15:21:00 发送这条消息
|
||||
) store
|
||||
( OK!
|
||||
^ 这时我用 Ctrl+C 停止框架,过一会儿再启动
|
||||
) getStore
|
||||
( 存储时间:2021-01-05 15:21:00
|
||||
</chat-box>
|
||||
|
||||
### 数据加锁
|
||||
|
||||
在特定情况下,使用 LightCache 必须配合锁使用,否则会出现数据错乱。我们来看下面的例子:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/test")
|
||||
*/
|
||||
public function test() {
|
||||
$s = LightCache::get("web_count");
|
||||
if($s === null) $s = 1;
|
||||
else $s += 1;
|
||||
LightCache::set("web_count", $s);
|
||||
return "<h1>It works!</h1>";
|
||||
}
|
||||
```
|
||||
|
||||
我们使用压测工具,例如 `ab`,对此路由接口开很多很多线程进行测试,假设我们设置请求总数为 200000 次,框架的工作进程数为 8(我用的是 2020 年末的 i5 MacBook Pro 13 inch)。
|
||||
|
||||
> 懒得再测了,下面就口述过程吧。
|
||||
|
||||
在运行完测试后,通过 `LightCache::get("web_count")`,获取到的数你会发现不是 200000。怎么回事呢?请自行翻阅多进程开发相关的书籍哦!(或者简单理解为,有一些情况下,进程 1 执行到了 `if-else` 语句,另一个进程也执行到了这里,两次在代码层面加的数是相同的,则虽然请求了两次,但是后执行 set 的那个进程又覆盖了前一个进程执行的值,导致最终结果加了 1 而不是 2)
|
||||
|
||||
!!! note "提示"
|
||||
|
||||
同样的场景,使用 ZMAtomic 就不需要使用锁了。Atomic 是一句话:`add(1)` 立即加值的。而 LightCache 需要加锁的情况一般都是 `get->改值->set` 这样的代码。
|
||||
|
||||
|
||||
解决这一问题,就需要用到锁。这种情况下,我们首先考虑的是自旋锁,框架也因此内置了一个方便使用的自旋锁组件。详见下一章:自旋锁。
|
||||
|
||||
## 如何临时缓存大变量
|
||||
|
||||
由于 LightCache 需要提前声明最大大小,所以在某些情况下,比如第三方 API 接口结果临时缓存,可能不太适合使用,这时对于 2.x 版本的多进程炸毛框架是一个新的问题。
|
||||
|
||||
解决方案有三种:
|
||||
|
||||
- 将 `global.php` 中的 `swoole.worker_num` 调整为 `1` 即可,所有除所有主 handler 事件的用户类外其他类均可使用如 `Hello::$store` 类似的静态变量全局存取
|
||||
- 使用 WorkerCache(需要 2.2.0 以上版本)
|
||||
- 使用 Redis(需要安装 `redis` 扩展)
|
||||
|
||||
以上,WorkerCache 是为了弥补 LightCache 的不足而诞生的,以下就是 WorkerCache 的具体内容。
|
||||
|
||||
### WorkerCache 跨进程大缓存
|
||||
|
||||
WorkerCache 和 LightCache 几乎完全不同,WorkerCache 存储的方式说白了就是 PHP 的静态变量,不过框架支持使用封装好的进程间通信进行跨进程读取。但由于需要设置一个存储变量的进程,所以配置文件必须先指定要将数据存到哪个 Worker/TaskWorker 进程中。关于框架内多进程的说明,请见 [进阶 - 多进程 Hack](/advanced/multi-process/)。
|
||||
|
||||
定义:`ZM\Store\WorkerCache`。
|
||||
|
||||
#### 配置
|
||||
|
||||
见 [基本配置](/guide/basic-config/)。
|
||||
|
||||
#### WorkerCache::get()
|
||||
|
||||
定义:`get($key)`。
|
||||
|
||||
`$key` 为指定要获取的键值对的值,如果不存在则返回 null。
|
||||
|
||||
#### WorkerCache::set()
|
||||
|
||||
定义:`set($key, $value, $async = false)`。
|
||||
|
||||
设置变量,你懂的。
|
||||
|
||||
注意,`$value` 可以是被无损 `json_encode` 和 `json_decode` 的变量,闭包(Closure)、资源(resource)等类型不支持存储。
|
||||
|
||||
`$async` 默认为 false,当为 true 时候,不会返回是否成功设置与否,否则会协程等待是否目标进程存储成功。
|
||||
|
||||
#### WorkerCache::unset()
|
||||
|
||||
定义:`unset($key, $async = false)`
|
||||
|
||||
删除键对应的值。`$async` 的意义同上。
|
||||
|
||||
#### WorkerCache::add()
|
||||
|
||||
定义:`add($key, int $value, $async = false)`
|
||||
|
||||
给 int 类型的值加一,如果值不存在,则默认为 0 且加上目标的 `$value`。
|
||||
|
||||
#### WorkerCache::sub()
|
||||
|
||||
定义:`sub($key, int $value, $async = false)`
|
||||
|
||||
给 int 类型的值减一,如果值不存在,则默认为 0 且减去目标的 `$value`。
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Store\WorkerCache;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
|
||||
class Hello {
|
||||
/**
|
||||
* @CQCommand("set_store")
|
||||
*/
|
||||
public function setStorage() {
|
||||
$arg1 = ctx()->getFullArg("请输入要设置的内容名称");
|
||||
$arg2 = ctx()->getFullArg("请输入要设置的内容");
|
||||
WorkerCache::set($arg1, $arg2);
|
||||
return "成功!";
|
||||
}
|
||||
|
||||
/**
|
||||
* @CQCommand("get_store")
|
||||
*/
|
||||
public function getStorage() {
|
||||
$arg1 = ctx()->getFullArg("请输入要获取的内容名称");
|
||||
$data = WorkerCache::get($arg1);
|
||||
return $data ?? "内容不存在!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) set_store hello world
|
||||
( 成功!
|
||||
) get_store hello
|
||||
( world
|
||||
) get_store foo
|
||||
( 内容不存在!
|
||||
</chat-box>
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
# MySQL 数据库
|
||||
|
||||
## 配置
|
||||
|
||||
炸毛框架的数据库组件支持原生 SQL、查询构造器,去掉了复杂的对象模型关联,同时默认为数据库连接池,使开发变得简单。
|
||||
|
||||
数据库的配置位于 `config/global.php` 文件的 `sql_config` 段。
|
||||
|
||||
数据库操作的唯一核心工具类和功能类为 `\ZM\DB\DB`,使用前需要配置 host 和 use 此类。
|
||||
|
||||
## 查询构造器
|
||||
|
||||
在 炸毛框架 中,数据库查询构造器为创建和执行数据库查询提供了一个方便的接口,它可用于执行应用程序中大部分数据库操作。同时,查询构造器使用 `prepare` 预处理来保护程序免受 SQL 注入攻击,因此没有必要转义任何传入的字符串。
|
||||
|
||||
### 新增数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->insert(['admin_name', 'admin_password'])->save();
|
||||
// INSERT INTO admin VALUES ('admin_name', 'admin_password')
|
||||
```
|
||||
|
||||
其中 `insert` 的参数是插入条目的数据列表。假设 admin 表有 `name`,`password` 两列。
|
||||
|
||||
> 自增 ID 插入 0 即可。
|
||||
|
||||
### 删除数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->delete()->where("name", "admin_name")->save();
|
||||
// DELETE FROM admin WHERE name = 'admin_name'
|
||||
```
|
||||
|
||||
其中 `where` 语句的第一个参数为列名,当只有两个参数时,第二个参数为值,效果等同于 SQL 语句:`WHERE name = 'admin_name'`,当含有第三个参数且第二个参数为 `=`,`!=`,`LIKE` 的时候,效果就是 `WHERE 第一个参数 第二个参数的操作符 第三个参数`。
|
||||
|
||||
### 更新数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->update(["name" => "fake_admin"])->where("name", "admin_name")->save();
|
||||
// UPDATE admin SET name = 'fake_admin' WHERE name = 'admin_name'
|
||||
```
|
||||
|
||||
`update()` 方法中是要 SET 的内容的键值对,例如上面把 `name` 列的值改为 `fake_admin`。
|
||||
|
||||
### 查询数据
|
||||
|
||||
```php
|
||||
$r = DB::table("admin")->select(["name"])->where("name", "fake_admin")->fetchFirst();
|
||||
// SELECT name FROM admin WHERE name = 'fake_admin'
|
||||
echo $r["name"];
|
||||
echo DB::table("admin")->where("name", "fake_admin")->value("name");
|
||||
// SELECT * FROM admin WHERE name = 'fake_admin'
|
||||
```
|
||||
|
||||
`select()` 方法的参数是要查询的列,默认留空为 `["*"]`,也就是所有列都获取,也可以在 table 后直接 where 查询。
|
||||
|
||||
其中 `fetchFirst()` 获取第一行数据。
|
||||
|
||||
如果只需获取一行中的一个字段值,也可以通过上面所示的 `value()` 方法直接获取。
|
||||
|
||||
多列数据获取需要使用 `fetchAll()`
|
||||
|
||||
```php
|
||||
$r = DB::table("admin")->select()->fetchAll();
|
||||
// SELECT * FROM admin WHERE 1
|
||||
foreach($r as $k => $v) {
|
||||
echo $v["name"].PHP_EOL;
|
||||
}
|
||||
```
|
||||
|
||||
### 查询条数
|
||||
|
||||
```php
|
||||
DB::table("admin")->where("name", "fake_admin")->count();
|
||||
//SELECT count(*) FROM admin WHERE name = 'fake_admin'
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 直接执行 SQL
|
||||
|
||||
> 在查询器外执行的 SQL 语句都不会被缓存,都是一定会请求数据库的。
|
||||
|
||||
### DB::rawQuery()
|
||||
|
||||
- 用途:直接执行模板查询的裸 SQL 语句。
|
||||
- 参数:`$line`,`$params`
|
||||
- 返回:查到的行的数组
|
||||
|
||||
`$line` 为请求的 SQL 语句,`$params` 为模板参数。
|
||||
|
||||
```php
|
||||
$r = DB::rawQuery("SELECT * FROM admin WHERE name = ?", ["fake_admin"]);
|
||||
//SELECT * FROM admin WHERE name = 'fake_admin'
|
||||
echo $r[0]["password"];
|
||||
```
|
||||
|
||||
> 参数查询已经从根本上杜绝了 SQL 注入的问题。
|
||||
@@ -1,62 +0,0 @@
|
||||
# Redis
|
||||
|
||||
炸毛框架内置了 Redis 连接池,供开发者使用。使用前需要先安装 `redis` 扩展:
|
||||
|
||||
```bash
|
||||
pecl install redis
|
||||
```
|
||||
|
||||
> 如果是 Docker 环境,则默认已安装。
|
||||
|
||||
## 配置
|
||||
|
||||
配置文件在 `config/global.php` 的全局配置文件下,详情见 [配置](/guide/basic-config/#redis_config)。
|
||||
|
||||
示例配置(假设 Redis Server 开到了本地):
|
||||
|
||||
```php
|
||||
/** Redis连接信息,host留空则启动时不创建Redis连接池 */
|
||||
$config['redis_config'] = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'timeout' => 1,
|
||||
'db_index' => 0,
|
||||
'auth' => ''
|
||||
];
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
当写好配置文件后,不可以使用 reload 进行重载,因为连接池需要在主进程中声明配置,才可以应用到多个工作进程中。所以必须输入 `stop` 或 Ctrl+C 停止后再启动框架。
|
||||
|
||||
定义:`ZM\Store\Redis\ZMRedis`
|
||||
|
||||
因为使用的是连接池,所以每次使用完一个连接需要归还连接给连接池。框架封装了两种方式自动归还,你可以选择下面其中的任意一种。
|
||||
|
||||
以下的方式获取的 `$redis` 都是 `redis` 扩展的对象 `\Redis`,关于 redis 扩展的方法文档,详情见:[Redis 文档](https://www.php.cn/course/49.html)。
|
||||
|
||||
### 对象模式
|
||||
|
||||
```php
|
||||
$obj = new ZMRedis();
|
||||
$redis = $obj->get();
|
||||
ctx()->reply($redis->ping("123"));
|
||||
```
|
||||
|
||||
### 回调模式
|
||||
|
||||
```php
|
||||
// 前面的代码
|
||||
ZMRedis::call(function($redis) {
|
||||
$redis->set("key1", "hello world");
|
||||
$result = $redis->get("key1");
|
||||
ctx()->reply($result);
|
||||
});
|
||||
// 后面的代码
|
||||
```
|
||||
|
||||
### 二者的区别
|
||||
|
||||
选一个喜欢的就好。硬要是说区别的话,对象模式是在 PHP 自动回收这个 `ZMRedis` 对象时会归还连接,也可以通过手动 `unset($obj)` 进行回收,否则就会执行到函数结尾自动回收。切记不可将 `$obj` 对象持久化存到静态或全局变量等。
|
||||
|
||||
回调模式看似是回调,但是是同步执行的,不会发生顺序错乱。也就是说到了 `ZMRedis::call()` 方法里面的时候,后面的代码不会提前执行,是顺序执行的。回调的作用仅仅是用作自动回收连接对象。
|
||||
@@ -1,790 +0,0 @@
|
||||
# 机器人 API(ZMRobot)
|
||||
|
||||
ZMRobot 类是封装好的 OneBot 标准的 API 接口调用类,可以在机器人连接后通过连接或者机器人 QQ 号获取对象并调用接口(如发送群消息、获取群列表等操作)。
|
||||
|
||||
| 属性项 | 属性值 | 备注 |
|
||||
| -------- | ---------------- | ------------------------------ |
|
||||
| 名称 | ZMRobot | |
|
||||
| 类型 | 实例化类 | `$r = new ZMRobot($conn)` |
|
||||
| 命名空间 | `ZM\API\ZMRobot` | 使用前先 `use ZM\API\ZMRobot;` |
|
||||
|
||||
## 属性
|
||||
|
||||
对象属性方法是对 API 的调整,例如是否以 `_async`、`_rate_limited` 后缀发送 API、设置协程返回还是异步返回结果等。
|
||||
|
||||
### ZMRobot::API_NORMAL
|
||||
|
||||
以默认(无后缀)方式请求 API。
|
||||
|
||||
### ZMRobot::API_ASYNC
|
||||
|
||||
以后缀 `_async` 方式异步请求 API。
|
||||
|
||||
### ZMRobot::API_RATE_LIMITED
|
||||
|
||||
以后缀 `_rate_limited` 方式请求 API。
|
||||
|
||||
## 方法
|
||||
|
||||
### setPrefix()
|
||||
|
||||
设置后缀。目前支持 `_async`、`_rate_limited`。
|
||||
|
||||
- **prefix**: `int` `默认:API_NORMAL`,可选 `ZMRobot::API_NORMAL`、`ZMRobot::API_ASYNC`、`ZMRobot::API_RATE_LIMITED`
|
||||
|
||||
设置后缀后,请求的 API 会发生变化。例如发送私聊消息:`sendPrivateMsg()`,请求的 API 为 `send_private_msg_async`,详见 [OneBot 文档](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/README.md)。
|
||||
|
||||
### setCallback()
|
||||
|
||||
设置 API 结果返回方式。默认为 true,就是直接通过框架处理后接收回包直接返回给结果。如果为 false,则 API 请求后只返回是否成功推送出 WS 数据包。
|
||||
|
||||
### getSelfId()
|
||||
|
||||
获取当前对象的机器人 QQ 或 OneBot 实例的 ID。
|
||||
|
||||
```php
|
||||
$bot = ZMRobot::get(123456);
|
||||
echo $bot->getSelfId(); //123456
|
||||
```
|
||||
|
||||
### ZMRobot::get()
|
||||
|
||||
静态方法,用来通过机器人 QQ 或 OneBot 实例的 ID 获取 ZMRobot 对象。
|
||||
|
||||
参数:`$robot_id`,必填。
|
||||
|
||||
```php
|
||||
$r = ZMRobot::get(123456);
|
||||
$r->sendPrivateMsg(55555, "hello");
|
||||
```
|
||||
|
||||
### ZMRobot::getRandom()
|
||||
|
||||
静态方法,随机获取一个连接到框架的机器人(多个机器人实例连接到框架时适用)。
|
||||
|
||||
如果框架没有连接到任何机器人实例,则会抛出一个异常:`ZM\Exception\RobotNotFoundException`。
|
||||
|
||||
```php
|
||||
try {
|
||||
$bot = ZMRobot::getRandom();
|
||||
$bot->sendPrivateMsg(55555, "foo");
|
||||
} catch (\ZM\Exception\RobotNotFoundException $e) {
|
||||
echo "还没有机器人连接到框架!\n";
|
||||
}
|
||||
```
|
||||
|
||||
### ZMRobot::getAllRobot()
|
||||
|
||||
获取所有连接到框架的机器人的 ZMRobot 对象。
|
||||
|
||||
返回值:`ZMRobot[]`。
|
||||
|
||||
```php
|
||||
$all = ZMRobot::getAllRobot();
|
||||
foreach($all as $v) {
|
||||
$v->sendPrivateMsg(55555, "机器人轮流给一个人发消息啦!");
|
||||
}
|
||||
```
|
||||
|
||||
### __construct()
|
||||
|
||||
构造方法。
|
||||
|
||||
参数:`$connection`:炸毛框架内部的连接对象,必填参数。
|
||||
|
||||
```php
|
||||
//从上下文获取 Websocket 连接对象
|
||||
$conn = ctx()->getConnection();
|
||||
$bot = new ZMRobot($conn);
|
||||
```
|
||||
|
||||
## 返回结果处理
|
||||
|
||||
因为框架的机器人是兼容 OneBot 标准的(原 CQHTTP),所以每次接收发送 API 请求的结果都是大体一样的结构。我们以 `sendPrivateMsg()` 为例,因为发送出去的每一条消息都会在 OneBot 实例(如 CQHTTP 插件、go-cqhttp 等)中对应一个消息 ID,以供我们核查消息和后续撤回等操作需要。
|
||||
|
||||
```php
|
||||
$bot = ZMRobot::get("123456"); // 机器人QQ号
|
||||
$obj = $bot->sendGroupMsg("234567", "你好");
|
||||
echo json_encode($obj, 128|256);
|
||||
```
|
||||
|
||||
```json
|
||||
// 输出结果
|
||||
{
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"message_id": 1243
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如上,`$obj` 就是我们的回包内容,我们通过调用 `sendPrivateMsg()` 这个 API 后,会拿到机器人发送此条消息的消息 ID,然后可以通过它来进行其他操作(例如撤回)。
|
||||
|
||||
```php
|
||||
$result = $bot->deleteMsg($obj["data"]["message_id"]);
|
||||
vardump($result["retcode"]); //如果成功撤回,输出 int(0)
|
||||
```
|
||||
|
||||
### 状态码和 Data
|
||||
|
||||
状态码一般情况成功都是 0 或者 200,在过去,炸毛框架兼容 CQHTTP 插件时,错误码的标准按照 CYKU 给出的标准编写,不同的 OneBot 标准的实现,可能有不同的数值,需要根据你对接的机器人客户端进行适配。
|
||||
|
||||
结果中返回的 `data` 字段根据下方不同 API 的调用而不同,具体查看每个 API 写明的 `响应数据` 表格。
|
||||
|
||||
### response 表
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | ---------- | ----------------------- |
|
||||
| `status` | String | "ok" | 状态码说明 |
|
||||
| `retcode` | number | 0 | 返回状态码 |
|
||||
| `data` | array | 见 data 表 | 根据不同的 API 返回不同 |
|
||||
|
||||
## 机器人 API 方法
|
||||
|
||||
### sendPrivateMsg()
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------- | -------- | ------- | ------------------------------------------------------------ |
|
||||
| `user_id` | number | - | 对方 QQ 号 |
|
||||
| `message` | message | - | 要发送的内容 |
|
||||
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------- |
|
||||
| `message_id` | number (int32) | 消息 ID |
|
||||
|
||||
例子
|
||||
|
||||
=== "代码"
|
||||
|
||||
```php
|
||||
$bot = ZMRobot::get(123456); // 123456是你的机器人QQ
|
||||
$bot->sendPrivateMsg("627577391", "你好啊!你好你好!");
|
||||
```
|
||||
|
||||
=== "效果"
|
||||
|
||||
<chat-box>
|
||||
( 你好啊!你好你好!
|
||||
</chat-box>
|
||||
|
||||
|
||||
### sendGroupMsg()
|
||||
|
||||
发送群组消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------- | -------- | ------- | ------------------------------------------------------------ |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `message` | message | - | 要发送的内容 |
|
||||
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------- |
|
||||
| `message_id` | number (int32) | 消息 ID |
|
||||
|
||||
### sendMsg()
|
||||
|
||||
发送消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| -------------- | -------- | ------- | ------------------------------------------------------------ |
|
||||
| `message_type` | string | - | 消息类型,支持 `private`、`group`、`discuss`,分别对应私聊、群组、讨论组,如不传入,则根据传入的 `*_id` 参数判断 |
|
||||
| `target_id` | number | - | 目标号码,如 QQ 号,群号,讨论组号 |
|
||||
| `message` | message | - | 要发送的内容 |
|
||||
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------- |
|
||||
| `message_id` | number (int32) | 消息 ID |
|
||||
|
||||
### deleteMsg()
|
||||
|
||||
撤回消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------ | -------------- | ------ | ------- |
|
||||
| `message_id` | number (int32) | - | 消息 ID |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### getMsg()
|
||||
|
||||
获取消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | ------ | -------------------------------- |
|
||||
| `message_id` | number | - | 消息 ID |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| -------------- | -------------- | ------------------------------------------------------------ |
|
||||
| `time` | number (int32) | 发送时间 |
|
||||
| `message_type` | string | 消息类型,同 [消息事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md) |
|
||||
| `message_id` | number (int32) | 消息 ID |
|
||||
| `real_id` | number (int32) | 消息真实 ID |
|
||||
| `sender` | object | 发送人信息,同 [消息事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md) |
|
||||
| `message` | message | 消息内容 |
|
||||
|
||||
### getForwardMsg()
|
||||
|
||||
获取合并转发消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------ | -------- | ----------- |
|
||||
| `id` | string | 合并转发 ID |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| --------- | ------- | ------------------------------------------------------------ |
|
||||
| `message` | message | 消息内容,使用 [消息的数组格式](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/array.md) 表示,数组中的消息段全部为 [`node` 消息段](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#合并转发自定义节点) |
|
||||
|
||||
### sendLike()
|
||||
|
||||
发送好友赞。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | ------ | -------------------------------- |
|
||||
| `user_id` | number | - | 对方 QQ 号 |
|
||||
| `times` | number | 1 | 赞的次数,每个好友每天最多 10 次 |
|
||||
|
||||
响应数据
|
||||
|
||||
无
|
||||
|
||||
### setGroupKick()
|
||||
|
||||
群组踢人。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| -------------------- | -------- | ------- | ------------------ |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要踢的 QQ 号 |
|
||||
| `reject_add_request` | boolean | `false` | 拒绝此人的加群请求 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupBan()
|
||||
|
||||
群组单人禁言。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | --------- | -------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要禁言的 QQ 号 |
|
||||
| `duration` | number | `30 * 60` | 禁言时长,单位秒,0 表示取消禁言 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupAnonymousBan()
|
||||
|
||||
群组匿名用户禁言。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------------- | ---------------- | --------- | ------------------------------------------------------------ |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `anonymous_or_flag` | object 或 string | - | 要禁言的匿名用户对象(群消息上报的 `anonymous` 字段)或用户的 flag |
|
||||
| `duration` | number | `30 * 60` | 禁言时长,单位秒,无法取消匿名用户禁言 |
|
||||
|
||||
上面的 `anonymous_or_flag` 两者任选其一传入即可。
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupWholeBan()
|
||||
|
||||
群组全员禁言
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | -------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `enable` | boolean | `true` | 是否禁言 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupAdmin()
|
||||
|
||||
群组设置管理员
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要设置管理员的 QQ 号 |
|
||||
| `enable` | boolean | `true` | true 为设置,false 为取消 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupAnonymous()
|
||||
|
||||
群组匿名
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ---------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `enable` | boolean | `true` | 是否允许匿名聊天 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupCard()
|
||||
|
||||
设置群名片(群备注)
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ---------------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要设置的 QQ 号 |
|
||||
| `card` | string | 空 | 群名片内容,不填或空字符串表示删除群名片 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupName()
|
||||
|
||||
设置群名。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------ |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `group_name` | string | 新群名 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupLeave()
|
||||
|
||||
退出群组
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------ | -------- | ------- | -------------------------------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `is_dismiss` | boolean | `false` | 是否解散,如果登录号是群主,则仅在此项为 true 时能够解散 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupSpecialTitle()
|
||||
|
||||
设置群组专属头衔
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------------- | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要设置的 QQ 号 |
|
||||
| `special_title` | string | 空 | 专属头衔,不填或空字符串表示删除专属头衔 |
|
||||
| `duration` | number | `-1` | 专属头衔有效期,单位秒,-1 表示永久,不过此项似乎没有效果,可能是只有某些特殊的时间长度有效,有待测试 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setFriendAddRequest()
|
||||
|
||||
处理加好友请求
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | ------ | ----------------------------------------- |
|
||||
| `flag` | string | - | 加好友请求的 flag(需从上报的数据中获得) |
|
||||
| `approve` | boolean | `true` | 是否同意请求 |
|
||||
| `remark` | string | 空 | 添加后的好友备注(仅在同意时有效) |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupAddRequest()
|
||||
|
||||
处理加群请求 / 邀请
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `flag` | string | - | 加群请求的 flag(需从上报的数据中获得) |
|
||||
| `sub_type` | string | - | `add` 或 `invite`,请求类型(需要和上报消息中的 `sub_type` 字段相符) |
|
||||
| `approve` | boolean | `true` | 是否同意请求/邀请 |
|
||||
| `reason` | string | 空 | 拒绝理由(仅在拒绝时有效) |
|
||||
|
||||
响应数据无
|
||||
|
||||
### getLoginInfo()
|
||||
|
||||
获取登录号信息
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ---------- | -------------- | ------- |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | QQ 昵称 |
|
||||
|
||||
### getStrangerInfo()
|
||||
|
||||
获取陌生人信息
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------- | ---------------------------------------------------- |
|
||||
| `user_id` | number | - | QQ 号 |
|
||||
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ---------- | -------------- | ------------------------------------- |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `sex` | string | 性别,`male` 或 `female` 或 `unknown` |
|
||||
| `age` | number (int32) | 年龄 |
|
||||
|
||||
### getFriendList()
|
||||
|
||||
获取好友列表
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
响应内容为 JSON 数组,每个元素如下:
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ---------- | -------------- | ------ |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `remark` | string | 备注名 |
|
||||
|
||||
### getGroupInfo()
|
||||
|
||||
获取群信息
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------- | ---------------------------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------------ | -------------- | -------------------- |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `group_name` | string | 群名称 |
|
||||
| `member_count` | number (int32) | 成员数 |
|
||||
| `max_member_count` | number (int32) | 最大成员数(群容量) |
|
||||
|
||||
### getGroupList()
|
||||
|
||||
获取群列表
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
响应内容为 JSON 数组,每个元素如下:
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------ |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `group_name` | string | 群名称 |
|
||||
|
||||
### getGroupMemberInfo()
|
||||
|
||||
获取群成员信息
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------- | ---------------------------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | QQ 号 |
|
||||
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------------- | -------------- | ------------------------------------- |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `card` | string | 群名片/备注 |
|
||||
| `sex` | string | 性别,`male` 或 `female` 或 `unknown` |
|
||||
| `age` | number (int32) | 年龄 |
|
||||
| `area` | string | 地区 |
|
||||
| `join_time` | number (int32) | 加群时间戳 |
|
||||
| `last_sent_time` | number (int32) | 最后发言时间戳 |
|
||||
| `level` | string | 成员等级 |
|
||||
| `role` | string | 角色,`owner` 或 `admin` 或 `member` |
|
||||
| `unfriendly` | boolean | 是否不良记录成员 |
|
||||
| `title` | string | 专属头衔 |
|
||||
| `title_expire_time` | number (int32) | 专属头衔过期时间戳 |
|
||||
| `card_changeable` | boolean | 是否允许修改群名片 |
|
||||
|
||||
### getGroupMemberList()
|
||||
|
||||
获取群成员列表
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ---- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
|
||||
响应数据
|
||||
|
||||
响应内容为 JSON 数组,每个元素的内容和上面的 `/get_group_member_info` 接口相同,但对于同一个群组的同一个成员,获取列表时和获取单独的成员信息时,某些字段可能有所不同,例如 `area`、`title` 等字段在获取列表时无法获得,具体应以单独的成员信息为准。
|
||||
|
||||
### getGroupHonorInfo()
|
||||
|
||||
获取群荣誉信息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------------- | ------ | ------------------------------------------------------------ |
|
||||
| `group_id` | number (int64) | - | 群号 |
|
||||
| `type` | string | - | 要获取的群荣誉类型,可传入 `talkative` `performer` `legend` `strong_newbie` `emotion` 以分别获取单个类型的群荣誉数据,或传入 `all` 获取所有数据 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| -------------------- | -------------- | ---------------------------------------------------------- |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `current_talkative` | object | 当前龙王,仅 `type` 为 `talkative` 或 `all` 时有数据 |
|
||||
| `talkative_list` | array | 历史龙王,仅 `type` 为 `talkative` 或 `all` 时有数据 |
|
||||
| `performer_list` | array | 群聊之火,仅 `type` 为 `performer` 或 `all` 时有数据 |
|
||||
| `legend_list` | array | 群聊炽焰,仅 `type` 为 `legend` 或 `all` 时有数据 |
|
||||
| `strong_newbie_list` | array | 冒尖小春笋,仅 `type` 为 `strong_newbie` 或 `all` 时有数据 |
|
||||
| `emotion_list` | array | 快乐之源,仅 `type` 为 `emotion` 或 `all` 时有数据 |
|
||||
|
||||
其中 `current_talkative` 字段的内容如下:
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ----------- | -------------- | -------- |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `avatar` | string | 头像 URL |
|
||||
| `day_count` | number (int32) | 持续天数 |
|
||||
|
||||
其它各 `*_list` 的每个元素是一个 JSON 对象,内容如下:
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------- | -------------- | -------- |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `avatar` | string | 头像 URL |
|
||||
| `description` | string | 荣誉描述 |
|
||||
|
||||
### getCookies()
|
||||
|
||||
获取 Cookies。
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
目前开源的 mirai 为底层的机器人客户端均不支持获取 Cookies 和 CSRF Token,包括 go-cqhttp。
|
||||
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| -------- | -------- | ------ | ----------------------- |
|
||||
| `domain` | string | 空 | 需要获取 cookies 的域名 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| --------- | -------- | ------- |
|
||||
| `cookies` | string | Cookies |
|
||||
|
||||
### getCsrfToken()
|
||||
|
||||
获取 CSRF Token
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------- | -------------- | ---------- |
|
||||
| `token` | number (int32) | CSRF Token |
|
||||
|
||||
### getCredentials()
|
||||
|
||||
获取 QQ 相关接口凭证,即上面两个合并。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| -------- | -------- | ------ | ----------------------- |
|
||||
| `domain` | string | 空 | 需要获取 cookies 的域名 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ---------- |
|
||||
| `cookies` | string | Cookies |
|
||||
| `csrf_token` | number (int32) | CSRF Token |
|
||||
|
||||
### getRecord()
|
||||
|
||||
获取语音。其实并不是真的获取语音,而是转换语音到指定的格式。
|
||||
|
||||
> **提示**:要使用此接口,通常需要安装 ffmpeg,请参考 OneBot 实现的相关说明。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------ | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `file` | string | - | 收到的语音文件名(CQ 码的 `file` 参数),如 `0B38145AA44505000B38145AA4450500.silk` |
|
||||
| `out_format` | string | - | 要转换到的格式,目前支持 `mp3`、`amr`、`wma`、`m4a`、`spx`、`ogg`、`wav`、`flac` |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------ | -------- | ------------------------------------------------------------ |
|
||||
| `file` | string | 转换后的语音文件路径,如 `/home/somebody/cqhttp/data/record/0B38145AA44505000B38145AA4450500.mp3` |
|
||||
|
||||
### getImage()
|
||||
|
||||
获取图片。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------ | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `file` | string | - | 收到的图片文件名(CQ 码的 `file` 参数),如 `6B4DE3DFD1BD271E3297859D41C530F5.jpg` |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------ | -------- | ------------------------------------------------------------ |
|
||||
| `file` | string | 下载后的图片文件路径,如 `/home/somebody/cqhttp/data/image/6B4DE3DFD1BD271E3297859D41C530F5.jpg` |
|
||||
|
||||
### canSendImage()
|
||||
|
||||
检查是否可以发送图片。
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------ | -------- | ------ |
|
||||
| `yes` | boolean | 是或否 |
|
||||
|
||||
### canSendRecord()
|
||||
|
||||
检查是否可以发送语音,返回同上。
|
||||
|
||||
### getStatus()
|
||||
|
||||
获取插件运行状态。
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| -------- | -------- | -------------------------------------------------------- |
|
||||
| `online` | boolean | 当前 QQ 在线,`null` 表示无法查询到在线状态 |
|
||||
| `good` | boolean | 状态符合预期,意味着各模块正常运行、功能正常,且 QQ 在线 |
|
||||
| ...... | - | OneBot 实例自行添加的其他内容 |
|
||||
|
||||
通常情况下建议只使用 `online` 和 `good` 这两个字段来判断运行状态,因为根据 OneBot 实现的不同,其它字段可能完全不同。
|
||||
|
||||
### getVersionInfo()
|
||||
|
||||
获取版本信息
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------------ | -------- | ----------------------------- |
|
||||
| `app_name` | string | 应用标识,如 `mirai-native` |
|
||||
| `app_version` | string | 应用版本,如 `1.2.3` |
|
||||
| `protocol_version` | string | OneBot 标准版本,如 `v11` |
|
||||
| …… | - | OneBot 实现自行添加的其它内容 |
|
||||
|
||||
### setRestartPlugin()
|
||||
|
||||
重启 OneBot 客户端
|
||||
|
||||
由于重启 OneBot 实现同时需要重启 API 服务,这意味着当前的 API 请求会被中断,因此需要异步地重启,接口返回的 `status` 是 `async`。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------- | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `delay` | number | `0` | 要延迟的毫秒数,如果默认情况下无法重启,可以尝试设置延迟为 2000 左右 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### cleanCache()
|
||||
|
||||
清理 OneBot 客户端的缓存。
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据:无
|
||||
|
||||
### callExtendedAPI() (扩充 API)
|
||||
|
||||
用来调用 OneBot 标准之外扩展出来的自定义 API。
|
||||
|
||||
使用不同 OneBot 客户端时,可能有一些 API 不在上方的 OneBot 标准里,这时可以使用此方法进行额外调用。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 |
|
||||
| -------- | -------- | ------ |
|
||||
| `action` | string | 必填 |
|
||||
| `params` | array | `[]` |
|
||||
|
||||
例子
|
||||
|
||||
```php
|
||||
$result = $bot->callExtendedAPI("get_group_root_files", ["group_id" => 123456]);
|
||||
//这里以 go-cqhttp 扩展的一个获取群文件的 API 为例
|
||||
var_dump($result["data"]);
|
||||
// 输出群文件列表
|
||||
```
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# 单例类(SingletonTrait)
|
||||
|
||||
单例类,顾名思义,就是让用户声明的类拥有单例的特性,而这一组件引入的方式也最直接。它是一个 PHP 的 `trait`。
|
||||
|
||||
我们传统写单例类的方式很手动,比如下面这样:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
class Foo {
|
||||
public $test = 0;
|
||||
private static $instance;
|
||||
public static function getInstance() {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new Foo();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
Foo::getInstance()->test = 4;
|
||||
$obj = Foo::getInstance()->test;
|
||||
var_dump($obj); // 4
|
||||
```
|
||||
|
||||
这就要求我们每个需要声明为单例的类都写一个成员静态方法和成员静态变量。
|
||||
|
||||
框架使用了 PHP Trait 来快速让一个类支持这一特性:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use ZM\Utils\SingletonTrait;
|
||||
class Foo {
|
||||
use SingletonTrait;
|
||||
public $test = 0;
|
||||
}
|
||||
|
||||
Foo::getInstance()->test = 5;
|
||||
var_dump(Foo::getInstance()->test);
|
||||
```
|
||||
|
||||
只需要在类中使用:`use \ZM\Utils\SingletonTrait;` 一句话即可。
|
||||
@@ -1,73 +0,0 @@
|
||||
# SpinLock 自旋锁
|
||||
|
||||
前面讲到 LightCache 轻量缓存在特定的情况下为了保证数据不被多进程的因素导致丢失或覆盖,在高并发情况下修改数据需要加锁,所以炸毛框架内置了 SpinLock 自旋锁。
|
||||
|
||||
## 配置
|
||||
|
||||
自旋锁使用无需配置,和 LightCache 同源。
|
||||
|
||||
## 使用
|
||||
|
||||
定义:`ZM\Store\Lock\SpinLock`
|
||||
|
||||
### SpinLock::lock($key)
|
||||
|
||||
给信号量 `$key` 上锁。如果该信号量已经被上锁,则原地等待直到其他资源释放锁。
|
||||
|
||||
```php
|
||||
SpinLock::lock("foo");
|
||||
```
|
||||
|
||||
### SpinLock::unlock($key)
|
||||
|
||||
给信号量 `$key` 释放锁。
|
||||
|
||||
```php
|
||||
SpinLock::unlock("foo");
|
||||
```
|
||||
|
||||
### SpinLock::tryLock($key)
|
||||
|
||||
给信号量 `$key` 上锁。如果该信号量已经被上锁,则立刻返回 false。
|
||||
|
||||
```php
|
||||
SpinLock::lock("foo");
|
||||
```
|
||||
|
||||
## 综合实例
|
||||
|
||||
我们这里以之前在 LightCache 中的实例进行继续讲解,如何给之前那样的情况加锁:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/test")
|
||||
*/
|
||||
public function test() {
|
||||
SpinLock::lock("web_count"); // 加上这行
|
||||
$s = LightCache::get("web_count");
|
||||
if($s === null) $s = 1;
|
||||
else $s += 1;
|
||||
LightCache::set("web_count", $s);
|
||||
SpinLock::unlock("web_count"); // 再加上这行
|
||||
return "<h1>It works!</h1>";
|
||||
}
|
||||
```
|
||||
|
||||
加两行就 OK。这时再使用压测工具请求 200000 次,值就会是 200000 了!
|
||||
|
||||
原理剖析:在 LightCache 获取前,先对此内容上锁,这时如果其他进程有同时也在执行这个代码的时候,就会在 `SpinLock::lock()` 这行代码处原地等待,防止继续执行。等前面的那个进程执行到 `SpinLock::unlock()` 释放锁时,其他进程才可继续执行,从而避免了多个进程并行执行这段代码导致的数据错乱。
|
||||
|
||||
!!! error "警告"
|
||||
|
||||
使用锁时务必谨慎,如果不按照下面的规则使用自旋锁可能导致 CPU 占用率上升。
|
||||
|
||||
自旋锁使用约定:
|
||||
|
||||
- 使用 `SpinLock::lock()` 指定信号量名称时必须指定为字符串,且最好与你的 LightCache 缓存名称相同。
|
||||
- 使用 `lock()` 时最好紧跟在 `LightCache::get()` 代码前。
|
||||
- 使用自旋锁后,`LightCache::get()` 到 `LightCache::set()` 之间的代码段一定不能有 **读写文件、数据库操作和网络请求** 等代码,最好为纯 PHP 逻辑代码,且越短越好,如示例代码。
|
||||
- 在 `LightCache::set()` 后最好紧跟 `SpinLock::unlock()`。
|
||||
|
||||
## 性能
|
||||
|
||||
使用自旋锁几乎没有性能损失,自旋锁要比其他类型的锁性能强很多,在上方举例使用的 `ab` 压测工具测试 100万请求 下,使用自旋锁和不适用自旋锁的测试成绩时间分别为:7.4s 和 6.9s。
|
||||
@@ -1,272 +0,0 @@
|
||||
# ZMRequest(HTTP 客户端)
|
||||
|
||||
框架提供了轻量的 HTTP 请求发起工具类,直接静态调用即可。
|
||||
|
||||
命名空间:`use ZM\Requests\ZMRequest;`
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
在使用 Swoole 4.6.0 以下(不包含)的版本时,最好使用 Swoole 官方推荐的 Saber 或者 ZMRequest 这个轻量的 HTTP 请求客户端,不要使用 curl_exec,因为在老版本的 Swoole 上对 curl 的协程 Hook 支持不是很完善。
|
||||
|
||||
|
||||
## ZMRequest::get()
|
||||
|
||||
发起 GET 请求。
|
||||
|
||||
定义:`ZMRequest::get($url, $headers = [], $set = [], $return_body = true)`
|
||||
|
||||
全局函数别名:`zm_request_get($url, $headers = [], $set = [], $return_body = true)`
|
||||
|
||||
`$url`:要请求的 url,如 `http://captive.apple.com/`
|
||||
|
||||
`$headers`:要请求的 Headers,例如:`["User-Agent" => "Chrome"]`,数组形式
|
||||
|
||||
`$set`:请求时的一些设置,例如超时时间等等。详见下方“设置参数”
|
||||
|
||||
`$return_body`:是否只返回请求回来的内容部分,默认为 true,如果为 false 时则会返回一个 `\Swoole\Coroutine\Http\Client` 对象,可查阅 [Swoole 文档](http://wiki.swoole.com/#/coroutine_client/http_client) 进行接下来的一系列操作。
|
||||
|
||||
如果 `$return_body` 为 true,但是请求失败(HTTP 状态码不是 200 或无法连接到目标服务器或者无法解析域名等问题)时,方法会返回 false,否则会返回内容。
|
||||
|
||||
返回值:`false|string|\Swoole\Coroutine\Http\Client`
|
||||
|
||||
```php
|
||||
$r = ZMRequest::get("http://captive.apple.com/", ["User-Agent" => "Chrome"]);
|
||||
echo $r.PHP_EOL; // <HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>
|
||||
```
|
||||
|
||||
```php
|
||||
$r = zm_request_get("http://captive.apple.com/", [], [], false);
|
||||
echo $r->body.PHP_EOL; // 这行输出和上方的一致
|
||||
dump($r);
|
||||
/*
|
||||
^ Swoole\Coroutine\Http\Client {#170
|
||||
+errCode: 0
|
||||
+errMsg: ""
|
||||
+connected: false
|
||||
+host: "captive.apple.com"
|
||||
+port: 80
|
||||
+ssl: false
|
||||
+setting: array:1 [
|
||||
"timeout" => 15.0
|
||||
]
|
||||
+requestMethod: "GET"
|
||||
+requestHeaders: []
|
||||
+requestBody: null
|
||||
+uploadFiles: null
|
||||
+downloadFile: null
|
||||
+downloadOffset: 0
|
||||
+statusCode: 200
|
||||
+headers: array:4 [
|
||||
"content-type" => "text/html"
|
||||
"content-length" => "68"
|
||||
"date" => "Thu, 07 Jan 2021 06:22:32 GMT"
|
||||
"connection" => "keep-alive"
|
||||
]
|
||||
+set_cookie_headers: null
|
||||
+cookies: null
|
||||
+body: "<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>"
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
## ZMRequest::post()
|
||||
|
||||
发送一个 POST 请求。
|
||||
|
||||
定义:`ZMRequest::post($url, array $header, $data, $set = [], $return_body = true)`
|
||||
|
||||
全局函数别名:`zm_request_post($url, array $header, $data, $set = [], $return_body = true)`
|
||||
|
||||
`$url`:同上,填入 url,必填
|
||||
|
||||
`$header`:请求的 Headers,必填,数组形式,例如 `["Content-Type" => "application/json"]`
|
||||
|
||||
`$data`:请求的数据体,默认应该传入数组,如果传入 `array` 类型,则会默认当作 `Content-Type: application/x-www-form-urlencoded` 方式自动转码和转换,例如 `["key1" => "b1", "key2" => "b2"]` 会变成 `key1=b1&key2=b2`
|
||||
|
||||
`$set`:同上,见下面的设置参数部分。
|
||||
|
||||
`$return_body`:同上。
|
||||
|
||||
```php
|
||||
$s = ZMRequest::post("http://captive.apple.com/", ["Content-Type" => "application/json"], json_encode(["key1" => "value1"]));
|
||||
```
|
||||
|
||||
## ZMRequest::request()
|
||||
|
||||
发起自定义一切参数的 HTTP 请求。
|
||||
|
||||
参数:
|
||||
|
||||
- `$url`:请求的链接,自动解析端口、HTTPS、DNS 等操作
|
||||
- `$attribute`:请求的属性,示例见下方
|
||||
- `$return_body`:可选参数,`bool` 类型,和上面的 `$return_body` 参数意义相同
|
||||
|
||||
其中 `$attribute` 参数对应可设置的有:
|
||||
|
||||
- `method`:可选 `GET`,`POST` 等 HTTP 请求的方式
|
||||
- `set`:设置 Swoole 客户端的参数
|
||||
- `headers`:要请求的 HTTP Headers
|
||||
- `data`:请求的 body 数据,为数组时自动转换头部为 `x-www-form-urlencoded`
|
||||
- `file`:要发送的文件,数组,可选多个文件
|
||||
|
||||
例1:使用 GET 请求发送带有 Body 的 HTTP 请求
|
||||
|
||||
```php
|
||||
$r = ZMRequest::request("http://example.com", [
|
||||
"method" => "GET",
|
||||
"data" => [
|
||||
"key1" => "value1"
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
例2:设置请求超时时间并指定自定义头部
|
||||
|
||||
```php
|
||||
$r = ZMRequest::request("http://example.com", [
|
||||
"method" => "POST",
|
||||
"headers" => [
|
||||
"X-Custom-Header" => "Hello world",
|
||||
"User-Agent" => "HEICORE"
|
||||
],
|
||||
"set" => ["timeout" => 10.0]
|
||||
]);
|
||||
```
|
||||
|
||||
例3:发送文件和 data
|
||||
|
||||
```php
|
||||
$r = ZMRequest::request("http://example.com/sendfile", [
|
||||
"file" => [
|
||||
[
|
||||
"path" => "/path/to/image1.jpg", // path字段必填
|
||||
"name" => "file1", // name字段必填,这个是 POST 过去的 key
|
||||
//"mime_type" => "image/jpeg", // 可选字段,底层会自动推断
|
||||
//"filename" => "a.jpg", // 可选字段,文件名称
|
||||
//"offset" => 0, // 可选字段,可以从指定文件的中间部分开始传输数据,此特性用于断点续传
|
||||
//"length" => 1024 // 可选字段,默认为整个文件的尺寸
|
||||
],
|
||||
[
|
||||
"path" => "/path/to/image2.jpg",
|
||||
"name" => "file2"
|
||||
]
|
||||
],
|
||||
"data" => [
|
||||
"key1" => "value1"
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
## ZMRequest::downloadFile()
|
||||
|
||||
下载文件到本地。
|
||||
|
||||
定义:`ZMRequest::downloadFile($url, $dst = null)`
|
||||
|
||||
`$url`:不多讲,下载链接。
|
||||
|
||||
`$dst`:本地位置,例如 `/tmp/hello.html`
|
||||
|
||||
下载成功返回 true 或指定的文件位置,失败返回 false。
|
||||
|
||||
```php
|
||||
ZMRequest::downloadFile("http://captive.apple.com/", "/tmp/apple.html");
|
||||
```
|
||||
|
||||
## ZMRequest::websocket()
|
||||
|
||||
创建一个 WebSocket 连接。因为 Swoole 提供的是同步协程的方案,但对于 WebSocket 这样的全双工通信,反而不是一个好的代码逻辑,炸毛框架将此同步协程的方案封装成了异步事件调用的方式。
|
||||
|
||||
定义:`ZMRequest::websocket($url, $set = ['websocket_mask' => true], $header = [])`
|
||||
|
||||
返回:一个 `\ZM\Requests\ZMWebSocket` 对象
|
||||
|
||||
效果等同于:`$s = new \ZM\Requests\ZMWebSocket($url, $set = ['websocket_mask' => true], $header = [])`
|
||||
|
||||
这个是 ZMRequest 扩展而来的异步 WebSocket 客户端,可供方便地连接、收发 WebSocket 消息所定制。
|
||||
|
||||
命名空间:`\ZM\Requests\ZMWebSocket`
|
||||
|
||||
```php
|
||||
$ws = ZMRequest::websocket("ws://127.0.0.1:12345/"); //使用工具函数
|
||||
// $ws = new ZMWebSocket("ws://127.0.0.1:12345/"); //直接构造
|
||||
if($ws->is_available) {
|
||||
$ws->onMessage(function(\Swoole\WebSocket\Frame $frame, $client) {
|
||||
var_dump($frame->data);
|
||||
});
|
||||
$ws->onClose(function($client){
|
||||
Console::info("Websocket closed.");
|
||||
});
|
||||
$result = $ws->upgrade();
|
||||
var_dump($result);
|
||||
}
|
||||
```
|
||||
|
||||
### 属性
|
||||
|
||||
#### is_available
|
||||
|
||||
`bool` 类型,用于判断构造对象是否成功或链接是否可用。在构建新的对象并执行 `upgrade()` 前,如果 ws 链接没有问题,则会变为 true;在 `onClose()` 回调执行后,此值变回 false。
|
||||
|
||||
### 方法
|
||||
|
||||
#### __construct()
|
||||
|
||||
客户端对象的构造方法。
|
||||
|
||||
参数:
|
||||
|
||||
- `$url`:要请求到的 WebSocket 目标地址,必须以 `ws(s)://` 开头
|
||||
- `$set`:可选,Swoole 客户端的参数,例如超时、是否使用 `websocket_mask` 等,如果为空数组则默认为 `["websocket_mask" => true]`,具体可设置的内容见 [Swoole 文档](https://wiki.swoole.com/#/coroutine_client/http_client?id=set)
|
||||
- `$header`:可选,请求的头部信息,数组
|
||||
|
||||
```php
|
||||
$a = new ZMWebSocket("ws://127.0.0.1:8080/", ["websocket_mask" => true], [
|
||||
"User-Agent" => "Firefox"
|
||||
]);
|
||||
```
|
||||
|
||||
#### onMessage()
|
||||
|
||||
设置收到消息的回调函数。
|
||||
|
||||
回调的参数:
|
||||
|
||||
- `$frame`:`Swoole\WebSocket\Frame` 类型,消息帧,一般只用 `$frame->data` 获取数据,具体见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server?id=swoolewebsocketframe)
|
||||
- `$client`:`Swoole\Coroutine\Http\Client` 类型,为客户端本身的对象,用于 push 数据等
|
||||
|
||||
```php
|
||||
$a->onMessage(function($frame, $client){
|
||||
echo "收到消息:".$frame->data.PHP_EOL;
|
||||
$client->push("hello world");
|
||||
});
|
||||
```
|
||||
|
||||
#### onClose()
|
||||
|
||||
设置连接断开后执行的回调函数。
|
||||
|
||||
回调的参数:
|
||||
|
||||
- `$client`:同上,但断开连接后不能使用 `push()` 发送数据了,只建议作为重连等机制的使用
|
||||
|
||||
```php
|
||||
$a->onClose(function($client){
|
||||
echo "WS 链接断开了!".PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
#### upgrade()
|
||||
|
||||
发起连接。
|
||||
|
||||
返回值:`true|false`,当为 `true` 时代表握手成功,此时可以在回调里愉快地收发消息了。如果为 `false` 表明握手失败。
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
这里由于是协程转异步,所以不能确定 `upgrade()` 和 `onMessage()` 哪个先会被触发(一般情况下如果服务器不是立刻响应回包信息,总是会先返回 `upgrade()` 的结果。
|
||||
|
||||
## 设置参数
|
||||
|
||||
见:[Swoole - HTTP 客户端](http://wiki.swoole.com/#/coroutine_client/http_client?id=set)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# ZMUtil 杂项工具类
|
||||
|
||||
调用前先 use:`use ZM\Utils\ZMUtil;`
|
||||
|
||||
## ZMUtil::stop()
|
||||
|
||||
停止框架运行。
|
||||
|
||||
## ZMUtil::reload()
|
||||
|
||||
重载框架,这会断开所有到框架的连接和重载所有 `src/` 目录下的用户源码并重新加载所有 Worker 进程。
|
||||
|
||||
## ZMUtil::getModInstance()
|
||||
|
||||
根据类名称拿到此类的单例(前提是目标的类的构造函数为空)。
|
||||
|
||||
```php
|
||||
class ASD{
|
||||
public $test = 0;
|
||||
}
|
||||
ZMUtil::getModInstance(ASD::class)->test = 5;
|
||||
```
|
||||
|
||||
25
docs/event/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 入门
|
||||
|
||||
## 事件是什么
|
||||
|
||||
简单来说,事件是一个底层的 Event Loop 收到消息后调用对应的方法的一个模型,比如给机器人发送消息后框架会调用你定义的方法来执行你的业务代码。
|
||||
|
||||
## 属性和注解是什么
|
||||
|
||||
属性(Attribute)是 PHP 8 最大的新变化之一,是 PHP 官方支持的、内置的注解实现,允许我们通过编程方式获取对应的元数据,可以大大方便我们对某一类代码进行处理。
|
||||
|
||||
而注解(Annotation)则是在 PHP 尚未支持属性的时代,用来代替的社区实现方案,通过解析 PHPDoc 注释来实现自己的注解机制。
|
||||
|
||||
炸毛框架同时支持注解和属性,在文档当中,有时会混用两者的字眼,在大多数情况下都可以安全地交换使用,例如 `#[BotEvent]` 和 `@BotEvent` 的行为是完全一致的。
|
||||
|
||||
## 注解和事件的关系
|
||||
|
||||
在炸毛框架中,注解是事件分发的一个重要角色,但注解本身并非事件,更恰当地说,注解代表了事件。
|
||||
|
||||
无论是机器人开发过程中场景的 `#[BotCommand]` 或是 HTTP 服务的路由 `#[RequestMapping]` 都是注解代表事件的例子。
|
||||
|
||||
## 阻断事件分发
|
||||
|
||||
在炸毛框架中,事件由一个统一的事件分发器进行分发,你可以在任意事件中阻断所有后续的分发。
|
||||
|
||||
(待考)
|
||||
86
docs/event/bot.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# 机器人事件
|
||||
|
||||
<aside>
|
||||
🛰️ 此页面下的所有注解命名空间为 `ZM\Annotation\OneBot`
|
||||
|
||||
</aside>
|
||||
|
||||
> 在使用注解绑定事件时,如果不存在 **必需** 参数,可一个参数都不写,效果就是此事件在任何情况下都会调用此方法,例如 `#[BotEvent()]` 会在收到任意机器人事件时调用。
|
||||
>
|
||||
|
||||
## BotAction
|
||||
|
||||
啊?
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| action | string | 动作名称 | “” |
|
||||
| need_response | string | 动作是否需要响应 | false |
|
||||
| level | int | 事件优先级(越大越先执行) | 20 |
|
||||
|
||||
## BotActionResponse
|
||||
|
||||
啊??
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| retcode | int | 响应码 | null |
|
||||
| level | int | 事件优先级(越大越先执行) | 20 |
|
||||
|
||||
## BotEvent
|
||||
|
||||
用于处理所有的机器人事件,具体的参数含义可以参见 [https://12.onebot.dev/connect/data-protocol/event/](https://12.onebot.dev/connect/data-protocol/event/)。
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| type | string | 对应标准中的事件类型 | null |
|
||||
| detail_type | string | 对应标准中的事件详细类型 | null |
|
||||
| sub_type | string | 对应标准中的事件子类型 | null |
|
||||
| level | int | 事件优先级(越大越先执行) | 20 |
|
||||
|
||||
## BotCommand
|
||||
|
||||
对于 `BotEvent` 的封装,用于支持常用的命令式调用(如:”天气 深圳”)。
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| name | string | 命令名称,应全局唯一 | “” |
|
||||
| match | string | 匹配第一个词的命令式消息,如 天气 北京 中的 天气 | “” |
|
||||
| pattern | string | 根据 * 号通配符进行模式匹配用户消息,如 查询*天气 | “” |
|
||||
| regex | string 合法的正则表达式 | 匹配正则表达式匹配到的用户消息 | “” |
|
||||
| start_with | string | 匹配消息开头相匹配的消息,如 我叫炸毛,这里写 我叫 | “” |
|
||||
| end_with | string | 匹配消息结尾相匹配的消息,以 start_with 类推 | “” |
|
||||
| keyword | string | 匹配消息中有相关关键词的消息 | “” |
|
||||
| alias | string | match 匹配到命令的别名,数组形式 | [] |
|
||||
| detail_type | string | 限定消息事件的详细类型,见 BotEvent | “” |
|
||||
| prefix | string | | |
|
||||
| level | int | 事件优先级(越大越先执行) | 20 |
|
||||
|
||||
> 机器人命令注册的实例可参见【一堆例子链接】
|
||||
>
|
||||
|
||||
## CommandArgument
|
||||
|
||||
与 BotCommand 搭配使用,可以自动识别参数及生成帮助。
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| name | string | 参数名称 | “” |
|
||||
| description | string | 参数描述 | “” |
|
||||
| required | bool | 参数是否必需 | false |
|
||||
| prompt | string | 当参数缺失时请求用户输入时提示的消息(需要参数设为必需) | “请输入{$name}” |
|
||||
| default | mixed | 当参数非必需且未填入时的默认值 | null |
|
||||
| timeout | int 单位秒 | 请求输入超时时间 | 60 |
|
||||
|
||||
## CommandHelp
|
||||
|
||||
与 BotCommand 搭配使用,可以补充生成的帮助信息。
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| description | string | 命令描述 | “” |
|
||||
| usage | string | 命令用法 | “” |
|
||||
| example | string | 命令示例 | “” |
|
||||
|
||||
> 关于自动帮助生成的更多信息,请参见 【这里链接】
|
||||
>
|
||||
@@ -1,3 +0,0 @@
|
||||
# 自定义注解
|
||||
|
||||
TODO:师傅,莫催,快肝完了!
|
||||
@@ -1,119 +0,0 @@
|
||||
# 事件分发器(进阶)
|
||||
|
||||
事件分发器是以上所有注解事件执行函数的一个分发器,如果你在上一章已经学会了如何创建自定义注解,那么本章就来说明如何用内置的事件分发器进行分发自定义事件。
|
||||
|
||||
如果你不需要了解或自定义有关事件分发的功能,此处可无需阅读。
|
||||
|
||||
## 属性
|
||||
|
||||
- 类名:`ZM\Event\EventDispatcher`
|
||||
|
||||
## 方法
|
||||
|
||||
### EventDispatcher::interrupt()
|
||||
|
||||
阻断当前正在运行的事件,只能在事件内部被调用的函数中实现。
|
||||
|
||||
### __construct()
|
||||
|
||||
构造方法。
|
||||
|
||||
```php
|
||||
EventDispatcher::__construct(string $class = '')
|
||||
```
|
||||
|
||||
初始化一个事件分发器,可进行一系列设置,对事件分发做限定。
|
||||
|
||||
#### 参数
|
||||
|
||||
`$class`:设置要分发的事件对应的注解类名,支持自定义注解(例如 `CQMessage::class`)
|
||||
|
||||
### setRuleFunction()
|
||||
|
||||
设置函数触发规则判定的函数(就是在执行事件函数前执行的规则判定)
|
||||
|
||||
```php
|
||||
setRuleFunction(callable $rule = null)
|
||||
```
|
||||
|
||||
#### 参数
|
||||
|
||||
`$rule`:支持回调或闭包。闭包的参数为执行对应事件函数所绑定的注解事件对象。
|
||||
|
||||
```php
|
||||
$dispatcher = new EventDispatcher(CustomEvent::class);
|
||||
$dispatcher->setRuleFunction(function($obj) {
|
||||
return $obj->name == "zhamao" ? true : false;
|
||||
});
|
||||
```
|
||||
|
||||
上方的 `$obj` 就是 CustomEvent 类的实例,参数绑定为注解中对应的参数。
|
||||
|
||||
### setResultFunction()
|
||||
|
||||
设置事件函数返回值处理的回调函数。
|
||||
|
||||
```php
|
||||
setReturnFunction(callable $return_func)
|
||||
```
|
||||
|
||||
#### 参数
|
||||
|
||||
`$return_func`:设置事件函数返回值处理的回调函数,回调参数绑定为对应单独事件函数的返回值。
|
||||
|
||||
```php
|
||||
$dispatcher = new EventDispatcher(CustomEvent::class);
|
||||
$dispatcher->setReturnFunction(function($return) {
|
||||
if (is_string($return)) Console::info("函数返回了 ".$return);
|
||||
});
|
||||
```
|
||||
|
||||
### dispatchEvents()
|
||||
|
||||
开始分发事件。
|
||||
|
||||
```php
|
||||
dispatchEvents(...$params)
|
||||
```
|
||||
|
||||
#### 参数
|
||||
|
||||
自定义参数,这里填入的参数将被填入被分发的函数参数中。
|
||||
|
||||
```php
|
||||
$dispatcher->dispatchEvents("foo", "bar");
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
class Test {
|
||||
/**
|
||||
* @CustomEvent("zhamao")
|
||||
*/
|
||||
public function test($arg1, $arg2) {
|
||||
echo "$arg1: $arg2"; //将输出 "foo: bar"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 机制
|
||||
|
||||
事件分发器的机制说简单不简单,说复杂也不复杂,它和中间件有着非常大的关系,因为它会自动检测和识别所要执行的函数有没有中间件,并且根据顺序进行执行。
|
||||
|
||||
在炸毛框架内部,一个完整的事件流程和中间件的关系如下图:
|
||||
|
||||

|
||||
|
||||
对于同一事件的优先级和响应顺序,优先级的关系如下图:
|
||||
|
||||

|
||||
|
||||
对于事件内单个事件被调用的单个函数下如果存在多个中间件,中间件模型和事件的关系如下图:
|
||||
|
||||

|
||||
|
||||
## 实战例子
|
||||
|
||||
我们假设 CustomEvent 是我们的自定义注解。还没写完,这部分太复杂了,而且举例子也不好举例,这块应该也不用着急更新。
|
||||
|
||||
TODO:待完成
|
||||
3
docs/event/extend.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# 扩展事件分发器
|
||||
|
||||
TODO
|
||||
@@ -1,387 +0,0 @@
|
||||
# 框架核心注解事件
|
||||
|
||||
框架核心注解事件区别于机器人和路由注解事件,这里框架注解事件都是**直接**或封装调用 Swoole 的回调事件的,所以对一些比较底层或者基础的操作都在这里做,例如收到 HTTP 或 WebSocket 连接后执行的事件函数。
|
||||
|
||||
## OnOpenEvent()
|
||||
|
||||
当有 WebSocket 连接接入框架时,触发注解事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------- |
|
||||
| 名称 | `@OnOpenEvent` |
|
||||
| 触发前提 | 当有 WebSocket 连接接入框架时,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnOpenEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | -------- | ------------------------------------------------------------ | ---- |
|
||||
| connect_type | `string` | 限定连接的类型,通过炸毛框架支持的方式指定传入类型,详见 [进阶 - 接入 WebSocket 客户端](/advanced/connect-ws-client) | |
|
||||
|
||||
### 用法
|
||||
|
||||
```java
|
||||
@OnOpenEvent("foo")
|
||||
@OnOpenEvent(connect_type="default")
|
||||
```
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。
|
||||
|
||||
## OnCloseEvent()
|
||||
|
||||
当有 WebSocket 连接断开框架时,触发注解事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------- |
|
||||
| 名称 | `@OnCloseEvent` |
|
||||
| 触发前提 | 当有 WebSocket 连接断开框架时,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnCloseEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | -------- | ------------------------------------------------------------ | ---- |
|
||||
| connect_type | `string` | 限定连接的类型,通过炸毛框架支持的方式指定传入类型,详见 [进阶 - 接入 WebSocket 客户端](/advanced/connect-ws-client) | |
|
||||
|
||||
### 用法
|
||||
|
||||
```java
|
||||
@OnCloseEvent("foo")
|
||||
@OnCloseEvent(connect_type="default")
|
||||
```
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。
|
||||
|
||||
## OnRequestEvent()
|
||||
|
||||
当 HTTP 请求接入时,触发注解事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------- |
|
||||
| 名称 | `@OnRequestEvent` |
|
||||
| 触发前提 | 当 HTTP 请求接入时,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnRequestEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | --------------------------------------------- | ------------------------ | ---------------- |
|
||||
| rule | `string`,必须是可执行且返回 bool 的 PHP 代码 | 前置条件 | 空,rule 为 true |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
## OnMessageEvent()
|
||||
|
||||
当有 WebSocket 连接接入框架后发送过来消息,触发注解事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------------------- |
|
||||
| 名称 | `@OnMessageEvent` |
|
||||
| 触发前提 | 当有 WebSocket 连接接入框架后发送过来消息,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnMessageEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | -------- | ------------------------------------------------------------ | ---- |
|
||||
| connect_type | `string` | 限定连接的类型,通过炸毛框架支持的方式指定传入类型,详见 [进阶 - 接入 WebSocket 客户端](/advanced/connect-ws-client) | |
|
||||
|
||||
### 用法
|
||||
|
||||
```java
|
||||
@OnMessageEvent("foo")
|
||||
@OnMessageEvent(connect_type="default")
|
||||
```
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。
|
||||
|
||||
## OnPipeMessageEvent()
|
||||
|
||||
当有 其他 Worker 进程通信发来指令,激活响应。(2.2.0 版本可用)
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------------------- |
|
||||
| 名称 | `@OnPipeMessageEvent` |
|
||||
| 触发前提 | 当有 WebSocket 连接接入框架后发送过来消息,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnPipeMessageEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | -------- | ------------ | ---- |
|
||||
| action | `string` | 限定动作名称 | |
|
||||
|
||||
### 用法
|
||||
|
||||
```java
|
||||
@OnPipeMessageEvent("foo")
|
||||
@OnPipeMessageEvent(action="bar")
|
||||
```
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$data`: 数组,内容如下:
|
||||
|
||||
```php
|
||||
[
|
||||
"action" => "你的上面的名称",
|
||||
... //其他自己发送时随便定义,带什么都行
|
||||
]
|
||||
```
|
||||
|
||||
## OnSwooleEvent()
|
||||
|
||||
绑定 Swoole 所相关的事件,例如 WebSocket 接入、收到 WS 消息、关闭 WS 连接,HTTP 请求到达等。这个是旧的统一的 Swoole 事件分发注解。**请尽量使用上面几个新的注解**。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------ |
|
||||
| 名称 | `@OnSwooleEvent` |
|
||||
| 触发前提 | 当参数指定的 `type` 对应的事件被触发后激活 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnSwooleEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | -------------------------------------------------------- | ----------------------------------------------- | ---------------- |
|
||||
| type | `string`,支持填入 `open`,`request`,`close`,`message` | 限定事件的类型,**必填** | |
|
||||
| rule | `string`,必须是可执行且返回 bool 的 PHP 代码 | 例如判断连接是否为 QQ 机器人(`connectIsQQ()`) | 空,rule 为 true |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。
|
||||
|
||||
## OnStart()
|
||||
|
||||
在框架加载后执行的注解事件,用于初始化 Worker 进程,此注解事件会在 Worker 进程中执行,且可以指定在哪个 Worker 进程中执行。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------ |
|
||||
| 名称 | `@OnStart` |
|
||||
| 触发前提 | 在框架加载后激活 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnStart` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| --------- | ------------------------------------------------------------ | ------------------------ | ---- |
|
||||
| worker_id | `int`,要在哪个 Worker 进程上执行,默认为 0,范围是 0~{你设定的 Worker 数量-1},如果是 -1 的话,则会在所有 Worker 进程上触发。 | 限定只执行的 Worker 进程 | |
|
||||
|
||||
## OnTick()
|
||||
|
||||
在框架加载后创建毫秒计时器。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ----------------------------- |
|
||||
| 名称 | `@OnTick` |
|
||||
| 触发前提 | 在框架加载后激活 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnTick` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| --------- | ------------------------------------------------------------ | ------------------------ | ---- |
|
||||
| tick_ms | `int`,**必填**,间隔的毫秒数,例如 1 秒间隔为 `1000`,范围大于 0,小于 86400000。 | | |
|
||||
| worker_id | `int`,要在哪个 Worker 进程上执行,默认为 0,范围是 0~{你设定的 Worker 数量-1},如果是 -1 的话,则会在所有 Worker 进程上触发。 | 限定只执行的 Worker 进程 | |
|
||||
|
||||
## OnSetup()
|
||||
|
||||
在框架加载前执行的代码。此部分代码是在主进程执行的,不可在此事件中使用任何协程相关的功能。
|
||||
|
||||
比如我们要改变所有进程的 ini 设置,这时使用 `@OnStart(-1)` 这样只设置了 Worker 进程的内容,而主进程和管理进程无法被覆盖到。如果需要设置全局的一些配置,务必在此 `@OnSetup` 注解下执行。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------ |
|
||||
| 名称 | `@OnSetup` |
|
||||
| 触发前提 | 在框架加载前激活 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnSetup` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
无。
|
||||
|
||||
## 示例1(机器人连接框架后输出信息)
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\Console\Console;
|
||||
class Hello {
|
||||
/**
|
||||
* 在机器人客户端连接框架后向终端输出信息
|
||||
* @OnOpenEvent("qq")
|
||||
* @param $conn
|
||||
*/
|
||||
public function onConnect(ConnectionObject $conn) {
|
||||
Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里的 Console 是终端输出组件,详情见组件一栏对应的文档查询。
|
||||
|
||||
## 示例2(阻断 Chrome 访问框架时多访问一次的问题)
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
use ZM\Event\EventDispatcher;
|
||||
class Hello {
|
||||
/**
|
||||
* 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰
|
||||
* @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
|
||||
*/
|
||||
public function onRequest() {
|
||||
EventDispatcher::interrupt();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中 EventDispatcher 为事件分发器,interrupt 是通用阻断方法,如果你平常只使用阻断,则只需掌握这一个方法即可,`EventDispatcher::interrupt()` 在所有事件内可用。
|
||||
|
||||
## 示例3(接收 WS 客户端发来的数据)
|
||||
|
||||
见 [接入 WebSocket 客户端](/advanced/connect-ws-client/)。
|
||||
|
||||
## 示例4(使用 OnStart 给所有 Worker 进程写入缓存提速)
|
||||
|
||||
如果你有一些数据存到了文件、数据库中,且是只读不写的,那么就可以使用此方法将这个文件或者数据库的内容读入 Worker 进程的内存中进行使用来提速。
|
||||
|
||||
假设我们有一个大文件 json,里面存着一份题库,例如:
|
||||
|
||||
```json
|
||||
{
|
||||
"0": {
|
||||
"question": "法的调整对象是( )。",
|
||||
"answer": {
|
||||
"A": "行为关系",
|
||||
"B": "思想关系",
|
||||
"C": "利益关系",
|
||||
"D": "各种社会资源"
|
||||
},
|
||||
"key": "A",
|
||||
"answer_type": 0
|
||||
},
|
||||
"1": {
|
||||
"question": "法律与其他社会规范的区别在于( )。",
|
||||
"answer": {
|
||||
"A": "是调整人们行为的规范",
|
||||
"B": "有约束力",
|
||||
"C": "由国家强制力保证执行",
|
||||
"D": "规定制裁措施"
|
||||
},
|
||||
"key": "C",
|
||||
"answer_type": 0
|
||||
},
|
||||
.....
|
||||
}
|
||||
```
|
||||
|
||||
那么我们可以使用 OnStart 来实现一个,将此文件读取到每个 Worker 进程中,并且快速取用的功能(以下做了一个简单的查题功能):
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Console\Console;
|
||||
class Hello {
|
||||
public static $tiku = [];
|
||||
/**
|
||||
* @OnStart(-1)
|
||||
*/
|
||||
public function onStart() { // 注意,此函数将会在每个 Worker 执行一次
|
||||
$file = file_get_contents("tiku.json"); //从文件读取json
|
||||
$json = json_decode($file, true); //json解析
|
||||
Hello::$tiku = $json; //将解析后的数组以静态变量的方式存到每个 Worker 的内存中
|
||||
Console::success("加载题库完成!");
|
||||
}
|
||||
/**
|
||||
* @CQCommand("找题")
|
||||
*/
|
||||
public function findQuestion() {
|
||||
$tiku_id = ctx()->getNumArg("请输入题目的序号");
|
||||
if(!isset(Hello::$tiku[$tiku_id])) return "题目id为".$tiku_id."的题目不存在!";
|
||||
$timu = Hello::$tiku[$tiku_id];
|
||||
$msg = "题目名称:".$timu["question"];
|
||||
foreach($timu["answer"] as $k => $v) {
|
||||
$msg .= "\n".$k.". ".$v;
|
||||
}
|
||||
$msg .= "\n正确答案:".$timu["key"];
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
终端效果:(我们假设运行框架的电脑是四核 CPU)
|
||||
|
||||
```log
|
||||
[14:28:00] [S] [#0] 加载题库完成!
|
||||
[14:28:00] [S] [#2] 加载题库完成!
|
||||
[14:28:00] [S] [#1] 加载题库完成!
|
||||
[14:28:00] [S] [#3] 加载题库完成!
|
||||
```
|
||||
|
||||
聊天效果:
|
||||
|
||||
<chat-box>
|
||||
) 找题 1
|
||||
( 题目名称:法律与其他社会规范的区别在于( )。\nA. 是调整人们行为的规范\nB. 有约束力\nC. 由国家强制力保证执行\nD. 规定制裁措施\n正确答案:C
|
||||
</chat-box>
|
||||
|
||||
## 示例5(创建每分钟自动执行的爬虫)
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnTick(tick_ms=60000,worker_id=0)
|
||||
*/
|
||||
public function onCrawl() {
|
||||
$data = Foo::bar(); //这里是你自己写的要爬的接口等等一系列操作
|
||||
LightCache::set("your_data_key_name", $data); //将爬虫数据存入 LightCache 轻量缓存
|
||||
}
|
||||
```
|
||||
|
||||
31
docs/event/framework.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 框架事件
|
||||
|
||||
<aside>
|
||||
🛰️ 此页面下的所有注解命名空间为 `ZM\Annotation\Framework`
|
||||
|
||||
</aside>
|
||||
|
||||
## BindEvent
|
||||
|
||||
相对底层的事件绑定,支持绑定所有透过框架分发的事件。
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| event_class | string | 时间名 | 必填 |
|
||||
| level | int | 事件优先级(越大越先执行) | 800 |
|
||||
|
||||
## Init
|
||||
|
||||
在 Worker 进程初始化时触发,用于进行 Worker 初始化。
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| worker | int 由 0 至 (最大Worker数-1) | 限定执行的 Worker 进程,-1 为在所有 Worker 执行 | 0 |
|
||||
|
||||
## Setup
|
||||
|
||||
在框架初始化时触发,在主进程执行,不可使用协程相关功能。
|
||||
|
||||
可用于改变所有进程的设置,相关更改会随着进程创建应用到所有 Worker 和 Manager 进程。
|
||||
|
||||
*没有参数*
|
||||
25
docs/event/http.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 路由事件
|
||||
|
||||
<aside>
|
||||
🛰️ 此页面下的所有注解命名空间为 `ZM\Annotation\Http`
|
||||
|
||||
</aside>
|
||||
|
||||
## Controller
|
||||
|
||||
对同一类下的路由进行修饰,只可在类上使用。
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| prefix | string | 路由前缀,应用到类下的所有路由 | 必填 |
|
||||
|
||||
## Route
|
||||
|
||||
路由事件,当对应的路由收到请求时触发。
|
||||
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| route | string | 路由 | 必填 |
|
||||
| name | string | 路由名称 | “” |
|
||||
| request_method | array<string> | 允许的请求方法 | [’GET’, ‘POST’] |
|
||||
| params | array<string, string> | 路由参数 | [] |
|
||||
@@ -1,70 +0,0 @@
|
||||
# 事件和注解
|
||||
|
||||
## 注解事件概念
|
||||
|
||||
我们知道事件,是一个底层的 event loop 收到消息后调用对应的各类方法的一个模型,比如给机器人发送消息后框架要做的就是指定到一个你定义的函数上,处理你的业务逻辑代码。比如在默认模块中,提供了 **你好** 的回复:**你好啊,我是由炸毛框架构建的机器人!**。这项简单回复的任务就是一个事件的触发到响应的全过程。
|
||||
|
||||
**注解**(Annotation)又称标注,Java 最早在 2004 年的 JDK 5 中引入的一种注释机制。目前 PHP 官方版本并未提供内置元注解和注解概念,但我们通过 `ReflectionClass` 反射类解析 PHP 代码注释从而实现了自己的一套注解机制。如果你没有写过 Java,并且不了解注解是什么,你可以理解为对 function 或 class 的一个修饰,因为传统的 PHP 代码逻辑我们都知道,不能简单给原先存在的函数贴标签,就比如,你不能在原本的 PHP 代码中给函数贴上一个可以影响它一生并且改变它行为的标签,而有了注解,就相当于有了给函数贴标签的机会。
|
||||
|
||||
在常见框架如 Spring,Swoft 等代码结构里面,注解更是其核心的存在。
|
||||
|
||||
在炸毛框架中,我们所有事件的绑定均采用这一方式进行调用模块内各个方法。包括 Swoole 自身的框架启动事件、WebSocket 连接握手事件、HTTP 请求事件等等,也包括 CQHTTP 发来的事件,如`message`,`notice`,`request` 等。
|
||||
|
||||
## 如何使用注解
|
||||
|
||||
就像我们日常开发写注释一样,只需在类、方法或成员变量上方按规则添加注释即可,这里以默认自带的 `Hello` 模块类为例子:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
class Hello {
|
||||
/**
|
||||
* @CQCommand(match="你好")
|
||||
* @return string
|
||||
*/
|
||||
public function hello(){
|
||||
return "你好啊,我是由炸毛框架构建的机器人!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中 `@CQCommand()` 就是一个基本的注解应用。注意需引入相关注解(Annotation)类,**且必须** 以 `/**` 开始并以 `*/` 结束,否则会导致无法解析!上方 `@return` 为 IDE 自动生成的 PHPDoc,不需要管。
|
||||
|
||||
有什么用?大有妙用!这个例子内注解类的用途是收到 QQ 消息后如果消息第一个词匹配到 `你好` 的话,框架就会自动处理,最终执行调用此 `hello()` 方法。注意 `CQCommand` 和其他任何后面讲到的注解类一样,需先 `use ZM\Annotation\` 下的对应注解类,否则也不能正常使用。
|
||||
|
||||
### 基本语法
|
||||
|
||||
先 use!先 use!先 use!重要的事情说三遍!`use ZM\Annotation\xxxx;`
|
||||
|
||||
**必须** 以 `/**` 开始并以 `*/` 结束。
|
||||
|
||||
```
|
||||
@注解类名(参数名1="参数1的值"[,参数名2="参数2的值"])
|
||||
```
|
||||
|
||||
对于只使用或只有一个参数的注解类,`@注解类名("参数的值")` 可以省略参数名。
|
||||
|
||||
对于没有参数的注解类,`@参数名()` 直接使用即可。
|
||||
|
||||
## 注解和事件的关系
|
||||
|
||||
在炸毛框架里,注解常常被当作事件分发的一个重要角色,但注解本身又不是事件,更恰当的说,是注解代表了事件。
|
||||
|
||||
机器人开发过程中常见的 `@CQCommand`,或者是 HTTP 服务器路由绑定 `@RequestMapping` 都是相当于由对应注解代表了事件,而 `@Middleware`,`@Closed` 等这类注解显然不代表任何事件,只能当作这个函数或类的修饰属性而已。代表了事件的注解,我们称之为**注解事件**,它会在某种事件达成条件后触发注解下方的函数本身。
|
||||
|
||||
值得注意的是,注解事件本身概念是我凭空捏造的,我不好解释所以只能创造这么一个词来代指这一抽象的概念,硬要解释的话,大致就好比一个社区里有一个卖牛奶的,有几家人订阅了每日上门送牛奶的服务,只要你打了“给我配送牛奶”的注解,他就会上门。而它送的不止一种奶,可以给你个性化定制,比如让卖牛奶的给你带包糖带瓶水,而描述这个的注解就只能做一个之前注解的修饰。假设你只写了带包糖的注解,没有写给我配送牛奶的注解,那他永远也不会给你送牛奶和糖过来。
|
||||
|
||||
## 阻断事件
|
||||
|
||||
由于炸毛框架内的注解事件统一由一个通用的事件分发器进行分发,所以你在任何注解事件内都可以用通用的方式阻断当前正在运行的事件。
|
||||
|
||||
首先就是要记得先 use 事件分发器的类:`use ZM\Event\EventDispatcher;`。
|
||||
|
||||
```php
|
||||
EventDispatcher::interrupt();
|
||||
EventDispatcher::interrupt($data); // 也可以带返回值,自定义注解事件时有用。
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1,167 +1,18 @@
|
||||
# 中间件注解
|
||||
# 中间件事件
|
||||
|
||||
对于 `@RequestMapping` 等注解绑定的事件函数,还支持中间件,可以完成 Session 会话、认证、日志记录等功能。中间件是用于控制 `请求到达` 和 `响应请求` 的整个流程的。从一定意义上来说相当于切面编程(AOP)。
|
||||
<aside>
|
||||
🛰️ 此页面下的所有注解命名空间为 `ZM\Annotation\Middleware`
|
||||
|
||||
在炸毛框架中,中间件最直白的意思就是注解事件执行前、执行后、执行过程中可进行插入代码但不破坏原有代码。
|
||||
</aside>
|
||||
|
||||
```伪代码
|
||||
@中间件1
|
||||
@带条件的注解1
|
||||
function 我的方法() {
|
||||
blablabla...
|
||||
}
|
||||
//插入中间件,下面是执行流程
|
||||
-> 判断注解1的执行条件是否为true
|
||||
-> 中间件1的前置插入代码
|
||||
-> 我的方法
|
||||
-> 中间件1的后置插入代码
|
||||
X -> 我的方法有异常时执行中间件1的异常处理
|
||||
## Middleware
|
||||
|
||||
//不插入中间件,下面是执行流程
|
||||
-> 判断注解1的执行条件是否为true
|
||||
-> 我的方法
|
||||
X -> 有异常则直接跳到最外层被框架捕获
|
||||
```
|
||||
当绑定了此中间件的方法被触发时触发。
|
||||
|
||||
中间件和事件分发器是紧密相连的,炸毛框架的内部分发器在分发注解事件的过程中会判断将要执行的事件是否含有中间件,框架内部执行流程图见下一章:事件分发器。
|
||||
|
||||
## 定义中间件
|
||||
|
||||
下方就是一个可以在终端打印路由函数运行的总时间的中间件,只需给中间件标明里面的 `@MiddlewareClass` 到中间件的类上就可以了。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Module\Middleware;
|
||||
|
||||
use Exception;
|
||||
use ZM\Annotation\Http\HandleAfter;
|
||||
use ZM\Annotation\Http\HandleBefore;
|
||||
use ZM\Annotation\Http\HandleException;
|
||||
use ZM\Annotation\Http\MiddlewareClass;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Http\MiddlewareInterface;
|
||||
|
||||
/**
|
||||
* @MiddlewareClass("timer")
|
||||
*/
|
||||
class TimerMiddleware implements MiddlewareInterface
|
||||
{
|
||||
private $starttime;
|
||||
|
||||
/**
|
||||
* @HandleBefore()
|
||||
* @return bool
|
||||
*/
|
||||
public function onBefore() {
|
||||
$this->starttime = microtime(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleAfter()
|
||||
*/
|
||||
public function onAfter() {
|
||||
Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleException(\Exception::class)
|
||||
* @param Exception $e
|
||||
* @throws Exception
|
||||
*/
|
||||
public function onException(Exception $e) {
|
||||
Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms but an Exception occurred.");
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
技术要素:
|
||||
|
||||
1. 将需要声明为中间件的 class 类标上注解 `@MiddlewareClass`,并带有参数,参数为中间件名称,字符串即可。
|
||||
2. 使用 `@MiddlewareClass` 的需要先 use:`use ZM\Annotation\Http\MiddlewareClass;`。
|
||||
3. 类成员中声明执行前插入、执行后插入和异常捕获函数也需要注解,分别是 `@HandleBefore`,`@HandleAfter`,`@HandleException`,都在 `ZM\Annotation\Http` 命名空间下。
|
||||
4. `@HandleBefore` 类似 `@CQBefore`,需要返回 bool 类型值,如果不返回,默认为 true。当为 true 时,则不会阻断执行事件函数本身。
|
||||
5. 中间件内的函数不可被绑定为注解事件。
|
||||
6. `@HandleException` 可以写多个,但其中的参数只能写想要捕获的异常的类全称,例如 `\Exception::class` 返回的就是 `\\Exception`,`\ZM\Exception\InterruptException::class` 返回的是 `ZM\\Exception\\InterruptException`,举的这两个例子这样写都是可以的。
|
||||
7. 如果 `@HandleException` 有多个的话,则会按照声明顺序依次让其捕获,看其是否为要被捕获的错误的类或父类。例如在最后一个 `@HandleException` 捕获 `\Throwable` 则最终此中间件会捕获所有异常。
|
||||
8. 中间件内可以正常使用和注解事件执行的内容同一上下文,例如 `@RequestMapping` 下你可以使用 `ctx()->getRequest()`,`@CQMessage` 可以使用 `ctx()->getMessage()` 等,以此类推。
|
||||
|
||||
## 使用中间件
|
||||
|
||||
如上图,我们举了一个非常简单的例子,打印出函数执行的时间。我们假设一个需要耗时较长的函数:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/testTime")
|
||||
* @Middleware("timer")
|
||||
*/
|
||||
public function testTime() {
|
||||
zm_sleep(3); //等待3秒再返回
|
||||
return "OK!";
|
||||
}
|
||||
```
|
||||
|
||||
在执行后,你的执行结果可能为:
|
||||
|
||||
```
|
||||
[11:18:56] [I] [#0] Using 3000.07 ms
|
||||
```
|
||||
|
||||
或者,我们也可以将中间件注解写到类上:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @Middleware("timer")
|
||||
*/
|
||||
class Hello {
|
||||
/**
|
||||
* @RequestMapping("/test/ping")
|
||||
*/
|
||||
public function ping(){
|
||||
return "pong";
|
||||
}
|
||||
/**
|
||||
* @RequestMapping("/test/ping2")
|
||||
*/
|
||||
public function ping2(){
|
||||
return "pong2";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
效果等同于给此类下每个注解事件写一个 `@Middleware`。
|
||||
|
||||
## 使用多个中间件
|
||||
|
||||
多个使用中间件可以同时生效多个流程的中间件。这里要注意,多个中间件中,`@HandleBefore` 方法中如果返回了 `false`,则不会执行接下来的中间件和事件本身要触发的函数,直接跳到最后此中间件的 `@HandleAfter` 方法。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("你好")
|
||||
* @Middleware("timer1")
|
||||
* @Middleware("timer2")
|
||||
*/
|
||||
public function hello() { return "成功执行!"; }
|
||||
```
|
||||
|
||||
## 使用中间件捕获异常
|
||||
|
||||
通常情况下,如果用户定义的函数内抛出了异常(包括 `message` 等事件),会返回到框架基层去返回默认定义的内容。如果想自己捕获可以使用 `try/catch` ,但不方便复用,多处使用的话就需要重复写代码。这里可以使用中间件的异常处理方便地捕获错误。这个函数写到中间件类里即可
|
||||
|
||||
```php
|
||||
/**
|
||||
* @HandleException(\Exception::class)
|
||||
* @param Exception|null $e
|
||||
*/
|
||||
public function onThrowing(?Exception $e) {
|
||||
ctx()->getResponse()->endWithStatus(500, "Error on this.");
|
||||
}
|
||||
```
|
||||
|
||||
这里的 `@HandleException` 中的参数为要捕获的类名,注意这里面的类名的命名空间需要写全称,不能上面 use 再使用,否则会无法找到异常类。
|
||||
|
||||
`context()` 为获取当前协程空间绑定的 `request` 和 `response` 对象。
|
||||
| 参数名称 | 允许值 | 用途 | 默认 |
|
||||
| --- | --- | --- | --- |
|
||||
| name | string | 中间件名称 | 必填 |
|
||||
| params | array<mixed> | 中间件参数 | [] |
|
||||
|
||||
> 关于中间件的具体用法,请参见【再来链接】
|
||||
>
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
# 机器人注解事件
|
||||
|
||||
QQ 机器人事件是指 CQHTTP 插件发来的 Event 事件,被框架处理后触发到单个类中方法的事件。
|
||||
|
||||
为了便于开发,这里的注解类对应 CQHTTP 插件返回的 `post_type` 类型,对号入座即可。
|
||||
|
||||
!!! tip "提示"
|
||||
|
||||
在使用注解绑定事件过程中,如果无 **必需** 参数,可一个参数也不写,效果就是此事件任何情况下都会调用此方法。例如:`@CQMessage()`
|
||||
|
||||
事件是用户需要从 OneBot 被动接收的数据,有以下几个大类:
|
||||
|
||||
- [消息事件](#cqmessage),包括私聊消息、群消息等,被 [`@CQCommand`](#cqcommand),`@CQMessage` 注解处理。
|
||||
- [通知事件](#cqnotice),包括群成员变动、好友变动等,被 `@CQNotice` 注解事件处理。
|
||||
- [请求事件](#cqrequest),包括加群请求、加好友请求等,被 `@CQRequest` 注解事件处理。
|
||||
- [元事件](#cqmetaevent),包括 OneBot 生命周期、心跳等,被 `@CQMetaEvent` 注解事件处理。
|
||||
|
||||
## CQMessage()
|
||||
|
||||
QQ 收到消息后触发的事件对应注解。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---- | ----------- |
|
||||
| 名称 | `@CQMessage` |
|
||||
| 触发前提 | 当 `post_type` 为 `message` 时触发 |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQMessage` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 当方法返回字符串时,效果等同于执行 `ctx()->reply("xxx")` |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | ------------------------------------- | -------------------------------------- | ---- |
|
||||
| message_type | `string`,支持填入 `private`,`group` | 限定消息事件的来源类型,如私聊或群消息 | 空 |
|
||||
| user_id | `int64` 或 `string` | 限定消息发送用户 ID(QQ 号) | 空 |
|
||||
| group_id | `int64` 或 `string` | 限定消息发送来源群 ID(QQ 群号) | 空 |
|
||||
| message | `string` | 限定消息内容文本 | 空 |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
下面这个例子的注释用途就是:
|
||||
|
||||
- 在用户 QQ 为 `123456` 的用户私聊给机器人发消息后机器人回复内容。
|
||||
- 用户发送文字为 `hello` 时返回 `你好啊,xxx` 的消息。
|
||||
|
||||
=== "代码"
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
|
||||
class Hello {
|
||||
/**
|
||||
* @CQMessage(message_type="private",user_id=123456)
|
||||
*/
|
||||
public function test() {
|
||||
return "你和机器人私聊发送了这些文本:".ctx()->getMessage();
|
||||
}
|
||||
/**
|
||||
* @CQMessage(message="hello")
|
||||
*/
|
||||
public function hello() {
|
||||
return "你好啊,".ctx()->getUserId();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "效果"
|
||||
|
||||
<chat-box>
|
||||
) 假设我是私聊机器人
|
||||
( 你和机器人私聊发送了这些文本:假设我是私聊机器人
|
||||
^ 假设我现在切到群里,在群里发hello
|
||||
) hello
|
||||
( 你好啊,123456
|
||||
</chat-box>
|
||||
|
||||
## CQCommand()
|
||||
|
||||
此注解是对 `@CQMessage` 类别的再封装,是命令解析格式处理消息的利器。例如,你想写一个疫情上报,指令是 `疫情 城市名称`,那么此方式来解析用户消息会更加方便。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | -------------------------------------------------------- |
|
||||
| 名称 | `@CQCommand` |
|
||||
| 触发前提 | 当根据参数规则匹配到用户命令式消息时触发 |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQCommand` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 当方法返回字符串时,效果等同于执行 `ctx()->reply("xxx")` |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | ------------------------------------- | ------------------------------------------------------ | ---- |
|
||||
| match | `string` | 匹配第一个词的命令式消息,如 `天气 北京` 中的 `天气` | 空 |
|
||||
| pattern | `string` | 根据 * 号通配符进行模式匹配用户消息,如 `查询*天气` | 空 |
|
||||
| regex | `string`,限定正则表达式 | 匹配正则表达式匹配到的用户消息 | 空 |
|
||||
| start_with | `string` | 匹配消息开头相匹配的消息,如 `我叫炸毛`,这里写 `我叫` | 空 |
|
||||
| end_with | `string` | 匹配消息结尾相匹配的消息,以 `start_with` 类推 | 空 |
|
||||
| keyword | `string` | 匹配消息中有相关关键词的消息 | 空 |
|
||||
| alias | `array[string]` | `match` 匹配到命令的别名,数组形式 | `{}` |
|
||||
| message_type | `string`,支持填入 `private`,`group` | 限定消息事件的来源类型,同 `@CQMessage` | 空 |
|
||||
| user_id | `int64` 或 `string` | 限定消息发送用户 ID,同 `@CQMessage` | 空 |
|
||||
| group_id | `int64` 或 `string` | 限定消息发送来源群 ID,同 `@CQMessage` | 空 |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
在 `@CQCommand` 注解事件中,从 `match` 到 `keyword` 六个参数中,必须且只能定义一个,`alias` 目前只能和 `match` 参数同时使用;
|
||||
|
||||
框架内部对于同一条消息事件,优先处理 `@CQCommand` 注解事件,如果未匹配到任何注解事件,则才会继续执行 `@CQMessage` 注解事件。
|
||||
|
||||
- 参数 `match` 匹配模式是:遇到空格、换行就会切分,比如 `点歌 xxx yyy` 会被分割为 `[点歌,xxx,yyy]`,然后抽取第一个词做为命令去匹配,剩下的为参数。
|
||||
- 参数 `pattern` 匹配模式是:\* 号位置变成参数,比如 `从*到*的随机数`,我们输入 `从1到9的随机数`,成功匹配,参数列表:`[1,9]`。
|
||||
- 参数 `regex` 匹配模式为 PHP 标准的 pcre 正则表达式,比如 `([01][0-9][2][0-3]):[0-5][0-9]` 用来匹配 `22:45`。
|
||||
- 参数 `start_with`, `end_with` 和 `keyword` 都是根据消息内容开头、结尾或者内容包含是否匹配来匹配,这里就不多说了,你懂的。
|
||||
- 参数 `alias` 用的时候一般是这样:`@CQCommand(match="你好",alias={"你好啊","你是谁"})`,用以扩充同义词下命令的适配广度。
|
||||
|
||||
### 用法
|
||||
|
||||
我们以参数 `match` 写一个简单的 demo:
|
||||
|
||||
=== "代码"
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
|
||||
class Hello {
|
||||
/**
|
||||
* @CQCommand(match="疫情",alias={"COVID"})
|
||||
*/
|
||||
public function virus(){
|
||||
$city = ctx()->getNextArg("请输入城市名称");
|
||||
return "城市 ".$city." 的疫情状况如下:"."{这里假装是疫情接口返回的数据}";
|
||||
}
|
||||
/**
|
||||
* 如果选择使用 match 参数的话,可以省略 `match=`
|
||||
* @CQCommand("掷硬币")
|
||||
*/
|
||||
public function randChoice() {
|
||||
return "你看到的是:" . (mt_rand(0,1) ? "正面" : "反面");
|
||||
}
|
||||
/**
|
||||
* @CQCommand(pattern="*把*翻译成*")
|
||||
*/
|
||||
public function translate() {
|
||||
ctx()->getNextArg(); // 为什么需要单独调用一次呢?看下面例子就知道啦
|
||||
$text = ctx()->getNextArg(); // 获取第二个星号匹配的内容
|
||||
$target = ctx()->getNextArg(); // 获取第三个星号匹配的内容
|
||||
// 这里 FakeTranslateAPI 是假设我们对接了一个翻译的 API,开发时请替换为自己的接口。
|
||||
return "翻译结果:" . FakeTranslateAPI::translate($text, $target);
|
||||
}
|
||||
}
|
||||
```
|
||||
=== "效果"
|
||||
|
||||
<chat-box>
|
||||
) 疫情 北京
|
||||
( 城市 北京 的疫情状况如下:blablablabla
|
||||
) COVID 香港
|
||||
( 城市 香港 的疫情状况如下:blablablabla
|
||||
) 掷硬币
|
||||
( 你看到的是:正面
|
||||
) 我想把我爱你翻译成英语
|
||||
( 翻译结果:I love you!
|
||||
</chat-box>
|
||||
|
||||
## CQNotice()
|
||||
|
||||
通知事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ----------------------------------------------------- |
|
||||
| 名称 | `@CQNotice` |
|
||||
| 触发前提 | 当 `post_type` 为 `notice` 时触发(通知类事件上报时) |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQNotice` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无作用 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ----------- | ------------------------------------ | ------------------------------------------------------------ | ---- |
|
||||
| notice_type | `string`,支持填入 onebot 标准的内容 | 限定通知事件的类型,见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
|
||||
| user_id | `int64` 或 `string` | 限定通知事件用户 ID(QQ 号),同上见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
|
||||
| group_id | `int64` 或 `string` | 限定通知事件群 ID(QQ 群号),同上见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
|
||||
| operator_id | `int64` 或 `string` | 限定操作者 QQ 号,同上见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
TODO:先放着,有时间再更。
|
||||
|
||||
## CQRequest()
|
||||
|
||||
请求事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------------------ |
|
||||
| 名称 | `@CQRequest` |
|
||||
| 触发前提 | 当 `post_type` 为 `request` 时触发(通知类事件上报时) |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQRequest` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无作用 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | ------------------------------------ | ------------------------------------------------------------ | ---- |
|
||||
| request_type | `string`,支持填入 onebot 标准的内容 | 限定请求事件的类型,见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
|
||||
| user_id | `int64` 或 `string` | 限定请求事件当事人用户 ID(QQ 号),见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
|
||||
| sub_type | `string` | 限定请求事件来源群 ID(QQ 群号),见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
|
||||
| comment | `string` | 限定验证消息内容,见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
TODO:先放着,有时间再更。
|
||||
|
||||
## CQMetaEvent()
|
||||
|
||||
元事件,元事件不属于用户交互的一部分,消息、通知、请求三大类事件是与聊天软件直接相关的、机器人真实接收到的事件,除了这些,OneBot 自己还会产生一类事件,这里称之为「元事件」,例如生命周期事件、心跳事件等,这类事件与兼容 OneBot 的客户端和炸毛框架本身的运行状态有关,而与聊天软件无关。元事件的上报方式和普通事件完全一样。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ----------------------------------------------------- |
|
||||
| 名称 | `@CQMetaEvent` |
|
||||
| 触发前提 | 当 `post_type` 为 `meta_event` 时触发(元事件上报时) |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQMetaEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无作用 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| --------------- | ------------------ | ------------------------------------------------------------ | ---- |
|
||||
| meta_event_type | `string`,**必需** | 限定元事件的类型,见 [OneBot - 元事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/meta.md) | |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
TODO:先放着,有时间再更。
|
||||
|
||||
## CQBefore()
|
||||
|
||||
所有机器人事件的前置注解事件,一般用作消息过滤、全局日志、全局替换等。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------------------------ |
|
||||
| 名称 | `@CQBefore` |
|
||||
| 触发前提 | 当 `post_type` 等于参数 `cq_event` 时触发 |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQBefore` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 仅可返回 `bool`,如果为 `false`,则阻断 `cq_event` 类的所有事件防止被执行 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | ------------------------------------------------------------ | ------------------------ | ---- |
|
||||
| cq_event | `string`,**必需**,支持 `message`,`notice`,`request`,`meta_event` | 限定机器人时间的类型 | |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
=== "代码"
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
class Test {
|
||||
/**
|
||||
* @CQBefore("message")
|
||||
*/
|
||||
public function filter(){
|
||||
// 可用于敏感词,如政治相关的词语不响应其他模块
|
||||
if(mb_strpos(ctx()->getMessage(), "谷歌") !== false) return false;
|
||||
else return true;
|
||||
}
|
||||
/**
|
||||
* @CQCommand("百科")
|
||||
*/
|
||||
public function wiki() {
|
||||
$content = ctx()->getNextArg("请说你要查百科的内容");
|
||||
// 这里假设你对接了一个查百科的接口
|
||||
return "已搜到匹配 $content 的如下结果:".FakeAPI::searchWiki($content);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "效果"
|
||||
|
||||
<chat-box>
|
||||
) 百科 北京
|
||||
( 已搜到匹配 北京 的如下结果:blablabla
|
||||
) 百科 谷歌被封
|
||||
^ 机器人没有任何回复
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
在设置了 `level` 参数后,如果设置了多个 `@CQBefore` 监听事件函数,更高 `level` 的事件函数返回了 `false`,则低 `level` 的绑定函数不会执行,所有 `@CQMessage` 绑定的事件也不会执行。
|
||||
|
||||
你也可以使用 `@CQBefore` 做一些消息的转发和过滤。比如你想去除用户发来的文字中的 emoji、图片等 CQ 码,只保留文本。
|
||||
|
||||
## CQAfter()
|
||||
|
||||
同上。只是在以上所有事件都调用后才会调用的。
|
||||
|
||||
## CQAPIResponse()
|
||||
|
||||
TODO:还没写完,先放着,有时间再更。
|
||||
@@ -1,231 +0,0 @@
|
||||
# 路由注解事件
|
||||
|
||||
炸毛框架提供了一个简易但是高效易用的 HTTP 路由注解,你可以使用路由功能来开发任何 Web 应用微服务、API 接口、中间件等。
|
||||
|
||||
!!! quote "开发提示"
|
||||
|
||||
本章节涉及的路由和控制器概念可能和其他传统框架有一些出入,而且炸毛框架非绝对根据 PSR 标准进行开发,目的是使用上一些常见的东西尽可能地灵活和不罗嗦。
|
||||
|
||||
## 控制器和路由
|
||||
|
||||
Controller 和 Route 为路由注解事件的核心注解事件,其中 Controller 的注解事件为 `@Controller`,Route 的注解事件为 `@RequestMapping`。
|
||||
|
||||
### Controller()
|
||||
|
||||
#### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------- |
|
||||
| 名称 | `@Controller` |
|
||||
| 触发前提 | 当路由 url 匹配到时进入触发 |
|
||||
| 命名空间 | `ZM\Annotation\Http\Controller` |
|
||||
| 适用位置 | 类 |
|
||||
| 返回值处理 | 对类注解修饰,无返回值 |
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | -------------- | ------------ | ---- |
|
||||
| prefix | `string`,必需 | 控制器的 url | 空 |
|
||||
|
||||
### RequestMapping()
|
||||
|
||||
#### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ----------------------------------------------------------- |
|
||||
| 名称 | `@RequestMapping` |
|
||||
| 触发前提 | 当路由 url 匹配到时进入触发 |
|
||||
| 命名空间 | `ZM\Annotation\Http\RequestMapping` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 返回类型是 `string` 时,自动调用 HTTP 响应并返回 200 状态码 |
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------------- | ----------------------------------------------------------- | -------------------------- | ------------------------------------------ |
|
||||
| route | `string`,必需 | 控制器的 url | 空 |
|
||||
| name | `string` | 路由的名称 | 空 |
|
||||
| request_method | `array`,限定 `RequestMethod::GET` 等常量 | 限制激活路由的 HTTP 方法 | `[RequestMethod::GET,RequestMethod::POST]` |
|
||||
| params | `array`,当路由中含有如 `{id}` 类似的动态路由时,会动态改变 | 动态参数的路由参数值的绑定 | `[]` |
|
||||
|
||||
#### 函数调用参数
|
||||
|
||||
- `$param`:如果路由中存在变量(动态路由),则会把动态路由所匹配的参数放入 `$param` 数组中。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping(route="/test/{ass}")
|
||||
*/
|
||||
public function testName($param) {
|
||||
return "Your name is ".($param["ass"] ?? "unknown");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 路由示例
|
||||
|
||||
=== "代码"
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\Http\Controller;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
/**
|
||||
* @Controller("/api")
|
||||
*/
|
||||
class Hello {
|
||||
/**
|
||||
* @RequestMapping("/index")
|
||||
*/
|
||||
public function index(){
|
||||
ctx()->getResponse()->end("This is API index page"); // 使用上下文获取响应对象
|
||||
}
|
||||
/**
|
||||
* @RequestMapping("/ping")
|
||||
*/
|
||||
public function ping(){
|
||||
return "pong"; // 直接返回字符串
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "效果"
|
||||
|
||||
!!! example "效果描述"
|
||||
当访问浏览器的 `http://localhost:20001/api/index` 时,浏览器会返回 `This is API index page`,当访问 `/api/ping` 的 url 时,浏览器会返回 `pong`。
|
||||
|
||||
```
|
||||
/ -> 无任何路由
|
||||
/api/index -> Hello->index
|
||||
/api/ping -> Hello->ping
|
||||
```
|
||||
|
||||
!!! tip "提示"
|
||||
|
||||
当 `@Controller` 为 `/` 的时候,效果和不写是一样的,`@RequestMapping` 为 `/` 或 `/index/inside` 等多级路由也是可以的。
|
||||
|
||||
### 绑定参数
|
||||
|
||||
在 `@RequestMapping` 中,不仅可以写静态的路由地址,也可以写绑定的参数。例如:`@RequestMapping(route="/index/{name}")`,则访问 `/index/xxx` 的时候,你在函数方法内可以这样获取此参数:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/index/{name}")
|
||||
*/
|
||||
public function index($arg) {
|
||||
return "Your param 'name' is ".$arg["name"];
|
||||
}
|
||||
```
|
||||
|
||||
## 获取请求参数 GET / POST
|
||||
|
||||
炸毛框架支持获取外部 HTTP 请求进来的 GET 和 POST 请求,通过获取 HTTP 请求对象 [Request](/advanced/inside-class/) 即可。对象具体属性和方法点这个链接进去就行。
|
||||
|
||||
### 示例
|
||||
|
||||
=== "获取 GET"
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/testUrl")
|
||||
*/
|
||||
public function testUrl() {
|
||||
$get = ctx()->getRequest()->get;
|
||||
if(isset($get["name"])) return "hello, ".$get["name"];
|
||||
else return "Unknown name!!";
|
||||
}
|
||||
```
|
||||
|
||||
=== "获取 POST(x-www-form-urlencoded)"
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/testUrl")
|
||||
*/
|
||||
public function testUrl() {
|
||||
$post = ctx()->getRequest()->post;
|
||||
if(isset($post["name"])) return "hello, ".$post["name"];
|
||||
else return "Unknown name!!";
|
||||
}
|
||||
```
|
||||
|
||||
=== "获取 JSON POST"
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/testUrl")
|
||||
*/
|
||||
public function testUrl() {
|
||||
$post = ctx()->getRequest()->rawContent();
|
||||
$json = json_decode($post, true);
|
||||
if ($json === null) return "Invalid json data!";
|
||||
if(isset($json["name"])) return "hello, ".$json["name"];
|
||||
else return "Unknown name!!";
|
||||
}
|
||||
```
|
||||
|
||||
## 设置路由请求方式
|
||||
|
||||
如果想要设置允许请求控制器的 HTTP 请求方式,可以使用方法在控制器中的 `@RequestMapping` 注解配置 `method` 参数,可以是 `GET`,`POST`,`PUT`, `PATCH`,`DELETE`,`OPTIONS`,`HEAD` 中的一个或多个。
|
||||
|
||||
- 限定 HTTP 方法:`@RequestMapping(method="GET")`,`@RequestMapping(method={"GET","POST"})`
|
||||
|
||||
## 静态文件服务器
|
||||
|
||||
框架支持了静态文件的访问。如需使用,则需要先到配置文件中配置相应的 `static_file_server` 参数中 `status` 为 `true`。
|
||||
|
||||
框架分为两种静态文件服务器,一种是全局的静态文件服务器,比如框架部署在 `http://127.0.0.1:20001/` 上通过 HTTP 访问,如果没有访问到 `@RequestMapping` 注解事件注册的路由地址,则会通过 url 自动查找静态文件服务器设置的根路径下面的文件,如果都不存在则会返回 404。
|
||||
|
||||
### 配置全局静态文件服务器
|
||||
|
||||
我们假设在你写的框架应用的根目录下,有如下文件和内容:
|
||||
|
||||
```
|
||||
resources/html/hello.html (下面是内容)
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
框架文档内容太多了,写不完!!!
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
然后在 `global.php` 配置文件中静态文件服务器参数为:
|
||||
|
||||
```php
|
||||
/** 静态文件访问 */
|
||||
$config['static_file_server'] = [
|
||||
'status' => true,
|
||||
'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
|
||||
'document_index' => [
|
||||
'index.html'
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
最终,我们通过 `vendor/bin/start server` 等方式,启动框架后,浏览器访问 `http://127.0.0.1:20001/hello.html` 即可获取内容。
|
||||
|
||||
### 配置局部静态文件服务器
|
||||
|
||||
所涉及的类的命名空间:`use ZM\Http\StaticFileHandler;`
|
||||
|
||||
局部静态文件服务器一般用于,比如机器人要发送图片,或者给其他 HTTP 服务提供文件下载的接口时可用。我们假设写了一个图片收集的一个静态文件夹区域,将其中一个子路由当作图片静态目录:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/images/{filename}")
|
||||
* @param $param
|
||||
* @return StaticFileHandler
|
||||
*/
|
||||
public function staticImage($param) {
|
||||
Console::info("[下载图片] " . $param["filename"]);
|
||||
return new StaticFileHandler($param["filename"], "/path/to/your/image_dir/");
|
||||
}
|
||||
```
|
||||
|
||||
这样当用户访问 `http://框架地址/images/aaa.jpg` 就可以快速地调用此路由下的局部文件服务器功能了。
|
||||
26
docs/guide/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 介绍
|
||||
|
||||
> 编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request,以此来贡献此文档!
|
||||
|
||||
炸毛框架主要面向聊天机器人(OneBot 标准)和 API 服务的开发。
|
||||
|
||||
框架使用 PHP 编写,无需安装任何额外扩展即可运行在任意主流平台,支持包括 Swoole 和 Workerman 在内的多种驱动,同时支持所有在 OneBot 标准内的通信方式,并支持使用注解(Annotation)和属性(Attribute)注册绑定各种事件,同时引入了依赖注入容器让开发更为便捷。
|
||||
|
||||
框架内置了对于 WebSocket 和 HTTP 的服务端和客户端支持,并针对聊天机器人消息处理进行优化扩展,提供常用会话机制和内部调用机制,让代码更为灵活。
|
||||
|
||||
## 环境要求
|
||||
|
||||
虽然我们已经大力简化了运行框架的要求,但仍然存在少量的必要项:
|
||||
|
||||
- PHP 8.0 或以上版本(使用命令 `php -v` 检查)
|
||||
- Tokenizer 扩展(使用命令 `php -m | grep tokenizer` 检查)
|
||||
- Composer 工具(使用命令 `composer` 检查)
|
||||
|
||||
## 框架特色
|
||||
|
||||
- 支持MySQL数据库(连接池),自带查询缓存提高多查询时的效率
|
||||
- WebSocket 服务器、HTTP 服务器兼容运行,一个框架多个用处
|
||||
- 支持命令、自然语言处理等多种插件形式
|
||||
- 支持多个机器人账号负载均衡
|
||||
- 完善的插件系统,可以随意加载和编写独立的插件
|
||||
- 灵活的注释和注解注册事件方式,支持 PHP 原生注解,提示更为友好
|
||||
@@ -1,149 +0,0 @@
|
||||
# 基本配置
|
||||
|
||||
到目前为止,炸毛框架的配置文件还没有任何变更,是默认的行为。在本章内容中,将列举出炸毛框架的配置文件的规则和使用。
|
||||
|
||||
!!! error "警告"
|
||||
|
||||
因为炸毛框架的全局配置中含有数据库名称和密码以及 access_token 等敏感字段,在使用版本控制软件过程中请不要将将敏感信息写入配置文件并提交至开源仓库!
|
||||
|
||||
## 全局配置文件 global.php
|
||||
|
||||
框架的全局配置文件在 `config/global.php` 文件中。下面是配置文件的各个选项,请根据自己的需要自行配置。
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| :--------------------------- | ------------------------------------------------ | ---------------------------- |
|
||||
| `host` | 框架监听的地址 | 0.0.0.0 |
|
||||
| `port` | 框架监听的端口 | 20001 |
|
||||
| `http_reverse_link` | 框架开到公网或外部的 HTTP 反代链接 | 见配置文件 |
|
||||
| `zm_data` | 框架的配置文件、日志文件等文件目录 | `./` 下的 `zm_data/` |
|
||||
| `debug_mode` | 框架是否启动 debug 模式 | false |
|
||||
| `crash_dir` | 存放崩溃和运行日志的目录 | `zm_data` 下的 `crash/` |
|
||||
| `swoole` | 对应 Swoole server 中 set 的参数,参考Swoole文档 | 见子表 `swoole` |
|
||||
| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` |
|
||||
| `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` |
|
||||
| `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_config` |
|
||||
| `redis_config` | Redis 连接信息 | 见子表 `redis_config` |
|
||||
| `access_token` | OneBot 客户端连接约定的token,留空则无 | 空 |
|
||||
| `http_header` | HTTP 请求自定义返回的header | 见配置文件 |
|
||||
| `http_default_code_page` | HTTP服务器在指定状态码下回复的默认页面 | 见配置文件 |
|
||||
| `init_atomics` | 框架启动时初始化的原子计数器列表 | 见配置文件 |
|
||||
| `info_level` | 终端日志显示等级(0-4) | 2 |
|
||||
| `context_class` | 上下文所定义的类,待上下文完善后见对应文档 | `\ZM\Context\Context::class` |
|
||||
| `static_file_server` | 静态文件服务器配置项 | 见子表 `static_file_server` |
|
||||
| `server_event_handler_class` | 注册 Swoole Server 事件注解的类列表 | 见配置文件 |
|
||||
| `command_register_class` | 注册自定义命令行选项指令的类 | 见配置文件 |
|
||||
| `modules` | 服务器启用的外部第三方和内部插件 | `['onebot' => true]` |
|
||||
|
||||
### 子表 **swoole**
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| --------------- | ------------------------------------------------------------ | ----------------------------------- |
|
||||
| `log_file` | Swoole 的日志文件 | `crash_dir` 下的 `swoole_error.log` |
|
||||
| `worker_num` | Worker 工作进程数 | 运行框架的主机 CPU 核心数 |
|
||||
| `dispatch_mode` | 数据包分发策略,见 [文档](https://wiki.swoole.com/#/server/setting?id=dispatch_mode) | 2 |
|
||||
| `max_coroutine` | 最大协程并发数 | 300000 |
|
||||
|
||||
### 子表 **light_cache**
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| -------------------------- | ----------------------------------------------- | ---------------------------- |
|
||||
| `size` | 最多可以缓存的 k-v 条目数(必须是 2 的 n 次方) | 1024 |
|
||||
| `max_strlen` | 作为 value 字符串的最大长度 | 16384 |
|
||||
| `hash_conflict_proportion` | Hash冲突率(越大越好,但是需要的内存更多) | 0.6 |
|
||||
| `persistence_path` | 持久化的键值对的存储路径 | `zm_data` 下的 `_cache.json` |
|
||||
| `auto_save_interval` | 持久化的键值对自动保存时间间隔(秒) | 900 |
|
||||
|
||||
### 子表 **sql_config**
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ------------------------ | ------------------------------ | ------------------------------------------------------------ |
|
||||
| `sql_host` | 数据库地址(留空则不使用数据库) | 空 |
|
||||
| `sql_port` | 数据库端口 | 3306 |
|
||||
| `sql_username` | 连接数据库的用户名 | |
|
||||
| `sql_database` | 要连接的数据库名 | |
|
||||
| `sql_password` | 数据库连接密码 | |
|
||||
| `sql_options` | PDO 数据库的 options 参数 | `[PDO::ATTR_STRINGIFY_FETCHES => false,PDO::ATTR_EMULATE_PREPARES => false]` |
|
||||
| `sql_default_fetch_mode` | PDO 的 fetch 模式 | `PDO::FETCH_ASSOC` |
|
||||
|
||||
### 子表 **redis_config**
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ---------- | ------------------------------------------ | ------ |
|
||||
| `host` | Redis 服务器地址,留空则启动时不创建连接池 | 空 |
|
||||
| `port` | Redis 服务器端口 | 6379 |
|
||||
| `timeout` | Redis 超时时间 | 1 |
|
||||
| `db_index` | Redis 要连接的数据库 index | 0 |
|
||||
| `auth` | 认证字符串 | 空 |
|
||||
|
||||
### 子表 static_file_server
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ---------------- | ---------------------- | ------------------------------ |
|
||||
| `status` | 是否开启静态文件服务器 | false |
|
||||
| `document_root` | 静态文件的根目录 | `{WORKING_DIR}/resources/html` |
|
||||
| `document_index` | 默认索引的文件名列表 | `["index.html"]` |
|
||||
|
||||
### 子表 worker_cache
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| -------- | --------------------------- | ------ |
|
||||
| `worker` | 跨进程缓存的存储工作进程 id | 0 |
|
||||
|
||||
## 多环境下的配置文件
|
||||
|
||||
炸毛框架的配置文件模块支持不同环境下的配置文件,主要结构为 `global.{环境}.php`。在一般情况下,炸毛框架默认从教程引导方式根据指令 `vendor/bin/start server` 启动的框架是不带环境控制的。这章将讲述如何根据不同的环境(production / development / staging)来编写配置文件。
|
||||
|
||||
### 使用环境参数
|
||||
|
||||
在启动框架时,额外增加参数 `--env` 可以指定当前的环境,从而使用不同的配置文件。现在框架支持以下几种环境: `production`,`staging`,`development`。
|
||||
|
||||
```bash
|
||||
vendor/bin/start server --env=development
|
||||
```
|
||||
|
||||
### 不同环境配置文件
|
||||
|
||||
由于框架默认只带有 `global.php` 文件,所以假设你现在需要区分开发环境和生产环境的配置,将 `global.php` 文件复制或改名为 `global.development.php` 或 `global.production.php` 即可。
|
||||
|
||||
### 优先级
|
||||
|
||||
如果指定了 `--env` 环境参数:`global.{对应环境}.php` > `global.php`,如果两个配置文件都找不到则报错。
|
||||
|
||||
如果未指定 `--env` 环境参数:`global.php` > `global.development.php` > `global.staging.php` > `global.production.php`。
|
||||
|
||||
## 其他自定义配置文件
|
||||
|
||||
炸毛框架的全局配置文件为 `global.php`,为了让不同的开发者更好的二次开发或者集成更多功能,炸毛框架的配置文件模块也支持自己编写的其他 `*.php` 或 `*.json` 格式的配置文件。例如炸毛框架默认附带了 `file_header.json` 这个配置文件(用来返回各类文件扩展名对应的 `Content-Type` 头参数的表)。
|
||||
|
||||
使用也非常简单,我们先以 `.json` 格式为例,我们创建一个 `example_a.json` 文件在 `config/` 目录(和 `global.php` 一个文件夹下),并编写自己的任意配置内容:
|
||||
|
||||
```json
|
||||
{
|
||||
"key1": "value1"
|
||||
}
|
||||
```
|
||||
|
||||
在框架中,启动后就会默认加载,使用只需要用以下方式即可:
|
||||
|
||||
```php
|
||||
use ZM\Config\ZMConfig; # 先 use 再使用!
|
||||
$r = ZMConfig::get("example_a", "key1"); # $r == "value1"
|
||||
```
|
||||
|
||||
如果需要用到变量或其他动态的内容,可以使用 `.php` 格式的配置文件。这里还是以 `example_a.php` 来举例:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$config['key1'] = "value1";
|
||||
$config['starttime'] = time();
|
||||
return $config;
|
||||
```
|
||||
|
||||
使用方式同上:
|
||||
|
||||
```php
|
||||
$r = ZMConfig::get("example_a", "key1"); # $r == "value1"
|
||||
$time = ZMConfig::get("example_a", "starttime"); # $time == 服务器启动时间
|
||||
```
|
||||
|
||||
同时,自定义配置文件也支持环境变量,例如:`example_a.development.json` 或 `example_a.production.php` 均可。
|
||||
47
docs/guide/configuration.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# 配置
|
||||
|
||||
炸毛框架的所有配置文件都存储在 `config` 目录中。每个选项都带有文档,所以你可以查阅这些文件并熟悉可用的配置项。
|
||||
|
||||
一般来说,我们建议你优先查看 `config/global.php` 文件及其文档,它包含了运行框架所需要的绝大部分配置,例如通信方式和时区等。
|
||||
|
||||
## 配置文件格式
|
||||
|
||||
除了 `php` 文件外,我们还支持以下格式:
|
||||
|
||||
- YAML
|
||||
- JSON
|
||||
- TOML
|
||||
|
||||
## 环境配置
|
||||
|
||||
根据运行的环境采用不同的配置值是很有必要的,例如你可能希望在本地和生产环境中使用不同的数据库配置。
|
||||
|
||||
为了方便这一功能,我们提供了基于环境的配置加载措施。你可以在启动框架时使用 `--env` 选项指定当前环境,例如 `./zhamao server --env=production` 。
|
||||
|
||||
如果未明确指定当前环境,则默认环境为 `development`。
|
||||
|
||||
如果要使某一配置文件只在特定环境下记载,你可以为给文件添加后缀,例如 `example.production.php` 只会在当前环境为 `production` 时加载。
|
||||
|
||||
你可以同时设置基础配置文件和环境特定配置文件,例如 `global.php` 和 `global.production.php` ,后者将会覆盖前者中的配置项。
|
||||
|
||||
### 配置安全
|
||||
|
||||
我们**非常不建议**你将数据库信息、访问密钥的敏感字段提交给版本控制,因为这可能会导致相关信息的泄露,同时也不利于其他开发人员修改自己的本地配置。
|
||||
|
||||
相反,我们建议你借助 DotEnv 等库,将相关配置信息写入 `.env` 文件中,并按需读取。
|
||||
|
||||
## 访问配置项
|
||||
|
||||
你可以在任何地方使用全局 `config` 函数获取配置项值。支持使用点语法获取,配置项名称由文件名开头,并允许指定默认值。
|
||||
|
||||
```php
|
||||
# 获取时区,未设置则返回 Asia/Shanghai
|
||||
$value = config('global.runtime.timezone', 'Asia/Shanghai');
|
||||
```
|
||||
|
||||
你也可以使用 `config` 函数设置配置项,传递数组即可:
|
||||
|
||||
```php
|
||||
# 将时区修改为 UTC
|
||||
config(['global.runtime.timezone' => 'UTC']);
|
||||
```
|
||||
112
docs/guide/get_started.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# 快速上手
|
||||
|
||||
在这里,我们将会以一个基础的复读机为例,帮助你快速熟悉框架的开发流程。
|
||||
|
||||
在开始之前,你需要先准备一个可用的 OneBot 机器人实现端(客户端)。
|
||||
|
||||
其中一些可用的选项为:
|
||||
|
||||
- Walle-Q
|
||||
- 更多…
|
||||
|
||||
## 机器人实现端
|
||||
|
||||
> 机器人实现端与炸毛框架相互独立。关于实现端本身的问题请向对应的开发者反馈。如果你确定相关问题由炸毛框架引起(例如缺少适配或代码问题等)请向我们报告。
|
||||
|
||||
框架支持多种通信方式,这里将以反向 WebSocket 为例,即框架充当 WS 服务端,实现端作为 WS 客户端连接到框架。
|
||||
|
||||
这里以 Walle-Q 实现端为例,在实际使用中,你可以自由选用不同的实现端。
|
||||
|
||||
你可以前往 Walle-Q 的 [发布页面](https://github.com/onebot-walle/walle-q/releases) 下载最新的发行版本,并运行以进行初始化。
|
||||
|
||||
在登录成功后,请关闭 Walle-Q 以修改配置文件。
|
||||
|
||||
首次运行后,将会在当前目录下生成 `walle-q.toml` 文件。在大多数场景下,这是你唯一需要接触的文件。
|
||||
|
||||
在于框架对接的情况中,我们只需要关注 `onebot.websocket_rev` 配置。
|
||||
|
||||
```toml
|
||||
[onebot]
|
||||
http = []
|
||||
http_webhook = []
|
||||
websocket = []
|
||||
|
||||
[[onebot.websocket_rev]]
|
||||
url = "ws://127.0.0.1:20001" # 这里是框架的监听地址
|
||||
reconnect_interval = 4
|
||||
```
|
||||
|
||||
修改完成并保存后,重新启动 Walle-Q 并登录即可。如果出现连接失败也请勿惊慌,因为框架此时尚未启动,失败是正常现象。
|
||||
|
||||
有些情况可能无法正常扫码登录,可使用 QQ+密码 的方式登录,在最上方插入配置:
|
||||
|
||||
```toml
|
||||
[qq.123456]
|
||||
password = "MyPassword"
|
||||
```
|
||||
|
||||
> 请将 123456 替换为你的机器人 QQ 号码,MyPassword 替换为机器人 QQ 密码。
|
||||
|
||||
## 编写第一个功能
|
||||
|
||||
在框架中,几乎所有事件的绑定都是通过注解进行的,详情可以参阅 注解的使用。
|
||||
|
||||
让我们新建第一个插件,插件的功能很简单,就是复读。我们假设这个复读插件的名字是 `repeater`
|
||||
|
||||
```bash
|
||||
./zhamao plugin:make
|
||||
# 然后根据提示,创建,比如名字输入 repeater
|
||||
# 选择类型的时候,输入 file
|
||||
```
|
||||
|
||||
我们就可以在目录 `plugins/repeater/` 下得到两个文件,其中 `main.php` 代码可能如下:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$plugin = new ZMPlugin(__DIR__);
|
||||
|
||||
/*
|
||||
* 发送 "测试repeater",回复 "这是repeater插件的第一个命令!"
|
||||
*/
|
||||
$cmd1 = BotCommand::make('repeater', match: '测试repeater')->on(fn () => '这是repeater插件的第一个命令!');
|
||||
|
||||
$plugin->addBotCommand($cmd1);
|
||||
|
||||
return $plugin;
|
||||
```
|
||||
|
||||
然后根据复读的原理(简单重复一遍用户发的消息),将上方 `$cmd1` 替换为下面的指令:
|
||||
|
||||
```php
|
||||
$cmd1 = BotCommand::make('repeater', match: '复读')->on(function(OneBotEvent $event, BotContext $ctx) {
|
||||
$ctx->reply($event->getMessage());
|
||||
});
|
||||
```
|
||||
|
||||
此后,保存文件。
|
||||
|
||||
> 借助容器的依赖注入功能,我们可以直接指定相应的类,相关实例会在调用时自动传入。上方的 OneBotEvent 和 BotContext 可以自由选择位置。
|
||||
|
||||
## 启动框架
|
||||
|
||||
在保存了上述的代码后,你就可以通过 `./zhamao server` 启动框架了。
|
||||
|
||||
启动后,Walle-Q 的日志应当会显示连接成功的信息。
|
||||
|
||||
此时,你可以通过任意账号向机器人发送 `复读 给我复读` 消息,机器人会回复 `复读 给我复读`。
|
||||
|
||||
至此,你的第一个功能,复读机,也就开发完成了。
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'复读 给我复读'},
|
||||
{type:1,content:'复读 给我复读'},
|
||||
]"></chat-box>
|
||||
|
||||
## 使用机器人 API 和更多事件
|
||||
|
||||
如果你希望机器人进行其他复杂的动作(操作),请参见 机器人 API。
|
||||
|
||||
关于更多可以注册绑定的事件,请参见 注解事件。
|
||||
@@ -1,134 +1,88 @@
|
||||
# 安装
|
||||
|
||||
> 这篇为炸毛框架以及环境的部署教程。
|
||||
我们希望尽可能轻松地使用炸毛,不论是本地开发或是部署都提供多种选择,尽量覆盖所有需求。
|
||||
|
||||
框架部署分为环境部署和框架部署。框架部署非常简单,只需要通用的指令,下方主要说环境部署。
|
||||
- 一键脚本
|
||||
- Docker
|
||||
- Composer
|
||||
|
||||
## Docker 部署 PHP 环境
|
||||
如果你不想干扰主机的环境,可以使用 Docker 进行拉取框架适用的 PHP7 with Swoole Extension Docker Container。本框架安装教程中使用的 DockerHub 及 Dockerfile 构建文件所构建的容器均为独立的容器,和框架无关,此 Docker 也可以用作运行**其他基于 php-cli 模式的项目**。
|
||||
## 一键脚本
|
||||
|
||||
方法一、直接拉取远程容器(推荐)
|
||||
```bash
|
||||
docker pull zmbot/swoole
|
||||
炸毛框架提供了一键脚本来设置运行环境并拉取脚手架,帮助你快速进行开发。
|
||||
|
||||
如果检测到本机未安装 PHP 或不符合运行要求,脚本将会自动拉取提前编译好的静态 PHP 运行时。
|
||||
|
||||
```shell
|
||||
# 将静态 PHP 和框架安装在当前目录(适用于 Linux、macOS)
|
||||
bash <(curl -fsSL https://zhamao.xin/v3.sh)
|
||||
|
||||
# 安装完成后启动
|
||||
./zhamao server
|
||||
```
|
||||
|
||||
方法二、从 Dockerfile 构建容器
|
||||
```bash
|
||||
git clone https://github.com/zhamao-robot/zhamao-swoole-docker.git
|
||||
cd zhamao-swoole-docker/
|
||||
docker build -t zm .
|
||||
> 关于静态运行时的更多用法,请参见 这里是链接
|
||||
|
||||
## Docker
|
||||
|
||||
待完善
|
||||
|
||||
## Composer
|
||||
|
||||
如果你已经有了必要的 PHP 环境和 Composer 工具,你可以直接在任意目录下初始化框架。
|
||||
|
||||
> 由于目前 3.0 仍处于预发布阶段,请使用 `composer require zhamao/framework:^3` 来安装
|
||||
|
||||
```shell
|
||||
# 在当前目录初始化框架
|
||||
composer require zhamao/framework
|
||||
./vendor/bin/zhamao init
|
||||
|
||||
## 安装完成后启动
|
||||
./zhamao server
|
||||
|
||||
## 生成新插件脚手架,用于开发
|
||||
./zhamao plugin:make
|
||||
```
|
||||
|
||||
!!! note "从 Dockerfile 构建容器的提示"
|
||||
## Windows 安装方法
|
||||
|
||||
使用 Dockerfile 构建后,需要将下方所有的 `zmbot/swoole` 全部更换成 `zm`,或者你上方指令中的 `-t` 参数后方的名称,具体可以详情查阅 Docker 的文档。
|
||||
由于 Windows 系统下的 PHP 环境配置较为复杂,我们推荐使用 Docker 或一键脚本来进行安装。
|
||||
|
||||
## 主机部署 PHP 环境
|
||||
如果你打算在 Windows 使用原生的 Win 环境 PHP,你需要先安装 PHP 和 Composer,然后在任意目录下执行上方 composer 的安装方法即可。
|
||||
|
||||
### Debian 系列(Ubuntu、Kali )
|
||||
### 包管理安装
|
||||
|
||||
需要的系统内软件包为:`php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql wget composer`
|
||||
Windows 也可以使用包管理安装 PHP、Composer,例如你可以使用 Scoop 包管理进行安装:
|
||||
|
||||
下面是一个一键安装的命令行(最小安装,需 root 权限):
|
||||
|
||||
```bash
|
||||
apt-get update && apt-get install -y software-properties-common && add-apt-repository ppa:ondrej/php && apt-get update && apt-get install php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql -y && apt-get install wget composer -y && wget https://github.com/swoole/swoole-src/archive/v4.5.7.tar.gz && tar -zxvf v4.5.7.tar.gz && cd swoole-src-4.5.7/ && phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}'))
|
||||
```powershell
|
||||
scoop install php
|
||||
scoop install composer
|
||||
```
|
||||
|
||||
### macOS (with Homebrew)
|
||||
采用这种包管理安装后,可直接使用 `php`、`composer` 命令在任意位置,无需配置环境变量。
|
||||
|
||||
macOS 系统下的部署相对简单很多,只需要使用 Homebrew 安装以下包和执行安装命令即可
|
||||
如果你使用包管理或已经安装了 PHP 到系统内,接下来就直接使用 Composer 来安装框架即可!
|
||||
|
||||
!!! note "给 macOS 开发者的提示"
|
||||
|
||||
因为苹果新的 Apple Sillicon 对 Homebrew 的支持目前仅限于 Rosetta2 转译版,
|
||||
所以在使用 M1-based Mac 时出现问题暂时无解。
|
||||
使用以下指令可能会遇到报错等问题,如有疑问可直接使用 Docker 或咨询我(炸毛框架开发者)。
|
||||
|
||||
```bash
|
||||
brew install php composer
|
||||
pecl install swoole
|
||||
```powershell
|
||||
composer create-project zhamao/framework-starter zhamao-v3
|
||||
cd zhamao-v3
|
||||
./zhamao plugin:make
|
||||
./zhamao server
|
||||
```
|
||||
|
||||
### 其他 Linux 发行版
|
||||
### 纯手动安装
|
||||
|
||||
其他 Linux 发行版,如 CentOS,Fedora,Arch 等暂时还没有经过严格的测试需要哪些依赖,大体和 Ubuntu、Debian 系需要的依赖包差不多,可根据安装过程中报错提示依次安装,或者直接使用 Docker 环境。
|
||||
如果你不想使用包管理的方式安装 PHP,且让 PHP 仅框架独立使用,你可以依次采用以下的方式来安装 PHP、Composer 和框架:
|
||||
|
||||
## 安装框架
|
||||
1. 从 GitHub 下载框架的脚手架,地址:<https://github.com/zhamao-robot/zhamao-framework-starter/archive/refs/heads/master.zip>
|
||||
2. 解压框架脚手架,重命名文件夹名称为你自己喜欢的名称,例如 `zhamao-v3`。
|
||||
3. 从 PHP 官网下载 PHP,选择 `Non Thread Safe` 版本,PHP 版本选择 8.0 ~ 8.2 均可(推荐 8.1),下载完成后解压到框架目录下的 `runtime\php` 目录,例如 `D:\zhamao-v3\runtime\php\`。
|
||||
4. 从 [Composer 官网](https://getcomposer.org/download/) 或 [阿里云镜像](https://mirrors.aliyun.com/composer/composer.phar) 下载 Composer,下载到 `runtime\` 目录。
|
||||
5. 在你的脚手架目录下执行 `.\runtime\php\php.exe .\runtime\composer.phar install` 安装框架依赖。
|
||||
6. 执行框架初始化命令:`./zhamao init`。
|
||||
7. 接下来你就可以使用和上方所有框架操作指令相同的内容了,例如 `./zhamao plugin:make`、`./zhamao server` 等。
|
||||
8. 如果你需要使用 Composer,你可以使用 `.\runtime\php\php.exe .\runtime\composer.phar` 来代替 `composer` 命令。
|
||||
|
||||
恭喜你,前方通过 Docker 或主机安装环境后可以开始构建框架的开发脚手架了!
|
||||
## 更多的环境部署和开发方式
|
||||
|
||||
如果你是通过**主机安装 PHP 部署的环境**,下方是通过脚手架来构建项目的命令行。
|
||||
|
||||
```bash
|
||||
composer create-project zhamao/framework-starter zhamao-app
|
||||
cd zhamao-app/ # 这个是你可以自己定义的名称
|
||||
vendor/bin/start server # 启动框架
|
||||
```
|
||||
|
||||
如果是通过 **Docker 部署的环境**,则需要在先克隆脚手架后在文件夹内使用 Docker 命令下的 `composer update`。(如果主机环境有 composer 也可以使用 `composer create-project` 的方式拉取脚手架。)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zhamao-robot/zhamao-framework-starter.git
|
||||
cd zhamao-framework-starter/
|
||||
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole composer update
|
||||
```
|
||||
|
||||
或者在 Docker 环境下,你可以直接使用如下方法拉取和快速启动一个最标准的框架。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zhamao-robot/zhamao-framework-starter.git
|
||||
cd zhamao-framework-starter
|
||||
./run-docker.sh # 在正式版炸毛框架 v2 发布后可用,测试版暂不放出
|
||||
```
|
||||
|
||||
|
||||
## 启动框架
|
||||
本地环境启动方式:
|
||||
```bash
|
||||
cd zhamao-framework-starter
|
||||
vendor/bin/start server
|
||||
```
|
||||
|
||||
使用 Docker 启动:
|
||||
```bash
|
||||
cd zhamao-framework-starter
|
||||
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start server
|
||||
```
|
||||
|
||||
启动后你会看到和下方类似的初始化内容,表明启动成功了
|
||||
|
||||
```verilog
|
||||
$ vendor/bin/start server
|
||||
host: 0.0.0.0 | port: 20001
|
||||
log_level: 2 | version: 2.0.0
|
||||
config: global.php | worker_num: 4
|
||||
working_dir: /Users/jerry/project/git-project/zhamao-framework
|
||||
______
|
||||
|__ / |__ __ _ _ __ ___ __ _ ___
|
||||
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
|
||||
/ /_| | | | (_| | | | | | | (_| | (_) |
|
||||
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/
|
||||
|
||||
[14:27:31] [I] [#0] Worker #0 启动中
|
||||
[14:27:31] [I] [#2] Worker #2 启动中
|
||||
[14:27:31] [I] [#1] Worker #1 启动中
|
||||
[14:27:31] [I] [#3] Worker #3 启动中
|
||||
[14:27:31] [S] [#3] Worker #3 已启动
|
||||
[14:27:31] [S] [#0] Worker #0 已启动
|
||||
[14:27:31] [S] [#2] Worker #2 已启动
|
||||
[14:27:31] [S] [#1] Worker #1 已启动
|
||||
```
|
||||
|
||||
单纯运行 炸毛框架 后,如果不部署或安装启动任何机器人客户端的话,仅仅相当于启动了一个 监听 20001 端口的WebSoket + HTTP 服务器。你可以通过浏览器访问:http://127.0.0.1:20001 ,或者你部署到了服务器后需要输入服务器地址。
|
||||
|
||||
!!! note "安装和部署总结"
|
||||
|
||||
根据上方描述,此文档中剩余提到的所有 Bash 命令,如果使用 Docker 部署环境,则需要加上 Docker 环境的指令:`docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole`,如执行其他 Linux 指令(以查看 PHP 版本为例):`docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole php -v`。
|
||||
|
||||
## 使用 IDE 等工具开发代码
|
||||
|
||||
我们使用文本编辑器进行炸毛框架开发,在使用集成开发环境 **IDEA** 或 **PhpStorm** 时,推荐通过插件市场搜索并安装 **PHP Annotations** 插件以提供注解命名空间自动补全、注解属性代码提醒、注解类跳转等,非常有助于提升开发效率的功能。
|
||||
|
||||
## 进阶环境部署和开发
|
||||
炸毛框架还支持更多种启动方式,如源码模式、守护进程模式,具体后续有关环境和部署的进阶教程,请查看 [进阶开发](/advanced/) 部分!
|
||||
除了上述方式之外,框架还支持源码模式、守护进程等运行方式,详情请参阅 [进阶开发]。
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# OneBot 实例
|
||||
|
||||
## 什么是 OneBot
|
||||
|
||||
OneBot 是一个聊天机器人应用接口标准,详情戳[这里](https://github.com/howmanybots/onebot)。
|
||||
|
||||
## OneBot 实现选择
|
||||
|
||||
如果你使用炸毛框架作为聊天机器人的开发框架,请先选择一种兼容 OneBot 标准的机器人接口。理论上,基于 OneBot 标准开发的**任何** SDK、框架和机器人应用,都可以无缝地在下面的不同实现中切换。当然,在一小部分细节上各实现可能有一些不同。
|
||||
|
||||
| 项目地址 | 平台 | 核心作者 | 备注 |
|
||||
| ------------------------------------------------------------ | --------------------------------------------- | -------------- | ------------------------------------------------------------ |
|
||||
| [richardchien/coolq-http-api](https://github.com/richardchien/coolq-http-api) | CKYU | richardchien | 可在 Mirai 平台使用 [mirai-native](https://github.com/iTXTech/mirai-native) 加载 |
|
||||
| [Mrs4s/go-cqhttp](https://github.com/Mrs4s/go-cqhttp) | [MiraiGo](https://github.com/Mrs4s/MiraiGo) | Mrs4s | 炸毛框架推荐使用此项目机器人应用 |
|
||||
| [yyuueexxiinngg/cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai) | [Mirai](https://github.com/mamoe/mirai) | yyuueexxiinngg | |
|
||||
| [takayama-lily/onebot](https://github.com/takayama-lily/onebot) | [OICQ](https://github.com/takayama-lily/oicq) | takayama | |
|
||||
| [ProtobufBot](https://github.com/ProtobufBot) | [Mirai](https://github.com/mamoe/mirai) | lz1998 | 事件和 API 数据内容和 OneBot 一致,通信方式不兼容 |
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
因为目前炸毛框架 2.0 只支持 WebSocket 方式的 OneBot 实现,所以目前上述项目的连接方式均只可选支持反向 WebSocket 通信的。后期会兼容 HTTP 和正向 WebSocket 通信方式。
|
||||
|
||||
如果你还没有自己的 QQ,或者是其他原因导致的暂时无法使用上述 OneBot 实例,可以使用炸毛项目中的 OneBot 协议聊天模拟器。但目前还处在开发中,暂不可用。
|
||||
@@ -1,10 +0,0 @@
|
||||
# 快速上手 - HTTP 服务器篇
|
||||
|
||||
HTTP 服务器篇主要讲解如何通过炸毛框架来实现微服务、API 通用接口等等这些东西的。
|
||||
|
||||
- [HTTP 服务器 - 路由和静态文件篇](/event/route-annotations/)
|
||||
- [HTTP 服务器 - 存储 - LightCache 轻量缓存](/component/light-cache/)
|
||||
- [HTTP 服务器 - 存储 - Redis](/component/redis/)
|
||||
- [HTTP 服务器 - 存储 - MySQL](/component/mysql/)
|
||||
- [HTTP 客户端](/component/zmrequest/)
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
# 快速上手 - 机器人篇
|
||||
|
||||
|
||||
|
||||
## 简介
|
||||
|
||||
看到这里,你已经完成了前面的环境部署,到了最关键的第一步了!
|
||||
|
||||
一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 **机器人问答模块** 的准备。
|
||||
|
||||
炸毛框架和机器人客户端是什么关系呢?炸毛框架就好比我们传统的一系列例如 Spring 框架、ThinkPHP 框架等,是服务端,而机器人客户端是一个 HTTP / WebSocket 客户端,时刻准备着连接到炸毛框架的。
|
||||
|
||||
## 机器人客户端
|
||||
|
||||
开发之前请注意,**机器人客户端和框架相互独立!**故有关**机器人客户端**出现的问题请到对应机器人客户端开发者或 GitHub 项目中咨询和讨论,炸毛框架为对接机器人客户端的一个快速开发的框架。
|
||||
|
||||
机器人客户端是炸毛框架以外的程序或软件,目前炸毛框架支持的机器人客户端通信标准为 OneBot 标准(原 CQHTTP),只要你的机器人客户端是 OneBot 标准的,就可以和炸毛框架进行无缝对接。
|
||||
|
||||
OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)。
|
||||
|
||||
这里以炸毛框架开发过程中使用的 [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 来举例进行第一个机器人的配置工作。
|
||||
|
||||
简要步骤描述为:
|
||||
|
||||
1. 下载 go-cqhttp 对应平台的 [release 文件](https://github.com/Mrs4s/go-cqhttp/releases)
|
||||
2. 双击 exe 文件或者使用 `./go-cqhttp` 启动
|
||||
3. 生成默认配置文件并修改默认配置
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
由于 go-cqhttp 项目还处于开发期,而且配置文件格式也发生了多次变化,但大体内容没有变(比如编写此文档时发布的版本中配置文件格式变成了 `hjson` 取代了原来的 `json`。
|
||||
|
||||
=== "config.json(旧格式)"
|
||||
|
||||
``` json hl_lines="2 3 30 31"
|
||||
{
|
||||
"uin": 你的QQ号,
|
||||
"password": "你的密码",
|
||||
"encrypt_password": false,
|
||||
"password_encrypted": "",
|
||||
"enable_db": true,
|
||||
"access_token": "",
|
||||
"relogin": {
|
||||
"enabled": true,
|
||||
"relogin_delay": 3,
|
||||
"max_relogin_times": 0
|
||||
},
|
||||
"ignore_invalid_cqcode": false,
|
||||
"force_fragmented": true,
|
||||
"heartbeat_interval": 0,
|
||||
"http_config": {
|
||||
"enabled": false,
|
||||
"host": "0.0.0.0",
|
||||
"port": 5700,
|
||||
"timeout": 0,
|
||||
"post_urls": {}
|
||||
},
|
||||
"ws_config": {
|
||||
"enabled": false,
|
||||
"host": "0.0.0.0",
|
||||
"port": 6700
|
||||
},
|
||||
"ws_reverse_servers": [
|
||||
{
|
||||
"enabled": true,
|
||||
"reverse_url": "ws://127.0.0.1:20001/",
|
||||
"reverse_api_url": "",
|
||||
"reverse_event_url": "",
|
||||
"reverse_reconnect_interval": 3000
|
||||
}
|
||||
],
|
||||
"post_message_format": "string",
|
||||
"debug": false,
|
||||
"log_level": ""
|
||||
}
|
||||
```
|
||||
|
||||
=== "config.hjson(新格式)"
|
||||
|
||||
``` json hl_lines="3 5 81 84"
|
||||
{
|
||||
// QQ号
|
||||
uin: 你的机器人QQ
|
||||
// QQ密码
|
||||
password: "你的QQ密码"
|
||||
// 是否启用密码加密
|
||||
encrypt_password: false
|
||||
// 加密后的密码, 如未启用密码加密将为空, 请勿随意修改.
|
||||
password_encrypted: ""
|
||||
// 是否启用内置数据库
|
||||
// 启用将会增加10-20MB的内存占用和一定的磁盘空间
|
||||
// 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
|
||||
enable_db: true
|
||||
// 访问密钥, 强烈推荐在公网的服务器设置
|
||||
access_token: ""
|
||||
// 重连设置
|
||||
relogin: {
|
||||
// 是否启用自动重连
|
||||
// 如不启用掉线后将不会自动重连
|
||||
enabled: true
|
||||
// 重连延迟, 单位秒
|
||||
relogin_delay: 3
|
||||
// 最大重连次数, 0为无限制
|
||||
max_relogin_times: 0
|
||||
}
|
||||
// API限速设置
|
||||
// 该设置为全局生效
|
||||
// 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配
|
||||
// 目前该限速设置为令牌桶算法, 请参考:
|
||||
//https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin
|
||||
_rate_limit: {
|
||||
// 是否启用限速
|
||||
enabled: false
|
||||
// 令牌回复频率, 单位秒
|
||||
frequency: 1
|
||||
// 令牌桶大小
|
||||
bucket_size: 1
|
||||
}
|
||||
// 是否忽略无效的CQ码
|
||||
// 如果为假将原样发送
|
||||
ignore_invalid_cqcode: false
|
||||
// 是否强制分片发送消息
|
||||
// 分片发送将会带来更快的速度
|
||||
// 但是兼容性会有些问题
|
||||
force_fragmented: false
|
||||
// 心跳频率, 单位秒
|
||||
// -1 为关闭心跳
|
||||
heartbeat_interval: 0
|
||||
// HTTP设置
|
||||
http_config: {
|
||||
// 是否启用正向HTTP服务器
|
||||
enabled: true
|
||||
// 服务端监听地址
|
||||
host: 0.0.0.0
|
||||
// 服务端监听端口
|
||||
port: 5700
|
||||
// 反向HTTP超时时间, 单位秒
|
||||
// 最小值为5,小于5将会忽略本项设置
|
||||
timeout: 0
|
||||
// 反向HTTP POST地址列表
|
||||
// 格式:
|
||||
// {
|
||||
// 地址: secret
|
||||
// }
|
||||
post_urls: {}
|
||||
}
|
||||
// 正向WS设置
|
||||
ws_config: {
|
||||
// 是否启用正向WS服务器
|
||||
enabled: true
|
||||
// 正向WS服务器监听地址
|
||||
host: 0.0.0.0
|
||||
// 正向WS服务器监听端口
|
||||
port: 6700
|
||||
}
|
||||
// 反向WS设置
|
||||
ws_reverse_servers: [
|
||||
// 可以添加多个反向WS推送
|
||||
{
|
||||
// 是否启用该推送
|
||||
enabled: true
|
||||
// 反向WS Universal 地址
|
||||
// 注意 设置了此项地址后下面两项将会被忽略
|
||||
reverse_url: ws://127.0.0.1:20001/
|
||||
// 反向WS API 地址
|
||||
reverse_api_url: ""
|
||||
// 反向WS Event 地址
|
||||
reverse_event_url: ""
|
||||
// 重连间隔 单位毫秒
|
||||
reverse_reconnect_interval: 3000
|
||||
}
|
||||
]
|
||||
// 上报数据类型
|
||||
// 可选: string array
|
||||
post_message_format: string
|
||||
// 是否使用服务器下发的新地址进行重连
|
||||
// 注意, 此设置可能导致在海外服务器上连接情况更差
|
||||
use_sso_address: false
|
||||
// 是否启用 DEBUG
|
||||
debug: false
|
||||
// 日志等级
|
||||
// WebUi 设置
|
||||
web_ui: {
|
||||
// 是否启用 WebUi
|
||||
enabled: true
|
||||
// 监听地址
|
||||
host: 127.0.0.1
|
||||
// 监听端口
|
||||
web_ui_port: 9999
|
||||
// 是否接收来自web的输入
|
||||
web_input: false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中 ws://127.0.0.1:20001/ 中的 127.0.0.1 和 20001 应分别对应炸毛框架配置的 HOST 和 PORT
|
||||
|
||||
## 第一次对话
|
||||
|
||||
一旦新的配置文件正确生效之后,所在的控制台(如果正在运行的话)应该会输出类似下面的内容:
|
||||
|
||||
```verilog
|
||||
[15:26:34] [I] [#2] 机器人 你的QQ号 已连接!
|
||||
```
|
||||
|
||||
表明机器人已成功连接到炸毛框架了!
|
||||
|
||||
这时,如果你是根据安装教程走下来并且未编写任何模块,炸毛自带一个示例模块,里面含有命令:`你好`,`随机数`。如果你对机器人回复:`你好`,它会回复你 `你好啊,我是由炸毛框架构建的机器人!`。这一历史性的对话标志着你已经成功地运行了炸毛框架,开始了编写更强大的 QQ 机器人的创意之旅!
|
||||
|
||||
## 编写一个命令
|
||||
|
||||
让我们转到框架的模块源代码部分,目录是 `src/Module/Example`,文件是 `Hello.php`。我们插入一段这样的代码:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("echo")
|
||||
*/
|
||||
public function repeat() {
|
||||
$repeat = ctx()->getFullArg("请输入你要回复的内容");
|
||||
ctx()->reply($repeat);
|
||||
//return $repeat; // 这样的效果等同于 ctx()->reply()
|
||||
}
|
||||
```
|
||||
|
||||
这样,一个简易的复读机就做好了!回到 QQ 机器人聊天,向机器人发送 `echo 你好啊`,它会回复你 `你好啊`。
|
||||
|
||||
> 如果你只回复 `echo` 的话,它会先和你进入一个会话状态,并问你 `请输入你要回复的内容`,这时你再次说一些内容例如 `哦豁`,会回复你 `哦豁`。效果和直接输入 `echo 哦豁` 是一致的,这是炸毛框架内的一个封装好的命令参数对话询问功能。有关参数询问功能,请看后面的进阶模块。
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
# 注册事件响应(机器人篇)
|
||||
|
||||
现在模块已经创建完毕,我们可以开始编写实际代码了。本段以机器人会话为例子来讲述事件注册和响应,有关 HTTP 服务器等注册事件响应请看后面事件和注解章节。
|
||||
|
||||
## 机器人聊天事件处理
|
||||
|
||||
首先知道,QQ 等聊天机器人的消息我们的处理逻辑为如下简单的模式:
|
||||
|
||||
- QQ 用户消息 -> 机器人客户端 -> 连接客户端的框架(炸毛框架)
|
||||
- 框架处理逻辑后返回给用户的消息 -> 机器人客户端 -> QQ用户
|
||||
|
||||
第一步,我们以框架这边的角度考虑,我们称之为“事件”,我们编写代码所做的就是要响应这一事件。
|
||||
|
||||
首先我们以一句简单的功能——查天气,我们要从零实现一个查天气的功能进行示范如何快捷有效地开发一个功能。
|
||||
|
||||
### 确定问法
|
||||
|
||||
我们首先要确定的用户问法是一般由我们自己定义,但最好贴合用户的自然语言来进行定义。比如我们这里提到的天气功能,用户一般就会询问“北京天气”,“北京天气怎么样”,“天气 北京”。
|
||||
|
||||
### 注册消息事件
|
||||
|
||||
我们以最简单的命令方式“天气 北京”进行处理。问法为参数化的,通过空格来分开,这也是炸毛框架默认支持最基本的聊天事件之一。我们通过上一部分的方式新建一个单文件模块 `Weather.php` 在 `src/Module` 目录下,并编写:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
class Weather {
|
||||
/**
|
||||
* @CQCommand("天气")
|
||||
* @return string
|
||||
*/
|
||||
public function searchWeather() {
|
||||
$city = ctx()->getNextArg("请告诉我你要查询的城市"); // 发送 “天气 北京”时,变量为“北京”
|
||||
// 这里假设是天气API接口的对接,返回了天气的数据
|
||||
$weather = "2020年12月22日,晴,-2~9℃ blablabla";
|
||||
return "$city 天气情况:".$weather;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! note "提示"
|
||||
为了简单起见,我们在这里的例子中没有接入真实的天气数据,但要接入也非常简单,你可以使用中国天气网、和风天气等网站提供的 API,本教程的进阶一栏后期会详细编写如何对接一个天气 API 接口。
|
||||
|
||||
在上方代码编写完毕后,运行框架(或运行过程中终端输入 `reload`),然后使用机器人客户端连接到炸毛框架,即可实现我们的第一个功能。我们在代码中编写了一个**注解事件**:`@CQCommand`,此注解事件是用于接收用户的普通消息并切分成各类命令规则的一个注解事件绑定。代码中的注解事件省去了注解中的键名,也可以写作 `@CQCommand(match="天气")`。
|
||||
|
||||
这里注解事件的概念请看 [事件和注解](/event/) 一栏描述的概念即可。到这里,我们就完成一个可以处理命令 `天气 xxx` 的方法了!
|
||||
|
||||
### 处理消息事件
|
||||
|
||||
第一行 `ctx()` 是炸毛框架内的上下文获取方式,每条用户聊天信息发过来,被炸毛框架收到,都会创建一次上下文,同时这次聊天的全部信息,比如用户的 ID(QQ 号码),发消息的时间,如果是群消息的话所在的群号等等,都被存到了上下文中。`ctx()` 获取的是一个上下文对象,内部有许多可操作上下文的方法,其中代码的 `getNextArg()`,作用是根据空格分隔获取命令中的下一个参数。
|
||||
|
||||
在之前我们知道:`天气 北京` 是我们发送的消息,我们要获取到用户发送的参数 `北京`。`getNextArg()` 是框架封装好的一个快速获取下一个参数的方法,我们这里直接使用它来获取。
|
||||
|
||||
对于 `getNextArg()` 中的文本,可为空,不为空的时候如果用户只发送天气两个字,机器人还是会响应,但是它会询问你这句话,然后你回复机器人“北京”,这里 `$city` 变量就接受到并赋值为“北京”了,代码会继续执行,和直接一次性发送机器人“天气 北京”是一个效果,此为框架封装的消息会话机制,以贴近自然会话的方式来编写代码逻辑。
|
||||
|
||||
最后,函数直接返回了一个字符串,作为事件的响应,炸毛框架会自动处理并调用机器人客户端的接口,最后返回给用户消息。这里也可以使用上下文的 `ctx()->reply("xxx")` 方法替代,不返回字符串。注意两者只能选择一种方式,取决于开发者的开发习惯。
|
||||
|
||||
<chat-box>
|
||||
) 天气 北京
|
||||
( 北京 天气情况:2020年12月22日,晴,-2~9℃ blablabla
|
||||
) 天气
|
||||
( 请告诉我你要查询的城市
|
||||
) 北京
|
||||
( 北京 天气情况:2020年12月22日,晴,-2~9℃ blablabla
|
||||
</chat-box>
|
||||
36
docs/guide/structure.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 目录结构
|
||||
|
||||
## 用户目录
|
||||
|
||||
### config 目录
|
||||
|
||||
`config` 目录包含框架、应用的所有配置文件。最好把这些文件都浏览一遍,并熟悉所有可用的选项。
|
||||
|
||||
```
|
||||
config/
|
||||
├── global.php # 全局配置文件
|
||||
├── container.php # 容器配置文件
|
||||
└── motd.txt # 框架启动时展示的文字信息
|
||||
```
|
||||
|
||||
### vendor 目录
|
||||
|
||||
`vendor` 目录包含你通过 Composer 安装的所有依赖,此目录为自动生成,无需操作。
|
||||
|
||||
### plugins 目录
|
||||
|
||||
`plugins` 目录包含你编写或加载到源代码模式的插件,里面的插件都会被框架自动扫描并解析,你可以在其中利用注解来注册事件绑定并进行相应处理。
|
||||
|
||||
比如你通过 `./zhamao plugin:make` 新建了一个名字叫 `test-app` 的插件,并且设置为单文件模式(`file`),那么这个插件内包含的文件及结构为:
|
||||
|
||||
```
|
||||
plugins/
|
||||
└── test-app/
|
||||
├── main.php # 你的插件源代码文件
|
||||
└── zmplugin.json # 插件元信息(如名称、版本等)
|
||||
```
|
||||
|
||||
### zm_data 目录
|
||||
|
||||
`zm_data` 目录存放了框架运行时持久化保存的数据,例如 KV 数据库、驱动日志等内容。
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
# 编写模块
|
||||
|
||||
到现在为止,我们还在使用框架的默认模块 `Example/Hello.php`,在开始编写自己的模块应用之前,我们先说明一些编写代码的约定。
|
||||
|
||||
## 加载模块
|
||||
|
||||
框架默认使用脚手架构建好后,目录结构大致为下面这样:
|
||||
|
||||
```bash
|
||||
zhamao-framework-starter/
|
||||
├── config/ # 项目的配置文件文件夹,如 global.php
|
||||
├── src/ # 项目的主要源码目录
|
||||
│ ├── Module/ # 用户编写的模块目录
|
||||
│ │ └── Example/ # 模块文件夹名称
|
||||
│ │ └── Hello.php # 模块内的类
|
||||
│ └── Custom/ # 用户自定义的全局方法、全局注解类等存放的目录
|
||||
├── vendor/ # Composer 依赖加载目录
|
||||
└── composer.json # Composer 配置文件
|
||||
```
|
||||
|
||||
其中我们脚手架包含的默认模块 `Example` 下的 `Hello` 类,就是用户写模块的位置。你也可以根据实际情况,自行添加更多的模块文件夹甚至单文件模块。
|
||||
|
||||
需要注意的是,所有文件夹名称和 `.php` 文件必须遵循 [psr-4 规范](https://learnku.com/docs/psr/psr-4-autoloader/1608),简单来说,`src/` 目录下的文件夹,子文件夹要写成命名空间,比如默认框架中 `Example/` 下的 `.php` 文件的命名空间为 `namespace Module\Example;`,且一个 `.php` 文件推荐只包含一个 `class`、`trait` 或 `interface`。
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\<your-module-dir>;
|
||||
class ModuleA {}
|
||||
```
|
||||
|
||||
!!! fail "警告"
|
||||
如果没有遵守上方的类和文件命名规则的话(文件名、文件夹名和命名空间的统一性),在加载框架时就会报错,无法找到对应的类。因为框架的注解解析依赖于 Composer 中 psr-4 规则的自动加载。
|
||||
|
||||
## 创建模块
|
||||
### 标准形式
|
||||
我们这里以 `Entertain` 娱乐模块的创建为例,新建一个内有 `Dice.php` 掷骰子功能的模块,目录结构如下,在 `Module/` 下新建文件夹 `Entertain/`,再在此子目录下新建 `Dice.php` 文件。
|
||||
```bash
|
||||
zhamao-framework-starter/
|
||||
└── src/
|
||||
└── Module/
|
||||
└── Entertain/
|
||||
└── Dice.php
|
||||
```
|
||||
新建的 PHP 文件按照如下方式编写:
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Entertain;
|
||||
class Dice {
|
||||
}
|
||||
```
|
||||
|
||||
这个时候它已经可以被称为一个模块了,尽管它还什么都没做。
|
||||
|
||||
### 单文件形式
|
||||
|
||||
如果你只开发很简单的一些功能,如一个 PHP 文件就可以实现的,可以少去创建模块文件夹的一步,直接将 `.php` 文件新建到 `Module/` 文件夹下,这时此文件的命名空间需要更正为 `namespace Module;` 即可,而文件夹结构也更加简单:
|
||||
|
||||
```bash
|
||||
zhamao-framework-starter/
|
||||
└── src/
|
||||
└── Module/
|
||||
└── Dice.php
|
||||
```
|
||||
|
||||
### Composer 外部引入形式
|
||||
|
||||
(暂未支持,敬请期待)
|
||||
|
||||
130
docs/index.md
@@ -1,130 +0,0 @@
|
||||
# 介绍
|
||||
|
||||
> 本文档为炸毛框架 v2 版本,如需查看 v1 版本,[点我](https://docs-v1.zhamao.xin/)。
|
||||
|
||||
> 如果是从 v1.x 版本升级到 v2.x,[点我看升级指南](/advanced/to-v2/)。
|
||||
|
||||
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(CQHTTP 对接),包含 websocket、http 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
|
||||
|
||||
框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
|
||||
|
||||
在 HTTP 和 WebSocket 服务器上,PHP 的扩展 Swoole 提供了高性能的支持,使其效率可媲美 nginx 静态网页处理的效率。
|
||||
|
||||
此外,QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快,未来也会兼容微信公众号开发者模式。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("你好")
|
||||
*/
|
||||
public function hello() {
|
||||
ctx()->reply("你好,我是炸毛!");
|
||||
}
|
||||
/**
|
||||
* @RequestMapping("/index")
|
||||
*/
|
||||
public function index() {
|
||||
return "<h1>hello!</h1>";
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 开始前
|
||||
|
||||
首先,你需要了解你需要知道哪些事情才能开始着手使用框架:
|
||||
|
||||
1. Linux 命令行(会跑 Linux 程序)
|
||||
2. php 7.2+ 开发环境
|
||||
3. HTTP 协议(可选)
|
||||
4. OneBot 机器人聊天接口标准(可选)
|
||||
|
||||
需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。
|
||||
|
||||
!!! bug "文档提示"
|
||||
|
||||
此文档采用 MkDocs 驱动,但因为本文档的搜索组件原生不支持中文搜索,所以搜索体验会大打折扣,敬请谅解!搜不到不是没这个东西哦!
|
||||
|
||||
|
||||
## 框架特色
|
||||
- 支持MySQL数据库(连接池),自带查询缓存提高多查询时的效率
|
||||
- Websocket 服务器、HTTP 服务器兼容运行,一个框架多个用处
|
||||
- 支持命令、自然语言处理等多种插件形式
|
||||
- 支持多个机器人账号负载均衡
|
||||
- 协程 + TaskWorker 进程重度任务处理机制,保证高效,单个请求响应时间为 0.1 ms 左右
|
||||
- 模块分离和自由组合,可根据自身需求自己建立模块内的目录结构和代码结构
|
||||
- 灵活的注释注解注册事件方式,弥补 PHP 语言缺少注解的遗憾
|
||||
|
||||
## 文档主题
|
||||
|
||||
### 主题
|
||||
<div class="tx-switch">
|
||||
<button data-md-color-scheme="default"><code>默认模式</code></button>
|
||||
<button data-md-color-scheme="slate"><code>暗黑模式</code></button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var buttons = document.querySelectorAll("button[data-md-color-scheme]");
|
||||
buttons.forEach(function(button) {
|
||||
button.addEventListener("click", function() {
|
||||
var attr = this.getAttribute("data-md-color-scheme");
|
||||
setCookie("_theme", attr);
|
||||
document.body.setAttribute("data-md-color-scheme", attr);
|
||||
var name = document.querySelector("#__code_0 code span:nth-child(7)");
|
||||
name.textContent = attr;
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
### 主色调
|
||||
<div class="tx-switch">
|
||||
<button data-md-color-primary="red"><code>red</code></button>
|
||||
<button data-md-color-primary="pink"><code>pink</code></button>
|
||||
<button data-md-color-primary="purple"><code>purple</code></button>
|
||||
<button data-md-color-primary="deep-purple"><code>deep purple</code></button>
|
||||
<button data-md-color-primary="indigo"><code>indigo</code></button>
|
||||
<button data-md-color-primary="blue"><code>blue</code></button>
|
||||
<button data-md-color-primary="light-blue"><code>light blue</code></button>
|
||||
<button data-md-color-primary="cyan"><code>cyan</code></button>
|
||||
<button data-md-color-primary="teal"><code>teal</code></button>
|
||||
<button data-md-color-primary="green"><code>green</code></button>
|
||||
<button data-md-color-primary="light-green"><code>light green</code></button>
|
||||
<button data-md-color-primary="lime"><code>lime</code></button>
|
||||
<button data-md-color-primary="yellow"><code>yellow</code></button>
|
||||
<button data-md-color-primary="amber"><code>amber</code></button>
|
||||
<button data-md-color-primary="orange"><code>orange</code></button>
|
||||
<button data-md-color-primary="deep-orange"><code>deep orange</code></button>
|
||||
<button data-md-color-primary="brown"><code>brown</code></button>
|
||||
<button data-md-color-primary="grey"><code>grey</code></button>
|
||||
<button data-md-color-primary="blue-grey"><code>blue grey</code></button>
|
||||
<button data-md-color-primary="black"><code>black</code></button>
|
||||
<button data-md-color-primary="white"><code>white</code></button>
|
||||
</div>
|
||||
|
||||
### 辅色调
|
||||
<div class="tx-switch"> <button data-md-color-accent="red"><code>red</code></button> <button data-md-color-accent="pink"><code>pink</code></button> <button data-md-color-accent="purple"><code>purple</code></button> <button data-md-color-accent="deep-purple"><code>deep purple</code></button> <button data-md-color-accent="indigo"><code>indigo</code></button> <button data-md-color-accent="blue"><code>blue</code></button> <button data-md-color-accent="light-blue"><code>light blue</code></button> <button data-md-color-accent="cyan"><code>cyan</code></button> <button data-md-color-accent="teal"><code>teal</code></button> <button data-md-color-accent="green"><code>green</code></button> <button data-md-color-accent="light-green"><code>light green</code></button> <button data-md-color-accent="lime"><code>lime</code></button> <button data-md-color-accent="yellow"><code>yellow</code></button> <button data-md-color-accent="amber"><code>amber</code></button> <button data-md-color-accent="orange"><code>orange</code></button> <button data-md-color-accent="deep-orange"><code>deep orange</code></button> </div>
|
||||
|
||||
<script>
|
||||
var buttons = document.querySelectorAll("button[data-md-color-primary]")
|
||||
buttons.forEach(function(button) {
|
||||
button.addEventListener("click", function() {
|
||||
var attr = this.getAttribute("data-md-color-primary")
|
||||
setCookie("_primary_color", attr)
|
||||
document.body.setAttribute("data-md-color-primary", attr)
|
||||
var name = document.querySelector("#__code_2 code span:nth-child(7)")
|
||||
name.textContent = attr.replace("-", " ")
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var buttons2 = document.querySelectorAll("button[data-md-color-accent]")
|
||||
buttons2.forEach(function(button) {
|
||||
button.addEventListener("click", function() {
|
||||
var attr = this.getAttribute("data-md-color-accent")
|
||||
setCookie("_accent_color", attr)
|
||||
document.body.setAttribute("data-md-color-accent", attr)
|
||||
var name = document.querySelector("#__code_3 code span:nth-child(7)")
|
||||
name.textContent = attr.replace("-", " ")
|
||||
})
|
||||
})
|
||||
</script>
|
||||