Compare commits
1758 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0f0883a88 | ||
|
|
c1695a78d6 | ||
|
|
15e37eded3 | ||
|
|
57cd91e6d4 | ||
|
|
a178334d8e | ||
|
|
dd8ba7e8da | ||
|
|
d26df91960 | ||
|
|
f249763d41 | ||
|
|
1bada9ab30 | ||
|
|
a185ab2973 | ||
|
|
ce83bef0ed | ||
|
|
66135d8222 | ||
|
|
e99e069e55 | ||
|
|
327cdbc873 | ||
|
|
6eabba9679 | ||
|
|
c99f6cfcf2 | ||
|
|
0579a73c1c | ||
|
|
12b3c14299 | ||
|
|
c21da4292b | ||
|
|
3f9cdfd887 | ||
|
|
8dac2bc444 | ||
|
|
13ec6ec323 | ||
|
|
e0c3ea4456 | ||
|
|
d6a3727713 | ||
|
|
48cd32742c | ||
|
|
852ceba828 | ||
|
|
905c8be6eb | ||
|
|
fad98dcc9d | ||
|
|
8b5409de5a | ||
|
|
bcf1ad0870 | ||
|
|
617e88e0b5 | ||
|
|
c9e0840173 | ||
|
|
5e4f99da6c | ||
|
|
28bc303fcf | ||
|
|
91c63f281b | ||
|
|
7b3769b1db | ||
|
|
211f9f0c15 | ||
|
|
37ccf4dacb | ||
|
|
971167cad3 | ||
|
|
332bed3136 | ||
|
|
e2a9906de0 | ||
|
|
c5879e4376 | ||
|
|
22324f4c16 | ||
|
|
fa9c3b4f2f | ||
|
|
f411f17386 | ||
|
|
ab3a82300c | ||
|
|
dbb9162267 | ||
|
|
84d3e4f617 | ||
|
|
6209b53321 | ||
|
|
62a2bcf71d | ||
|
|
cdefe9d4a7 | ||
|
|
2bebad1112 | ||
|
|
9f186f1e9c | ||
|
|
a6873302f3 | ||
|
|
615f40d458 | ||
|
|
c4b49b34b8 | ||
|
|
d12fb47902 | ||
|
|
21132f475a | ||
|
|
a55e0a457d | ||
|
|
fa7b049316 | ||
|
|
f8f5bc476b | ||
|
|
e5c622cb89 | ||
|
|
aa70c41041 | ||
|
|
3cce9b67d4 | ||
|
|
c0342ea6d1 | ||
|
|
2813f4c062 | ||
|
|
9ca9de0d7e | ||
|
|
603db9de7f | ||
|
|
c4e72507e0 | ||
|
|
d06d1c177c | ||
|
|
4ff1cf68fc | ||
|
|
8d92933e43 | ||
|
|
9497fb1bb6 | ||
|
|
fc65d56977 | ||
|
|
fe4cba61e2 | ||
|
|
8144bea613 | ||
|
|
7a431d86d2 | ||
|
|
7ecfd86ffa | ||
|
|
66b9e7e9b3 | ||
|
|
6bed109f97 | ||
|
|
fbc5eed5c5 | ||
|
|
43b665652e | ||
|
|
5760d3ef0f | ||
|
|
e712b99287 | ||
|
|
85d88b6800 | ||
|
|
1a62a47935 | ||
|
|
689d842d58 | ||
|
|
8215e73a95 | ||
|
|
d3fc274f08 | ||
|
|
e4bcb8b518 | ||
|
|
9a942c483d | ||
|
|
e9fd223808 | ||
|
|
5dfc66fc21 | ||
|
|
bab82dc290 | ||
|
|
4c1125b9e1 | ||
|
|
85ef2d9687 | ||
|
|
b7fc815d58 | ||
|
|
ad1cc964c9 | ||
|
|
96a2b250a3 | ||
|
|
d72ab58f98 | ||
|
|
abd453f2f6 | ||
|
|
4b7283dbe8 | ||
|
|
f5a068fffc | ||
|
|
faf5dec08a | ||
|
|
e4070e249d | ||
|
|
fe5ec9677a | ||
|
|
5fdd5adef8 | ||
|
|
bc250ad4b8 | ||
|
|
22050b7488 | ||
|
|
6df906aa24 | ||
|
|
d2f20128bb | ||
|
|
cef19488d2 | ||
|
|
0ceb2de79d | ||
|
|
79feaaf801 | ||
|
|
34c005001d | ||
|
|
3508d7a472 | ||
|
|
1e58587b1c | ||
|
|
e99f952c28 | ||
|
|
b0742021b6 | ||
|
|
5e784f64a6 | ||
|
|
32aae08ef2 | ||
|
|
af46fc501b | ||
|
|
07c3a958fa | ||
|
|
ecdbf8765f | ||
|
|
5682943c24 | ||
|
|
5fbc5d3164 | ||
|
|
90af11a842 | ||
|
|
b644a65f22 | ||
|
|
6db82c8176 | ||
|
|
fa149fcaa9 | ||
|
|
9772e4b48a | ||
|
|
08cc4a4815 | ||
|
|
aa554627fb | ||
|
|
420e1a9d63 | ||
|
|
b8ed8566ee | ||
|
|
bbb3cee927 | ||
|
|
2737fa4697 | ||
|
|
14913ae312 | ||
|
|
d2fe6217a6 | ||
|
|
aef46d4c76 | ||
|
|
ad494b86e3 | ||
|
|
c0a594655d | ||
|
|
ed4afa63f0 | ||
|
|
08fccf4adc | ||
|
|
a52e9e152d | ||
|
|
38befc94ca | ||
|
|
df4c8cc352 | ||
|
|
2305d30d35 | ||
|
|
cdc7e671ce | ||
|
|
d541397594 | ||
|
|
4205b68b8c | ||
|
|
6ffb458ffc | ||
|
|
e43dd53e4f | ||
|
|
5ef4516e3d | ||
|
|
51b1d78d8a | ||
|
|
3fdcbf92b6 | ||
|
|
742875d5eb | ||
|
|
4a79eeb9df | ||
|
|
43cee0eb4a | ||
|
|
92beb20455 | ||
|
|
930f55b080 | ||
|
|
e7239b3c5c | ||
|
|
1e92ca5518 | ||
|
|
c9e3e88443 | ||
|
|
05f20eb761 | ||
|
|
a9033c610b | ||
|
|
e9e1cd9028 | ||
|
|
4e8239377f | ||
|
|
83a501b69f | ||
|
|
efe80ce0eb | ||
|
|
e0fc1c5ef7 | ||
|
|
a38ddf83f2 | ||
|
|
e7fbe8bb49 | ||
|
|
523166da48 | ||
|
|
a24990e06a | ||
|
|
3f11451f3c | ||
|
|
0b888b9597 | ||
|
|
acddde8faa | ||
|
|
c587a50505 | ||
|
|
7befd4277e | ||
|
|
bbc29d5d5f | ||
|
|
9dc7c08b61 | ||
|
|
0f690a99df | ||
|
|
d8c8cca6fc | ||
|
|
9bccc99228 | ||
|
|
4f1016a939 | ||
|
|
71fb50b4ce | ||
|
|
7359b04001 | ||
|
|
5700b97e98 | ||
|
|
19e50be276 | ||
|
|
14595d3a60 | ||
|
|
eea9d1f3ce | ||
|
|
157147ded7 | ||
|
|
b8d51c6462 | ||
|
|
7fe3436819 | ||
|
|
a2cd57641b | ||
|
|
80bfc2c86f | ||
|
|
e3bd895908 | ||
|
|
018ff205f5 | ||
|
|
9fee5c61a6 | ||
|
|
ba8e7b3fbb | ||
|
|
dbe53481f1 | ||
|
|
8b9cfbcf29 | ||
|
|
db67d662d1 | ||
|
|
dcf947c498 | ||
|
|
c94025013b | ||
|
|
304b22b1c1 | ||
|
|
f23ece5e9e | ||
|
|
651194f12e | ||
|
|
ae00f49b30 | ||
|
|
60f5341a9a | ||
|
|
3b17ebcb09 | ||
|
|
c861f49e29 | ||
|
|
1d2f50c896 | ||
|
|
4f676c0ccb | ||
|
|
5801d0fc14 | ||
|
|
525ac41258 | ||
|
|
39a0d001ef | ||
|
|
0e9c1c19ce | ||
|
|
0011e04823 | ||
|
|
e919170ee8 | ||
|
|
8024edc1b0 | ||
|
|
984c68dc09 | ||
|
|
c35a177ac1 | ||
|
|
ea82e2dbfe | ||
|
|
034bd7dcb8 | ||
|
|
343905362c | ||
|
|
79d5b70364 | ||
|
|
11a2fcf1f4 | ||
|
|
7c16f9f134 | ||
|
|
fb0ef19158 | ||
|
|
3c7b7e021f | ||
|
|
a38fa4625f | ||
|
|
46376121d0 | ||
|
|
7b2b026bad | ||
|
|
9796e104a5 | ||
|
|
0bf3020db7 | ||
|
|
12e8f81a58 | ||
|
|
f073e61965 | ||
|
|
85e6066292 | ||
|
|
f57d3cf02c | ||
|
|
aa03c00dd5 | ||
|
|
1a3c4640ec | ||
|
|
29d8396910 | ||
|
|
d1e38910ef | ||
|
|
0c4abb5db3 | ||
|
|
6f7b44cb08 | ||
|
|
3a2c40a43e | ||
|
|
288e265aaa | ||
|
|
42dec0464e | ||
|
|
5b09a111cd | ||
|
|
769e2b0223 | ||
|
|
1fed482025 | ||
|
|
46705dd55f | ||
|
|
c11f946979 | ||
|
|
e928ff1fce | ||
|
|
333d4517b5 | ||
|
|
d64d06a70a | ||
|
|
39834f507c | ||
|
|
5199609d54 | ||
|
|
53a5a33fa1 | ||
|
|
a397908bd4 | ||
|
|
bd9e0bba9c | ||
|
|
7179efd3bd | ||
|
|
de0bca2076 | ||
|
|
5f3f350e1b | ||
|
|
7eeacc59db | ||
|
|
d6d96d2d2b | ||
|
|
bb39b7db89 | ||
|
|
706fd0d588 | ||
|
|
97fc99c5e4 | ||
|
|
666767539d | ||
|
|
28234f64ec | ||
|
|
80bede367d | ||
|
|
6722051e82 | ||
|
|
c9fc9916b2 | ||
|
|
9ff7160870 | ||
|
|
9eec44249c | ||
|
|
146fb3321a | ||
|
|
05fcb5cb0c | ||
|
|
09cf62cadd | ||
|
|
632e113d63 | ||
|
|
c4743d3b26 | ||
|
|
10960eec59 | ||
|
|
b3c79be4e7 | ||
|
|
efe6c99199 | ||
|
|
521a0dbec6 | ||
|
|
916997d92a | ||
|
|
0af07058df | ||
|
|
84c98dd5c1 | ||
|
|
a229c12baf | ||
|
|
e0ecd0ad0a | ||
|
|
96ca57509f | ||
|
|
e9e090fdf6 | ||
|
|
6af0922542 | ||
|
|
1bb400b7dc | ||
|
|
bbb4550f10 | ||
|
|
96b2af9cc0 | ||
|
|
e47f601a51 | ||
|
|
af33f473b5 | ||
|
|
c73f1c0061 | ||
|
|
b5d63dfd12 | ||
|
|
952836e1f1 | ||
|
|
4912f97461 | ||
|
|
e2a0b7a033 | ||
|
|
0e12a08076 | ||
|
|
3ae3acf705 | ||
|
|
b8770433d7 | ||
|
|
7030148a76 | ||
|
|
4ed09dddcf | ||
|
|
aba74681ef | ||
|
|
c768395094 | ||
|
|
d7620bf4ff | ||
|
|
3a9adfa089 | ||
|
|
c8bc67e7bf | ||
|
|
7c72003b91 | ||
|
|
6a4392266d | ||
|
|
5d1b805b93 | ||
|
|
dffd860d5c | ||
|
|
8d8777ba95 | ||
|
|
00e255a4a8 | ||
|
|
bba40830fb | ||
|
|
a3974195c2 | ||
|
|
7c4c26e86e | ||
|
|
1be70fd8f2 | ||
|
|
84e3b22357 | ||
|
|
a3c509da83 | ||
|
|
9944dc4693 | ||
|
|
5b1fc8af84 | ||
|
|
ee708d1d1b | ||
|
|
d0df50705a | ||
|
|
cb2b762a6f | ||
|
|
3971c63dda | ||
|
|
3fe9c60480 | ||
|
|
00050c9c6b | ||
|
|
afc0fd78c8 | ||
|
|
ac46e1ca22 | ||
|
|
9efdeb6b97 | ||
|
|
30ac03e0cc | ||
|
|
89001bec0a | ||
|
|
c6b9dfff22 | ||
|
|
b8aa32dcaa | ||
|
|
c589d5e0a5 | ||
|
|
1b090c1c6e | ||
|
|
40474319e3 | ||
|
|
b79fdbd7e0 | ||
|
|
f3f65fa99e | ||
|
|
6a9408a8bc | ||
|
|
2b88764c7e | ||
|
|
4a75844c98 | ||
|
|
9d9a3ebe54 | ||
|
|
79b9d412c1 | ||
|
|
579cf4ee3b | ||
|
|
554c847ea4 | ||
|
|
b7d9ac7436 | ||
|
|
a951a719d1 | ||
|
|
31b03beb2c | ||
|
|
2b3d5ff223 | ||
|
|
7e5bbb4bb7 | ||
|
|
fcecccc9b8 | ||
|
|
29ef080399 | ||
|
|
459bce93c1 | ||
|
|
0ba161f4c5 | ||
|
|
cfb8736a1f | ||
|
|
9686df4898 | ||
|
|
a613507835 | ||
|
|
bc5046c04d | ||
|
|
77d3ba0c3b | ||
|
|
68bd66089a | ||
|
|
7a1c2c7f2d | ||
|
|
4f7259747c | ||
|
|
e3079ce6e0 | ||
|
|
e26d75c894 | ||
|
|
e176600a5a | ||
|
|
7147db0ef2 | ||
|
|
f0d4aa324e | ||
|
|
6870a29ec9 | ||
|
|
50f044455a | ||
|
|
6cc20ce23f | ||
|
|
defd0d2361 | ||
|
|
f49f73409c | ||
|
|
3d783c59c2 | ||
|
|
7582727753 | ||
|
|
b16b214099 | ||
|
|
a101feff21 | ||
|
|
7eef3e7989 | ||
|
|
d3182f278f | ||
|
|
cf3abdf643 | ||
|
|
b8edd410b5 | ||
|
|
d0122328b7 | ||
|
|
3b2460c5d1 | ||
|
|
41742cd6df | ||
|
|
a8a2badc99 | ||
|
|
423594e20c | ||
|
|
bb251c8aee | ||
|
|
d728e2d7c0 | ||
|
|
6e2e915d95 | ||
|
|
9d7d2b3fda | ||
|
|
600944fc22 | ||
|
|
142de587e4 | ||
|
|
cda1c88434 | ||
|
|
b4bf8304ec | ||
|
|
9ea1fa6df7 | ||
|
|
829ed0a575 | ||
|
|
bbdf4932e3 | ||
|
|
cc76e91e12 | ||
|
|
b59aca0b5c | ||
|
|
dd4d8d8880 | ||
|
|
479e9f83a9 | ||
|
|
a8481e0ad5 | ||
|
|
827dde4d1b | ||
|
|
4a027cec20 | ||
|
|
00fa06a274 | ||
|
|
23cb7ffd6f | ||
|
|
2aab517e0c | ||
|
|
25c2a0d90e | ||
|
|
a603211f1a | ||
|
|
34745fb3e5 | ||
|
|
05680ab152 | ||
|
|
ffb05d5aab | ||
|
|
fe4ab6e9d5 | ||
|
|
1ea764c860 | ||
|
|
be94c816ca | ||
|
|
201c0e086b | ||
|
|
569145196e | ||
|
|
7bae000a28 | ||
|
|
0d57b8a163 | ||
|
|
34820cc395 | ||
|
|
efdc4c5229 | ||
|
|
29c9ba56ff | ||
|
|
e6be45516d | ||
|
|
2b5b522f3e | ||
|
|
4ff310ea8b | ||
|
|
ee4f894933 | ||
|
|
482b9d43dd | ||
|
|
ff64a1a510 | ||
|
|
f328a5a439 | ||
|
|
d418a9e086 | ||
|
|
e87e69fa1d | ||
|
|
5d52cb823f | ||
|
|
5a18eccad5 | ||
|
|
df627b0ad5 | ||
|
|
395ccaad22 | ||
|
|
cc96bbf17e | ||
|
|
a19ee35ca4 | ||
|
|
39211297e2 | ||
|
|
46967f1e00 | ||
|
|
40b52d8f3b | ||
|
|
c5f7073cc1 | ||
|
|
a1a8e8a962 | ||
|
|
3d30f4e6c2 | ||
|
|
c5114203b2 | ||
|
|
1be7e67655 | ||
|
|
b8eab81ad2 | ||
|
|
75242f8844 | ||
|
|
d423c2bd05 | ||
|
|
89260dc751 | ||
|
|
dd7e24850d | ||
|
|
42f805d7cc | ||
|
|
94f1e764bc | ||
|
|
69fd97485d | ||
|
|
68e8c4bb59 | ||
|
|
7c7965a3f7 | ||
|
|
189986dd88 | ||
|
|
b8c2b3a97a | ||
|
|
5b079018e8 | ||
|
|
3a834ae90d | ||
|
|
c9d9afc72b | ||
|
|
dac34c8988 | ||
|
|
40f18764b9 | ||
|
|
5f95c23029 | ||
|
|
026dca2d84 | ||
|
|
11b152cfbb | ||
|
|
af5db70c07 | ||
|
|
b4d718126a | ||
|
|
f73ee2c13b | ||
|
|
aebcca76e4 | ||
|
|
e55fd2cede | ||
|
|
e272c06028 | ||
|
|
ada0de8dbc | ||
|
|
874bf4b051 | ||
|
|
f8e70bd7f7 | ||
|
|
04cb03e0b2 | ||
|
|
df287e7122 | ||
|
|
bef48be571 | ||
|
|
7e77ac1ecb | ||
|
|
f33faf66a6 | ||
|
|
77ecb89533 | ||
|
|
02bd1deb2c | ||
|
|
8476ed2e51 | ||
|
|
a3e32f88b0 | ||
|
|
92f9240da7 | ||
|
|
62cd82a9ec | ||
|
|
97ce98177f | ||
|
|
a614789baf | ||
|
|
a6d9617e7f | ||
|
|
4b62aa04aa | ||
|
|
1ca7e257ee | ||
|
|
24d7300f23 | ||
|
|
04038253cf | ||
|
|
f5fc2301bc | ||
|
|
ce82cc8dd9 | ||
|
|
d7e823dfdd | ||
|
|
bbb69b3ec2 | ||
|
|
8831aa1ce1 | ||
|
|
db2e4edb27 | ||
|
|
fa7a33cb9b | ||
|
|
fd5088836b | ||
|
|
45746684d5 | ||
|
|
d647a9d6a0 | ||
|
|
cbb5ab2ecc | ||
|
|
fc01f00915 | ||
|
|
986e2cf0b4 | ||
|
|
ddc7aecd53 | ||
|
|
bf845208db | ||
|
|
dd582fef65 | ||
|
|
f6fd9a5edf | ||
|
|
fead758660 | ||
|
|
abfcc1020f | ||
|
|
c2215452d6 | ||
|
|
10d68790e7 | ||
|
|
7a47cb9031 | ||
|
|
56da2a5725 | ||
|
|
6185082311 | ||
|
|
7e5f350de5 | ||
|
|
925667d840 | ||
|
|
87669010e3 | ||
|
|
4703b03b82 | ||
|
|
e27ea9e117 | ||
|
|
1df0a1dfb4 | ||
|
|
7b02bc006b | ||
|
|
ccea4b5b1b | ||
|
|
3096a6be79 | ||
|
|
ab23311851 | ||
|
|
035fba3fe5 | ||
|
|
7d388cb10a | ||
|
|
5152cee99e | ||
|
|
1a011fd971 | ||
|
|
220ce21a4a | ||
|
|
01b50c442e | ||
|
|
371c87f5a4 | ||
|
|
155f42dd37 | ||
|
|
9289591ba0 | ||
|
|
99d8559c56 | ||
|
|
0adbdbdf1b | ||
|
|
13560616c8 | ||
|
|
621706b1ff | ||
|
|
a5ed59bd64 | ||
|
|
a6ce767532 | ||
|
|
19fd36b195 | ||
|
|
829612e720 | ||
|
|
9ceb00bf54 | ||
|
|
560562755b | ||
|
|
aebd47e535 | ||
|
|
31f051a39e | ||
|
|
49f27ee4cd | ||
|
|
eabbe82b39 | ||
|
|
01fd5f78e8 | ||
|
|
5869c8b6bc | ||
|
|
5142497c7a | ||
|
|
9adb825a88 | ||
|
|
cde9c819d1 | ||
|
|
14195a2647 | ||
|
|
a35f1505e3 | ||
|
|
c444e2d3fa | ||
|
|
287e3e378d | ||
|
|
41c2dabe26 | ||
|
|
5c00a28920 | ||
|
|
854ed2147d | ||
|
|
dc057b5ca7 | ||
|
|
a9585ed9cc | ||
|
|
36499a36b1 | ||
|
|
ff2f311d43 | ||
|
|
99403fad7d | ||
|
|
29a651b261 | ||
|
|
2e92682df2 | ||
|
|
b667184c35 | ||
|
|
5d57666cd4 | ||
|
|
ee3c3a0497 | ||
|
|
6c84319fca | ||
|
|
a17991b25e | ||
|
|
cfb8818f7b | ||
|
|
d167efb52b | ||
|
|
ad0ce38bc7 | ||
|
|
09dbdf1df7 | ||
|
|
68aef19c2c | ||
|
|
99b6198153 | ||
|
|
76866e6292 | ||
|
|
7393fdf6cf | ||
|
|
0870ccf4d8 | ||
|
|
e8675c9c14 | ||
|
|
1114a74f94 | ||
|
|
3ea7d61bb7 | ||
|
|
f077b41ffc | ||
|
|
ad5cb79d40 | ||
|
|
1fdaecf7b4 | ||
|
|
dd2ff9359b | ||
|
|
c8e3a5ee4c | ||
|
|
5dddf25e5e | ||
|
|
7ad8abce6b | ||
|
|
175864403e | ||
|
|
2082fbc4dd | ||
|
|
9247860b50 | ||
|
|
a4e41ffb24 | ||
|
|
19815415d0 | ||
|
|
c940698933 | ||
|
|
6426c76bce | ||
|
|
04632182b4 | ||
|
|
29bbbd804a | ||
|
|
fd33eb3d1c | ||
|
|
4845e2e6a6 | ||
|
|
414b423311 | ||
|
|
e507c148ca | ||
|
|
ae4be9e08e | ||
|
|
296ae69cf9 | ||
|
|
f613a8cfc6 | ||
|
|
b0369a3af1 | ||
|
|
27d69e90fa | ||
|
|
19a4f63ffa | ||
|
|
a32b15e89c | ||
|
|
644d8e22a1 | ||
|
|
722b3b4788 | ||
|
|
3b97c6ecd9 | ||
|
|
20aa89dd6a | ||
|
|
0d2754ab95 | ||
|
|
010e9c2fbe | ||
|
|
8d4511e2b1 | ||
|
|
77f1869e3c | ||
|
|
ec032b91a3 | ||
|
|
68d9d3a659 | ||
|
|
c4cc7ea18c | ||
|
|
e75fbadd53 | ||
|
|
4957f32d06 | ||
|
|
c2715f9b5e | ||
|
|
d3a3c34287 | ||
|
|
95ec53381b | ||
|
|
8c6f1120e4 | ||
|
|
ea1c2a34e2 | ||
|
|
a233c492e6 | ||
|
|
aea2124d61 | ||
|
|
317970f010 | ||
|
|
b0fda87923 | ||
|
|
73cb7e9397 | ||
|
|
95d3c7dfce | ||
|
|
40d5c1f11e | ||
|
|
847d7f98fc | ||
|
|
3a13b8bf30 | ||
|
|
11e30d5860 | ||
|
|
6f1a0d90b0 | ||
|
|
098269adf6 | ||
|
|
0d63447987 | ||
|
|
edfb32647b | ||
|
|
20496b2edb | ||
|
|
4739539722 | ||
|
|
dba2bdd9e7 | ||
|
|
905662e963 | ||
|
|
ac2dfcfce3 | ||
|
|
fd6fef4292 | ||
|
|
eee7b8f0f1 | ||
|
|
ef88924f36 | ||
|
|
c9378b0eab | ||
|
|
cde96cb8b4 | ||
|
|
ffbd9b1f26 | ||
|
|
59524c78e6 | ||
|
|
1ad0c3a3ec | ||
|
|
e85d4c24d3 | ||
|
|
18280b4ff9 | ||
|
|
e504fb845f | ||
|
|
003186edd9 | ||
|
|
61e958d757 | ||
|
|
2ee349605e | ||
|
|
7d200d9a73 | ||
|
|
0468178c43 | ||
|
|
6f4a94c8d1 | ||
|
|
e83a65ec40 | ||
|
|
53ea6b00ab | ||
|
|
45865c0c18 | ||
|
|
979d8057a4 | ||
|
|
b81f75b794 | ||
|
|
8f7578ab06 | ||
|
|
dd39a8b63f | ||
|
|
7595309dc8 | ||
|
|
93abe72041 | ||
|
|
60c513ea3b | ||
|
|
385abae741 | ||
|
|
a680538f80 | ||
|
|
11571c2045 | ||
|
|
f1d267e2b2 | ||
|
|
003b52fbc9 | ||
|
|
6e2df6fcfc | ||
|
|
cd151667c1 | ||
|
|
1a3ecbb4e9 | ||
|
|
0b1f433fe4 | ||
|
|
7aa44b8623 | ||
|
|
6e68100f86 | ||
|
|
c869e88c40 | ||
|
|
9771e1e741 | ||
|
|
9034a70b8f | ||
|
|
e58ebebb80 | ||
|
|
edab43504e | ||
|
|
900b11280c | ||
|
|
0f449f151a | ||
|
|
8186649a9f | ||
|
|
364ef34248 | ||
|
|
c66714d1f5 | ||
|
|
d42fcca81f | ||
|
|
16d1591675 | ||
|
|
d7b2f93797 | ||
|
|
9203046d97 | ||
|
|
b860a749f3 | ||
|
|
6c689fe55f | ||
|
|
c18fd1ef20 | ||
|
|
7e56389c34 | ||
|
|
28771c38e7 | ||
|
|
de7e74ff19 | ||
|
|
2cf661b54f | ||
|
|
16ea92c970 | ||
|
|
3ad55cf6d5 | ||
|
|
d0951041eb | ||
|
|
150d9f18b4 | ||
|
|
09f48c32c7 | ||
|
|
596594d8b3 | ||
|
|
59d17aa950 | ||
|
|
836d4f8209 | ||
|
|
c13aa8d7a4 | ||
|
|
5fb07b7aa7 | ||
|
|
ee12cc21c1 | ||
|
|
6abfc7a2e2 | ||
|
|
18758421fd | ||
|
|
644a986747 | ||
|
|
ad164f3539 | ||
|
|
d5a7ae5fa3 | ||
|
|
a845b7b892 | ||
|
|
a29414a477 | ||
|
|
1c3ce47dcc | ||
|
|
8f30693473 | ||
|
|
0f5f7c515a | ||
|
|
8e76493cc6 | ||
|
|
8617c72aab | ||
|
|
7f0a150b74 | ||
|
|
a53aaff620 | ||
|
|
cab81c5c04 | ||
|
|
5923b6169a | ||
|
|
0f5eb829c6 | ||
|
|
d87c06de9a | ||
|
|
76e8870738 | ||
|
|
247b9ee0c1 | ||
|
|
80dd76b078 | ||
|
|
9163cee90e | ||
|
|
fe46d63031 | ||
|
|
d14d5a5942 | ||
|
|
7181652f01 | ||
|
|
4024cfb7c7 | ||
|
|
1cf5a24c26 | ||
|
|
06795ca32c | ||
|
|
c4fd5ee60a | ||
|
|
e30b66e5c1 | ||
|
|
9ee8d9b828 | ||
|
|
6107bde666 | ||
|
|
e69a91feb3 | ||
|
|
174df30978 | ||
|
|
8ffac1376a | ||
|
|
433a46bba9 | ||
|
|
ba3d5299a0 | ||
|
|
0aa6911159 | ||
|
|
1cef313689 | ||
|
|
4a198ccd8f | ||
|
|
11e08fea73 | ||
|
|
a5be461021 | ||
|
|
d2e04843a4 | ||
|
|
d0276e63c6 | ||
|
|
2d4fa5735b | ||
|
|
1c367eb687 | ||
|
|
56cf534340 | ||
|
|
01fbc8b39d | ||
|
|
91f3ea7c46 | ||
|
|
d11a89d02c | ||
|
|
b580a9f7ad | ||
|
|
c2db186620 | ||
|
|
679a0002a7 | ||
|
|
81fdcce40f | ||
|
|
8af540f9b2 | ||
|
|
79898d87c0 | ||
|
|
3c400af559 | ||
|
|
c8375b846a | ||
|
|
765f74b619 | ||
|
|
2fcb7fb59b | ||
|
|
d828ed83a5 | ||
|
|
22993283f0 | ||
|
|
69f0d3b2bc | ||
|
|
ab0a5898cb | ||
|
|
60a41cae41 | ||
|
|
6c7f7f9543 | ||
|
|
d571d6e121 | ||
|
|
b7b933c89d | ||
|
|
7e98a78333 | ||
|
|
342b9d6fc6 | ||
|
|
ef7ed58a22 | ||
|
|
160c736b38 | ||
|
|
01bb72523e | ||
|
|
ec62d7be5a | ||
|
|
f8cbd31f61 | ||
|
|
8c04a432a8 | ||
|
|
3a9001e091 | ||
|
|
a172909ddf | ||
|
|
54d695f851 | ||
|
|
0147e7f1e1 | ||
|
|
86886ded16 | ||
|
|
6c44abded9 | ||
|
|
f3fb777924 | ||
|
|
1323bba420 | ||
|
|
e55a1d8713 | ||
|
|
d951a6057d | ||
|
|
9755c59687 | ||
|
|
2918c55fa9 | ||
|
|
f01f5d7837 | ||
|
|
01313b16e1 | ||
|
|
601d037201 | ||
|
|
9bc56f5d17 | ||
|
|
8237cd21ed | ||
|
|
bb3f1f2c10 | ||
|
|
f8463823d9 | ||
|
|
08c46fd66e | ||
|
|
1939d42d09 | ||
|
|
9a0a04ed76 | ||
|
|
813a49bf67 | ||
|
|
77924ff248 | ||
|
|
9dad51fa0b | ||
|
|
8c39a81644 | ||
|
|
7749fac683 | ||
|
|
91f4b2fd9d | ||
|
|
e0a3259765 | ||
|
|
38c1a768fc | ||
|
|
3c71af064c | ||
|
|
7841f54db8 | ||
|
|
936dee9da5 | ||
|
|
a10d5ee43b | ||
|
|
a0b8529606 | ||
|
|
6bf1eb5bde | ||
|
|
fb1b5969f5 | ||
|
|
8eca06fd5f | ||
|
|
3c8642f30f | ||
|
|
3796a3dbde | ||
|
|
637104643c | ||
|
|
9364cfe1cf | ||
|
|
4ae66ee3aa | ||
|
|
a6534518f8 | ||
|
|
e327311477 | ||
|
|
78c766c52e | ||
|
|
3c89ecb2c7 | ||
|
|
948972184e | ||
|
|
8874a3fec7 | ||
|
|
dde28136e1 | ||
|
|
ce03296078 | ||
|
|
457efa1c79 | ||
|
|
cfd1fc275b | ||
|
|
9d56dd122f | ||
|
|
b1e35e6824 | ||
|
|
775fdec0b8 | ||
|
|
f403014ef6 | ||
|
|
5982424864 | ||
|
|
2ad5ccabec | ||
|
|
9ffc3520e7 | ||
|
|
7ba28a9770 | ||
|
|
bec72dfd5f | ||
|
|
cf6b4f5432 | ||
|
|
fe82c44687 | ||
|
|
49e4f15bd1 | ||
|
|
7dd85b2ba6 | ||
|
|
7b64793449 | ||
|
|
5485f2013e | ||
|
|
6ed24ec310 | ||
|
|
e39335864d | ||
|
|
27ec2c276a | ||
|
|
b2f8da500b | ||
|
|
80a21dbf4a | ||
|
|
33bd39859d | ||
|
|
7367a769b1 | ||
|
|
1ca4c67f7e | ||
|
|
120a67159d | ||
|
|
aa808587df | ||
|
|
57df991dd3 | ||
|
|
665184bfa2 | ||
|
|
ceee696443 | ||
|
|
7fda376f19 | ||
|
|
bd60cb3a18 | ||
|
|
534ed6ae6c | ||
|
|
acac93bbd1 | ||
|
|
070e4b8527 | ||
|
|
a4e8761add | ||
|
|
590b76a884 | ||
|
|
98729cfa7b | ||
|
|
2bd077136f | ||
|
|
6fba94594b | ||
|
|
24e9ff4c86 | ||
|
|
379357ee8e | ||
|
|
ef7a64644b | ||
|
|
c163c20c3d | ||
|
|
3ddbeac419 | ||
|
|
ef7a2ec123 | ||
|
|
8dd4cfa6b2 | ||
|
|
7491a6faac | ||
|
|
eaf1a59e89 | ||
|
|
4cbc5040a2 | ||
|
|
b90b129d98 | ||
|
|
64c0ae2734 | ||
|
|
1c2a7a219a | ||
|
|
868b8e7206 | ||
|
|
6115828dfe | ||
|
|
af9b5ab014 | ||
|
|
5497bd97b2 | ||
|
|
c53db63cc7 | ||
|
|
1263603c7b | ||
|
|
d1036c3be7 | ||
|
|
5adbd6e8f1 | ||
|
|
b83ce02849 | ||
|
|
735a9c8a5d | ||
|
|
bf46881e3b | ||
|
|
f68226f4b4 | ||
|
|
07d864e48b | ||
|
|
0326864510 | ||
|
|
2a97060b96 | ||
|
|
6cffd81540 | ||
|
|
eb2b11ed3e | ||
|
|
7cb52bebf5 | ||
|
|
3731f5d32c | ||
|
|
7161b14631 | ||
|
|
2e7ff88be6 | ||
|
|
6368ee5c12 | ||
|
|
42106e907d | ||
|
|
3f71162e9d | ||
|
|
008d8de24e | ||
|
|
e3000dcc7c | ||
|
|
f98cf4cbfc | ||
|
|
807f885f92 | ||
|
|
15ecd8d592 | ||
|
|
588a6f1bf4 | ||
|
|
f633bc81cf | ||
|
|
3006dc6584 | ||
|
|
a7c63e22e5 | ||
|
|
4304158088 | ||
|
|
0f446a1f77 | ||
|
|
71639a91b9 | ||
|
|
c1fb67f143 | ||
|
|
b0395a6ed2 | ||
|
|
7d0e6ac50b | ||
|
|
c6ea524821 | ||
|
|
7c82769448 | ||
|
|
a4b69c3911 | ||
|
|
e301abe9e3 | ||
|
|
d38ccba618 | ||
|
|
849ac56296 | ||
|
|
67bb821a0e | ||
|
|
43c51d48d9 | ||
|
|
48b7f4924e | ||
|
|
30c149be31 | ||
|
|
42d9271ea0 | ||
|
|
00e666e423 | ||
|
|
6b29b686c7 | ||
|
|
db99a21514 | ||
|
|
6fb1c474d2 | ||
|
|
f1c7c35e48 | ||
|
|
923549bb5e | ||
|
|
0aa18ded72 | ||
|
|
ee08d8d740 | ||
|
|
0c23fa7c9d | ||
|
|
fe91765ab0 | ||
|
|
8541e180cc | ||
|
|
a3d950e2a3 | ||
|
|
e1c80636ba | ||
|
|
4fb971a935 | ||
|
|
117d0fbcef | ||
|
|
389cafc240 | ||
|
|
4591b59465 | ||
|
|
d4a082382d | ||
|
|
885437de8d | ||
|
|
39cd9f4a44 | ||
|
|
01a2244fed | ||
|
|
e0e3dd128e | ||
|
|
0f6408d7f6 | ||
|
|
b610dc4969 | ||
|
|
bc6fa7fd27 | ||
|
|
0e34cc49df | ||
|
|
629881d16b | ||
|
|
92569f5b3a | ||
|
|
4ff32511b9 | ||
|
|
5afed4b85e | ||
|
|
2aa687a40b | ||
|
|
57567ebb1b | ||
|
|
6448169caa | ||
|
|
e5901f28e3 | ||
|
|
2d3f0dd95a | ||
|
|
2e4e827887 | ||
|
|
22704b32d6 | ||
|
|
2839e595cc | ||
|
|
a9f28dfb47 | ||
|
|
c1bdd079cf | ||
|
|
467d05a358 | ||
|
|
05c8a6e664 | ||
|
|
59b2ecebda | ||
|
|
fe8cf38a7f | ||
|
|
e754ceace8 | ||
|
|
41ceb9bd82 | ||
|
|
b0b7c63561 | ||
|
|
c13f65611f | ||
|
|
b9feeabfb3 | ||
|
|
8e9ba7a2d2 | ||
|
|
b3e9b1c2be | ||
|
|
e969b5b7e4 | ||
|
|
8e0a684a9a | ||
|
|
152aa0f92b | ||
|
|
97fb0ba95a | ||
|
|
01355b9a28 | ||
|
|
772922cefb | ||
|
|
95a3aa7290 | ||
|
|
6f8a4af5eb | ||
|
|
b332c4e23d | ||
|
|
47c0c3fa3f | ||
|
|
71cb8612d8 | ||
|
|
af12aecd36 | ||
|
|
fec116f131 | ||
|
|
d1fdec0970 | ||
|
|
77b6c53a42 | ||
|
|
f2eaac50a6 | ||
|
|
ec8b0ada14 | ||
|
|
1e28be9671 | ||
|
|
8a0e813734 | ||
|
|
2975d550eb | ||
|
|
248a9c4070 | ||
|
|
327d0d20a9 | ||
|
|
1ffd2812dc | ||
|
|
29da4d209d | ||
|
|
35a369a953 | ||
|
|
4785cae8f0 | ||
|
|
9eaa575d1a | ||
|
|
996ed78a0e | ||
|
|
1759572b2e | ||
|
|
0a9f9eea90 | ||
|
|
223fb540b1 | ||
|
|
c5f1c95f7b | ||
|
|
60086ead7c | ||
|
|
3bbcbca926 | ||
|
|
63c1deb630 | ||
|
|
424e2428fe | ||
|
|
2fdef45156 | ||
|
|
63ac99e906 | ||
|
|
4cd4550a36 | ||
|
|
16af625aef | ||
|
|
8e72794c07 | ||
|
|
d9cf6d7e1b | ||
|
|
57c4f935e2 | ||
|
|
f4e4252227 | ||
|
|
84bbcf7753 | ||
|
|
df129f0d20 | ||
|
|
a76da167eb | ||
|
|
1547b3f53b | ||
|
|
68f5b30f7d | ||
|
|
d9b9c0f42c | ||
|
|
aa0597c27e | ||
|
|
4d59cd1521 | ||
|
|
6ba4170f08 | ||
|
|
827bc97e8e | ||
|
|
7107409b1b | ||
|
|
6c086fab6f | ||
|
|
54d215b8d1 | ||
|
|
4cd47a5c77 | ||
|
|
d027d67e08 | ||
|
|
a7d9e635eb | ||
|
|
e7196efaea | ||
|
|
0a2a903c74 | ||
|
|
8104c26b19 | ||
|
|
420245c630 | ||
|
|
20cf83907d | ||
|
|
0a04d2ee83 | ||
|
|
bc05781edd | ||
|
|
ce9d91e990 | ||
|
|
973d13b705 | ||
|
|
51e4427108 | ||
|
|
d09ab541ab | ||
|
|
a9e1dc3cd5 | ||
|
|
1551e6c640 | ||
|
|
03a1bdfc7f | ||
|
|
24545aa9cb | ||
|
|
c11426419c | ||
|
|
cf7e7c1a06 | ||
|
|
ab388f1ef5 | ||
|
|
101d5c7eb0 | ||
|
|
cad253b85f | ||
|
|
f17009a988 | ||
|
|
1f76278d2b | ||
|
|
e032d29c91 | ||
|
|
31d1b0c994 | ||
|
|
611c6d415c | ||
|
|
e3b7ac00fd | ||
|
|
7c952822db | ||
|
|
b9e435c0e2 | ||
|
|
356d40e640 | ||
|
|
123ffd4e66 | ||
|
|
c952659620 | ||
|
|
ff59f31e84 | ||
|
|
011fc8cc9e | ||
|
|
7428022b2f | ||
|
|
3815eb1983 | ||
|
|
3778f10edb | ||
|
|
00a4b867ea | ||
|
|
3a1024e848 | ||
|
|
ea8e1e9c57 | ||
|
|
478d63893b | ||
|
|
3066631301 | ||
|
|
b0998d5f95 | ||
|
|
a074cb9664 | ||
|
|
43a3d1e5ac | ||
|
|
24c8a4a9d1 | ||
|
|
b5ccba552f | ||
|
|
8d2ee364ba | ||
|
|
14006068c8 | ||
|
|
b4358ffc66 | ||
|
|
33135b1df1 | ||
|
|
9a66c38e01 | ||
|
|
81132ecab0 | ||
|
|
313fc09c8d | ||
|
|
5610486a89 | ||
|
|
9f0231405a | ||
|
|
e650dd0503 | ||
|
|
6fc6cb75e4 | ||
|
|
184efc6f27 | ||
|
|
1117b57101 | ||
|
|
0928ba71f2 | ||
|
|
7170eded68 | ||
|
|
32e891d6ba | ||
|
|
5a273676fb | ||
|
|
c3f6edf7c5 | ||
|
|
e0c0cc8613 | ||
|
|
fbeb3dd81e | ||
|
|
9d89af37be | ||
|
|
fbc8a36232 | ||
|
|
ea028ea1a1 | ||
|
|
35b1c12bb5 | ||
|
|
fc89d96635 | ||
|
|
27129652f2 | ||
|
|
23ef992a7f | ||
|
|
04533e17ec | ||
|
|
7f2fcba542 | ||
|
|
c115e2f985 | ||
|
|
88642c2003 | ||
|
|
2c678b5363 | ||
|
|
bc3b72fafe | ||
|
|
df3b8cf09c | ||
|
|
65393b7809 | ||
|
|
9782c849ad | ||
|
|
e7efaed08a | ||
|
|
337b3e5b5d | ||
|
|
7b1b7d1372 | ||
|
|
e5b838a2b3 | ||
|
|
f9cc2ceb11 | ||
|
|
4f107c5618 | ||
|
|
b1c5aaff43 | ||
|
|
d0a4473e2b | ||
|
|
1c79361094 | ||
|
|
f72114c223 | ||
|
|
dbd59cd958 | ||
|
|
d65f8a3c82 | ||
|
|
96587a4e45 | ||
|
|
76ab47c82e | ||
|
|
af8344c555 | ||
|
|
10ff02b8a0 | ||
|
|
cb6cf1e34b | ||
|
|
953e924aa2 | ||
|
|
2e0c262d32 | ||
|
|
cbf2e6a140 | ||
|
|
49c5a9f621 | ||
|
|
fb8f63f305 | ||
|
|
49ff61ad65 | ||
|
|
695fb60aa4 | ||
|
|
da20fafa39 | ||
|
|
d6a9ecd912 | ||
|
|
f95d721c9c | ||
|
|
69d6417985 | ||
|
|
ab2bbc28c8 | ||
|
|
b0b39429ed | ||
|
|
ff14cbc752 | ||
|
|
dd013aaaa3 | ||
|
|
119f61ef67 | ||
|
|
a99588c766 | ||
|
|
6f35fe9936 | ||
|
|
3112425e43 | ||
|
|
1ab3aefaa5 | ||
|
|
be08732e6b | ||
|
|
97b58b5f9a | ||
|
|
e4855875cf | ||
|
|
7a267cc07b | ||
|
|
f4f351cf9d | ||
|
|
970b811ab9 | ||
|
|
99604dbe35 | ||
|
|
09b1d89718 | ||
|
|
9d1a9f3134 | ||
|
|
ccb889233c | ||
|
|
939f2cbf97 | ||
|
|
580e0cb36a | ||
|
|
4bd8835f23 | ||
|
|
22f32da0c5 | ||
|
|
aa8a094383 | ||
|
|
4a72b7f089 | ||
|
|
a33e4905cf | ||
|
|
40bd2f0742 | ||
|
|
1fd73fe79e | ||
|
|
fd7d3e06f4 | ||
|
|
7a4d27da69 | ||
|
|
afbadf7d81 | ||
|
|
7b65c64431 | ||
|
|
4f2c0e94d9 | ||
|
|
7593d7a3e9 | ||
|
|
35ddc4a472 | ||
|
|
3400d1e803 | ||
|
|
f672428fde | ||
|
|
4171d993d0 | ||
|
|
d22310ea31 | ||
|
|
1521d1e883 | ||
|
|
0412615f6e | ||
|
|
2bd666efe7 | ||
|
|
541e1f760b | ||
|
|
f176bed436 | ||
|
|
403545cd9b | ||
|
|
54e4ed27ae | ||
|
|
e5ddf5616a | ||
|
|
967c4f04d9 | ||
|
|
36d4d445a6 | ||
|
|
5ed0ae2fa9 | ||
|
|
503a719609 | ||
|
|
2dcbba63cb | ||
|
|
5e86bdfda7 | ||
|
|
4f74a0440c | ||
|
|
858709f610 | ||
|
|
6689d48fad | ||
|
|
8581a7c308 | ||
|
|
9878efb198 | ||
|
|
9855c50367 | ||
|
|
04c59041e0 | ||
|
|
7fb1ecc9b0 | ||
|
|
ecd2cdd28e | ||
|
|
4b0ad22f8d | ||
|
|
789558da6c | ||
|
|
3f96117a57 | ||
|
|
282d6b5746 | ||
|
|
86d2bb9e2a | ||
|
|
ab4fbf0437 | ||
|
|
ae527a78ef | ||
|
|
8218868681 | ||
|
|
251f4ca4ac | ||
|
|
5d5c8ced24 | ||
|
|
f0ec9b7826 | ||
|
|
90b8e785a4 | ||
|
|
cb11a71ff5 | ||
|
|
54dd90fc4a | ||
|
|
4bb080bd58 | ||
|
|
5115a048a3 | ||
|
|
259351cdd2 | ||
|
|
972b8f83bc | ||
|
|
9f6b1c1e25 | ||
|
|
6229a103aa | ||
|
|
84116e531b | ||
|
|
2c1b944b7c | ||
|
|
0e66ca148d | ||
|
|
ce70c1ca3a | ||
|
|
56b91a59a2 | ||
|
|
8f1fbcb19e | ||
|
|
a4799a1bc0 | ||
|
|
cc24ff22e9 | ||
|
|
72ca335c4c | ||
|
|
0ef6476e58 | ||
|
|
6ba63d1466 | ||
|
|
f9d28e1b6b | ||
|
|
63534d3eb5 | ||
|
|
2ab2cf01db | ||
|
|
2fc039dd70 | ||
|
|
230d1a1f86 | ||
|
|
f7921bf388 | ||
|
|
feed984ba8 | ||
|
|
5324861f16 | ||
|
|
ae80939d2e | ||
|
|
c117e4b087 | ||
|
|
96580e2284 | ||
|
|
bc3932c8b8 | ||
|
|
f7cd4f34d3 | ||
|
|
3a76f51707 | ||
|
|
041ba8f2ed | ||
|
|
17e5d15b1b | ||
|
|
a6ba59eac0 | ||
|
|
e89ff02b59 | ||
|
|
b28be29dc8 | ||
|
|
9ad85e01de | ||
|
|
d49790ba78 | ||
|
|
598f01de95 | ||
|
|
992e137339 | ||
|
|
124666cca6 | ||
|
|
848359bf7b | ||
|
|
fb24af1900 | ||
|
|
3d6df3cc09 | ||
|
|
ea58ed46f2 | ||
|
|
32817a4275 | ||
|
|
95033cd5b7 | ||
|
|
9999548bc2 | ||
|
|
3d04bd4444 | ||
|
|
21e618cca2 | ||
|
|
02eb386155 | ||
|
|
35bd038802 | ||
|
|
1ff59aee56 | ||
|
|
d64fae1832 | ||
|
|
ae169810d0 | ||
|
|
3c7619098a | ||
|
|
a881bfd63b | ||
|
|
09d3131d46 | ||
|
|
53550b8975 | ||
|
|
569164ed56 | ||
|
|
f61d79d53e | ||
|
|
bba28d6b57 | ||
|
|
469f30044a | ||
|
|
0993d87799 | ||
|
|
09fa33236f | ||
|
|
4e9abd6512 | ||
|
|
2996c0b38e | ||
|
|
36e366abe0 | ||
|
|
f9c6c6c127 | ||
|
|
34772ef2bf | ||
|
|
305af935a7 | ||
|
|
33f3c9acbf | ||
|
|
9e560c79ae | ||
|
|
705d7f3da0 | ||
|
|
08372facd7 | ||
|
|
886baa427b | ||
|
|
8ff94bc138 | ||
|
|
d0438390cc | ||
|
|
d22266a947 | ||
|
|
e34fb25759 | ||
|
|
675955b2e6 | ||
|
|
4516bce0ee | ||
|
|
4853fbcec3 | ||
|
|
72e5f9a83e | ||
|
|
1a1ddc34a2 | ||
|
|
40ebfb676c | ||
|
|
3bb7e958dc | ||
|
|
19dd16fcf0 | ||
|
|
f596749645 | ||
|
|
482813ea88 | ||
|
|
60e47e85a3 | ||
|
|
22c06fee5e | ||
|
|
4a42ce87a2 | ||
|
|
a98e4dbcd4 | ||
|
|
7baa37ccd1 | ||
|
|
417017add9 | ||
|
|
b5e3cc2503 | ||
|
|
2e11fe2b58 | ||
|
|
70f1258bab | ||
|
|
d91fa33330 | ||
|
|
8f0b5dc049 | ||
|
|
7f14a82053 | ||
|
|
b05db6d458 | ||
|
|
ea87092d73 | ||
|
|
f468f7ae27 | ||
|
|
f12c79d292 | ||
|
|
6e76514a24 | ||
|
|
88f3d3821e | ||
|
|
bed2e20a91 | ||
|
|
c7700bdfef | ||
|
|
778f6367f9 | ||
|
|
6115eea401 | ||
|
|
ff62ef729a | ||
|
|
d0a432164d | ||
|
|
0f8d9e64ef | ||
|
|
d1f5096a16 | ||
|
|
003de399dc | ||
|
|
36989ce5ff | ||
|
|
fa987b4e30 | ||
|
|
b47c494240 | ||
|
|
1ab8a19f8e | ||
|
|
a31134195b | ||
|
|
5580a4ee3d | ||
|
|
870276fa48 | ||
|
|
608660a101 | ||
|
|
25cb8015d0 | ||
|
|
e686e8f58c | ||
|
|
14b699b5b1 | ||
|
|
ce0c86c8e5 | ||
|
|
11d1f26724 | ||
|
|
a8c0978f0d | ||
|
|
f643911014 | ||
|
|
0a0227cca4 | ||
|
|
8bacc3b6ba | ||
|
|
d516330a41 | ||
|
|
2d83faf144 | ||
|
|
c3fd6bf88f | ||
|
|
2a646becfd | ||
|
|
a825657516 | ||
|
|
b71dbe9832 | ||
|
|
f06b4040bc | ||
|
|
563cd4b843 | ||
|
|
834dc9bec9 | ||
|
|
e6e58a03a6 | ||
|
|
d9f4adbe26 | ||
|
|
1777153411 | ||
|
|
9b0ca581f1 | ||
|
|
493cf7d46a | ||
|
|
004bf36dc1 | ||
|
|
fcf7fb4b9f | ||
|
|
130a85a5fc | ||
|
|
d46eec568c | ||
|
|
7dc6d0ffb2 | ||
|
|
6529e45868 | ||
|
|
3ba6ea9c7e | ||
|
|
b6ff6f5453 | ||
|
|
cfa529b742 | ||
|
|
2eabc76c1a | ||
|
|
463e67d64c | ||
|
|
1f96af1024 | ||
|
|
563aa92958 | ||
|
|
f09b864e30 | ||
|
|
7ce15d5a71 | ||
|
|
cc767b164e | ||
|
|
230ff75c0b | ||
|
|
da39739fb5 | ||
|
|
c33be22057 | ||
|
|
902f35d21b | ||
|
|
d8426780d2 | ||
|
|
a1aaf90d2e | ||
|
|
53ed4d4072 | ||
|
|
da75554447 | ||
|
|
04998d6a60 | ||
|
|
3d88a28465 | ||
|
|
2dd8f75d52 | ||
|
|
7a4bdd0ada | ||
|
|
0ed0b5ee43 | ||
|
|
2475ee90ee | ||
|
|
511c19d5aa | ||
|
|
c4fed86f1e | ||
|
|
18904ebbaa | ||
|
|
da3672e6be | ||
|
|
52e9836bbf | ||
|
|
3201d90a53 | ||
|
|
cd9c1d9660 | ||
|
|
0f54a1f638 | ||
|
|
713316f87c | ||
|
|
88d38ba8d1 | ||
|
|
164f3275f4 | ||
|
|
a44d6b8b79 | ||
|
|
942cf57c36 | ||
|
|
18bc75242b | ||
|
|
3b38c8b408 | ||
|
|
22e718423e | ||
|
|
0dc0e7226e | ||
|
|
8069784198 | ||
|
|
a9e71567fe | ||
|
|
0fae151731 | ||
|
|
3bb0b55955 | ||
|
|
071ca80bae | ||
|
|
1abd78305f | ||
|
|
e1ef9a94af | ||
|
|
9f62023175 | ||
|
|
c9a6d2f5a8 | ||
|
|
07fca8b895 | ||
|
|
1a65c4a579 | ||
|
|
7a28a8950c | ||
|
|
89e8b0d8b9 | ||
|
|
74803df5bd | ||
|
|
2a8e030fb4 | ||
|
|
c6ebe994cc | ||
|
|
be3677cfa8 | ||
|
|
0c2e56271b | ||
|
|
b019d40009 | ||
|
|
361484be95 | ||
|
|
f0ce6cc28f | ||
|
|
ede298a142 | ||
|
|
aca1a4d34c | ||
|
|
e63c5c074c | ||
|
|
0d0b5dd552 | ||
|
|
08704e7f60 | ||
|
|
d48ed18102 | ||
|
|
3602acd187 | ||
|
|
ad477eb608 | ||
|
|
0a393845e9 | ||
|
|
59b98209ac | ||
|
|
ac26713f86 | ||
|
|
9250ef6f65 | ||
|
|
cabbeb07d0 | ||
|
|
43171645c0 | ||
|
|
79c1484288 | ||
|
|
e730875db3 | ||
|
|
18e9fb99b5 | ||
|
|
fd82b9b555 | ||
|
|
5c3bf0067e | ||
|
|
ac6b90c986 | ||
|
|
7dcd8b29fc | ||
|
|
d951da2c02 | ||
|
|
605aee35c4 | ||
|
|
dc1f1985e8 | ||
|
|
cebcc26baf | ||
|
|
75a12fc6f5 | ||
|
|
a3e0d89eb0 | ||
|
|
9594cc674f | ||
|
|
87cdee4fe8 | ||
|
|
1df55b78c2 | ||
|
|
f7820a23be | ||
|
|
f80b9cdca5 | ||
|
|
f2180b22c7 | ||
|
|
a745088213 | ||
|
|
14be7bd2b1 | ||
|
|
d453ea66da | ||
|
|
e7ae86e261 | ||
|
|
b7417f41c5 | ||
|
|
9e7aa381ed | ||
|
|
b02453e9f4 | ||
|
|
6b7f8fd31c | ||
|
|
4652242d6b | ||
|
|
a47ca2f357 | ||
|
|
e5a1e58159 | ||
|
|
01a1e34e99 | ||
|
|
e711220a66 | ||
|
|
1d20f529a0 | ||
|
|
79a94d25bd | ||
|
|
92793b8ff8 | ||
|
|
982024f359 | ||
|
|
e851bd4d61 | ||
|
|
e4b9383e96 | ||
|
|
95ac9aac14 | ||
|
|
b895eec69c | ||
|
|
1fe4e80f82 | ||
|
|
eb1f5f2632 | ||
|
|
114b792300 | ||
|
|
56a352a9d2 | ||
|
|
4c4d60dd83 | ||
|
|
134ab0fe98 | ||
|
|
bbd394272f | ||
|
|
8a2571f514 | ||
|
|
bf15be8144 | ||
|
|
bf6f8de7fa | ||
|
|
2e75e6ffb6 | ||
|
|
b987be54bf | ||
|
|
533e8a3742 | ||
|
|
9d007e64f6 | ||
|
|
8ec1578f50 | ||
|
|
4e8cc36d3a | ||
|
|
5a64cb2323 | ||
|
|
b2aba82a1b | ||
|
|
9d9500ba1b | ||
|
|
674c5a11c1 | ||
|
|
8e2b2947ae | ||
|
|
0413ca7cba | ||
|
|
15cf7800a4 | ||
|
|
db6114a4ee | ||
|
|
818495a697 | ||
|
|
7aa41c4050 | ||
|
|
1975b8af1d | ||
|
|
b870f5f4d1 | ||
|
|
3ebc720934 | ||
|
|
748976f393 | ||
|
|
83e5c7fadb | ||
|
|
f919a34166 | ||
|
|
77a9eca634 | ||
|
|
72b732a55d | ||
|
|
0236897d1f | ||
|
|
46f95e5e13 | ||
|
|
24287c0857 | ||
|
|
c930e4dd92 | ||
|
|
7d8856e4bc | ||
|
|
6d026bbf42 | ||
|
|
713770a448 | ||
|
|
ac873fa757 | ||
|
|
6b3513c1c4 | ||
|
|
840374c48c | ||
|
|
7f454f279c | ||
|
|
267c09f20c | ||
|
|
e44e77a3a6 | ||
|
|
4d9dd13ffb | ||
|
|
369e75cb7e | ||
|
|
0a6fa2431e | ||
|
|
0bc40d1748 | ||
|
|
919dc3cdea | ||
|
|
7c64b27ef4 | ||
|
|
369577a2c8 | ||
|
|
fbc25e5134 | ||
|
|
aba1628d36 | ||
|
|
90336e1edf | ||
|
|
dde32fcaee | ||
|
|
6324e79aba | ||
|
|
7a22f4b20f | ||
|
|
aad621bd84 | ||
|
|
0b4e1f3dee | ||
|
|
78f88db560 | ||
|
|
cc1c425ecf | ||
|
|
186290e355 | ||
|
|
f1f1d784ff | ||
|
|
320f183b49 | ||
|
|
a1c7efeb85 | ||
|
|
ba5f635687 | ||
|
|
bae5afc0da | ||
|
|
befd5c3b08 | ||
|
|
0b4e96a90f | ||
|
|
57edf38c1a | ||
|
|
502dd1ec1f | ||
|
|
02361ddfb2 | ||
|
|
bcf6cc1019 | ||
|
|
b97a2e7cf3 | ||
|
|
27158e1ee7 | ||
|
|
28abad0276 | ||
|
|
b59549ebe9 | ||
|
|
b9f788fbe8 | ||
|
|
9d89334cc5 | ||
|
|
6c67ff3fe8 | ||
|
|
6ef59f703a | ||
|
|
c8f11578d6 | ||
|
|
bbd055c798 | ||
|
|
53879fcefb | ||
|
|
f33be18be3 | ||
|
|
513c7dbd85 | ||
|
|
02a9623310 | ||
|
|
fd6e7f3096 | ||
|
|
58c1d52f52 | ||
|
|
b71c881b43 | ||
|
|
4d8cf41b7a | ||
|
|
2b0467e00f | ||
|
|
cef6646f50 | ||
|
|
ffc2c7dea3 | ||
|
|
d7652a7a32 | ||
|
|
b695f90ded | ||
|
|
d7f1246c32 | ||
|
|
739d1f2455 | ||
|
|
be7c6e700b | ||
|
|
320dd49a87 | ||
|
|
9bfa680fa4 | ||
|
|
ed32f9994d | ||
|
|
c6eb850abe | ||
|
|
6c458b81b2 | ||
|
|
6741f59aef | ||
|
|
6a0fd46fc4 | ||
|
|
b2c8beae71 | ||
|
|
b376327438 | ||
|
|
9eb4fecbe8 | ||
|
|
f672f4d1bb | ||
|
|
c8b085e963 | ||
|
|
8cd3daee9d | ||
|
|
0b03aec038 | ||
|
|
b04fd1a937 | ||
|
|
bdf4222d70 | ||
|
|
261f7ebbc2 | ||
|
|
91f25e9ec3 | ||
|
|
dd86bb88c6 | ||
|
|
0665f2de5f | ||
|
|
364e5df974 | ||
|
|
d3cdaccbc5 | ||
|
|
75dbc990e4 | ||
|
|
5bdd6e15e4 | ||
|
|
f7b5a2e971 | ||
|
|
a486eefd81 | ||
|
|
1fae364e7d | ||
|
|
76db0c41d3 | ||
|
|
7318b8917d | ||
|
|
fefcd682b8 | ||
|
|
a32c0b028b | ||
|
|
ffe62b8f8e | ||
|
|
5076374b5d | ||
|
|
d368e24f75 | ||
|
|
343c5eb587 | ||
|
|
f7fc379e56 | ||
|
|
ccf4c4bbb3 | ||
|
|
a32995abec | ||
|
|
abd87f3584 | ||
|
|
bbf7277abc | ||
|
|
b38b335fa1 | ||
|
|
cf41e71494 | ||
|
|
e75408d20d | ||
|
|
d79f3c6a80 | ||
|
|
1a5e196e4e | ||
|
|
138bfc8362 | ||
|
|
464795779f | ||
|
|
e65c80962d | ||
|
|
7171d1d6b2 | ||
|
|
162c6e95d3 | ||
|
|
63743656d4 | ||
|
|
a3a9032af7 | ||
|
|
194ef2b4ca | ||
|
|
89b8342ca0 | ||
|
|
ba32df2fb8 | ||
|
|
08234afe4f | ||
|
|
41eb28992e | ||
|
|
20a6da4944 | ||
|
|
544496a09b | ||
|
|
26e7d562aa | ||
|
|
d1814a4e0f | ||
|
|
a11adad23f | ||
|
|
834c8cc7d5 | ||
|
|
1801ea7873 | ||
|
|
b208634e40 | ||
|
|
7b3d071fd3 | ||
|
|
663a8bb06d | ||
|
|
6c34f083e3 | ||
|
|
6d9237c399 | ||
|
|
8a7186c1b1 | ||
|
|
c120686fae | ||
|
|
75c83e4117 | ||
|
|
d16b846d4e | ||
|
|
fe44c35406 | ||
|
|
376bcc4a0b | ||
|
|
26d9e63e83 | ||
|
|
a7ac2cee13 | ||
|
|
7a850704e5 | ||
|
|
d4cc561d90 | ||
|
|
1430935c7e | ||
|
|
497289fe69 | ||
|
|
f7d75b830c | ||
|
|
a20222747d | ||
|
|
75ac1e9770 | ||
|
|
64d606013d | ||
|
|
40e8e47398 | ||
|
|
6faf7e71ba | ||
|
|
b20abb824d | ||
|
|
fa8c209ea3 | ||
|
|
5181010d22 | ||
|
|
5c2a82b7ee | ||
|
|
f69de50ed1 | ||
|
|
56e142ac05 | ||
|
|
32c2d60f7e | ||
|
|
cb792cbb26 | ||
|
|
c36b10a416 | ||
|
|
f1d87cfbb2 | ||
|
|
cde6530832 | ||
|
|
a5b1fec6b1 | ||
|
|
7cf76d91a0 | ||
|
|
28dc314397 | ||
|
|
66b5e5dd3f | ||
|
|
678af5f744 | ||
|
|
85fcc28662 | ||
|
|
f6035de548 | ||
|
|
0ea82b9f98 | ||
|
|
3260e5127a | ||
|
|
f260d83923 | ||
|
|
cece720f68 | ||
|
|
03c0449a0b | ||
|
|
4ded7987e2 | ||
|
|
49bbf826b9 | ||
|
|
42fc52a067 | ||
|
|
72e97d57b2 | ||
|
|
dbd99e1f57 | ||
|
|
8e68824ac0 | ||
|
|
5fe79c4bad | ||
|
|
0fe89c34b3 | ||
|
|
4a451ad5ae | ||
|
|
27984ceef2 | ||
|
|
8987a2d177 | ||
|
|
915b6a918c | ||
|
|
40bf82552b | ||
|
|
211d17b609 | ||
|
|
6290bbdd4e | ||
|
|
aa9a3238a2 | ||
|
|
a303a60c4e | ||
|
|
7e67fa3076 | ||
|
|
7ef966536d | ||
|
|
4c3827158f | ||
|
|
6c577daf5d | ||
|
|
9e54c689a1 |
12
.env.docker
12
.env.docker
@@ -1,13 +1,16 @@
|
||||
APP_NAME=Dootask
|
||||
TIMEZONE=PRC
|
||||
|
||||
APP_NAME=DooTask
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_DEBUG=false
|
||||
APP_SCHEME=auto
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_ID=
|
||||
APP_IPPR=
|
||||
APP_PORT=2222
|
||||
APP_DEV_PORT=
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_LEVEL=debug
|
||||
@@ -53,10 +56,11 @@ PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
JUKE_KEY_JOKE=
|
||||
JUKE_KEY_SOUP=
|
||||
|
||||
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
LARAVELS_LISTEN_IP=0.0.0.0
|
||||
LARAVELS_LISTEN_PORT=20000
|
||||
|
||||
DOCKER_ID=
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
TIMEZONE=PRC
|
||||
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
|
||||
33
.github/workflows/publish-desktop.yml
vendored
Normal file
33
.github/workflows/publish-desktop.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Publish Desktop
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
APPLEID: ${{ secrets.APPLEID }}
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
DP_KEY: ${{ secrets.DP_KEY }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GH_REPOSITORY: ${{ github.repository }}
|
||||
run: ./cmd electron all
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,6 +5,7 @@
|
||||
/public/.well-known
|
||||
/public/.user.ini
|
||||
/storage/*.key
|
||||
/config/LICENSE
|
||||
/vendor
|
||||
/build
|
||||
/tmp
|
||||
@@ -19,7 +20,6 @@ Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
test.*
|
||||
composer.lock
|
||||
package-lock.json
|
||||
laravels-timer-process.pid
|
||||
.DS_Store
|
||||
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "resources/drawio"]
|
||||
path = resources/drawio
|
||||
url = https://github.com/jgraph/drawio.git
|
||||
[submodule "resources/mobile"]
|
||||
path = resources/mobile
|
||||
url = https://github.com/kuaifan/dootask-app.git
|
||||
13
.gitpod.yml
Normal file
13
.gitpod.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
# This configuration file was automatically generated by Gitpod.
|
||||
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
|
||||
# and commit this file to your remote git repository to share the goodness with others.
|
||||
|
||||
tasks:
|
||||
- init: sudo ./cmd install
|
||||
command: ./cmd dev
|
||||
|
||||
ports:
|
||||
- port: 2222
|
||||
visibility: public
|
||||
- port: 22222
|
||||
visibility: public
|
||||
2564
CHANGELOG.md
Normal file
2564
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
106
README.md
106
README.md
@@ -5,41 +5,54 @@ English | **[中文文档](./README_CN.md)**
|
||||
- [Screenshot Preview](README_PREVIEW.md)
|
||||
- [Demo site](http://www.dootask.com/)
|
||||
|
||||
**QQ Group**
|
||||
|
||||
Group No.: `546574618`
|
||||
|
||||
## Setup
|
||||
|
||||
> `Docker` & `Docker Compose` must be installed
|
||||
- `Docker v20.10+` & `Docker Compose v2.0+` must be installed
|
||||
- System: `Centos/Debian/Ubuntu/macOS`
|
||||
- Hardware suggestion: 2 cores and above 4G memory
|
||||
|
||||
|
||||
### Deployment project
|
||||
### Deployment (Pro Edition)
|
||||
|
||||
```bash
|
||||
# 1、Clone the repository
|
||||
|
||||
# Clone projects on github
|
||||
git clone https://github.com/kuaifan/dootask.git
|
||||
# or you can use gitee
|
||||
git clone https://gitee.com/aipaw/dootask.git
|
||||
git clone -b pro --depth=1 https://github.com/kuaifan/dootask.git
|
||||
# Or you can use gitee
|
||||
git clone -b pro --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、enter directory
|
||||
# 2、Enter directory
|
||||
cd dootask
|
||||
|
||||
# 3、Build project
|
||||
# 3、Installation(Custom port installation: ./cmd install --port 2222)
|
||||
./cmd install
|
||||
```
|
||||
Installed, project url: **`http://IP:PORT`**(`PORT`Default is`2222`)。
|
||||
|
||||
### Default Account
|
||||
### Reset password
|
||||
|
||||
```text
|
||||
account: admin@dootask.com
|
||||
password: 123456
|
||||
```bash
|
||||
# Reset default account password
|
||||
./cmd repassword
|
||||
```
|
||||
|
||||
### Change port
|
||||
|
||||
```bash
|
||||
./cmd php bin/run --port=2222
|
||||
./cmd up -d
|
||||
./cmd port 2222
|
||||
```
|
||||
|
||||
### Change App Url
|
||||
|
||||
```bash
|
||||
# This URL only affects the email reply.
|
||||
./cmd url {Your domain url}
|
||||
|
||||
# example:
|
||||
./cmd url https://domain.com
|
||||
```
|
||||
|
||||
### Stop server
|
||||
@@ -51,25 +64,40 @@ password: 123456
|
||||
./cmd start
|
||||
```
|
||||
|
||||
### Development compilation
|
||||
|
||||
```bash
|
||||
# Development mode, Mac OS only
|
||||
./cmd dev
|
||||
|
||||
# Production projects, macOS only
|
||||
./cmd prod
|
||||
```
|
||||
|
||||
### Shortcuts for running command
|
||||
|
||||
```bash
|
||||
# You can do this using the following command
|
||||
./cmd artisan "your command" // To run a artisan command
|
||||
./cmd php "your command" // To run a php command
|
||||
./cmd composer "your command" // To run a composer command
|
||||
./cmd supervisorctl "your command" // To run a supervisorctl command
|
||||
./cmd test "your command" // To run a phpunit command
|
||||
./cmd mysql "your command" // To run a mysql command (backup: Backup database, recovery: Restore database)
|
||||
./cmd artisan "your command" # To run a artisan command
|
||||
./cmd php "your command" # To run a php command
|
||||
./cmd nginx "your command" # To run a nginx command
|
||||
./cmd redis "your command" # To run a redis command
|
||||
./cmd composer "your command" # To run a composer command
|
||||
./cmd supervisorctl "your command" # To run a supervisorctl command
|
||||
./cmd test "your command" # To run a phpunit command
|
||||
./cmd mysql "your command" # To run a mysql command (backup: Backup database, recovery: Restore database)
|
||||
```
|
||||
|
||||
### NGINX OPEN HTTPS
|
||||
```
|
||||
// .env add
|
||||
APP_SCHEME=1
|
||||
### NGINX PROXY SSL
|
||||
|
||||
// nginx add
|
||||
```bash
|
||||
# 1、Nginx config add
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# 2、Running commands in a project
|
||||
./cmd https
|
||||
```
|
||||
|
||||
## Upgrade
|
||||
@@ -77,7 +105,7 @@ proxy_set_header X-Forwarded-Proto $scheme;
|
||||
**Note: Please back up your data before upgrading!**
|
||||
|
||||
```bash
|
||||
# Method 1: Enter directory and run command
|
||||
# Method 1: Running commands in a project
|
||||
./cmd update
|
||||
|
||||
# Or method 2: use this method if method 1 fails
|
||||
@@ -88,9 +116,31 @@ git pull
|
||||
./cmd mysql recovery
|
||||
```
|
||||
|
||||
* Please try again if the upgrade fails across a large version.
|
||||
* If 502 after the upgrade please run `./cmd restart` restart the service.
|
||||
|
||||
## Transfer
|
||||
|
||||
Follow these steps to complete the project migration after the new project is installed:
|
||||
|
||||
1. Backup original database
|
||||
|
||||
```bash
|
||||
# Run command under old project
|
||||
./cmd mysql backup
|
||||
```
|
||||
|
||||
2. Copy `database backup file` and `public/uploads` directory to the new project.
|
||||
|
||||
3. Restore database to new project
|
||||
```bash
|
||||
# Run command under new project
|
||||
./cmd mysql recovery
|
||||
```
|
||||
|
||||
## Uninstall
|
||||
|
||||
```bash
|
||||
# Enter directory and run command
|
||||
# Running commands in a project
|
||||
./cmd uninstall
|
||||
```
|
||||
|
||||
21
README_CLIENT.md
Normal file
21
README_CLIENT.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# 客户端说明
|
||||
|
||||
## 1、App客户端
|
||||
|
||||
#### 1.1、说明
|
||||
目录 `resources/mobile`,使用`eeui.app`框架,遵从eeui的开发文档进行打包开发app
|
||||
|
||||
#### 1.2、编译App
|
||||
1. 在项目目录执行 `./cmd appbuild` 编译
|
||||
2. 进入 `resources/mobile` eeui框架内打包Android或iOS应用
|
||||
|
||||
|
||||
## 2、PC/Mac客户端
|
||||
|
||||
#### 2.1、说明
|
||||
目录 `electron`,使用`electron`框架,遵从electron的开发文档进行打包客户端
|
||||
|
||||
#### 2.2、编译客户端
|
||||
在项目目录执行 `./cmd electron` 根据提示编译
|
||||
|
||||
|
||||
103
README_CN.md
103
README_CN.md
@@ -5,41 +5,54 @@
|
||||
- [截图预览](README_PREVIEW.md)
|
||||
- [演示站点](http://www.dootask.com/)
|
||||
|
||||
**QQ交流群**
|
||||
|
||||
- QQ群号: `546574618`
|
||||
|
||||
## 安装程序
|
||||
|
||||
> 必须安装 `Docker` 和 `Docker Compose`
|
||||
- 必须安装:`Docker v20.10+` 和 `Docker Compose v2.0+`
|
||||
- 支持环境:`Centos/Debian/Ubuntu/macOS`
|
||||
- 硬件建议:2核4G以上
|
||||
|
||||
|
||||
### 部署项目
|
||||
### 部署项目(Pro版)
|
||||
|
||||
```bash
|
||||
# 1、克隆项目到您的本地或服务器
|
||||
|
||||
# 通过github克隆项目
|
||||
git clone https://github.com/kuaifan/dootask.git
|
||||
git clone -b pro --depth=1 https://github.com/kuaifan/dootask.git
|
||||
# 或者你也可以使用gitee
|
||||
git clone https://gitee.com/aipaw/dootask.git
|
||||
git clone -b pro --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、进入目录
|
||||
cd dootask
|
||||
|
||||
# 3、一键构建项目
|
||||
# 3、一键安装项目(自定义端口安装 ./cmd install --port 2222)
|
||||
./cmd install
|
||||
```
|
||||
安装完毕,项目地址为:**`http://IP:PORT`**(`PORT`默认为`2222`)。
|
||||
|
||||
### 默认账号
|
||||
### 重置密码
|
||||
|
||||
```text
|
||||
account: admin@dootask.com
|
||||
password: 123456
|
||||
```bash
|
||||
# 重置默认管理员密码
|
||||
./cmd repassword
|
||||
```
|
||||
|
||||
### 更换端口
|
||||
|
||||
```bash
|
||||
./cmd php bin/run --port=2222
|
||||
./cmd up -d
|
||||
./cmd port 2222
|
||||
```
|
||||
|
||||
### 更换URL
|
||||
|
||||
```bash
|
||||
# 此地址仅影响邮件回复功能
|
||||
./cmd url {域名地址}
|
||||
|
||||
# 例如:
|
||||
./cmd url https://domain.com
|
||||
```
|
||||
|
||||
### 停止服务
|
||||
@@ -51,25 +64,41 @@ password: 123456
|
||||
./cmd start
|
||||
```
|
||||
|
||||
### 开发编译
|
||||
|
||||
```bash
|
||||
# 开发模式,仅限macOS
|
||||
./cmd dev
|
||||
|
||||
# 编译项目,仅限macOS
|
||||
./cmd prod
|
||||
```
|
||||
|
||||
|
||||
### 运行命令的快捷方式
|
||||
|
||||
```bash
|
||||
# 你可以使用以下命令来执行
|
||||
./cmd artisan "your command" // 运行 artisan 命令
|
||||
./cmd php "your command" // 运行 php 命令
|
||||
./cmd composer "your command" // 运行 composer 命令
|
||||
./cmd supervisorctl "your command" // 运行 supervisorctl 命令
|
||||
./cmd test "your command" // 运行 phpunit 命令
|
||||
./cmd mysql "your command" // 运行 mysql 命令 (backup: 备份数据库,recovery: 还原数据库)
|
||||
./cmd artisan "your command" # 运行 artisan 命令
|
||||
./cmd php "your command" # 运行 php 命令
|
||||
./cmd nginx "your command" # 运行 nginx 命令
|
||||
./cmd redis "your command" # 运行 redis 命令
|
||||
./cmd composer "your command" # 运行 composer 命令
|
||||
./cmd supervisorctl "your command" # 运行 supervisorctl 命令
|
||||
./cmd test "your command" # 运行 phpunit 命令
|
||||
./cmd mysql "your command" # 运行 mysql 命令 (backup: 备份数据库,recovery: 还原数据库)
|
||||
```
|
||||
|
||||
### 代理开启 HTTPS
|
||||
```
|
||||
// .env 文件添加
|
||||
APP_SCHEME=1
|
||||
### NGINX 代理 SSL
|
||||
|
||||
// nginx 代理配置添加
|
||||
```bash
|
||||
# 1、Nginx 代理配置添加
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# 2、在项目下运行命令
|
||||
./cmd https
|
||||
```
|
||||
|
||||
## 升级更新
|
||||
@@ -77,7 +106,7 @@ proxy_set_header X-Forwarded-Proto $scheme;
|
||||
**注意:在升级之前请备份好你的数据!**
|
||||
|
||||
```bash
|
||||
# 方法1:进入项目所在目录,运行一下命令即可
|
||||
# 方法1:在项目下运行命令
|
||||
./cmd update
|
||||
|
||||
# (或者)方法2:如果方法1失败请使用此方法
|
||||
@@ -88,9 +117,31 @@ git pull
|
||||
./cmd mysql recovery
|
||||
```
|
||||
|
||||
* 跨越大版本升级失败时请重试执行一次。
|
||||
* 如果升级后出现502请运行 `./cmd restart` 重启服务即可。
|
||||
|
||||
## 迁移项目
|
||||
|
||||
在新项目安装好之后按照以下步骤完成项目迁移:
|
||||
|
||||
1、备份原数据库
|
||||
|
||||
```bash
|
||||
# 在旧的项目下运行命令
|
||||
./cmd mysql backup
|
||||
```
|
||||
|
||||
2、将`数据库备份文件`及`public/uploads`目录拷贝至新项目
|
||||
|
||||
3、还原数据库至新项目
|
||||
```bash
|
||||
# 在新的项目下运行命令
|
||||
./cmd mysql recovery
|
||||
```
|
||||
|
||||
## 卸载项目
|
||||
|
||||
```bash
|
||||
# 进入项目所在目录,运行一下命令即可
|
||||
# 在项目下运行命令
|
||||
./cmd uninstall
|
||||
```
|
||||
|
||||
26
README_PUBLISH.md
Normal file
26
README_PUBLISH.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 发布说明
|
||||
|
||||
## 发布前
|
||||
|
||||
1. 添加环境变量 `APPLEID`、`APPLEIDPASS` 用于公证
|
||||
2. 添加环境变量 `CSC_LINK`、`CSC_KEY_PASSWORD` 用于签名
|
||||
3. 添加环境变量 `GH_TOKEN`、`GH_REPOSITORY` 用于发布到GitHub
|
||||
4. 添加环境变量 `DP_KEY` 用于发布到私有服务器
|
||||
|
||||
## 通过 GitHub Actions 发布
|
||||
|
||||
1. 执行 `npm run version` 生成版本
|
||||
2. 执行 `npm run build` 编译前端
|
||||
3. 执行 `git commit` 提交并推送
|
||||
4. 添加并推送标签
|
||||
|
||||
## 本地发布
|
||||
|
||||
1. 执行 `npm run version` 生成版本
|
||||
2. 执行 `npm run build` 编译前端
|
||||
3. 执行 `./cmd electron` 相关操作
|
||||
|
||||
## 编译App
|
||||
|
||||
1. 执行 `./cmd appbuild` 或 `./cmd appbuild setting` 编译
|
||||
2. 进入 `resources/mobile` eeui框架内打包Android或iOS应用
|
||||
1457
_ide_helper.php
1457
_ide_helper.php
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ namespace App\Exceptions;
|
||||
use App\Module\Base;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
@@ -57,4 +58,24 @@ class Handler extends ExceptionHandler
|
||||
}
|
||||
return parent::render($request, $e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写report优雅记录
|
||||
* @param Throwable $e
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function report(Throwable $e)
|
||||
{
|
||||
if ($e instanceof ApiException) {
|
||||
if ($e->getCode() !== -1) {
|
||||
Log::error($e->getMessage(), [
|
||||
'code' => $e->getCode(),
|
||||
'data' => $e->getData(),
|
||||
'exception' => ' at ' . $e->getFile() . ':' . $e->getLine()
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
parent::report($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,15 @@ if (!function_exists('asset_main')) {
|
||||
if (!function_exists('seeders_at')) {
|
||||
function seeders_at($data)
|
||||
{
|
||||
$diff = time() - strtotime("2021-07-01");
|
||||
$diff = time() - strtotime("2021-07-02");
|
||||
$time = strtotime($data) + $diff;
|
||||
return date("Y-m-d H:i:s", $time);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('md5s')) {
|
||||
function md5s($val, $len = 16)
|
||||
{
|
||||
return substr(md5($val), 32 - $len);
|
||||
}
|
||||
}
|
||||
|
||||
1132
app/Http/Controllers/Api/ApproveController.php
Executable file
1132
app/Http/Controllers/Api/ApproveController.php
Executable file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
104
app/Http/Controllers/Api/PublicController.php
Executable file
104
app/Http/Controllers/Api/PublicController.php
Executable file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserBot;
|
||||
use App\Models\UserCheckinMac;
|
||||
use App\Models\UserCheckinRecord;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\Base;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Request;
|
||||
|
||||
/**
|
||||
* @apiDefine public
|
||||
*
|
||||
* 公开
|
||||
*/
|
||||
class PublicController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* 签到 - 路由器(openwrt)功能安装脚本
|
||||
*
|
||||
* @apiParam {String} key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function checkin__install()
|
||||
{
|
||||
$key = trim(Request::input('key'));
|
||||
//
|
||||
$setting = Base::setting('checkinSetting');
|
||||
if ($setting['open'] !== 'open') {
|
||||
return <<<EOF
|
||||
#!/bin/sh
|
||||
echo "function off"
|
||||
EOF;
|
||||
}
|
||||
if ($key != $setting['key']) {
|
||||
return <<<EOF
|
||||
#!/bin/sh
|
||||
echo "key error"
|
||||
EOF;
|
||||
}
|
||||
//
|
||||
$reportUrl = Base::fillUrl("api/public/checkin/report");
|
||||
return <<<EOE
|
||||
#!/bin/sh
|
||||
echo 'installing...'
|
||||
|
||||
cat > /etc/init.d/dootask-checkin-report <<EOF
|
||||
#!/bin/sh
|
||||
mac=\\\$(awk 'NR!=1&&\\\$3=="0x2" {print \\\$4}' /proc/net/arp | tr "\\n" ",")
|
||||
tmp='{"key":"{$setting['key']}","mac":"'\\\${mac}'","time":"'\\\$(date +%s)'"}'
|
||||
curl -4 -X POST "{$reportUrl}" -H "Content-Type: application/json" -d \\\${tmp}
|
||||
EOF
|
||||
|
||||
chmod +x /etc/init.d/dootask-checkin-report
|
||||
crontab -l >/tmp/cronbak
|
||||
sed -i '/\/etc\/init.d\/dootask-checkin-report/d' /tmp/cronbak
|
||||
sed -i '/^$/d' /tmp/cronbak
|
||||
echo "* * * * * sh /etc/init.d/dootask-checkin-report" >>/tmp/cronbak
|
||||
crontab /tmp/cronbak
|
||||
rm -f /tmp/cronbak
|
||||
/etc/init.d/cron enable
|
||||
/etc/init.d/cron restart
|
||||
|
||||
echo 'installed'
|
||||
EOE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {post} 签到 - 路由器(openwrt)上报
|
||||
*
|
||||
* @apiParam {String} key
|
||||
* @apiParam {String} mac 使用逗号分割多个
|
||||
* @apiParam {String} time
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function checkin__report()
|
||||
{
|
||||
$key = trim(Request::input('key'));
|
||||
$mac = trim(Request::input('mac'));
|
||||
$time = intval(Request::input('time'));
|
||||
//
|
||||
$setting = Base::setting('checkinSetting');
|
||||
if ($setting['open'] !== 'open') {
|
||||
return 'function off';
|
||||
}
|
||||
if (!in_array('auto', $setting['modes'])) {
|
||||
return 'mode off';
|
||||
}
|
||||
if ($key != $setting['key']) {
|
||||
return 'key error';
|
||||
}
|
||||
if ($error = UserBot::checkinBotCheckin($mac, $time)) {
|
||||
return $error;
|
||||
}
|
||||
return 'success';
|
||||
}
|
||||
}
|
||||
521
app/Http/Controllers/Api/ReportController.php
Executable file
521
app/Http/Controllers/Api/ReportController.php
Executable file
@@ -0,0 +1,521 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Models\AbstractModel;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\Report;
|
||||
use App\Models\ReportReceive;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Tasks\PushTask;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
/**
|
||||
* @apiDefine report
|
||||
*
|
||||
* 汇报
|
||||
*/
|
||||
class ReportController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @api {get} api/report/my 01. 我发送的汇报
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup report
|
||||
* @apiName my
|
||||
*
|
||||
* @apiParam {Object} [keys] 搜索条件
|
||||
* - keys.type: 汇报类型,weekly:周报,daily:日报
|
||||
* - keys.created_at: 汇报时间
|
||||
* @apiParam {Number} [page] 当前页,默认:1
|
||||
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:50
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function my(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$builder = Report::with(['receivesUser'])->whereUserid($user->userid);
|
||||
$keys = Request::input('keys');
|
||||
if (is_array($keys)) {
|
||||
if (in_array($keys['type'], [Report::WEEKLY, Report::DAILY])) {
|
||||
$builder->whereType($keys['type']);
|
||||
}
|
||||
if (is_array($keys['created_at'])) {
|
||||
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($keys['created_at'][0])));
|
||||
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($keys['created_at'][1])));
|
||||
}
|
||||
}
|
||||
$list = $builder->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/report/receive 02. 我接收的汇报
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup report
|
||||
* @apiName receive
|
||||
*
|
||||
* @apiParam {Object} [keys] 搜索条件
|
||||
* - keys.key: 关键词
|
||||
* - keys.type: 汇报类型,weekly:周报,daily:日报
|
||||
* - keys.created_at: 汇报时间
|
||||
* @apiParam {Number} [page] 当前页,默认:1
|
||||
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:50
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function receive(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
$builder = Report::with(['receivesUser']);
|
||||
$builder->whereHas("receivesUser", function ($query) use ($user) {
|
||||
$query->where("report_receives.userid", $user->userid);
|
||||
});
|
||||
$keys = Request::input('keys');
|
||||
if (is_array($keys)) {
|
||||
if ($keys['key']) {
|
||||
$builder->where(function($query) use ($keys) {
|
||||
$query->whereHas('sendUser', function ($q2) use ($keys) {
|
||||
$q2->where("users.email", "LIKE", "%{$keys['key']}%");
|
||||
})->orWhere("title", "LIKE", "%{$keys['key']}%");
|
||||
});
|
||||
}
|
||||
if (in_array($keys['type'], [Report::WEEKLY, Report::DAILY])) {
|
||||
$builder->whereType($keys['type']);
|
||||
}
|
||||
if (is_array($keys['created_at'])) {
|
||||
if ($keys['created_at'][0] > 0) $builder->where('created_at', '>=', date('Y-m-d H:i:s', Base::dayTimeF($keys['created_at'][0])));
|
||||
if ($keys['created_at'][1] > 0) $builder->where('created_at', '<=', date('Y-m-d H:i:s', Base::dayTimeE($keys['created_at'][1])));
|
||||
}
|
||||
}
|
||||
$list = $builder->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
|
||||
if ($list->items()) {
|
||||
foreach ($list->items() as $item) {
|
||||
$item->receive_time = ReportReceive::query()->whereRid($item["id"])->whereUserid($user->userid)->value("receive_time");
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/report/store 03. 保存并发送工作汇报
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup report
|
||||
* @apiName store
|
||||
*
|
||||
* @apiParam {Number} id 汇报ID,0为新建
|
||||
* @apiParam {String} [sign] 唯一签名,通过[api/report/template]接口返回
|
||||
* @apiParam {String} title 汇报标题
|
||||
* @apiParam {Array} type 汇报类型,weekly:周报,daily:日报
|
||||
* @apiParam {Number} content 内容
|
||||
* @apiParam {Number} [receive] 汇报对象
|
||||
* @apiParam {Number} offset 时间偏移量
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function store(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$input = [
|
||||
"id" => Request::input("id", 0),
|
||||
"sign" => Request::input("sign"),
|
||||
"title" => Request::input("title"),
|
||||
"type" => Request::input("type"),
|
||||
"content" => Request::input("content"),
|
||||
"receive" => Request::input("receive"),
|
||||
// 以当前日期为基础的周期偏移量。例如选择了上一周那么就是 -1,上一天同理。
|
||||
"offset" => Request::input("offset", 0),
|
||||
];
|
||||
$validator = Validator::make($input, [
|
||||
'id' => 'numeric',
|
||||
'title' => 'required',
|
||||
'type' => ['required', Rule::in([Report::WEEKLY, Report::DAILY])],
|
||||
'content' => 'required',
|
||||
'offset' => ['numeric', 'max:0'],
|
||||
], [
|
||||
'id.numeric' => 'ID只能是数字',
|
||||
'title.required' => '请填写标题',
|
||||
'type.required' => '请选择汇报类型',
|
||||
'type.in' => '汇报类型错误',
|
||||
'content.required' => '请填写汇报内容',
|
||||
'offset.numeric' => '工作汇报周期格式错误,只能是数字',
|
||||
'offset.max' => '只能提交当天/本周或者之前的的工作汇报',
|
||||
]);
|
||||
if ($validator->fails())
|
||||
return Base::retError($validator->errors()->first());
|
||||
|
||||
// 接收人
|
||||
if (is_array($input["receive"])) {
|
||||
// 删除当前登录人
|
||||
$input["receive"] = array_diff($input["receive"], [$user->userid]);
|
||||
|
||||
// 查询用户是否存在
|
||||
if (count($input["receive"]) !== User::whereIn("userid", $input["receive"])->count())
|
||||
return Base::retError("用户不存在");
|
||||
|
||||
foreach ($input["receive"] as $userid) {
|
||||
$input["receive_content"][] = [
|
||||
"receive_time" => Carbon::now()->toDateTimeString(),
|
||||
"userid" => $userid,
|
||||
"read" => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 在事务中运行
|
||||
return AbstractModel::transaction(function () use ($input, $user) {
|
||||
$id = $input["id"];
|
||||
if ($id) {
|
||||
// 编辑
|
||||
$report = Report::getOne($id);
|
||||
$report->updateInstance([
|
||||
"title" => $input["title"],
|
||||
"type" => $input["type"],
|
||||
"content" => htmlspecialchars($input["content"]),
|
||||
]);
|
||||
} else {
|
||||
// 生成唯一标识
|
||||
$sign = Base::isNumber($input["sign"]) ? $input["sign"] : Report::generateSign($input["type"], $input["offset"]);
|
||||
// 检查唯一标识是否存在
|
||||
if (empty($input["id"]) && Report::query()->whereSign($sign)->whereType($input["type"])->count() > 0) {
|
||||
throw new ApiException("请勿重复提交工作汇报");
|
||||
}
|
||||
$report = Report::createInstance([
|
||||
"sign" => $sign,
|
||||
"title" => $input["title"],
|
||||
"type" => $input["type"],
|
||||
"userid" => $user->userid,
|
||||
"content" => htmlspecialchars($input["content"]),
|
||||
]);
|
||||
}
|
||||
$report->save();
|
||||
|
||||
// 删除关联
|
||||
$report->Receives()->delete();
|
||||
if ($input["receive_content"]) {
|
||||
// 保存接收人
|
||||
$report->Receives()->createMany($input["receive_content"]);
|
||||
}
|
||||
|
||||
// 推送消息
|
||||
$userids = [];
|
||||
foreach ($input["receive_content"] as $item) {
|
||||
$userids[] = $item['userid'];
|
||||
}
|
||||
if ($userids) {
|
||||
$params = [
|
||||
'ignoreFd' => Request::header('fd'),
|
||||
'userid' => $userids,
|
||||
'msg' => [
|
||||
'type' => 'report',
|
||||
'action' => 'unreadUpdate',
|
||||
]
|
||||
];
|
||||
Task::deliver(new PushTask($params, false));
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('保存成功', $report);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/report/template 04. 生成汇报模板
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup report
|
||||
* @apiName template
|
||||
*
|
||||
* @apiParam {Array} [type] 汇报类型,weekly:周报,daily:日报
|
||||
* @apiParam {Number} [offset] 偏移量
|
||||
* @apiParam {String} [date] 时间
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function template(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
$type = trim(Request::input("type"));
|
||||
$offset = abs(intval(Request::input("offset", 0)));
|
||||
$id = intval(Request::input("offset", 0));
|
||||
$now_dt = trim(Request::input("date")) ? Carbon::parse(Request::input("date")) : Carbon::now();
|
||||
// 获取开始时间
|
||||
if ($type === Report::DAILY) {
|
||||
$start_time = Carbon::today();
|
||||
if ($offset > 0) {
|
||||
// 将当前时间调整为偏移量当天结束
|
||||
$now_dt->subDays($offset)->endOfDay();
|
||||
// 开始时间偏移量计算
|
||||
$start_time->subDays($offset);
|
||||
}
|
||||
$end_time = Carbon::instance($start_time)->endOfDay();
|
||||
} else {
|
||||
$start_time = Carbon::now();
|
||||
if ($offset > 0) {
|
||||
// 将当前时间调整为偏移量当周结束
|
||||
$now_dt->subWeeks($offset)->endOfDay();
|
||||
// 开始时间偏移量计算
|
||||
$start_time->subWeeks($offset);
|
||||
}
|
||||
$start_time->startOfWeek();
|
||||
$end_time = Carbon::instance($start_time)->endOfWeek();
|
||||
}
|
||||
// 生成唯一标识
|
||||
$sign = Report::generateSign($type, 0, Carbon::instance($start_time));
|
||||
$one = Report::whereSign($sign)->whereType($type)->first();
|
||||
// 如果已经提交了相关汇报
|
||||
if ($one && $id > 0) {
|
||||
return Base::retSuccess('success', [
|
||||
"id" => $one->id,
|
||||
"sign" => $one->sign,
|
||||
"title" => $one->title,
|
||||
"content" => $one->content,
|
||||
]);
|
||||
}
|
||||
|
||||
// 已完成的任务
|
||||
$completeContent = "";
|
||||
$complete_task = ProjectTask::query()
|
||||
->whereNotNull("complete_at")
|
||||
->whereBetween("complete_at", [$start_time->toDateTimeString(), $end_time->toDateTimeString()])
|
||||
->whereHas("taskUser", function ($query) use ($user) {
|
||||
$query->where("userid", $user->userid);
|
||||
})
|
||||
->orderByDesc("id")
|
||||
->get();
|
||||
if ($complete_task->isNotEmpty()) {
|
||||
foreach ($complete_task as $task) {
|
||||
$complete_at = Carbon::parse($task->complete_at);
|
||||
$pre = $type == Report::WEEKLY ? ('<span>[' . Doo::translate('周' . ['日', '一', '二', '三', '四', '五', '六'][$complete_at->dayOfWeek]) . ']</span> ') : '';
|
||||
$completeContent .= "<li>{$pre}[{$task->project->name}] {$task->name}</li>";
|
||||
}
|
||||
} else {
|
||||
$completeContent = '<li> </li>';
|
||||
}
|
||||
|
||||
// 未完成的任务
|
||||
$unfinishedContent = "";
|
||||
$unfinished_task = ProjectTask::query()
|
||||
->whereNull("complete_at")
|
||||
->whereNotNull("start_at")
|
||||
->where("end_at", "<", $end_time->toDateTimeString())
|
||||
->whereHas("taskUser", function ($query) use ($user) {
|
||||
$query->where("userid", $user->userid);
|
||||
})
|
||||
->orderByDesc("id")
|
||||
->get();
|
||||
if ($unfinished_task->isNotEmpty()) {
|
||||
foreach ($unfinished_task as $task) {
|
||||
empty($task->end_at) || $end_at = Carbon::parse($task->end_at);
|
||||
$pre = (!empty($end_at) && $end_at->lt($now_dt)) ? '<span style="color:#ff0000;">[' . Doo::translate('超期') . ']</span> ' : '';
|
||||
$unfinishedContent .= "<li>{$pre}[{$task->project->name}] {$task->name}</li>";
|
||||
}
|
||||
} else {
|
||||
$unfinishedContent = '<li> </li>';
|
||||
}
|
||||
// 生成标题
|
||||
if ($type === Report::WEEKLY) {
|
||||
$title = $user->nickname . "的周报[" . $start_time->format("m/d") . "-" . $end_time->format("m/d") . "]";
|
||||
$title .= "[" . $start_time->month . "月第" . $start_time->weekOfMonth . "周]";
|
||||
} else {
|
||||
$title = $user->nickname . "的日报[" . $start_time->format("Y/m/d") . "]";
|
||||
}
|
||||
// 生成内容
|
||||
$content = '<h2>' . Doo::translate('已完成工作') . '</h2><ol>' .
|
||||
$completeContent . '</ol><h2>' .
|
||||
Doo::translate('未完成的工作') . '</h2><ol>' .
|
||||
$unfinishedContent . '</ol>';
|
||||
if ($type === Report::WEEKLY) {
|
||||
$content .= "<h2>" . Doo::translate("下周拟定计划") . "[" . $start_time->addWeek()->format("m/d") . "-" . $end_time->addWeek()->format("m/d") . "]</h2><ol><li> </li></ol>";
|
||||
}
|
||||
$data = [
|
||||
"time" => $start_time->toDateTimeString(),
|
||||
"sign" => $sign,
|
||||
"title" => $title,
|
||||
"content" => $content,
|
||||
"complete_task" => $complete_task,
|
||||
"unfinished_task" => $unfinished_task,
|
||||
];
|
||||
if ($one) {
|
||||
$data['id'] = $one->id;
|
||||
}
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/report/detail 05. 报告详情
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup report
|
||||
* @apiName detail
|
||||
*
|
||||
* @apiParam {Number} [id] 报告id
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function detail(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
$id = intval(trim(Request::input("id")));
|
||||
if (empty($id))
|
||||
return Base::retError("缺少ID参数");
|
||||
|
||||
$one = Report::getOne($id);
|
||||
$one->type_val = $one->getRawOriginal("type");
|
||||
|
||||
// 标记为已读
|
||||
if (!empty($one->receivesUser)) {
|
||||
foreach ($one->receivesUser as $item) {
|
||||
if ($item->userid === $user->userid && $item->pivot->read === 0) {
|
||||
$one->receivesUser()->updateExistingPivot($user->userid, [
|
||||
"read" => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Base::retSuccess("success", $one);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/report/mark 06. 标记已读/未读
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup report
|
||||
* @apiName mark
|
||||
*
|
||||
* @apiParam {Number} id 报告id(组)
|
||||
* @apiParam {Number} action 操作
|
||||
* - read: 标记已读(默认)
|
||||
* - unread: 标记未读
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function mark(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$id = Request::input('id');
|
||||
$action = Request::input('action');
|
||||
//
|
||||
if (is_array($id)) {
|
||||
if (count(Base::arrayRetainInt($id)) > 100) {
|
||||
return Base::retError("最多只能操作100条数据");
|
||||
}
|
||||
$builder = Report::whereIn("id", Base::arrayRetainInt($id));
|
||||
} else {
|
||||
$builder = Report::whereId(intval($id));
|
||||
}
|
||||
$builder ->chunkById(100, function ($list) use ($action, $user) {
|
||||
/** @var Report $item */
|
||||
foreach ($list as $item) {
|
||||
$item->receivesUser()->updateExistingPivot($user->userid, [
|
||||
"read" => $action === 'unread' ? 0 : 1,
|
||||
]);
|
||||
}
|
||||
});
|
||||
return Base::retSuccess("操作成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/report/last_submitter 07. 获取最后一次提交的接收人
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup report
|
||||
* @apiName last_submitter
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function last_submitter(): array
|
||||
{
|
||||
$one = Report::getLastOne();
|
||||
return Base::retSuccess("success", empty($one["receives"]) ? [] : $one["receives"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/report/unread 08. 获取未读
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup report
|
||||
* @apiName unread
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function unread(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$data = Report::whereHas("Receives", function (Builder $query) use ($user) {
|
||||
$query->where("userid", $user->userid)->where("read", 0);
|
||||
})->orderByDesc('created_at')->paginate(Base::getPaginate(50, 20));
|
||||
return Base::retSuccess("success", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/report/read 09. 标记汇报已读,可批量
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup report
|
||||
* @apiName read
|
||||
*
|
||||
* @apiParam {String} [ids] 报告id
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function read(): array
|
||||
{
|
||||
$user = User::auth();
|
||||
$ids = Request::input("ids");
|
||||
if (!is_array($ids) && !is_string($ids)) {
|
||||
return Base::retError("请传入正确的工作汇报Id");
|
||||
}
|
||||
|
||||
if (is_string($ids)) {
|
||||
$ids = Base::explodeInt($ids);
|
||||
}
|
||||
|
||||
$data = Report::with(["receivesUser" => function (BelongsToMany $query) use ($user) {
|
||||
$query->where("report_receives.userid", $user->userid)->where("read", 0);
|
||||
}])->whereIn("id", $ids)->get();
|
||||
|
||||
if ($data->isNotEmpty()) {
|
||||
foreach ($data as $item) {
|
||||
(!empty($item->receivesUser) && $item->receivesUser->isNotEmpty()) && $item->receivesUser()->updateExistingPivot($user->userid, [
|
||||
"read" => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
return Base::retSuccess("success", $data);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
* 给apidoc项目增加顺序编号
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL & ~E_NOTICE);
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
$path = dirname(__FILE__). '/';
|
||||
$lists = scandir($path);
|
||||
|
||||
@@ -2,10 +2,26 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Module\Base;
|
||||
use App\Tasks\DeleteTmpTask;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Arr;
|
||||
use Cache;
|
||||
use Request;
|
||||
use Redirect;
|
||||
use Response;
|
||||
use App\Models\File;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\LoopTask;
|
||||
use App\Module\Extranet;
|
||||
use App\Tasks\AppPushTask;
|
||||
use App\Module\RandomColor;
|
||||
use App\Tasks\JokeSoupTask;
|
||||
use App\Tasks\DeleteTmpTask;
|
||||
use App\Tasks\EmailNoticeTask;
|
||||
use App\Tasks\AutoArchivedTask;
|
||||
use App\Tasks\DeleteBotMsgTask;
|
||||
use App\Tasks\CheckinRemindTask;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use App\Tasks\UnclaimedTaskRemindTask;
|
||||
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
|
||||
|
||||
|
||||
/**
|
||||
@@ -21,6 +37,10 @@ class IndexController extends InvokeController
|
||||
if ($action) {
|
||||
$app .= "__" . $action;
|
||||
}
|
||||
if ($app === 'manifest.txt') {
|
||||
$app = 'manifest';
|
||||
$child = 'txt';
|
||||
}
|
||||
if (!method_exists($this, $app)) {
|
||||
$app = method_exists($this, $method) ? $method : 'main';
|
||||
}
|
||||
@@ -29,11 +49,121 @@ class IndexController extends InvokeController
|
||||
|
||||
/**
|
||||
* 首页
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
return view('main', ['version' => Base::getVersion()]);
|
||||
$hotFile = public_path('hot');
|
||||
$manifestFile = public_path('manifest.json');
|
||||
if (file_exists($hotFile)) {
|
||||
$array = Base::json2array(file_get_contents($hotFile));
|
||||
$style = null;
|
||||
$script = preg_replace("/^(\/\/(.*?))(:\d+)?\//i", "$1:" . $array['APP_DEV_PORT'] . "/", asset_main("resources/assets/js/app.js"));
|
||||
} else {
|
||||
$array = Base::json2array(file_get_contents($manifestFile));
|
||||
$style = asset_main($array['resources/assets/js/app.js']['css'][0]);
|
||||
$script = asset_main($array['resources/assets/js/app.js']['file']);
|
||||
}
|
||||
return response()->view('main', [
|
||||
'version' => Base::getVersion(),
|
||||
'style' => $style,
|
||||
'script' => $script,
|
||||
])->header('Link', "<" . url('manifest.txt') . ">; rel=\"prefetch\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Manifest
|
||||
* @param $child
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response|string
|
||||
*/
|
||||
public function manifest($child = '')
|
||||
{
|
||||
if (empty($child)) {
|
||||
$murl = url('manifest.txt');
|
||||
return response($murl)->header('Link', "<{$murl}>; rel=\"prefetch\"");
|
||||
}
|
||||
$array = [
|
||||
"office/web-apps/apps/api/documents/api.js?hash=" . Base::getVersion(),
|
||||
"office/7.5.1-23/web-apps/vendor/requirejs/require.js",
|
||||
"office/7.5.1-23/web-apps/apps/api/documents/api.js",
|
||||
"office/7.5.1-23/sdkjs/common/AllFonts.js",
|
||||
"office/7.5.1-23/web-apps/vendor/xregexp/xregexp-all-min.js",
|
||||
"office/7.5.1-23/web-apps/vendor/sockjs/sockjs.min.js",
|
||||
"office/7.5.1-23/web-apps/vendor/jszip/jszip.min.js",
|
||||
"office/7.5.1-23/web-apps/vendor/jszip-utils/jszip-utils.min.js",
|
||||
"office/7.5.1-23/sdkjs/common/libfont/wasm/fonts.js",
|
||||
"office/7.5.1-23/sdkjs/common/Charts/ChartStyles.js",
|
||||
"office/7.5.1-23/sdkjs/slide/themes//themes.js",
|
||||
|
||||
"office/7.5.1-23/web-apps/apps/presentationeditor/main/app.js",
|
||||
"office/7.5.1-23/sdkjs/slide/sdk-all-min.js",
|
||||
"office/7.5.1-23/sdkjs/slide/sdk-all.js",
|
||||
|
||||
"office/7.5.1-23/web-apps/apps/documenteditor/main/app.js",
|
||||
"office/7.5.1-23/sdkjs/word/sdk-all-min.js",
|
||||
"office/7.5.1-23/sdkjs/word/sdk-all.js",
|
||||
|
||||
"office/7.5.1-23/web-apps/apps/spreadsheeteditor/main/app.js",
|
||||
"office/7.5.1-23/sdkjs/cell/sdk-all-min.js",
|
||||
"office/7.5.1-23/sdkjs/cell/sdk-all.js",
|
||||
];
|
||||
foreach ($array as &$item) {
|
||||
$item = url($item);
|
||||
}
|
||||
return implode(PHP_EOL, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本号
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
return Redirect::to(Base::fillUrl('api/system/version'), 301);
|
||||
}
|
||||
|
||||
/**
|
||||
* 头像
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
|
||||
*/
|
||||
public function avatar()
|
||||
{
|
||||
$segment = Request::segment(2);
|
||||
if ($segment && preg_match('/.*?\.png$/i', $segment)) {
|
||||
$name = substr($segment, 0, -4);
|
||||
} else {
|
||||
$name = Request::input('name', 'H');
|
||||
}
|
||||
$size = Request::input('size', 128);
|
||||
$color = Request::input('color');
|
||||
$background = Request::input('background');
|
||||
//
|
||||
if (preg_match('/^[\x{4e00}-\x{9fa5}]+$/u', $name)) {
|
||||
$name = mb_substr($name, mb_strlen($name) - 2);
|
||||
}
|
||||
if (empty($color)) {
|
||||
$color = '#ffffff';
|
||||
$cacheKey = "avatarBackgroundColor::" . md5($name);
|
||||
$background = Cache::rememberForever($cacheKey, function() {
|
||||
return RandomColor::one(['luminosity' => 'dark']);
|
||||
});
|
||||
}
|
||||
//
|
||||
$avatar = new InitialAvatar();
|
||||
$content = $avatar->name($name)
|
||||
->size($size)
|
||||
->color($color)
|
||||
->background($background)
|
||||
->fontSize(0.35)
|
||||
->autoFont()
|
||||
->generate()
|
||||
->stream('png', 100);
|
||||
//
|
||||
return response($content)
|
||||
->header('Pragma', 'public')
|
||||
->header('Cache-Control', 'max-age=1814400')
|
||||
->header('Content-type', 'image/png')
|
||||
->header('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + 1814400));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,13 +185,183 @@ class IndexController extends InvokeController
|
||||
// 限制内网访问
|
||||
return "Forbidden Access";
|
||||
}
|
||||
// 自动归档
|
||||
Task::deliver(new AutoArchivedTask());
|
||||
// 邮件通知
|
||||
Task::deliver(new EmailNoticeTask());
|
||||
// App推送
|
||||
Task::deliver(new AppPushTask());
|
||||
// 删除过期的临时表数据
|
||||
Task::deliver(new DeleteTmpTask('wg_tmp_msgs', 1));
|
||||
Task::deliver(new DeleteTmpTask('tmp', 24));
|
||||
Task::deliver(new DeleteTmpTask('task_worker', 12));
|
||||
Task::deliver(new DeleteTmpTask('tmp'));
|
||||
Task::deliver(new DeleteTmpTask('file'));
|
||||
Task::deliver(new DeleteTmpTask('file_pack'));
|
||||
// 删除机器人消息
|
||||
Task::deliver(new DeleteBotMsgTask());
|
||||
// 周期任务
|
||||
Task::deliver(new LoopTask());
|
||||
// 签到提醒
|
||||
Task::deliver(new CheckinRemindTask());
|
||||
// 获取笑话/心灵鸡汤
|
||||
Task::deliver(new JokeSoupTask());
|
||||
// 未领取任务通知
|
||||
Task::deliver(new UnclaimedTaskRemindTask());
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
/**
|
||||
* 桌面客户端发布
|
||||
*/
|
||||
public function desktop__publish($name = '')
|
||||
{
|
||||
$publishVersion = Request::header('publish-version');
|
||||
$latestFile = public_path("uploads/desktop/latest");
|
||||
$latestVersion = file_exists($latestFile) ? trim(file_get_contents($latestFile)) : "0.0.1";
|
||||
if (strtolower($name) === 'latest') {
|
||||
$name = $latestVersion;
|
||||
}
|
||||
// 上传
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $publishVersion)) {
|
||||
$publishKey = Request::header('publish-key');
|
||||
if ($publishKey !== env('APP_KEY')) {
|
||||
return Base::retError("key error");
|
||||
}
|
||||
if (version_compare($publishVersion, $latestVersion) > -1) { // 限制上传版本必须 ≥ 当前版本
|
||||
$publishPath = "uploads/desktop/{$publishVersion}/";
|
||||
$res = Base::upload([
|
||||
"file" => Request::file('file'),
|
||||
"type" => 'desktop',
|
||||
"path" => $publishPath,
|
||||
"fileName" => true
|
||||
]);
|
||||
if (Base::isSuccess($res)) {
|
||||
file_put_contents($latestFile, $publishVersion);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
// 列表
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $name)) {
|
||||
$path = "uploads/desktop/{$name}";
|
||||
$dirPath = public_path($path);
|
||||
$lists = Base::readDir($dirPath);
|
||||
$files = [];
|
||||
foreach ($lists as $file) {
|
||||
if (str_ends_with($file, '.yml') || str_ends_with($file, '.yaml')) {
|
||||
continue;
|
||||
}
|
||||
$fileName = Base::leftDelete($file, $dirPath);
|
||||
$files[] = [
|
||||
'name' => substr($fileName, 1),
|
||||
'time' => date("Y-m-d H:i:s", filemtime($file)),
|
||||
'size' => Base::readableBytes(filesize($file)),
|
||||
'url' => Base::fillUrl($path . $fileName),
|
||||
];
|
||||
}
|
||||
//
|
||||
$path = "uploads/android";
|
||||
$dirPath = public_path($path);
|
||||
$lists = Base::readDir($dirPath);
|
||||
$apkFile = null;
|
||||
foreach ($lists as $file) {
|
||||
if (!str_ends_with($file, '.apk')) {
|
||||
continue;
|
||||
}
|
||||
if ($apkFile && strtotime($apkFile['time']) > filemtime($file)) {
|
||||
continue;
|
||||
}
|
||||
$fileName = Base::leftDelete($file, $dirPath);
|
||||
$apkFile = [
|
||||
'name' => substr($fileName, 1),
|
||||
'time' => date("Y-m-d H:i:s", filemtime($file)),
|
||||
'size' => Base::readableBytes(filesize($file)),
|
||||
'url' => Base::fillUrl($path . $fileName),
|
||||
];
|
||||
}
|
||||
if ($apkFile) {
|
||||
$files = array_merge([$apkFile], $files);
|
||||
}
|
||||
return view('desktop', ['version' => $name, 'files' => $files]);
|
||||
}
|
||||
// 下载
|
||||
if ($name && file_exists($latestFile)) {
|
||||
$publishVersion = file_get_contents($latestFile);
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $publishVersion)) {
|
||||
$filePath = public_path("uploads/desktop/{$publishVersion}/{$name}");
|
||||
if (file_exists($filePath)) {
|
||||
return Response::download($filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return abort(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drawio 图标搜索
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function drawio__iconsearch()
|
||||
{
|
||||
$query = trim(Request::input('q'));
|
||||
$page = trim(Request::input('p'));
|
||||
$size = trim(Request::input('c'));
|
||||
return Extranet::drawioIconSearch($query, $page, $size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览文件
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function online__preview()
|
||||
{
|
||||
$key = trim(Request::input('key'));
|
||||
//
|
||||
$data = parse_url($key);
|
||||
$path = Arr::get($data, 'path');
|
||||
$file = public_path($path);
|
||||
//
|
||||
if (file_exists($file)) {
|
||||
parse_str($data['query'], $query);
|
||||
$name = Arr::get($query, 'name');
|
||||
$ext = strtolower(Arr::get($query, 'ext'));
|
||||
$userAgent = strtolower(Request::server('HTTP_USER_AGENT'));
|
||||
if ($ext === 'pdf'
|
||||
&& (str_contains($userAgent, 'electron') || str_contains($userAgent, 'chrome'))) {
|
||||
return Response::download($file, $name, [
|
||||
'Content-Type' => 'application/pdf'
|
||||
], 'inline');
|
||||
}
|
||||
//
|
||||
if (in_array($ext, File::localExt)) {
|
||||
$url = Base::fillUrl($path);
|
||||
} else {
|
||||
$url = 'http://' . env('APP_IPPR') . '.3/' . $path;
|
||||
}
|
||||
if ($ext !== 'pdf') {
|
||||
$url = Base::urlAddparameter($url, [
|
||||
'fullfilename' => $name . '.' . $ext
|
||||
]);
|
||||
}
|
||||
$toUrl = Base::fillUrl("fileview/onlinePreview?url=" . urlencode(base64_encode($url)));
|
||||
return Redirect::to($toUrl, 301);
|
||||
}
|
||||
return abort(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置语言和皮肤
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
|
||||
*/
|
||||
public function setting__theme_language()
|
||||
{
|
||||
return view('setting', [
|
||||
'theme' => Request::input('theme'),
|
||||
'language' => Request::input('language')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取所有中文
|
||||
* @return array|string
|
||||
@@ -123,9 +423,6 @@ class IndexController extends InvokeController
|
||||
$list = array_merge(Base::readDir(app_path()), Base::readDir(resource_path()));
|
||||
$array = [];
|
||||
foreach ($list as $item) {
|
||||
if (Base::rightExists($item, "language.all.js")) {
|
||||
continue;
|
||||
}
|
||||
if (Base::rightExists($item, ".php") || Base::rightExists($item, ".vue") || Base::rightExists($item, ".js")) {
|
||||
$content = file_get_contents($item);
|
||||
preg_match_all("/(['\"])(.*?)[\u{4e00}-\u{9fa5}\u{FE30}-\u{FFA0}]+([\s\S]((?!\n).)*)\\1/u", $content, $matchs);
|
||||
|
||||
@@ -12,34 +12,10 @@ class VerifyCsrfToken extends Middleware
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
// 上传图片
|
||||
'api/system/imgupload/',
|
||||
// 接口部分
|
||||
'api/*',
|
||||
|
||||
// 上传文件
|
||||
'api/system/fileupload/',
|
||||
|
||||
// 保存任务优先级
|
||||
'api/system/priority/',
|
||||
|
||||
// 添加任务
|
||||
'api/project/task/add/',
|
||||
|
||||
// 修改任务
|
||||
'api/project/task/update/',
|
||||
|
||||
// 上传任务问题
|
||||
'api/project/task/upload/',
|
||||
|
||||
// 聊天发文件
|
||||
'api/dialog/msg/sendfile/',
|
||||
|
||||
// 保存文件内容
|
||||
'api/file/content/save/',
|
||||
|
||||
// 保存文件内容(office)
|
||||
'api/file/content/office/',
|
||||
|
||||
// 保存文件内容(上传)
|
||||
'api/file/content/upload/',
|
||||
// 发布桌面端
|
||||
'desktop/publish/',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace App\Http\Middleware;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Module\Doo;
|
||||
use Closure;
|
||||
use Request;
|
||||
|
||||
class WebApi
|
||||
{
|
||||
@@ -21,17 +21,42 @@ class WebApi
|
||||
global $_A;
|
||||
$_A = [];
|
||||
|
||||
if (Request::input('__Access-Control-Allow-Origin') || Request::header('__Access-Control-Allow-Origin')) {
|
||||
header('Access-Control-Allow-Origin:*');
|
||||
header('Access-Control-Allow-Methods:GET,POST,PUT,DELETE,OPTIONS');
|
||||
header('Access-Control-Allow-Headers:Content-Type, platform, platform-channel, token, release, Access-Control-Allow-Origin');
|
||||
Doo::load();
|
||||
|
||||
$encrypt = Doo::pgpParseStr($request->header('encrypt'));
|
||||
if ($request->isMethod('post')) {
|
||||
$version = $request->header('version');
|
||||
if ($version && version_compare($version, '0.25.48', '<')) {
|
||||
// 旧版本兼容 php://input
|
||||
parse_str($request->getContent(), $content);
|
||||
if ($content) {
|
||||
$request->merge($content);
|
||||
}
|
||||
} elseif ($encrypt['encrypt_type'] === 'pgp' && $content = $request->input('encrypted')) {
|
||||
// 新版本解密提交的内容
|
||||
$content = Doo::pgpDecryptApi($content, $encrypt['encrypt_id']);
|
||||
if ($content) {
|
||||
$request->merge($content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 强制 https
|
||||
$APP_SCHEME = env('APP_SCHEME', 'auto');
|
||||
if (in_array(strtolower($APP_SCHEME), ['https', 'on', 'ssl', '1', 'true', 'yes'], true)) {
|
||||
$request->setTrustedProxies([$request->getClientIp()], $request::HEADER_X_FORWARDED_PROTO);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
$response = $next($request);
|
||||
|
||||
// 加密返回内容
|
||||
if ($encrypt['client_type'] === 'pgp' && $content = $response->getContent()) {
|
||||
$content = Doo::pgpEncryptApi($content, $encrypt['client_key']);
|
||||
if ($content) {
|
||||
$response->setContent(json_encode(['encrypted' => $content]));
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
251
app/Ldap/LdapUser.php
Normal file
251
app/Ldap/LdapUser.php
Normal file
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
namespace App\Ldap;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use LdapRecord\Configuration\ConfigurationException;
|
||||
use LdapRecord\Container;
|
||||
use LdapRecord\LdapRecordException;
|
||||
use LdapRecord\Models\Model;
|
||||
|
||||
class LdapUser extends Model
|
||||
{
|
||||
protected static $init = null;
|
||||
/**
|
||||
* The object classes of the LDAP model.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $objectClasses = [
|
||||
'inetOrgPerson',
|
||||
'organizationalPerson',
|
||||
'person',
|
||||
'top',
|
||||
'posixAccount',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getPhoto()
|
||||
{
|
||||
return $this->jpegPhoto && is_array($this->jpegPhoto) ? $this->jpegPhoto[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getDisplayName()
|
||||
{
|
||||
$nickname = $this->displayName ?: $this->uid;
|
||||
return is_array($nickname) ? $nickname[0] : $nickname;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LdapUser
|
||||
*/
|
||||
public static function static(): LdapUser
|
||||
{
|
||||
return new static;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务是否打开
|
||||
* @return bool
|
||||
*/
|
||||
public static function isOpen(): bool
|
||||
{
|
||||
return Base::settingFind('thirdAccessSetting', 'ldap_open') === 'open';
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步本地是否打开
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSyncLocal(): bool
|
||||
{
|
||||
return Base::settingFind('thirdAccessSetting', 'ldap_sync_local') === 'open';
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化配置
|
||||
* @return bool
|
||||
*/
|
||||
public static function initConfig()
|
||||
{
|
||||
if (is_bool(self::$init)) {
|
||||
return self::$init;
|
||||
}
|
||||
//
|
||||
$setting = Base::setting('thirdAccessSetting');
|
||||
if ($setting['ldap_open'] !== 'open') {
|
||||
return self::$init = false;
|
||||
}
|
||||
//
|
||||
$connection = Container::getDefaultConnection();
|
||||
try {
|
||||
$connection->setConfiguration([
|
||||
"hosts" => [$setting['ldap_host']],
|
||||
"port" => intval($setting['ldap_port']),
|
||||
"base_dn" => $setting['ldap_base_dn'],
|
||||
"username" => $setting['ldap_user_dn'],
|
||||
"password" => $setting['ldap_password'],
|
||||
]);
|
||||
return self::$init = true;
|
||||
} catch (ConfigurationException $e) {
|
||||
info($e->getMessage());
|
||||
return self::$init = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取
|
||||
* @param $username
|
||||
* @param $password
|
||||
* @return Model|null
|
||||
*/
|
||||
public static function userFirst($username, $password): ?Model
|
||||
{
|
||||
if (!self::initConfig()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return self::static()
|
||||
->where([
|
||||
'cn' => $username,
|
||||
'userPassword' => $password
|
||||
])->first();
|
||||
} catch (\Exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* @param $username
|
||||
* @param $password
|
||||
* @param User|null $user
|
||||
* @return User|mixed|null
|
||||
*/
|
||||
public static function userLogin($username, $password, $user = null)
|
||||
{
|
||||
if (!self::initConfig()) {
|
||||
return null;
|
||||
}
|
||||
$row = self::userFirst($username, $password);
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
if (empty($user)) {
|
||||
$user = User::reg($username, $password);
|
||||
}
|
||||
if ($user) {
|
||||
$userimg = $row->getPhoto();
|
||||
if ($userimg) {
|
||||
$path = "uploads/user/ldap/";
|
||||
$file = "{$path}{$user->userid}.jpeg";
|
||||
Base::makeDir(public_path($path));
|
||||
if (Base::saveContentImage(public_path($file), $userimg)) {
|
||||
$user->userimg = $file;
|
||||
}
|
||||
}
|
||||
$user->nickname = $row->getDisplayName();
|
||||
$user->save();
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步
|
||||
* @param User $user
|
||||
* @param $password
|
||||
* @return void
|
||||
*/
|
||||
public static function userSync(User $user, $password)
|
||||
{
|
||||
if ($user->isLdap()) {
|
||||
return;
|
||||
}
|
||||
//
|
||||
if (!self::initConfig()) {
|
||||
return;
|
||||
}
|
||||
//
|
||||
if (self::isSyncLocal()) {
|
||||
$row = self::userFirst($user->email, $password);
|
||||
if ($row) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$userimg = public_path($user->getRawOriginal('userimg'));
|
||||
if (file_exists($userimg)) {
|
||||
$userimg = file_get_contents($userimg);
|
||||
} else {
|
||||
$userimg = '';
|
||||
}
|
||||
self::static()->create([
|
||||
'cn' => $user->email,
|
||||
'gidNumber' => 0,
|
||||
'homeDirectory' => '/home/ldap/dootask/' . env("APP_NAME"),
|
||||
'sn' => $user->email,
|
||||
'uid' => $user->email,
|
||||
'uidNumber' => $user->userid,
|
||||
'userPassword' => $password,
|
||||
'displayName' => $user->nickname,
|
||||
'jpegPhoto' => $userimg,
|
||||
]);
|
||||
$user->identity = Base::arrayImplode(array_merge(array_diff($user->identity, ['ldap']), ['ldap']));
|
||||
$user->save();
|
||||
} catch (LdapRecordException $e) {
|
||||
info("[LDAP] sync fail: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param $username
|
||||
* @param $array
|
||||
* @return void
|
||||
*/
|
||||
public static function userUpdate($username, $array)
|
||||
{
|
||||
if (empty($array)) {
|
||||
return;
|
||||
}
|
||||
if (!self::initConfig()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$row = self::static()
|
||||
->where([
|
||||
'cn' => $username,
|
||||
])->first();
|
||||
$row?->update($array);
|
||||
} catch (\Exception $e) {
|
||||
info("[LDAP] update fail: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param $username
|
||||
* @return void
|
||||
*/
|
||||
public static function userDelete($username)
|
||||
{
|
||||
if (!self::initConfig()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$row = self::static()
|
||||
->where([
|
||||
'cn' => $username,
|
||||
])->first();
|
||||
$row?->delete();
|
||||
} catch (\Exception $e) {
|
||||
info("[LDAP] delete fail: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,21 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* App\Model\AbstractModel
|
||||
* App\Models\AbstractModel
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
|
||||
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelAppend()
|
||||
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelHidden()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|static with($relations)
|
||||
* @method static \Illuminate\Database\Query\Builder|static select($columns = [])
|
||||
* @method static \Illuminate\Database\Query\Builder|static whereIn($column, $values, $boolean = 'and', $not = false)
|
||||
* @method static \Illuminate\Database\Query\Builder|static whereNotIn($column, $values, $boolean = 'and')
|
||||
* @method int change(array $array)
|
||||
* @method int remove()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class AbstractModel extends Model
|
||||
@@ -36,6 +41,42 @@ class AbstractModel extends Model
|
||||
|
||||
protected $appendattrs = [];
|
||||
|
||||
/**
|
||||
* 通过模型修改数据
|
||||
* @param AbstractModel $builder
|
||||
* @param $array
|
||||
* @return int
|
||||
*/
|
||||
protected function scopeChange($builder, $array)
|
||||
{
|
||||
$line = 0;
|
||||
$rows = $builder->get();
|
||||
foreach ($rows as $row) {
|
||||
$row->updateInstance($array);
|
||||
if ($row->save()) {
|
||||
$line++;
|
||||
}
|
||||
}
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过模型删除数据
|
||||
* @param AbstractModel $builder
|
||||
* @return int
|
||||
*/
|
||||
protected function scopeRemove($builder)
|
||||
{
|
||||
$line = 0;
|
||||
$rows = $builder->get();
|
||||
foreach ($rows as $row) {
|
||||
if ($row->delete()) {
|
||||
$line++;
|
||||
}
|
||||
}
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据忽略错误
|
||||
* @return bool
|
||||
@@ -44,7 +85,7 @@ class AbstractModel extends Model
|
||||
{
|
||||
try {
|
||||
return $this->save();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -62,6 +103,24 @@ class AbstractModel extends Model
|
||||
return $this->$key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消附加值
|
||||
* @return static
|
||||
*/
|
||||
protected function scopeCancelAppend()
|
||||
{
|
||||
return $this->setAppends([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消隐藏值
|
||||
* @return static
|
||||
*/
|
||||
protected function scopeCancelHidden()
|
||||
{
|
||||
return $this->setHidden([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为数组 / JSON 序列化准备日期。
|
||||
* @param DateTimeInterface $date
|
||||
@@ -133,16 +192,23 @@ class AbstractModel extends Model
|
||||
* @param $where
|
||||
* @param array $update 存在时更新的内容
|
||||
* @param array $insert 不存在时插入的内容,如果没有则插入更新内容
|
||||
* @param bool $isInsert 是否是插入数据
|
||||
* @return AbstractModel|\Illuminate\Database\Eloquent\Builder|Model|object|static|null
|
||||
*/
|
||||
public static function updateInsert($where, $update = [], $insert = [])
|
||||
public static function updateInsert($where, $update = [], $insert = [], &$isInsert = true)
|
||||
{
|
||||
$row = static::where($where)->first();
|
||||
if (empty($row)) {
|
||||
$row = new static;
|
||||
$row->updateInstance(array_merge($where, $insert ?: $update));
|
||||
$array = array_merge($where, $insert ?: $update);
|
||||
if (isset($array[$row->primaryKey])) {
|
||||
unset($array[$row->primaryKey]);
|
||||
}
|
||||
$row->updateInstance($array);
|
||||
$isInsert = true;
|
||||
} elseif ($update) {
|
||||
$row->updateInstance($update);
|
||||
$isInsert = false;
|
||||
}
|
||||
if (!$row->save()) {
|
||||
return null;
|
||||
@@ -170,10 +236,9 @@ class AbstractModel extends Model
|
||||
info($eb);
|
||||
}
|
||||
if ($e instanceof ApiException) {
|
||||
throw new ApiException($e->getMessage(), $e->getData(), $e->getCode());
|
||||
throw new ApiException( $e->getMessage() , $e->getData(), $e->getCode());
|
||||
} else {
|
||||
info($e);
|
||||
throw new ApiException($e->getMessage() ?: '处理错误');
|
||||
throw new ApiException( $e->getMessage() ?: '处理错误');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
app/Models/ApproveProcMsg.php
Normal file
28
app/Models/ApproveProcMsg.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* App\Models\ApproveProcMsg
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $proc_inst_id 流程实例ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property int|null $msg_id 消息ID
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereMsgId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereProcInstId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ApproveProcMsg whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ApproveProcMsg extends AbstractModel
|
||||
{
|
||||
|
||||
}
|
||||
93
app/Models/Deleted.php
Normal file
93
app/Models/Deleted.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* App\Models\Deleted
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $type 删除的数据类型(如:project、task、dialog)
|
||||
* @property int|null $did 删除的数据ID
|
||||
* @property int|null $userid 关系会员ID
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Deleted newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Deleted newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Deleted query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Deleted whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Deleted whereDid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Deleted whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Deleted whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Deleted whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Deleted extends AbstractModel
|
||||
{
|
||||
const UPDATED_AT = null;
|
||||
|
||||
/**
|
||||
* 获取删除的ID
|
||||
* @param $type
|
||||
* @param $userid
|
||||
* @param $time
|
||||
* @return array
|
||||
*/
|
||||
public static function ids($type, $userid, $time): array
|
||||
{
|
||||
$builder = self::where([
|
||||
'type' => $type,
|
||||
'userid' => $userid
|
||||
])->orderByDesc('id');
|
||||
if (empty($time)) {
|
||||
$builder = $builder->take(50);
|
||||
} else {
|
||||
$builder = $builder->where('created_at', '>=', Carbon::parse($time))->take(500);
|
||||
}
|
||||
return $builder->pluck('did')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 忘记(恢复或添加数据时删除记录)
|
||||
* @param $type
|
||||
* @param $id
|
||||
* @param $userid
|
||||
* @return void
|
||||
*/
|
||||
public static function forget($type, $id, $userid): void
|
||||
{
|
||||
if (is_array($userid)) {
|
||||
self::where([
|
||||
'type' => $type,
|
||||
'did' => $id,
|
||||
])->whereIn('userid', $userid)->delete();
|
||||
} else {
|
||||
self::where([
|
||||
'type' => $type,
|
||||
'did' => $id,
|
||||
'userid' => $userid,
|
||||
])->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录(删除数据时添加记录)
|
||||
* @param $type
|
||||
* @param $id
|
||||
* @param $userid
|
||||
* @return void
|
||||
*/
|
||||
public static function record($type, $id, $userid): void
|
||||
{
|
||||
$array = is_array($userid) ? $userid : [$userid];
|
||||
foreach ($array as $value) {
|
||||
if (!self::where('type', $type)->where('did', $id)->where('userid', $value)->exists()) {
|
||||
self::updateInsert([
|
||||
'type' => $type,
|
||||
'did' => $id,
|
||||
'userid' => $value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,47 +2,53 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use Request;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\PushTask;
|
||||
use App\Exceptions\ApiException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Request;
|
||||
|
||||
/**
|
||||
* Class File
|
||||
* App\Models\File
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $pid 上级ID
|
||||
* @property string|null $pids 上级ID递归
|
||||
* @property int|null $cid 复制ID
|
||||
* @property string|null $name 名称
|
||||
* @property string|null $type 类型
|
||||
* @property string|null $ext 后缀名
|
||||
* @property int|null $size 大小(B)
|
||||
* @property int|null $userid 拥有者ID
|
||||
* @property int|null $share 是否共享(1:共享所有人,2:指定成员)
|
||||
* @property int|null $created_id 创建者ID
|
||||
* @property int|null $share 是否共享
|
||||
* @property int|null $pshare 所属分享ID
|
||||
* @property int|null $created_id 创建者
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|File onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereCid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereCreatedId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereExt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File wherePid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File wherePids($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File wherePshare($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereShare($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereSize($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File whereUserid($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|File withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|File withoutTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File withTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|File withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class File extends AbstractModel
|
||||
@@ -50,43 +56,299 @@ class File extends AbstractModel
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* 是否有访问权限(没有时抛出异常)
|
||||
* @param $userid
|
||||
* 文件文件
|
||||
*/
|
||||
public function exceAllow($userid)
|
||||
const codeExt = [
|
||||
'txt',
|
||||
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
|
||||
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
|
||||
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
|
||||
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
|
||||
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
|
||||
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
|
||||
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx', 'plist'
|
||||
];
|
||||
|
||||
/**
|
||||
* office文件
|
||||
*/
|
||||
const officeExt = [
|
||||
'doc', 'docx',
|
||||
'xls', 'xlsx',
|
||||
'ppt', 'pptx',
|
||||
];
|
||||
|
||||
/**
|
||||
* 图片文件
|
||||
*/
|
||||
const imageExt = [
|
||||
'jpg', 'jpeg', 'webp', 'png', 'gif', 'bmp'
|
||||
];
|
||||
|
||||
/**
|
||||
* 本地媒体文件
|
||||
*/
|
||||
const localExt = [
|
||||
'jpg', 'jpeg', 'webp', 'png', 'gif', 'bmp', 'ico', 'raw',
|
||||
'tif', 'tiff',
|
||||
'mp3', 'wav', 'mp4', 'flv',
|
||||
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
|
||||
];
|
||||
|
||||
/**
|
||||
* 压缩包下载大小限制
|
||||
*/
|
||||
const zipMaxSize = 1024 * 1024 * 1024; // 1G
|
||||
|
||||
|
||||
/**
|
||||
* 获取文件列表
|
||||
* @param user $user
|
||||
* @param int $pid
|
||||
* @return array
|
||||
*/
|
||||
public function getFileList($user, int $pid, $type = "all", $isGetparent = true)
|
||||
{
|
||||
if (!$this->chackAllow($userid)) {
|
||||
throw new ApiException('没有访问权限');
|
||||
$permission = 1000;
|
||||
$userids = $user->isTemp() ? [$user->userid] : [0, $user->userid];
|
||||
$builder = File::wherePid($pid)
|
||||
->when($type=='dir',function($q){
|
||||
$q->whereType('folder');
|
||||
});
|
||||
if ($pid > 0) {
|
||||
File::permissionFind($pid, $userids, 0, $permission);
|
||||
} else {
|
||||
$builder->whereUserid($user->userid);
|
||||
}
|
||||
//
|
||||
$array = $builder->take(500)->get()->toArray();
|
||||
foreach ($array as &$item) {
|
||||
$item['permission'] = $permission;
|
||||
}
|
||||
//
|
||||
if ($pid > 0) {
|
||||
// 遍历获取父级
|
||||
if($isGetparent){
|
||||
while ($pid > 0) {
|
||||
$file = File::whereId($pid)->first();
|
||||
if (empty($file)) {
|
||||
break;
|
||||
}
|
||||
$pid = $file->pid;
|
||||
$temp = $file->toArray();
|
||||
$temp['permission'] = $file->getPermission($userids);
|
||||
$array[] = $temp;
|
||||
}
|
||||
}
|
||||
// 去除没有权限的文件
|
||||
$isUnset = false;
|
||||
foreach ($array as $index1 => $item1) {
|
||||
if ($item1['permission'] === -1) {
|
||||
foreach ($array as $index2 => $item2) {
|
||||
if ($item2['pid'] === $item1['id']) {
|
||||
$array[$index2]['pid'] = 0;
|
||||
}
|
||||
}
|
||||
$isUnset = true;
|
||||
unset($array[$index1]);
|
||||
}
|
||||
}
|
||||
if ($isUnset) {
|
||||
$array = array_values($array);
|
||||
}
|
||||
} else {
|
||||
// 获取共享相关
|
||||
DB::statement("SET SQL_MODE=''");
|
||||
$pre = DB::connection()->getTablePrefix();
|
||||
$list = File::select(["files.*", DB::raw("MAX({$pre}file_users.permission) as permission")])
|
||||
->join('file_users', 'files.id', '=', 'file_users.file_id')
|
||||
->where('files.userid', '!=', $user->userid)
|
||||
->whereIn('file_users.userid', $userids)
|
||||
->groupBy('files.id')
|
||||
->take(100)
|
||||
->when($type=='dir',function($q){
|
||||
$q->where('files.type','folder');
|
||||
})
|
||||
->get();
|
||||
if ($list->isNotEmpty()) {
|
||||
foreach ($list as $file) {
|
||||
$temp = $file->toArray();
|
||||
$temp['pid'] = 0;
|
||||
$array[] = $temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 图片直接返回预览地址
|
||||
foreach ($array as &$item) {
|
||||
$item = File::handleImageUrl($item);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件内容(上传文件)
|
||||
* @param user $user
|
||||
* @param int $pid
|
||||
* @param string $webkitRelativePath
|
||||
* @return array
|
||||
*/
|
||||
public function contentUpload($user, int $pid, $webkitRelativePath)
|
||||
{
|
||||
$userid = $user->userid;
|
||||
if ($pid > 0) {
|
||||
if (File::wherePid($pid)->count() >= 300) {
|
||||
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
|
||||
}
|
||||
$row = File::permissionFind($pid, $user, 1);
|
||||
$userid = $row->userid;
|
||||
} else {
|
||||
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
|
||||
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
|
||||
}
|
||||
}
|
||||
//
|
||||
$dirs = explode("/", $webkitRelativePath);
|
||||
$addItem = [];
|
||||
while (count($dirs) > 1) {
|
||||
$dirName = array_shift($dirs);
|
||||
if ($dirName) {
|
||||
AbstractModel::transaction(function () use ($dirName, $user, $userid, &$pid, &$addItem) {
|
||||
$dirRow = File::wherePid($pid)->whereType('folder')->whereName($dirName)->lockForUpdate()->first();
|
||||
if (empty($dirRow)) {
|
||||
$dirRow = File::createInstance([
|
||||
'pid' => $pid,
|
||||
'type' => 'folder',
|
||||
'name' => $dirName,
|
||||
'userid' => $userid,
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
$dirRow->handleDuplicateName();
|
||||
if ($dirRow->saveBeforePP()) {
|
||||
$addItem[] = File::find($dirRow->id);
|
||||
}
|
||||
}
|
||||
if (empty($dirRow)) {
|
||||
throw new ApiException('创建文件夹失败');
|
||||
}
|
||||
$pid = $dirRow->id;
|
||||
});
|
||||
foreach ($addItem as $tmpRow) {
|
||||
$tmpRow->pushMsg('add', $tmpRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
$setting = Base::setting('system');
|
||||
$path = 'uploads/tmp/' . date("Ym") . '/';
|
||||
$data = Base::upload([
|
||||
"file" => Request::file('files'),
|
||||
"type" => 'more',
|
||||
"autoThumb" => false,
|
||||
"path" => $path,
|
||||
"size" => ($setting['file_upload_limit'] ?: 0) * 1024
|
||||
]);
|
||||
if (Base::isError($data)) {
|
||||
throw new ApiException($data['msg']);
|
||||
}
|
||||
$data = $data['data'];
|
||||
//
|
||||
$type = match ($data['ext']) {
|
||||
'text', 'md', 'markdown' => 'document',
|
||||
'drawio' => 'drawio',
|
||||
'mind' => 'mind',
|
||||
'doc', 'docx' => "word",
|
||||
'xls', 'xlsx' => "excel",
|
||||
'ppt', 'pptx' => "ppt",
|
||||
'wps' => "wps",
|
||||
'jpg', 'jpeg', 'webp', 'png', 'gif', 'bmp', 'ico', 'raw', 'svg' => "picture",
|
||||
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z', 'gz', 'apk', 'dmg' => "archive",
|
||||
'tif', 'tiff' => "tif",
|
||||
'dwg', 'dxf' => "cad",
|
||||
'ofd' => "ofd",
|
||||
'pdf' => "pdf",
|
||||
'txt' => "txt",
|
||||
'htaccess', 'htgroups', 'htpasswd', 'conf', 'bat', 'cmd', 'cpp', 'c', 'cc', 'cxx', 'h', 'hh', 'hpp', 'ino', 'cs', 'css',
|
||||
'dockerfile', 'go', 'golang', 'html', 'htm', 'xhtml', 'vue', 'we', 'wpy', 'java', 'js', 'jsm', 'jsx', 'json', 'jsp', 'less', 'lua', 'makefile', 'gnumakefile',
|
||||
'ocamlmakefile', 'make', 'mysql', 'nginx', 'ini', 'cfg', 'prefs', 'm', 'mm', 'pl', 'pm', 'p6', 'pl6', 'pm6', 'pgsql', 'php',
|
||||
'inc', 'phtml', 'shtml', 'php3', 'php4', 'php5', 'phps', 'phpt', 'aw', 'ctp', 'module', 'ps1', 'py', 'r', 'rb', 'ru', 'gemspec', 'rake', 'guardfile', 'rakefile',
|
||||
'gemfile', 'rs', 'sass', 'scss', 'sh', 'bash', 'bashrc', 'sql', 'sqlserver', 'swift', 'ts', 'typescript', 'str', 'vbs', 'vb', 'v', 'vh', 'sv', 'svh', 'xml',
|
||||
'rdf', 'rss', 'wsdl', 'xslt', 'atom', 'mathml', 'mml', 'xul', 'xbl', 'xaml', 'yaml', 'yml',
|
||||
'asp', 'properties', 'gitignore', 'log', 'bas', 'prg', 'python', 'ftl', 'aspx', 'plist' => "code",
|
||||
'mp3', 'wav', 'mp4', 'flv',
|
||||
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm' => "media",
|
||||
'xmind' => "xmind",
|
||||
'rp' => "axure",
|
||||
default => "",
|
||||
};
|
||||
if ($data['ext'] == 'markdown') {
|
||||
$data['ext'] = 'md';
|
||||
}
|
||||
$file = File::createInstance([
|
||||
'pid' => $pid,
|
||||
'name' => Base::rightDelete($data['name'], '.' . $data['ext']),
|
||||
'type' => $type,
|
||||
'ext' => $data['ext'],
|
||||
'userid' => $userid,
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
$file->handleDuplicateName();
|
||||
// 开始创建
|
||||
return AbstractModel::transaction(function () use ($addItem, $webkitRelativePath, $type, $user, $data, $file) {
|
||||
$file->size = $data['size'] * 1024;
|
||||
$file->saveBeforePP();
|
||||
//
|
||||
$data = Base::uploadMove($data, "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $file->id . "/");
|
||||
$content = [
|
||||
'from' => '',
|
||||
'type' => $type,
|
||||
'ext' => $data['ext'],
|
||||
'url' => $data['path'],
|
||||
];
|
||||
if (isset($data['width'])) {
|
||||
$content['width'] = $data['width'];
|
||||
$content['height'] = $data['height'];
|
||||
}
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => $content,
|
||||
'text' => '',
|
||||
'size' => $file->size,
|
||||
'userid' => $user->userid,
|
||||
]);
|
||||
$content->save();
|
||||
//
|
||||
$tmpRow = File::find($file->id);
|
||||
$tmpRow->pushMsg('add', $tmpRow);
|
||||
//
|
||||
$data = File::handleImageUrl($tmpRow->toArray());
|
||||
$data['full_name'] = $webkitRelativePath ?: $data['name'];
|
||||
//
|
||||
$addItem[] = $data;
|
||||
|
||||
return ['data'=>$data,'addItem'=>$addItem];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有访问权限
|
||||
* ① 自己的文件夹
|
||||
* ② 共享所有人的文件夹
|
||||
* ③ 在指定共享人员内
|
||||
* @param $userid
|
||||
* @return bool
|
||||
* @param array $userids
|
||||
* @return int -1:没有权限,0:访问权限,1:读写权限,1000:所有者或创建者
|
||||
*/
|
||||
public function chackAllow($userid)
|
||||
public function getPermission(array $userids)
|
||||
{
|
||||
if ($userid == $this->userid) {
|
||||
// ① 自己的文件夹
|
||||
return true;
|
||||
if (in_array($this->userid, $userids) || in_array($this->created_id, $userids)) {
|
||||
// ① 自己的文件夹 或 自己创建的文件夹
|
||||
return 1000;
|
||||
}
|
||||
$row = $this->getShareInfo();
|
||||
if ($row) {
|
||||
if ($row->share == 1) {
|
||||
// ② 共享所有人的文件夹
|
||||
return true;
|
||||
} elseif ($row->share == 2) {
|
||||
// ③ 在指定共享人员内
|
||||
if (FileUser::whereFileId($row->id)->whereUserid($userid)->exists()) {
|
||||
return true;
|
||||
}
|
||||
$fileUser = FileUser::whereFileId($row->id)->whereIn('userid', $userids)->orderByDesc('permission')->first();
|
||||
if ($fileUser) {
|
||||
// ② 在指定共享成员内
|
||||
return $fileUser->permission;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,7 +357,7 @@ class File extends AbstractModel
|
||||
*/
|
||||
public function getShareInfo()
|
||||
{
|
||||
if ($this->share > 0) {
|
||||
if ($this->share) {
|
||||
return $this;
|
||||
}
|
||||
$pid = $this->pid;
|
||||
@@ -104,7 +366,7 @@ class File extends AbstractModel
|
||||
if (empty($row)) {
|
||||
break;
|
||||
}
|
||||
if ($row->share > 0) {
|
||||
if ($row->share) {
|
||||
return $row;
|
||||
}
|
||||
$pid = $row->pid;
|
||||
@@ -114,7 +376,7 @@ class File extends AbstractModel
|
||||
|
||||
/**
|
||||
* 是否处于共享文件夹内(不含自身)
|
||||
* @return bool
|
||||
* @return File|false
|
||||
*/
|
||||
public function isNnShare()
|
||||
{
|
||||
@@ -124,31 +386,119 @@ class File extends AbstractModel
|
||||
if (empty($row)) {
|
||||
break;
|
||||
}
|
||||
if ($row->share > 0) {
|
||||
return true;
|
||||
if ($row->share) {
|
||||
return $row;
|
||||
}
|
||||
$pid = $row->pid;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 目录内是否存在共享文件或文件夹
|
||||
* @return bool
|
||||
*/
|
||||
public function isSubShare()
|
||||
{
|
||||
return $this->type == 'folder' && File::where("pids", "like", "%,{$this->id},%")->whereShare(1)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置/关闭 共享(同时遍历取消里面的共享)
|
||||
* @param $share
|
||||
* @return bool
|
||||
*/
|
||||
public function setShare($share)
|
||||
public function updataShare($share = null)
|
||||
{
|
||||
AbstractModel::transaction(function () use ($share) {
|
||||
$this->share = $share;
|
||||
$this->save();
|
||||
$list = self::wherePid($this->id)->get();
|
||||
if ($list->isNotEmpty()) {
|
||||
foreach ($list as $item) {
|
||||
$item->setShare(0);
|
||||
if ($share === null) {
|
||||
$share = FileUser::whereFileId($this->id)->count() == 0 ? 0 : 1;
|
||||
}
|
||||
if ($this->share != $share) {
|
||||
AbstractModel::transaction(function () use ($share) {
|
||||
$this->share = $share;
|
||||
$this->pshare = $share ? $this->id : 0;
|
||||
$this->save();
|
||||
File::where("pids", "like", "%,{$this->id},%")->update(['pshare' => $share ? $this->id : 0]);
|
||||
if ($share === 0) {
|
||||
FileUser::deleteFileAll($this->id, $this->userid);
|
||||
}
|
||||
$list = self::wherePid($this->id)->get();
|
||||
if ($list->isNotEmpty()) {
|
||||
foreach ($list as $item) {
|
||||
$item->updataShare(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理重名
|
||||
* @return void
|
||||
*/
|
||||
public function handleDuplicateName()
|
||||
{
|
||||
$builder = self::wherePid($this->pid)->whereUserid($this->userid)->whereExt($this->ext);
|
||||
$exist = $builder->clone()->whereName($this->name)->exists();
|
||||
if (!$exist) {
|
||||
return; // 未重名,不需要处理
|
||||
}
|
||||
// 发现重名,自动重命名
|
||||
$nextNum = 2;
|
||||
if (preg_match("/(.*?)(\s+\(\d+\))*$/", $this->name)) {
|
||||
$preName = preg_replace("/(.*?)(\s+\(\d+\))*$/", "$1", $this->name);
|
||||
$nextNum = $builder->clone()->where("name", "LIKE", "{$preName}%")->count() + 1;
|
||||
}
|
||||
$newName = "{$this->name} ({$nextNum})";
|
||||
if ($builder->clone()->whereName($newName)->exists()) {
|
||||
$nextNum = rand(100, 9999);
|
||||
$newName = "{$this->name} ({$nextNum})";
|
||||
}
|
||||
$this->name = $newName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前更新pids/pshare
|
||||
* @return bool
|
||||
*/
|
||||
public function saveBeforePP()
|
||||
{
|
||||
$pid = $this->pid;
|
||||
$pshare = $this->share ? $this->id : 0;
|
||||
$array = [];
|
||||
while ($pid > 0) {
|
||||
$array[] = $pid;
|
||||
$file = self::select(['id', 'pid', 'share'])->find($pid);
|
||||
if ($file) {
|
||||
$pid = $file->pid;
|
||||
if ($file->share) {
|
||||
$pshare = $file->id;
|
||||
}
|
||||
} else {
|
||||
$pid = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
$opids = $this->pids;
|
||||
if ($array) {
|
||||
$array = array_values(array_reverse($array));
|
||||
$this->pids = ',' . implode(',', $array) . ',';
|
||||
} else {
|
||||
$this->pids = '';
|
||||
}
|
||||
$this->pshare = $pshare;
|
||||
if (!$this->save()) {
|
||||
return false;
|
||||
}
|
||||
// 更新子文件(夹)
|
||||
if ($opids != $this->pids) {
|
||||
self::wherePid($this->id)->chunkById(100, function ($lists) {
|
||||
/** @var self $item */
|
||||
foreach ($lists as $item) {
|
||||
$item->saveBeforePP();
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -161,6 +511,7 @@ class File extends AbstractModel
|
||||
AbstractModel::transaction(function () {
|
||||
$this->delete();
|
||||
$this->pushMsg('delete');
|
||||
FileUser::deleteFileAll($this->id);
|
||||
FileContent::whereFid($this->id)->delete();
|
||||
$list = self::wherePid($this->id)->get();
|
||||
if ($list->isNotEmpty()) {
|
||||
@@ -172,6 +523,50 @@ class File extends AbstractModel
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制删除文件
|
||||
* @return true
|
||||
*/
|
||||
public function forceDeleteFile()
|
||||
{
|
||||
AbstractModel::transaction(function () {
|
||||
$this->forceDelete();
|
||||
FileContent::withTrashed()
|
||||
->whereFid($this->id)
|
||||
->orderBy('id')
|
||||
->chunk(500, function ($contents) {
|
||||
/** @var FileContent $content */
|
||||
foreach ($contents as $content) {
|
||||
$content->forceDeleteContent();
|
||||
}
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件分享链接
|
||||
* @param $userid
|
||||
* @param $refresh
|
||||
* @return array
|
||||
*/
|
||||
public function getShareLink($userid, $refresh = false)
|
||||
{
|
||||
if ($this->type == 'folder') {
|
||||
throw new ApiException('文件夹不支持分享');
|
||||
}
|
||||
return FileLink::generateLink($this->id, $userid, $refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件名称加后缀
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNameAndExt()
|
||||
{
|
||||
return $this->ext ? "{$this->name}.{$this->ext}" : $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送消息
|
||||
* @param $action
|
||||
@@ -186,19 +581,7 @@ class File extends AbstractModel
|
||||
];
|
||||
}
|
||||
//
|
||||
if ($userid === null) {
|
||||
$userid = [$this->userid];
|
||||
if ($this->share == 1) {
|
||||
$builder = WebSocket::select(['userid']);
|
||||
if ($action == 'content') {
|
||||
$builder->wherePath('file/content/' . $this->id);
|
||||
}
|
||||
$userid = array_merge($userid, $builder->pluck('userid')->toArray());
|
||||
} elseif ($this->share == 2) {
|
||||
$userid = array_merge($userid, FileUser::whereFileId($this->id)->pluck('userid')->toArray());
|
||||
}
|
||||
$userid = array_values(array_filter(array_unique($userid)));
|
||||
}
|
||||
$userid = $this->pushUserid($action, $userid);
|
||||
if (empty($userid)) {
|
||||
return;
|
||||
}
|
||||
@@ -222,18 +605,340 @@ class File extends AbstractModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件并检测权限
|
||||
* @param $id
|
||||
* @param null $noExistTis
|
||||
* 获取推送会员
|
||||
* @param $action
|
||||
* @param $userid
|
||||
* @return array|int[]|mixed|null[]
|
||||
*/
|
||||
public function pushUserid($action, $userid = null) {
|
||||
$wherePath = "/manage/file";
|
||||
if ($userid === null) {
|
||||
$array = [$this->userid];
|
||||
if ($action == 'add' && $this->pid == 0) {
|
||||
return $array;
|
||||
}
|
||||
if ($action == 'content') {
|
||||
$wherePath = "/single/file/{$this->id}";
|
||||
} elseif ($this->pid > 0) {
|
||||
$wherePath = "/manage/file/{$this->pid}";
|
||||
} else {
|
||||
$tmpArray = FileUser::whereFileId($this->id)->pluck('userid')->toArray();
|
||||
if (empty($tmpArray)) {
|
||||
return $array;
|
||||
}
|
||||
if (!in_array(0, $tmpArray)) {
|
||||
return $tmpArray;
|
||||
}
|
||||
}
|
||||
$tmpArray = WebSocket::wherePath($wherePath)->pluck('userid')->toArray();
|
||||
if (empty($tmpArray)) {
|
||||
return $array;
|
||||
}
|
||||
$array = array_values(array_filter(array_unique(array_merge($array, $tmpArray))));
|
||||
} else {
|
||||
$array = is_array($userid) ? $userid : [$userid];
|
||||
if (in_array(0, $array)) {
|
||||
return WebSocket::wherePath($wherePath)->pluck('userid')->toArray();
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* code获取文件ID、名称
|
||||
* @param $code
|
||||
* @return File
|
||||
*/
|
||||
public static function allowFind($id, $noExistTis = null)
|
||||
public static function code2IdName($code) {
|
||||
$arr = explode(",", base64_decode($code));
|
||||
if (empty($arr)) {
|
||||
return null;
|
||||
}
|
||||
$fileId = intval($arr[0]);
|
||||
if (empty($fileId)) {
|
||||
return null;
|
||||
}
|
||||
return File::select(['id', 'name'])->find($fileId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理返回图片地址
|
||||
* @param array $item
|
||||
* @return array
|
||||
*/
|
||||
public static function handleImageUrl($item)
|
||||
{
|
||||
if (in_array($item['ext'], self::imageExt) ) {
|
||||
$content = Base::json2array(FileContent::whereFid($item['id'])->orderByDesc('id')->value('content'));
|
||||
if ($content) {
|
||||
$item['image_url'] = Base::fillUrl($content['url']);
|
||||
$item['image_width'] = intval($content['width']);
|
||||
$item['image_height'] = intval($content['height']);
|
||||
}
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件并检测权限
|
||||
* @param int $id
|
||||
* @param User|array|int $user 要求权限的用户,如:[0, 1]
|
||||
* @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者或创建者
|
||||
* @param int $permission
|
||||
* @return File
|
||||
*/
|
||||
public static function permissionFind(int $id, $user, int $limit = 0, int &$permission = -1)
|
||||
{
|
||||
$file = File::find($id);
|
||||
if (empty($file)) {
|
||||
throw new ApiException($noExistTis ?: '文件不存在或已被删除');
|
||||
throw new ApiException('文件不存在或已被删除');
|
||||
}
|
||||
//
|
||||
if ($user instanceof User) {
|
||||
$userids = $user->isTemp() ? [$user->userid] : [0, $user->userid];
|
||||
} else {
|
||||
$userids = is_array($user) ? $user : [$user];
|
||||
}
|
||||
$permission = $file->getPermission($userids);
|
||||
if ($permission < $limit) {
|
||||
$msg = match ($limit) {
|
||||
1000 => '仅限所有者或创建者操作',
|
||||
1 => '没有修改写入权限',
|
||||
default => '没有查看访问权限',
|
||||
};
|
||||
throw new ApiException($msg);
|
||||
}
|
||||
$file->exceAllow(User::userid());
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化内容数据
|
||||
* @param array $data [path, size, ext, name]
|
||||
* @return array
|
||||
*/
|
||||
public static function formatFileData(array $data)
|
||||
{
|
||||
$fileName = $data['name'];
|
||||
$filePath = $data['path'];
|
||||
$fileSize = $data['size'];
|
||||
$fileExt = $data['ext'];
|
||||
$publicPath = public_path($filePath);
|
||||
//
|
||||
switch ($fileExt) {
|
||||
case 'md':
|
||||
case 'text':
|
||||
// 文本
|
||||
$data['content'] = [
|
||||
'type' => $fileExt,
|
||||
'content' => file_get_contents($publicPath) ?: 'Content deleted',
|
||||
];
|
||||
$data['file_mode'] = $fileExt;
|
||||
break;
|
||||
|
||||
case 'drawio':
|
||||
// 图表
|
||||
$data['content'] = [
|
||||
'xml' => file_get_contents($publicPath)
|
||||
];
|
||||
$data['file_mode'] = $fileExt;
|
||||
break;
|
||||
|
||||
case 'mind':
|
||||
// 思维导图
|
||||
$data['content'] = Base::json2array(file_get_contents($publicPath));
|
||||
$data['file_mode'] = $fileExt;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (in_array($fileExt, self::codeExt) && $fileSize < 2 * 1024 * 1024)
|
||||
{
|
||||
// 文本预览,限制2M内的文件
|
||||
$data['content'] = [
|
||||
'content' => file_get_contents($publicPath) ?: 'Content deleted',
|
||||
];
|
||||
$data['file_mode'] = 'code';
|
||||
}
|
||||
elseif (in_array($fileExt, File::officeExt))
|
||||
{
|
||||
// office预览
|
||||
$data['content'] = json_decode('{}');
|
||||
$data['file_mode'] = 'office';
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他预览
|
||||
$name = Base::rightDelete($fileName, ".{$fileExt}") . ".{$fileExt}";
|
||||
$data['content'] = [
|
||||
'preview' => true,
|
||||
'name' => $name,
|
||||
'key' => urlencode(Base::urlAddparameter($filePath, [
|
||||
'name' => $name,
|
||||
'ext' => $fileExt
|
||||
])),
|
||||
];
|
||||
$data['file_mode'] = 'preview';
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移交文件
|
||||
* @param $originalUserid
|
||||
* @param $newUserid
|
||||
* @return void
|
||||
*/
|
||||
public static function transfer($originalUserid, $newUserid)
|
||||
{
|
||||
if (!self::whereUserid($originalUserid)->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建一个文件夹存放移交的文件
|
||||
$name = User::userid2nickname($originalUserid) ?: ('ID:' . $originalUserid);
|
||||
$file = File::createInstance([
|
||||
'pid' => 0,
|
||||
'name' => "【{$name}】移交的文件",
|
||||
'type' => "folder",
|
||||
'ext' => "",
|
||||
'userid' => $newUserid,
|
||||
'created_id' => 0,
|
||||
]);
|
||||
$file->handleDuplicateName();
|
||||
$file->saveBeforePP();
|
||||
|
||||
// 移交文件
|
||||
self::whereUserid($originalUserid)->chunkById(100, function($list) use ($file, $newUserid) {
|
||||
/** @var self $item */
|
||||
foreach ($list as $item) {
|
||||
if ($item->pid === 0) {
|
||||
$item->pid = $file->id;
|
||||
}
|
||||
$item->userid = $newUserid;
|
||||
$item->saveBeforePP();
|
||||
}
|
||||
});
|
||||
|
||||
// 移交文件权限
|
||||
FileUser::whereUserid($originalUserid)->chunkById(100, function ($list) use ($newUserid) {
|
||||
/** @var FileUser $item */
|
||||
foreach ($list as $item) {
|
||||
$row = FileUser::whereFileId($item->file_id)->whereUserid($newUserid)->first();
|
||||
if ($row) {
|
||||
// 已存在则删除原数据,判断改变已存在的数据
|
||||
$row->permission = max($row->permission, $item->permission);
|
||||
$row->save();
|
||||
$item->delete();
|
||||
} else {
|
||||
// 不存在则改变原数据
|
||||
$item->userid = $newUserid;
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件树并计算文件总大小
|
||||
*
|
||||
* @param int $fileId
|
||||
* @param User $user
|
||||
* @param int $permission 0-访问权限、1-读写权限、1000-所有者或创建者
|
||||
* @param string $path
|
||||
* @param int $totalSize
|
||||
* @return object
|
||||
*/
|
||||
public static function getFilesTree(int $fileId, User $user, $permission = 1, $path = '', &$totalSize = 0) {
|
||||
$file = File::permissionFind($fileId, $user, $permission);
|
||||
$file->path = ltrim($path . '/' . $file->name, '/');
|
||||
$file->children = [];
|
||||
if ($file->type == 'folder') {
|
||||
$files = $file->getFileList($user, $fileId, 'all', false);
|
||||
foreach ($files as &$childFile) {
|
||||
$childFile['path'] = $file->path . '/' . $childFile['name'];
|
||||
if ($childFile['type'] == 'folder') {
|
||||
$childFile['children'] = self::getFilesTree($childFile['id'], $user, $permission, $file->path, $totalSize);
|
||||
} else {
|
||||
$totalSize += $childFile['size'];
|
||||
}
|
||||
}
|
||||
$file->children = $files;
|
||||
} else {
|
||||
$totalSize += $file->size;
|
||||
}
|
||||
$file->totalSize = $totalSize;
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件夹文件添加到压缩文件
|
||||
*
|
||||
* @param \ZipArchive $zip
|
||||
* @param object $file
|
||||
* @return void
|
||||
*/
|
||||
public static function addFileTreeToZip($zip, $file)
|
||||
{
|
||||
if ($file->type != 'folder' && $file->name != '') {
|
||||
$content = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||||
$content = Base::json2array($content?->content ?: []);
|
||||
$typeExtensions = [
|
||||
'word' => 'docx',
|
||||
'excel' => 'xlsx',
|
||||
'ppt' => 'pptx',
|
||||
];
|
||||
if (array_key_exists($file->type, $typeExtensions)) {
|
||||
$filePath = empty($content) ? public_path('assets/office/empty.' . $typeExtensions[$file->type]) : public_path($content['url']);
|
||||
}
|
||||
//
|
||||
$relativePath = $file->path . '.' . $file->ext;
|
||||
if (file_exists($filePath)) {
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
} else {
|
||||
if (empty($content['url'])) {
|
||||
$zip->addFromString($relativePath, $content['content']);
|
||||
} else {
|
||||
$filePath = public_path($content['url']);
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isset($file->children)) {
|
||||
foreach ($file->children as $childFile) {
|
||||
try {
|
||||
self::addFileTreeToZip($zip, (object)$childFile);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 在压缩包中创建文件夹
|
||||
$zip->addEmptyDir($file->path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件推送消息
|
||||
* @param $action
|
||||
* @param array|null $data 发送内容
|
||||
* @param array $userid 会员ID
|
||||
*/
|
||||
public static function filePushMsg($action, $data = null, $userid = null)
|
||||
{
|
||||
$userid = User::auth()->userid();
|
||||
if (empty($userid)) {
|
||||
return;
|
||||
}
|
||||
$msg = [
|
||||
'type' => 'file',
|
||||
'action' => $action,
|
||||
'data' => $data,
|
||||
];
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'msg' => $msg
|
||||
];
|
||||
Task::deliver(new PushTask($params));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,10 @@ namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Response;
|
||||
|
||||
/**
|
||||
* Class FileContent
|
||||
* App\Models\FileContent
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $fid 文件ID
|
||||
* @property string|null $content 内容
|
||||
@@ -22,7 +20,7 @@ use Response;
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|FileContent onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereContent($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereCreatedAt($value)
|
||||
@@ -33,8 +31,8 @@ use Response;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereText($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent whereUserid($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|FileContent withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|FileContent withoutTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent withTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileContent withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class FileContent extends AbstractModel
|
||||
@@ -42,41 +40,111 @@ class FileContent extends AbstractModel
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* 获取格式内容
|
||||
* @param $type
|
||||
* @param $content
|
||||
* @return array|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
* 强制删除文件内容
|
||||
* @return void
|
||||
*/
|
||||
public static function formatContent($type, $content)
|
||||
public function forceDeleteContent()
|
||||
{
|
||||
$content = Base::json2array($content);
|
||||
if (in_array($type, ['word', 'excel', 'ppt'])) {
|
||||
if (empty($content)) {
|
||||
return Response::download(resource_path('assets/statics/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $type)));
|
||||
$this->forceDelete();
|
||||
$content = Base::json2array($this->content ?: []);
|
||||
if (str_starts_with($content['url'], 'uploads/')) {
|
||||
$path = public_path($content['url']);
|
||||
if (file_exists($path)) {
|
||||
@unlink($path);
|
||||
}
|
||||
return Response::download(public_path($content['url']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转预览地址
|
||||
* @param array $array
|
||||
* @return string
|
||||
*/
|
||||
public static function toPreviewUrl($array)
|
||||
{
|
||||
$fileExt = $array['ext'];
|
||||
$fileName = $array['name'];
|
||||
$filePath = $array['path'];
|
||||
$name = Base::rightDelete($fileName, ".{$fileExt}") . ".{$fileExt}";
|
||||
$key = urlencode(Base::urlAddparameter($filePath, [
|
||||
'name' => $name,
|
||||
'ext' => $fileExt
|
||||
]));
|
||||
return Base::fillUrl("online/preview/{$name}?key={$key}");
|
||||
}
|
||||
|
||||
/**
|
||||
* 转预览地址
|
||||
* @param File $file
|
||||
* @param $content
|
||||
* @return string
|
||||
*/
|
||||
public static function formatPreview($file, $content)
|
||||
{
|
||||
$content = Base::json2array($content ?: []);
|
||||
$filePath = $content['url'];
|
||||
if (in_array($file->type, ['word', 'excel', 'ppt'])) {
|
||||
if (empty($content)) {
|
||||
$filePath = 'assets/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $file->type);
|
||||
}
|
||||
}
|
||||
return self::toPreviewUrl([
|
||||
'ext' => $file->ext,
|
||||
'name' => $file->name,
|
||||
'path' => $filePath,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取格式内容(或下载)
|
||||
* @param File $file
|
||||
* @param $content
|
||||
* @param $download
|
||||
* @return array|\Symfony\Component\HttpFoundation\StreamedResponse
|
||||
*/
|
||||
public static function formatContent($file, $content, $download = false)
|
||||
{
|
||||
$name = $file->ext ? "{$file->name}.{$file->ext}" : null;
|
||||
$content = Base::json2array($content ?: []);
|
||||
if (in_array($file->type, ['word', 'excel', 'ppt'])) {
|
||||
if (empty($content)) {
|
||||
$filePath = public_path('assets/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $file->type));
|
||||
} else {
|
||||
$filePath = public_path($content['url']);
|
||||
}
|
||||
return Base::streamDownload($filePath, $name);
|
||||
}
|
||||
if (empty($content)) {
|
||||
switch ($type) {
|
||||
case 'document':
|
||||
$content = [
|
||||
"type" => "md",
|
||||
"content" => "",
|
||||
];
|
||||
break;
|
||||
|
||||
case 'sheet':
|
||||
$content = [
|
||||
[
|
||||
"name" => "Sheet1",
|
||||
"config" => json_decode('{}'),
|
||||
]
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
$content = json_decode('{}');
|
||||
break;
|
||||
$content = match ($file->type) {
|
||||
'document' => [
|
||||
"type" => $file->ext,
|
||||
"content" => "",
|
||||
],
|
||||
default => json_decode('{}'),
|
||||
};
|
||||
if ($download) {
|
||||
abort(403, "This file is empty.");
|
||||
}
|
||||
} else {
|
||||
$path = $content['url'];
|
||||
if ($file->ext) {
|
||||
$res = File::formatFileData([
|
||||
'path' => $path,
|
||||
'ext' => $file->ext,
|
||||
'size' => $file->size,
|
||||
'name' => $file->name,
|
||||
]);
|
||||
$content = $res['content'];
|
||||
} else {
|
||||
$content['preview'] = false;
|
||||
}
|
||||
if ($download) {
|
||||
$filePath = public_path($path);
|
||||
if (isset($filePath)) {
|
||||
return Base::streamDownload($filePath, $name);
|
||||
} else {
|
||||
abort(403, "This file not support download.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', [ 'content' => $content ]);
|
||||
|
||||
70
app/Models/FileLink.php
Normal file
70
app/Models/FileLink.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\FileLink
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $file_id 文件ID
|
||||
* @property int|null $num 累计访问
|
||||
* @property string|null $code 链接码
|
||||
* @property int|null $userid 会员ID
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\File|null $file
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereCode($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereFileId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereNum($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileLink whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class FileLink extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function file(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(File::class, 'id', 'file_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成链接
|
||||
* @param $fileId
|
||||
* @param $userid
|
||||
* @param $refresh
|
||||
* @return array
|
||||
*/
|
||||
public static function generateLink($fileId, $userid, $refresh = false)
|
||||
{
|
||||
$fileLink = FileLink::whereFileId($fileId)->whereUserid($userid)->first();
|
||||
if (empty($fileLink)) {
|
||||
$fileLink = FileLink::createInstance([
|
||||
'file_id' => $fileId,
|
||||
'userid' => $userid,
|
||||
'code' => base64_encode("{$fileId},{$userid}," . Base::generatePassword()),
|
||||
]);
|
||||
$fileLink->save();
|
||||
} else {
|
||||
if ($refresh == 'yes') {
|
||||
$fileLink->code = base64_encode("{$fileId},{$userid}," . Base::generatePassword());
|
||||
$fileLink->save();
|
||||
}
|
||||
}
|
||||
return [
|
||||
'id' => $fileId,
|
||||
'url' => Base::fillUrl('single/file/' . $fileLink->code),
|
||||
'code' => $fileLink->code,
|
||||
'num' => $fileLink->num
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ namespace App\Models;
|
||||
|
||||
|
||||
/**
|
||||
* Class FileUser
|
||||
* App\Models\FileUser
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $file_id 项目ID
|
||||
* @property int|null $userid 成员ID
|
||||
* @property int|null $permission 权限:0只读,1读写
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileUser newModelQuery()
|
||||
@@ -18,10 +18,41 @@ namespace App\Models;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereFileId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileUser wherePermission($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|FileUser whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class FileUser extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* 删除所有共享成员(同时删除成员分享的链接)
|
||||
* @param $file_id
|
||||
* @param int $retain_link_userid 保留指定会员的链接
|
||||
* @return mixed
|
||||
*/
|
||||
public static function deleteFileAll($file_id, $retain_link_userid = 0)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($retain_link_userid, $file_id) {
|
||||
if ($retain_link_userid > 0) {
|
||||
FileLink::whereFileId($file_id)->where('userid', '!=', $retain_link_userid)->delete();
|
||||
} else {
|
||||
FileLink::whereFileId($file_id)->delete();
|
||||
}
|
||||
FileUser::whereFileId($file_id)->delete();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 删除指定共享成员(同时删除成员分享的链接)
|
||||
* @param $file_id
|
||||
* @param $userid
|
||||
* @return mixed
|
||||
*/
|
||||
public static function deleteFileUser($file_id, $userid)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($userid, $file_id) {
|
||||
FileLink::whereFileId($file_id)->whereUserid($userid)->delete();
|
||||
return self::whereFileId($file_id)->whereUserid($userid)->delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
91
app/Models/Meeting.php
Normal file
91
app/Models/Meeting.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Cache;
|
||||
use App\Module\Base;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* App\Models\Meeting
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $meetingid 会议ID,不是数字
|
||||
* @property string|null $name 会议主题
|
||||
* @property string|null $channel 频道
|
||||
* @property int|null $userid 创建人
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
* @property string|null $end_at
|
||||
* @property Carbon|null $deleted_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereChannel($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereEndAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereMeetingid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Meeting whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Meeting extends AbstractModel
|
||||
{
|
||||
const CACHE_KEY = 'meeting_share_link_code';
|
||||
const CACHE_EXPIRED_TIME = 6; // 小时
|
||||
|
||||
/**
|
||||
* 获取分享链接
|
||||
* @return mixed
|
||||
*/
|
||||
public function getShareLink()
|
||||
{
|
||||
$code = base64_encode("{$this->meetingid}" . Base::generatePassword());
|
||||
Cache::put(self::CACHE_KEY.'_'.$code, [
|
||||
'id' => $this->id,
|
||||
'meetingid' => $this->meetingid,
|
||||
'channel' => $this->channel,
|
||||
], Carbon::now()->addHours(self::CACHE_EXPIRED_TIME));
|
||||
return Base::fillUrl("meeting/{$this->meetingid}/".$code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分享信息
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getShareInfo($code)
|
||||
{
|
||||
if(Cache::has(self::CACHE_KEY.'_'.$code)){
|
||||
return Cache::get(self::CACHE_KEY.'_'.$code);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存访客信息
|
||||
* @return mixed
|
||||
*/
|
||||
public static function setTouristInfo($data)
|
||||
{
|
||||
Cache::put(Meeting::CACHE_KEY.'_'.$data['uid'], [
|
||||
'uid' => $data['uid'],
|
||||
'userimg' => $data['userimg'],
|
||||
'nickname' => $data['nickname'],
|
||||
], Carbon::now()->addHours(self::CACHE_EXPIRED_TIME));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访客信息
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getTouristInfo($touristId)
|
||||
{
|
||||
if(Cache::has(Meeting::CACHE_KEY.'_'.$touristId)){
|
||||
return Cache::get(Meeting::CACHE_KEY.'_'.$touristId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3,43 +3,43 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\PushTask;
|
||||
use Arr;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Request;
|
||||
|
||||
/**
|
||||
* Class Project
|
||||
* App\Models\Project
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property string|null $name 名称
|
||||
* @property string|null $desc 描述、备注
|
||||
* @property int|null $userid 创建人
|
||||
* @property int|mixed $dialog_id 聊天会话ID
|
||||
* @property int|null $personal 是否个人项目
|
||||
* @property string|null $user_simple 成员总数|1,2,3
|
||||
* @property int|null $dialog_id 聊天会话ID
|
||||
* @property string|null $archived_at 归档时间
|
||||
* @property int|null $archived_userid 归档会员
|
||||
* @property int|null $is_fixed 是否固定
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property-read int $owner_userid
|
||||
* @property-read int $task_complete
|
||||
* @property-read int $task_my_complete
|
||||
* @property-read int $task_my_num
|
||||
* @property-read int $task_my_percent
|
||||
* @property-read int $task_num
|
||||
* @property-read int $task_percent
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectColumn[] $projectColumn
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectColumn> $projectColumn
|
||||
* @property-read int|null $project_column_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectLog[] $projectLog
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectLog> $projectLog
|
||||
* @property-read int|null $project_log_count
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectUser[] $projectUser
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectUser> $projectUser
|
||||
* @property-read int|null $project_user_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project allData($userid = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project authData($userid = null, $owner = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|Project onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereArchivedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereArchivedUserid($value)
|
||||
@@ -48,110 +48,28 @@ use Request;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereDesc($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereIsFixed($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project wherePersonal($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserSimple($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project whereUserid($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|Project withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|Project withoutTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project withTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|Project withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Project extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
const projectSelect = [
|
||||
'projects.*',
|
||||
'project_users.owner',
|
||||
protected $hidden = [
|
||||
'deleted_at',
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'task_num',
|
||||
'task_complete',
|
||||
'task_percent',
|
||||
'task_my_num',
|
||||
'task_my_complete',
|
||||
'task_my_percent',
|
||||
'owner_userid',
|
||||
];
|
||||
|
||||
/**
|
||||
* 生成任务数据
|
||||
*/
|
||||
private function generateTaskData()
|
||||
{
|
||||
if (!isset($this->appendattrs['task_num'])) {
|
||||
$builder = ProjectTask::whereProjectId($this->id)->whereParentId(0)->whereNull('archived_at');
|
||||
$this->appendattrs['task_num'] = $builder->count();
|
||||
$this->appendattrs['task_complete'] = $builder->whereNotNull('complete_at')->count();
|
||||
$this->appendattrs['task_percent'] = $this->appendattrs['task_num'] ? intval($this->appendattrs['task_complete'] / $this->appendattrs['task_num'] * 100) : 0;
|
||||
//
|
||||
$builder = ProjectTask::whereProjectId($this->id)->whereParentId(0)->authData(User::userid())->whereNull('archived_at');
|
||||
$this->appendattrs['task_my_num'] = $builder->count();
|
||||
$this->appendattrs['task_my_complete'] = $builder->whereNotNull('complete_at')->count();
|
||||
$this->appendattrs['task_my_percent'] = $this->appendattrs['task_my_num'] ? intval($this->appendattrs['task_my_complete'] / $this->appendattrs['task_my_num'] * 100) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务数量
|
||||
* @return int
|
||||
*/
|
||||
public function getTaskNumAttribute()
|
||||
{
|
||||
$this->generateTaskData();
|
||||
return $this->appendattrs['task_num'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务完成数量
|
||||
* @return int
|
||||
*/
|
||||
public function getTaskCompleteAttribute()
|
||||
{
|
||||
$this->generateTaskData();
|
||||
return $this->appendattrs['task_complete'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务完成率
|
||||
* @return int
|
||||
*/
|
||||
public function getTaskPercentAttribute()
|
||||
{
|
||||
$this->generateTaskData();
|
||||
return $this->appendattrs['task_percent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务数量(我的)
|
||||
* @return int
|
||||
*/
|
||||
public function getTaskMyNumAttribute()
|
||||
{
|
||||
$this->generateTaskData();
|
||||
return $this->appendattrs['task_my_num'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务完成数量(我的)
|
||||
* @return int
|
||||
*/
|
||||
public function getTaskMyCompleteAttribute()
|
||||
{
|
||||
$this->generateTaskData();
|
||||
return $this->appendattrs['task_my_complete'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务完成率(我的)
|
||||
* @return int
|
||||
*/
|
||||
public function getTaskMyPercentAttribute()
|
||||
{
|
||||
$this->generateTaskData();
|
||||
return $this->appendattrs['task_my_percent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 负责人会员ID
|
||||
* @return int
|
||||
@@ -159,7 +77,7 @@ class Project extends AbstractModel
|
||||
public function getOwnerUseridAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['owner_userid'])) {
|
||||
$ownerUser = $this->projectUser->where('owner', 1)->first();
|
||||
$ownerUser = ProjectUser::whereProjectId($this->id)->whereOwner(1)->first();
|
||||
$this->appendattrs['owner_userid'] = $ownerUser ? $ownerUser->userid : 0;
|
||||
}
|
||||
return $this->appendattrs['owner_userid'];
|
||||
@@ -190,19 +108,73 @@ class Project extends AbstractModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询自己的项目
|
||||
* 查询所有项目(与正常查询多返回owner字段)
|
||||
* @param self $query
|
||||
* @param null $userid
|
||||
* @return self
|
||||
*/
|
||||
public function scopeAuthData($query, $userid = null)
|
||||
public function scopeAllData($query, $userid = null)
|
||||
{
|
||||
$userid = $userid ?: User::userid();
|
||||
$query->join('project_users', 'projects.id', '=', 'project_users.project_id')
|
||||
->where('project_users.userid', $userid);
|
||||
$query
|
||||
->select([
|
||||
'projects.*',
|
||||
'project_users.owner',
|
||||
'project_users.top_at',
|
||||
])
|
||||
->leftJoin('project_users', function ($leftJoin) use ($userid) {
|
||||
$leftJoin
|
||||
->on('project_users.userid', '=', DB::raw($userid))
|
||||
->on('projects.id', '=', 'project_users.project_id');
|
||||
});
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询自己负责或参与的项目
|
||||
* @param self $query
|
||||
* @param null $userid
|
||||
* @param null $owner
|
||||
* @return self
|
||||
*/
|
||||
public function scopeAuthData($query, $userid = null, $owner = null)
|
||||
{
|
||||
$userid = $userid ?: User::userid();
|
||||
$query
|
||||
->select([
|
||||
'projects.*',
|
||||
'project_users.owner',
|
||||
'project_users.top_at',
|
||||
])
|
||||
->join('project_users', 'projects.id', '=', 'project_users.project_id')
|
||||
->where('project_users.userid', $userid);
|
||||
if ($owner !== null) {
|
||||
$query->where('project_users.owner', $owner);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务统计数据
|
||||
* @param $userid
|
||||
* @return array
|
||||
*/
|
||||
public function getTaskStatistics($userid)
|
||||
{
|
||||
$array = [];
|
||||
$builder = ProjectTask::whereProjectId($this->id)->whereNull('archived_at');
|
||||
$array['task_num'] = $builder->count();
|
||||
$array['task_complete'] = $builder->whereNotNull('complete_at')->count();
|
||||
$array['task_percent'] = $array['task_num'] ? intval($array['task_complete'] / $array['task_num'] * 100) : 0;
|
||||
//
|
||||
$builder = ProjectTask::authData($userid, 1)->where('project_tasks.project_id', $this->id)->whereNull('project_tasks.archived_at');
|
||||
$array['task_my_num'] = $builder->count();
|
||||
$array['task_my_complete'] = $builder->whereNotNull('project_tasks.complete_at')->count();
|
||||
$array['task_my_percent'] = $array['task_my_num'] ? intval($array['task_my_complete'] / $array['task_my_num'] * 100) : 0;
|
||||
//
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入项目
|
||||
* @param int $userid 加入的会员ID
|
||||
@@ -237,9 +209,11 @@ class Project extends AbstractModel
|
||||
WebSocketDialogUser::updateInsert([
|
||||
'dialog_id' => $this->dialog_id,
|
||||
'userid' => $userid,
|
||||
], [
|
||||
'important' => 1
|
||||
]);
|
||||
}
|
||||
WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->delete();
|
||||
WebSocketDialogUser::whereDialogId($this->dialog_id)->whereNotIn('userid', $userids)->whereImportant(1)->remove();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -249,7 +223,7 @@ class Project extends AbstractModel
|
||||
*/
|
||||
public function relationUserids()
|
||||
{
|
||||
return $this->projectUser->pluck('userid')->toArray();
|
||||
return ProjectUser::whereProjectId($this->id)->orderBy('id')->pluck('userid')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,7 +241,7 @@ class Project extends AbstractModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 归档任务、取消归档
|
||||
* 归档项目、取消归档
|
||||
* @param Carbon|null $archived_at 归档时间
|
||||
* @return bool
|
||||
*/
|
||||
@@ -277,14 +251,23 @@ class Project extends AbstractModel
|
||||
if ($archived_at === null) {
|
||||
// 取消归档
|
||||
$this->archived_at = null;
|
||||
$this->archived_userid = User::userid();
|
||||
$this->addLog("项目取消归档");
|
||||
$this->pushMsg('add', $this);
|
||||
$this->pushMsg('recovery', $this);
|
||||
ProjectTask::whereProjectId($this->id)->whereArchivedFollow(1)->change([
|
||||
'archived_at' => null,
|
||||
'archived_follow' => 0
|
||||
]);
|
||||
} else {
|
||||
// 归档任务
|
||||
// 归档项目
|
||||
$this->archived_at = $archived_at;
|
||||
$this->archived_userid = User::userid();
|
||||
$this->addLog("项目归档");
|
||||
$this->pushMsg('archived');
|
||||
ProjectTask::whereProjectId($this->id)->whereArchivedAt(null)->change([
|
||||
'archived_at' => $archived_at,
|
||||
'archived_follow' => 1
|
||||
]);
|
||||
}
|
||||
$this->save();
|
||||
});
|
||||
@@ -299,9 +282,7 @@ class Project extends AbstractModel
|
||||
{
|
||||
AbstractModel::transaction(function () {
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
if ($dialog) {
|
||||
$dialog->deleteDialog();
|
||||
}
|
||||
$dialog?->deleteDialog();
|
||||
$columns = ProjectColumn::whereProjectId($this->id)->get();
|
||||
foreach ($columns as $column) {
|
||||
$column->deleteColumn(false);
|
||||
@@ -316,18 +297,23 @@ class Project extends AbstractModel
|
||||
/**
|
||||
* 添加项目日志
|
||||
* @param string $detail
|
||||
* @param array $record
|
||||
* @param int $userid
|
||||
* @return ProjectLog
|
||||
*/
|
||||
public function addLog($detail, $userid = 0)
|
||||
public function addLog($detail, $record = [], $userid = 0)
|
||||
{
|
||||
$log = ProjectLog::createInstance([
|
||||
$array = [
|
||||
'project_id' => $this->id,
|
||||
'column_id' => 0,
|
||||
'task_id' => 0,
|
||||
'userid' => $userid ?: User::userid(),
|
||||
'detail' => $detail,
|
||||
]);
|
||||
];
|
||||
if ($record) {
|
||||
$array['record'] = $record;
|
||||
}
|
||||
$log = ProjectLog::createInstance($array);
|
||||
$log->save();
|
||||
return $log;
|
||||
}
|
||||
@@ -335,45 +321,268 @@ class Project extends AbstractModel
|
||||
/**
|
||||
* 推送消息
|
||||
* @param string $action
|
||||
* @param array $data 发送内容,默认为[id=>项目ID]
|
||||
* @param array $userid 指定会员,默认为项目所有成员
|
||||
* @param array|self $data 发送内容,默认为[id=>项目ID]
|
||||
* @param array $userid 指定会员,默认为项目所有成员
|
||||
*/
|
||||
public function pushMsg($action, $data = null, $userid = null)
|
||||
{
|
||||
if ($data === null) {
|
||||
$data = ['id' => $this->id];
|
||||
} elseif ($data instanceof self) {
|
||||
$data = $data->toArray();
|
||||
}
|
||||
//
|
||||
$array = [$userid, []];
|
||||
if ($userid === null) {
|
||||
$userid = $this->relationUserids();
|
||||
$array[0] = $this->relationUserids();
|
||||
} elseif (!is_array($userid)) {
|
||||
$array[0] = [$userid];
|
||||
}
|
||||
//
|
||||
if (isset($data['owner'])) {
|
||||
$owners = ProjectUser::whereProjectId($data['id'])->whereOwner(1)->pluck('userid')->toArray();
|
||||
$array = [array_intersect($array[0], $owners), array_diff($array[0], $owners)];
|
||||
}
|
||||
//
|
||||
foreach ($array as $index => $item) {
|
||||
if ($index > 0) {
|
||||
$data['owner'] = 0;
|
||||
}
|
||||
$params = [
|
||||
'ignoreFd' => Request::header('fd'),
|
||||
'userid' => array_values($item),
|
||||
'msg' => [
|
||||
'type' => 'project',
|
||||
'action' => $action,
|
||||
'data' => $data,
|
||||
]
|
||||
];
|
||||
$task = new PushTask($params, false);
|
||||
Task::deliver($task);
|
||||
}
|
||||
$params = [
|
||||
'ignoreFd' => Request::header('fd'),
|
||||
'userid' => $userid,
|
||||
'msg' => [
|
||||
'type' => 'project',
|
||||
'action' => $action,
|
||||
'data' => $data,
|
||||
]
|
||||
];
|
||||
$task = new PushTask($params, false);
|
||||
Task::deliver($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户获取项目信息(用于判断会员是否存在项目内)
|
||||
* 添加工作流
|
||||
* @param $flows
|
||||
* @return mixed
|
||||
*/
|
||||
public function addFlow($flows)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($flows) {
|
||||
$projectFlow = ProjectFlow::whereProjectId($this->id)->first();
|
||||
if (empty($projectFlow)) {
|
||||
$projectFlow = ProjectFlow::createInstance([
|
||||
'project_id' => $this->id,
|
||||
'name' => 'Default'
|
||||
]);
|
||||
if (!$projectFlow->save()) {
|
||||
throw new ApiException('工作流创建失败');
|
||||
}
|
||||
}
|
||||
//
|
||||
$ids = [];
|
||||
$idc = [];
|
||||
$hasStart = false;
|
||||
$hasEnd = false;
|
||||
$upTaskList = [];
|
||||
foreach ($flows as $item) {
|
||||
$id = intval($item['id']);
|
||||
$turns = Base::arrayRetainInt($item['turns'] ?: [], true);
|
||||
$userids = Base::arrayRetainInt($item['userids'] ?: [], true);
|
||||
$usertype = trim($item['usertype']);
|
||||
$userlimit = intval($item['userlimit']);
|
||||
$columnid = intval($item['columnid']);
|
||||
if ($usertype == 'replace' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置流转模式时必须填写状态负责人");
|
||||
}
|
||||
if ($usertype == 'merge' && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置剔除模式时必须填写状态负责人");
|
||||
}
|
||||
if ($userlimit && empty($userids)) {
|
||||
throw new ApiException("状态[{$item['name']}]设置错误,设置限制负责人时必须填写状态负责人");
|
||||
}
|
||||
$flow = ProjectFlowItem::updateInsert([
|
||||
'id' => $id,
|
||||
'project_id' => $this->id,
|
||||
'flow_id' => $projectFlow->id,
|
||||
], [
|
||||
'name' => trim($item['name']),
|
||||
'status' => trim($item['status']),
|
||||
'sort' => intval($item['sort']),
|
||||
'turns' => $turns,
|
||||
'userids' => $userids,
|
||||
'usertype' => trim($item['usertype']),
|
||||
'userlimit' => $userlimit,
|
||||
'columnid' => $columnid,
|
||||
], [], $isInsert);
|
||||
if ($flow) {
|
||||
$ids[] = $flow->id;
|
||||
if ($flow->id != $id) {
|
||||
$idc[$id] = $flow->id;
|
||||
}
|
||||
if ($flow->status == 'start') {
|
||||
$hasStart = true;
|
||||
}
|
||||
if ($flow->status == 'end') {
|
||||
$hasEnd = true;
|
||||
}
|
||||
if (!$isInsert) {
|
||||
$upTaskList[$flow->id] = $flow->status . "|" . $flow->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$hasStart) {
|
||||
throw new ApiException('至少需要1个开始状态');
|
||||
}
|
||||
if (!$hasEnd) {
|
||||
throw new ApiException('至少需要1个结束状态');
|
||||
}
|
||||
ProjectFlowItem::whereFlowId($projectFlow->id)->whereNotIn('id', $ids)->chunk(100, function($list) {
|
||||
foreach ($list as $item) {
|
||||
$item->deleteFlowItem();
|
||||
}
|
||||
});
|
||||
//
|
||||
foreach ($upTaskList as $id => $value) {
|
||||
ProjectTask::whereFlowItemId($id)->change([
|
||||
'flow_item_name' => $value
|
||||
]);
|
||||
}
|
||||
//
|
||||
$projectFlow = ProjectFlow::with(['projectFlowItem'])->whereProjectId($this->id)->find($projectFlow->id);
|
||||
$itemIds = $projectFlow->projectFlowItem->pluck('id')->toArray();
|
||||
foreach ($projectFlow->projectFlowItem as $item) {
|
||||
$turns = $item->turns;
|
||||
foreach ($idc as $oid => $nid) {
|
||||
if (in_array($oid, $turns)) {
|
||||
$turns = array_diff($turns, [$oid]);
|
||||
$turns[] = $nid;
|
||||
}
|
||||
}
|
||||
if (!in_array($item->id, $turns)) {
|
||||
$turns[] = $item->id;
|
||||
}
|
||||
$turns = array_values(array_filter(array_unique(array_intersect($turns, $itemIds))));
|
||||
sort($turns);
|
||||
$item->turns = $turns;
|
||||
ProjectFlowItem::whereId($item->id)->update([ 'turns' => Base::array2json($turns) ]);
|
||||
}
|
||||
return $projectFlow;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
* @param $params
|
||||
* - name 项目名称
|
||||
* - desc
|
||||
* - flow
|
||||
* - personal
|
||||
* - columns
|
||||
* @return array
|
||||
*/
|
||||
public static function createProject($params, $userid)
|
||||
{
|
||||
$name = trim(Arr::get($params, 'name', ''));
|
||||
$desc = trim(Arr::get($params, 'desc', ''));
|
||||
$flow = trim(Arr::get($params, 'flow', 'close'));
|
||||
$isPersonal = intval(Arr::get($params, 'personal'));
|
||||
if (mb_strlen($name) < 2) {
|
||||
return Base::retError('项目名称不可以少于2个字');
|
||||
} elseif (mb_strlen($name) > 32) {
|
||||
return Base::retError('项目名称最多只能设置32个字');
|
||||
}
|
||||
if (mb_strlen($desc) > 255) {
|
||||
return Base::retError('项目介绍最多只能设置255个字');
|
||||
}
|
||||
// 列表
|
||||
$columns = explode(",", Arr::get($params, 'columns'));
|
||||
$insertColumns = [];
|
||||
$sort = 0;
|
||||
foreach ($columns AS $column) {
|
||||
$column = trim($column);
|
||||
if ($column) {
|
||||
$insertColumns[] = [
|
||||
'name' => $column,
|
||||
'sort' => $sort++,
|
||||
];
|
||||
}
|
||||
}
|
||||
if (empty($insertColumns)) {
|
||||
$insertColumns[] = [
|
||||
'name' => 'Default',
|
||||
'sort' => 0,
|
||||
];
|
||||
}
|
||||
if (count($insertColumns) > 30) {
|
||||
return Base::retError('项目列表最多不能超过30个');
|
||||
}
|
||||
// 开始创建
|
||||
$project = Project::createInstance([
|
||||
'name' => $name,
|
||||
'desc' => $desc,
|
||||
'userid' => $userid,
|
||||
]);
|
||||
if ($isPersonal) {
|
||||
if (Project::whereUserid($userid)->wherePersonal(1)->exists()) {
|
||||
return Base::retError('个人项目已存在,无须重复创建');
|
||||
}
|
||||
$project->personal = 1;
|
||||
}
|
||||
AbstractModel::transaction(function() use ($flow, $insertColumns, $project) {
|
||||
$project->save();
|
||||
ProjectUser::createInstance([
|
||||
'project_id' => $project->id,
|
||||
'userid' => $project->userid,
|
||||
'owner' => 1,
|
||||
])->save();
|
||||
foreach ($insertColumns AS $column) {
|
||||
$column['project_id'] = $project->id;
|
||||
ProjectColumn::createInstance($column)->save();
|
||||
}
|
||||
$dialog = WebSocketDialog::createGroup($project->name, $project->userid, 'project');
|
||||
if (empty($dialog)) {
|
||||
throw new ApiException('创建项目聊天室失败');
|
||||
}
|
||||
$project->dialog_id = $dialog->id;
|
||||
$project->save();
|
||||
//
|
||||
if ($flow == 'open') {
|
||||
$project->addFlow(Base::json2array('[{"id":-10,"name":"待处理","status":"start","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0},{"id":-11,"name":"进行中","status":"progress","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0},{"id":-12,"name":"待测试","status":"test","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0},{"id":-13,"name":"已完成","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0},{"id":-14,"name":"已取消","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0,"columnid":0}]'));
|
||||
}
|
||||
});
|
||||
//
|
||||
$data = Project::find($project->id);
|
||||
$data->addLog("创建项目");
|
||||
$data->pushMsg('add', $data);
|
||||
return Base::retSuccess('添加成功', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目信息(用于判断会员是否存在项目内)
|
||||
* @param int $project_id
|
||||
* @param bool $ignoreArchived 排除已归档
|
||||
* @param null|bool $archived true:仅限未归档, false:仅限已归档, null:不限制
|
||||
* @param null|bool $mustOwner true:仅限项目负责人, false:仅限非项目负责人, null:不限制
|
||||
* @return self
|
||||
*/
|
||||
public static function userProject($project_id, $ignoreArchived = true)
|
||||
public static function userProject($project_id, $archived = true, $mustOwner = null)
|
||||
{
|
||||
$builder = self::select(self::projectSelect)->authData()->where('projects.id', intval($project_id));
|
||||
if ($ignoreArchived) {
|
||||
$builder->whereNull('projects.archived_at');
|
||||
}
|
||||
$project = $builder->first();
|
||||
$project = self::authData()->where('projects.id', intval($project_id))->first();
|
||||
if (empty($project)) {
|
||||
throw new ApiException('项目不存在或不在成员列表内');
|
||||
throw new ApiException('项目不存在或不在成员列表内', [ 'project_id' => $project_id ], -4001);
|
||||
}
|
||||
if ($archived === true && $project->archived_at != null) {
|
||||
throw new ApiException('项目已归档', [ 'project_id' => $project_id ], -4001);
|
||||
}
|
||||
if ($archived === false && $project->archived_at == null) {
|
||||
throw new ApiException('项目未归档', [ 'project_id' => $project_id ]);
|
||||
}
|
||||
if ($mustOwner === true && !$project->owner) {
|
||||
throw new ApiException('仅限项目负责人操作', [ 'project_id' => $project_id ]);
|
||||
}
|
||||
if ($mustOwner === false && $project->owner) {
|
||||
throw new ApiException('禁止项目负责人操作', [ 'project_id' => $project_id ]);
|
||||
}
|
||||
return $project;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,8 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Request;
|
||||
|
||||
/**
|
||||
* Class ProjectColumn
|
||||
* App\Models\ProjectColumn
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property string|null $name 列表名称
|
||||
@@ -21,11 +20,11 @@ use Request;
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property-read \App\Models\Project|null $project
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProjectTask[] $projectTask
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectTask> $projectTask
|
||||
* @property-read int|null $project_task_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectColumn onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereColor($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereCreatedAt($value)
|
||||
@@ -35,8 +34,8 @@ use Request;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereProjectId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereSort($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectColumn withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectColumn withoutTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn withTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectColumn withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectColumn extends AbstractModel
|
||||
@@ -69,7 +68,9 @@ class ProjectColumn extends AbstractModel
|
||||
AbstractModel::transaction(function () use ($pushMsg) {
|
||||
$tasks = ProjectTask::whereColumnId($this->id)->get();
|
||||
foreach ($tasks as $task) {
|
||||
$task->deleteTask($pushMsg);
|
||||
if(!$task->archived_at){
|
||||
$task->deleteTask($pushMsg);
|
||||
}
|
||||
}
|
||||
$this->delete();
|
||||
$this->addLog("删除列表:" . $this->name);
|
||||
@@ -120,7 +121,7 @@ class ProjectColumn extends AbstractModel
|
||||
$userid = $this->project->relationUserids();
|
||||
}
|
||||
$params = [
|
||||
'ignoreFd' => Request::header('fd'),
|
||||
'ignoreFd' => $action == 'recovery' ? 0 : Request::header('fd'),
|
||||
'userid' => $userid,
|
||||
'msg' => [
|
||||
'type' => 'projectColumn',
|
||||
|
||||
51
app/Models/ProjectFlow.php
Normal file
51
app/Models/ProjectFlow.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectFlow
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property string|null $name 流程名称
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ProjectFlowItem> $projectFlowItem
|
||||
* @property-read int|null $project_flow_item_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereProjectId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlow whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectFlow extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function projectFlowItem(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(ProjectFlowItem::class, 'flow_id', 'id')->orderBy('sort');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function deleteFlow()
|
||||
{
|
||||
return AbstractModel::transaction(function() {
|
||||
ProjectFlowItem::whereProjectId($this->project_id)->chunk(100, function($list) {
|
||||
foreach ($list as $item) {
|
||||
$item->deleteFlowItem();
|
||||
}
|
||||
});
|
||||
return $this->delete();
|
||||
});
|
||||
}
|
||||
}
|
||||
92
app/Models/ProjectFlowItem.php
Normal file
92
app/Models/ProjectFlowItem.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectFlowItem
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $flow_id 流程ID
|
||||
* @property string|null $name 名称
|
||||
* @property string|null $status 状态
|
||||
* @property array $turns 可流转
|
||||
* @property array $userids 状态负责人ID
|
||||
* @property string|null $usertype 流转模式
|
||||
* @property int|null $userlimit 限制负责人
|
||||
* @property int|null $columnid 对应的项目列表
|
||||
* @property int|null $sort 排序
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\ProjectFlow|null $projectFlow
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereColumnid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereFlowId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereProjectId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereSort($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereTurns($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUserids($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUserlimit($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectFlowItem whereUsertype($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectFlowItem extends AbstractModel
|
||||
{
|
||||
protected $hidden = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return array
|
||||
*/
|
||||
public function getTurnsAttribute($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
return Base::json2array($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return array
|
||||
*/
|
||||
public function getUseridsAttribute($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
return Base::json2array($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function projectFlow(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(ProjectFlow::class, 'id', 'flow_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|null
|
||||
*/
|
||||
public function deleteFlowItem()
|
||||
{
|
||||
ProjectTask::whereFlowItemId($this->id)->change([
|
||||
'flow_item_id' => 0,
|
||||
'flow_item_name' => "",
|
||||
]);
|
||||
return $this->delete();
|
||||
}
|
||||
}
|
||||
55
app/Models/ProjectInvite.php
Normal file
55
app/Models/ProjectInvite.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectInvite
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $num 累计邀请
|
||||
* @property string|null $code 链接码
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read bool $already
|
||||
* @property-read \App\Models\Project|null $project
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereCode($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereNum($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereProjectId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectInvite whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectInvite extends AbstractModel
|
||||
{
|
||||
protected $appends = [
|
||||
'already',
|
||||
];
|
||||
|
||||
/**
|
||||
* 是否已加入
|
||||
* @return bool
|
||||
*/
|
||||
public function getAlreadyAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['already'])) {
|
||||
$this->appendattrs['already'] = false;
|
||||
if (User::userid()) {
|
||||
$this->appendattrs['already'] = (bool)$this->project?->projectUser?->where('userid', User::userid())->count();
|
||||
}
|
||||
}
|
||||
return $this->appendattrs['already'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function project(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(Project::class, 'id', 'project_id');
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,21 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* Class ProjectLog
|
||||
* App\Models\ProjectLog
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $column_id 列表ID
|
||||
* @property int|null $task_id 项目ID
|
||||
* @property int|null $task_id 任务ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property string|null $detail 详细信息
|
||||
* @property array $record 记录数据
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\ProjectTask|null $projectTask
|
||||
* @property-read \App\Models\User|null $user
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog newQuery()
|
||||
@@ -23,6 +26,7 @@ namespace App\Models;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereDetail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereProjectId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereRecord($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereTaskId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectLog whereUserid($value)
|
||||
@@ -31,6 +35,18 @@ namespace App\Models;
|
||||
class ProjectLog extends AbstractModel
|
||||
{
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return array
|
||||
*/
|
||||
public function getRecordAttribute($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
return Base::json2array($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
@@ -39,4 +55,12 @@ class ProjectLog extends AbstractModel
|
||||
return $this->hasOne(User::class, 'userid', 'userid');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function projectTask(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(ProjectTask::class, 'id', 'task_id');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
202
app/Models/ProjectPermission.php
Normal file
202
app/Models/ProjectPermission.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectPermission
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property string|null $permissions 权限
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Project|null $project
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission wherePermissions($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission whereProjectId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectPermission whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectPermission extends AbstractModel
|
||||
{
|
||||
|
||||
const TASK_LIST_ADD = 'task_list_add'; // 添加列
|
||||
const TASK_LIST_UPDATE = 'task_list_update'; // 修改列
|
||||
const TASK_LIST_REMOVE = 'task_list_remove'; // 删除列
|
||||
const TASK_LIST_SORT = 'task_list_sort'; // 列表排序
|
||||
const TASK_ADD = 'task_add'; // 任务添加
|
||||
const TASK_UPDATE = 'task_update'; // 任务更新
|
||||
const TASK_STATUS = 'task_status'; // 任务状态
|
||||
const TASK_REMOVE = 'task_remove'; // 任务删除
|
||||
const TASK_ARCHIVED = 'task_archived'; // 任务归档
|
||||
const TASK_MOVE = 'task_move'; // 任务移动
|
||||
|
||||
// 权限列表
|
||||
const PERMISSIONS = [
|
||||
'project_leader' => 1, // 项目负责人
|
||||
'project_member' => 2, // 项目成员
|
||||
'task_leader' => 3, // 任务负责人
|
||||
'task_assist' => 4, // 任务协助人
|
||||
];
|
||||
|
||||
// 权限描述
|
||||
const PERMISSIONS_DESC = [
|
||||
1 => "项目负责人",
|
||||
2 => "项目成员",
|
||||
3 => "任务负责人",
|
||||
4 => "任务协助人",
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = ['project_id', 'permissions'];
|
||||
|
||||
/**
|
||||
* 权限
|
||||
* @param $value
|
||||
* @return string
|
||||
*/
|
||||
public function getPermissionsAttribute($value)
|
||||
{
|
||||
return Base::json2array($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限值
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param string $key
|
||||
* @return object|array
|
||||
*/
|
||||
public static function getPermission($projectId, $key = '')
|
||||
{
|
||||
$projectPermission = self::initPermissions($projectId);
|
||||
$currentPermissions = $projectPermission->permissions;
|
||||
if ($key) {
|
||||
if (!isset($currentPermissions[$key])) {
|
||||
throw new ApiException('项目权限设置不存在');
|
||||
}
|
||||
return $currentPermissions[$key];
|
||||
}
|
||||
return $projectPermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化项目权限
|
||||
*
|
||||
* @param int $projectId
|
||||
* @return ProjectPermission
|
||||
*/
|
||||
public static function initPermissions($projectId)
|
||||
{
|
||||
$permissions = [
|
||||
self::TASK_LIST_ADD => $projectTaskList = [self::PERMISSIONS['project_leader'], self::PERMISSIONS['project_member']],
|
||||
self::TASK_LIST_UPDATE => $projectTaskList,
|
||||
self::TASK_LIST_REMOVE => [self::PERMISSIONS['project_leader']],
|
||||
self::TASK_LIST_SORT => $projectTaskList,
|
||||
self::TASK_ADD => $projectTaskList,
|
||||
self::TASK_UPDATE => [self::PERMISSIONS['project_leader'], self::PERMISSIONS['task_leader'], self::PERMISSIONS['task_assist']],
|
||||
self::TASK_STATUS => $taskStatus = [self::PERMISSIONS['project_leader'], self::PERMISSIONS['task_leader']],
|
||||
self::TASK_REMOVE => $taskStatus,
|
||||
self::TASK_ARCHIVED => $taskStatus,
|
||||
self::TASK_MOVE => $taskStatus
|
||||
];
|
||||
return self::firstOrCreate(
|
||||
['project_id' => $projectId],
|
||||
['permissions' => Base::array2json($permissions)]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新项目权限
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param array $permissions
|
||||
* @return ProjectPermission
|
||||
*/
|
||||
public static function updatePermissions($projectId, $newPermissions)
|
||||
{
|
||||
$projectPermission = self::initPermissions($projectId);
|
||||
$currentPermissions = $projectPermission->permissions;
|
||||
$mergedPermissions = empty($newPermissions) ? $currentPermissions : array_merge($currentPermissions, $newPermissions);
|
||||
|
||||
$projectPermission->permissions = Base::array2json($mergedPermissions);
|
||||
$projectPermission->save();
|
||||
|
||||
return $projectPermission;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否有执行特定动作的权限
|
||||
* @param string $action 动作名称
|
||||
* @param Project $project 项目实例
|
||||
* @param ProjectTask $task 任务实例
|
||||
* @return bool
|
||||
*/
|
||||
public static function userTaskPermission(Project $project, $action, ProjectTask $task = null)
|
||||
{
|
||||
$userid = User::userid();
|
||||
$permissions = self::getPermission($project->id, $action);
|
||||
switch ($action) {
|
||||
// 任务添加,任务更新, 任务状态, 任务删除, 任务完成, 任务归档, 任务移动
|
||||
case self::TASK_LIST_ADD:
|
||||
case self::TASK_LIST_UPDATE:
|
||||
case self::TASK_LIST_REMOVE:
|
||||
case self::TASK_LIST_SORT:
|
||||
case self::TASK_ADD:
|
||||
case self::TASK_UPDATE:
|
||||
case self::TASK_STATUS:
|
||||
case self::TASK_REMOVE:
|
||||
case self::TASK_ARCHIVED:
|
||||
case self::TASK_MOVE:
|
||||
$verify = false;
|
||||
// 项目负责人
|
||||
if (in_array(self::PERMISSIONS['project_leader'], $permissions)) {
|
||||
if ($project->owner) {
|
||||
$verify = true;
|
||||
}
|
||||
}
|
||||
// 项目成员
|
||||
if (!$verify && in_array(self::PERMISSIONS['project_member'], $permissions)) {
|
||||
$user = ProjectUser::whereProjectId($project->id)->whereUserid(intval($userid))->first();
|
||||
if (!empty($user)) {
|
||||
$verify = true;
|
||||
}
|
||||
}
|
||||
// 任务负责人
|
||||
if (!$verify && $task && in_array(self::PERMISSIONS['task_leader'], $permissions)) {
|
||||
if ($task->isOwner()) {
|
||||
$verify = true;
|
||||
}
|
||||
}
|
||||
// 任务协助人
|
||||
if (!$verify && $task && in_array(self::PERMISSIONS['task_assist'], $permissions)) {
|
||||
if ($task->isAssister()) {
|
||||
$verify = true;
|
||||
}
|
||||
}
|
||||
//
|
||||
if (!$verify) {
|
||||
$desc = [];
|
||||
rsort($permissions);
|
||||
foreach ($permissions as $permission) {
|
||||
$desc[] = self::PERMISSIONS_DESC[$permission];
|
||||
}
|
||||
$desc = array_reverse($desc);
|
||||
throw new ApiException(sprintf("仅限%s操作", implode('、', $desc)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,10 +2,12 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
use App\Exceptions\ApiException;
|
||||
|
||||
/**
|
||||
* Class ProjectTaskContent
|
||||
* App\Models\ProjectTaskContent
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $task_id 任务ID
|
||||
@@ -29,4 +31,61 @@ class ProjectTaskContent extends AbstractModel
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取内容详情
|
||||
* @return array
|
||||
*/
|
||||
public function getContentInfo()
|
||||
{
|
||||
$content = Base::json2array($this->content);
|
||||
if (isset($content['url'])) {
|
||||
$filePath = public_path($content['url']);
|
||||
$array = $this->toArray();
|
||||
$array['content'] = file_get_contents($filePath) ?: '';
|
||||
if ($array['content']) {
|
||||
$replace = Base::fillUrl('uploads/task');
|
||||
$array['content'] = str_replace('{{RemoteURL}}uploads/task', $replace, $array['content']);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存任务详情至文件并返回文件路径
|
||||
* @param $task_id
|
||||
* @param $content
|
||||
* @return string
|
||||
*/
|
||||
public static function saveContent($task_id, $content)
|
||||
{
|
||||
@ini_set("pcre.backtrack_limit", 999999999);
|
||||
//
|
||||
$oldContent = $content;
|
||||
$path = 'uploads/task/content/' . date("Ym") . '/' . $task_id . '/';
|
||||
//
|
||||
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg|webp);base64,(.*?)\"/s", $content, $matchs);
|
||||
foreach ($matchs[2] as $key => $text) {
|
||||
$tmpPath = $path . 'attached/';
|
||||
Base::makeDir(public_path($tmpPath));
|
||||
$tmpPath .= md5($text) . "." . $matchs[1][$key];
|
||||
if (Base::saveContentImage(public_path($tmpPath), base64_decode($text))) {
|
||||
$paramet = getimagesize(public_path($tmpPath));
|
||||
$content = str_replace($matchs[0][$key], '<img src="{{RemoteURL}}' . $tmpPath . '" original-width="' . $paramet[0] . '" original-height="' . $paramet[1] . '"', $content);
|
||||
}
|
||||
}
|
||||
$pattern = '/<img(.*?)src=("|\')https*:\/\/(.*?)\/(uploads\/task\/content\/(.*?))\2/is';
|
||||
$content = preg_replace($pattern, '<img$1src=$2{{RemoteURL}}$4$2', $content);
|
||||
//
|
||||
$filePath = $path . md5($content);
|
||||
$publicPath = public_path($filePath);
|
||||
Base::makeDir(dirname($publicPath));
|
||||
$result = file_put_contents($publicPath, $content);
|
||||
if(!$result && $oldContent){
|
||||
throw new ApiException("保存任务详情至文件失败,请重试");
|
||||
}
|
||||
//
|
||||
return $filePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,25 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
use Cache;
|
||||
|
||||
/**
|
||||
* Class ProjectTaskFile
|
||||
* App\Models\ProjectTaskFile
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $task_id 任务ID
|
||||
* @property string|null $name 文件名称
|
||||
* @property int|null $size 文件大小(B)
|
||||
* @property string|null $ext 文件格式
|
||||
* @property string|null $path 文件地址
|
||||
* @property string|null $thumb 缩略图
|
||||
* @property string $path 文件地址
|
||||
* @property string $thumb 缩略图
|
||||
* @property int|null $userid 上传用户ID
|
||||
* @property int|null $download 下载次数
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read int $height
|
||||
* @property-read int $width
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFile newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFile newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFile query()
|
||||
@@ -39,6 +41,11 @@ use App\Module\Base;
|
||||
*/
|
||||
class ProjectTaskFile extends AbstractModel
|
||||
{
|
||||
protected $appends = [
|
||||
'width',
|
||||
'height',
|
||||
];
|
||||
|
||||
/**
|
||||
* 地址
|
||||
* @param $value
|
||||
@@ -58,4 +65,50 @@ class ProjectTaskFile extends AbstractModel
|
||||
{
|
||||
return Base::fillUrl($value ?: Base::extIcon($this->ext));
|
||||
}
|
||||
|
||||
/**
|
||||
* 宽
|
||||
* @return int
|
||||
*/
|
||||
public function getWidthAttribute()
|
||||
{
|
||||
$this->generateSizeData();
|
||||
return $this->appendattrs['width'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 高
|
||||
* @return int
|
||||
*/
|
||||
public function getHeightAttribute()
|
||||
{
|
||||
$this->generateSizeData();
|
||||
return $this->appendattrs['height'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成尺寸数据
|
||||
*/
|
||||
private function generateSizeData()
|
||||
{
|
||||
if (!isset($this->appendattrs['width'])) {
|
||||
$width = -1;
|
||||
$height = -1;
|
||||
if (in_array($this->ext, ['jpg', 'jpeg', 'webp', 'gif', 'png'])) {
|
||||
$path = public_path($this->getRawOriginal('path'));
|
||||
[$width, $height] = Cache::remember("File::size-" . md5($path), now()->addDays(7), function () use ($path) {
|
||||
$width = -1;
|
||||
$height = -1;
|
||||
if (file_exists($path)) {
|
||||
$paramet = getimagesize($path);
|
||||
$width = $paramet[0];
|
||||
$height = $paramet[1];
|
||||
}
|
||||
return [$width, $height];
|
||||
});
|
||||
}
|
||||
$this->appendattrs['width'] = $width;
|
||||
$this->appendattrs['height'] = $height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
34
app/Models/ProjectTaskFlowChange.php
Normal file
34
app/Models/ProjectTaskFlowChange.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectTaskFlowChange
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $task_id 任务ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property int|null $before_flow_item_id (变化前)工作流状态ID
|
||||
* @property string|null $before_flow_item_name (变化前)工作流状态名称
|
||||
* @property int|null $after_flow_item_id (变化后)工作流状态ID
|
||||
* @property string|null $after_flow_item_name (变化后)工作流状态名称
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereAfterFlowItemId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereAfterFlowItemName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereBeforeFlowItemId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereBeforeFlowItemName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereTaskId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskFlowChange whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectTaskFlowChange extends AbstractModel
|
||||
{
|
||||
|
||||
}
|
||||
39
app/Models/ProjectTaskPushLog.php
Normal file
39
app/Models/ProjectTaskPushLog.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectTaskPushLog
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 用户id
|
||||
* @property int|null $task_id 任务id
|
||||
* @property int|null $type 提醒类型:0 任务开始提醒,1 距离到期提醒,2到期超时提醒
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereTaskId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog whereUserid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog withTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskPushLog withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectTaskPushLog extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
}
|
||||
@@ -3,9 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* Class ProjectTaskTag
|
||||
* App\Models\ProjectTaskTag
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $task_id 任务ID
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* Class ProjectTaskUser
|
||||
* App\Models\ProjectTaskUser
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $task_id 任务ID
|
||||
@@ -14,6 +13,7 @@ namespace App\Models;
|
||||
* @property int|null $owner 是否任务负责人
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\ProjectTask|null $projectTask
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskUser newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskUser newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskUser query()
|
||||
@@ -30,4 +30,45 @@ namespace App\Models;
|
||||
class ProjectTaskUser extends AbstractModel
|
||||
{
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function projectTask(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(ProjectTask::class, 'id', 'task_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 移交任务身份
|
||||
* @param $originalUserid
|
||||
* @param $newUserid
|
||||
* @return void
|
||||
*/
|
||||
public static function transfer($originalUserid, $newUserid)
|
||||
{
|
||||
self::whereUserid($originalUserid)->chunk(100, function ($list) use ($originalUserid, $newUserid) {
|
||||
$tastIds = [];
|
||||
/** @var self $item */
|
||||
foreach ($list as $item) {
|
||||
$row = self::whereTaskId($item->task_id)->whereUserid($newUserid)->first();
|
||||
if ($row) {
|
||||
// 已存在则删除原数据,判断改变已存在的数据
|
||||
$row->owner = max($row->owner, $item->owner);
|
||||
$row->save();
|
||||
$item->delete();
|
||||
} else {
|
||||
// 不存在则改变原数据
|
||||
$item->userid = $newUserid;
|
||||
$item->save();
|
||||
}
|
||||
if ($item->projectTask) {
|
||||
$item->projectTask->addLog("移交{任务}身份", ['userid' => [$originalUserid, ' => ', $newUserid]]);
|
||||
if (!in_array($item->task_pid, $tastIds)) {
|
||||
$tastIds[] = $item->task_pid;
|
||||
$item->projectTask->syncDialogUser();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* Class ProjectUser
|
||||
* App\Models\ProjectUser
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $project_id 项目ID
|
||||
* @property int|null $userid 成员ID
|
||||
* @property int|null $owner 是否负责人
|
||||
* @property string|null $top_at 置顶时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\Project|null $project
|
||||
@@ -22,6 +20,7 @@ use App\Module\Base;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereOwner($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereProjectId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereTopAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectUser whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
@@ -37,17 +36,59 @@ class ProjectUser extends AbstractModel
|
||||
return $this->hasOne(Project::class, 'id', 'project_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 移交项目身份
|
||||
* @param $originalUserid
|
||||
* @param $newUserid
|
||||
* @return void
|
||||
*/
|
||||
public static function transfer($originalUserid, $newUserid)
|
||||
{
|
||||
self::whereUserid($originalUserid)->chunkById(100, function ($list) use ($originalUserid, $newUserid) {
|
||||
/** @var self $item */
|
||||
foreach ($list as $item) {
|
||||
$row = self::whereProjectId($item->project_id)->whereUserid($newUserid)->first();
|
||||
if ($row) {
|
||||
// 已存在则删除原数据,判断改变已存在的数据
|
||||
$row->owner = max($row->owner, $item->owner);
|
||||
$row->save();
|
||||
$item->delete();
|
||||
} else {
|
||||
// 不存在则改变原数据
|
||||
$item->userid = $newUserid;
|
||||
$item->save();
|
||||
}
|
||||
if ($item->project) {
|
||||
if ($item->project->personal) {
|
||||
$name = User::userid2nickname($originalUserid) ?: ('ID:' . $originalUserid);
|
||||
$item->project->name = "【{$name}】{$item->project->name}";
|
||||
$item->project->save();
|
||||
}
|
||||
$item->project->addLog("移交项目身份", ['userid' => [$originalUserid, ' => ', $newUserid]]);
|
||||
$item->project->syncDialogUser();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出项目
|
||||
*/
|
||||
public function exitProject()
|
||||
{
|
||||
$tasks = ProjectTask::whereProjectId($this->project_id)->authData($this->userid)->get();
|
||||
foreach ($tasks as $task) {
|
||||
if (ProjectTaskUser::whereTaskId($task->id)->whereUserid($this->userid)->delete()) {
|
||||
$task->syncDialogUser();
|
||||
}
|
||||
}
|
||||
ProjectTaskUser::whereProjectId($this->project_id)
|
||||
->whereUserid($this->userid)
|
||||
->chunk(100, function ($list) {
|
||||
$tastIds = [];
|
||||
/** @var ProjectTaskUser $item */
|
||||
foreach ($list as $item) {
|
||||
$item->delete();
|
||||
if (!in_array($item->task_pid, $tastIds)) {
|
||||
$tastIds[] = $item->task_pid;
|
||||
$item->projectTask?->syncDialogUser();
|
||||
}
|
||||
}
|
||||
});
|
||||
$this->delete();
|
||||
}
|
||||
}
|
||||
|
||||
156
app/Models/Report.php
Normal file
156
app/Models/Report.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\Traits\Creator;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use JetBrains\PhpStorm\Pure;
|
||||
|
||||
/**
|
||||
* App\Models\Report
|
||||
*
|
||||
* @property int $id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property string $title 标题
|
||||
* @property string $type 汇报类型
|
||||
* @property int $userid
|
||||
* @property string $content
|
||||
* @property string $sign 汇报唯一标识
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ReportReceive> $Receives
|
||||
* @property-read int|null $receives_count
|
||||
* @property-read mixed $receives
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $receivesUser
|
||||
* @property-read int|null $receives_user_count
|
||||
* @property-read \App\Models\User|null $sendUser
|
||||
* @method static Builder|Report newModelQuery()
|
||||
* @method static Builder|Report newQuery()
|
||||
* @method static Builder|Report query()
|
||||
* @method static Builder|Report whereContent($value)
|
||||
* @method static Builder|Report whereCreatedAt($value)
|
||||
* @method static Builder|Report whereId($value)
|
||||
* @method static Builder|Report whereSign($value)
|
||||
* @method static Builder|Report whereTitle($value)
|
||||
* @method static Builder|Report whereType($value)
|
||||
* @method static Builder|Report whereUpdatedAt($value)
|
||||
* @method static Builder|Report whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Report extends AbstractModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
const WEEKLY = "weekly";
|
||||
const DAILY = "daily";
|
||||
|
||||
protected $fillable = [
|
||||
"title",
|
||||
"type",
|
||||
"userid",
|
||||
"content",
|
||||
];
|
||||
|
||||
protected $appends = [
|
||||
'receives',
|
||||
];
|
||||
|
||||
public function Receives(): HasMany
|
||||
{
|
||||
return $this->hasMany(ReportReceive::class, "rid");
|
||||
}
|
||||
|
||||
public function receivesUser(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, ReportReceive::class, "rid", "userid")
|
||||
->withPivot("receive_time", "read");
|
||||
}
|
||||
|
||||
public function sendUser()
|
||||
{
|
||||
return $this->hasOne(User::class, "userid", "userid");
|
||||
}
|
||||
|
||||
public function getTypeAttribute($value): string
|
||||
{
|
||||
return match ($value) {
|
||||
Report::WEEKLY => "周报",
|
||||
Report::DAILY => "日报",
|
||||
default => "",
|
||||
};
|
||||
}
|
||||
|
||||
public function getContentAttribute($value): string
|
||||
{
|
||||
return htmlspecialchars_decode($value);
|
||||
}
|
||||
|
||||
public function getReceivesAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['receives'])) {
|
||||
$this->appendattrs['receives'] = empty( $this->receivesUser ) ? [] : array_column($this->receivesUser->toArray(), "userid");
|
||||
}
|
||||
return $this->appendattrs['receives'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单条记录
|
||||
* @param $id
|
||||
* @return Report|Builder|Model|object|null
|
||||
* @throw ApiException
|
||||
*/
|
||||
public static function getOne($id)
|
||||
{
|
||||
$one = self::whereId($id)->first();
|
||||
if (empty($one))
|
||||
throw new ApiException("记录不存在");
|
||||
return $one;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后一条提交记录
|
||||
* @param User|null $user
|
||||
* @return Builder|Model|\Illuminate\Database\Query\Builder|object
|
||||
*/
|
||||
public static function getLastOne(User $user = null)
|
||||
{
|
||||
$user === null && $user = User::auth();
|
||||
$one = self::whereUserid($user->userid)->orderByDesc("created_at")->first();
|
||||
if ( empty($one) )
|
||||
throw new ApiException("记录不存在");
|
||||
return $one;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一标识
|
||||
* @param $type
|
||||
* @param $offset
|
||||
* @param Carbon|null $time
|
||||
* @return string
|
||||
*/
|
||||
public static function generateSign($type, $offset, Carbon $time = null): string
|
||||
{
|
||||
$user = User::auth();
|
||||
$now_dt = $time === null ? Carbon::now() : $time;
|
||||
$time_s = match ($type) {
|
||||
Report::WEEKLY => function() use ($now_dt, $offset) {
|
||||
// 如果设置了周期偏移量
|
||||
empty( $offset ) || $now_dt->subWeeks( abs( $offset ) );
|
||||
$now_dt->startOfWeek(); // 设置为当周第一天
|
||||
return $now_dt->year . $now_dt->weekOfYear;
|
||||
},
|
||||
Report::DAILY => function() use ($now_dt, $offset) {
|
||||
// 如果设置了周期偏移量
|
||||
empty( $offset ) || $now_dt->subDays( abs( $offset ) );
|
||||
return $now_dt->format("Ymd");
|
||||
},
|
||||
default => "",
|
||||
};
|
||||
return $user->userid . ( is_callable($time_s) ? $time_s() : "" );
|
||||
}
|
||||
}
|
||||
39
app/Models/ReportReceive.php
Normal file
39
app/Models/ReportReceive.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* App\Models\ReportReceive
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $rid
|
||||
* @property string|null $receive_time 接收时间
|
||||
* @property int $userid 接收人
|
||||
* @property int $read 是否已读
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereRead($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereReceiveTime($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereRid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ReportReceive whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ReportReceive extends AbstractModel
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
// 关闭时间戳自动写入
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
"rid",
|
||||
"receive_time",
|
||||
"userid",
|
||||
"read",
|
||||
];
|
||||
}
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* Class Setting
|
||||
* App\Models\Setting
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property string|null $name
|
||||
* @property string|null $desc 参数描述、备注
|
||||
@@ -25,5 +26,40 @@ namespace App\Models;
|
||||
*/
|
||||
class Setting extends AbstractModel
|
||||
{
|
||||
|
||||
/**
|
||||
* 验证邮箱地址(过滤忽略地址)
|
||||
* @param $array
|
||||
* @param \Closure $resultClosure
|
||||
* @param \Closure|null $emptyClosure
|
||||
* @return array|mixed
|
||||
*/
|
||||
public static function validateAddr($array, $resultClosure, $emptyClosure = null)
|
||||
{
|
||||
if (!is_array($array)) {
|
||||
$array = [$array];
|
||||
}
|
||||
$ignoreAddr = Base::settingFind('emailSetting', 'ignore_addr');
|
||||
$ignoreAddr = explode("\n", $ignoreAddr);
|
||||
$ignoreArray = ['admin@dootask.com', 'test@dootask.com'];
|
||||
foreach ($ignoreAddr as $item) {
|
||||
if (Base::isEmail($item)) {
|
||||
$ignoreArray[] = trim($item);
|
||||
}
|
||||
}
|
||||
if ($ignoreArray) {
|
||||
$array = array_diff($array, $ignoreArray);
|
||||
}
|
||||
if ($array) {
|
||||
if ($resultClosure instanceof \Closure) {
|
||||
foreach ($array as $value) {
|
||||
$resultClosure($value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($emptyClosure instanceof \Closure) {
|
||||
$emptyClosure();
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
||||
37
app/Models/TaskWorker.php
Normal file
37
app/Models/TaskWorker.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* App\Models\TaskWorker
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $args
|
||||
* @property string|null $error
|
||||
* @property string|null $start_at 开始时间
|
||||
* @property string|null $end_at 结束时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereArgs($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereEndAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereError($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereStartAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker withTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|TaskWorker withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class TaskWorker extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
}
|
||||
@@ -3,9 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* Class Tmp
|
||||
* App\Models\Tmp
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property string|null $name
|
||||
* @property string|null $value
|
||||
|
||||
171
app/Models/UmengAlias.php
Normal file
171
app/Models/UmengAlias.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Hedeqiang\UMeng\Android;
|
||||
use Hedeqiang\UMeng\IOS;
|
||||
|
||||
/**
|
||||
* App\Models\UmengAlias
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 会员ID
|
||||
* @property string|null $alias 别名
|
||||
* @property string|null $platform 平台类型
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereAlias($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias wherePlatform($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UmengAlias whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UmengAlias extends AbstractModel
|
||||
{
|
||||
protected $table = 'umeng_alias';
|
||||
|
||||
/**
|
||||
* 获取推送配置
|
||||
* @return array|false
|
||||
*/
|
||||
public static function getPushConfig()
|
||||
{
|
||||
$setting = Base::setting('appPushSetting');
|
||||
if ($setting['push'] !== 'open') {
|
||||
return false;
|
||||
}
|
||||
$config = [];
|
||||
if ($setting['ios_key']) {
|
||||
$config['iOS'] = [
|
||||
'appKey' => $setting['ios_key'],
|
||||
'appMasterSecret' => $setting['ios_secret'],
|
||||
'production_mode' => true,
|
||||
];
|
||||
}
|
||||
if ($setting['android_key']) {
|
||||
$config['Android'] = [
|
||||
'appKey' => $setting['android_key'],
|
||||
'appMasterSecret' => $setting['android_secret'],
|
||||
'production_mode' => true,
|
||||
];
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送消息
|
||||
* @param string $alias
|
||||
* @param string $platform
|
||||
* @param array $array [title, subtitle, body, description, extra, seconds, badge]
|
||||
* @return array|false
|
||||
*/
|
||||
public static function pushMsgToAlias($alias, $platform, $array)
|
||||
{
|
||||
$config = self::getPushConfig();
|
||||
if ($config === false) {
|
||||
return false;
|
||||
}
|
||||
//
|
||||
$title = $array['title'] ?: ''; // 标题
|
||||
$subtitle = $array['subtitle'] ?: ''; // 副标题(iOS)
|
||||
$body = $array['body'] ?: ''; // 通知内容
|
||||
$description = $array['description'] ?: 'no description'; // 描述
|
||||
$extra = is_array($array['extra']) ? $array['extra'] : []; // 额外参数
|
||||
$seconds = intval($array['seconds']) ?: 86400; // 有效时间(单位:秒)
|
||||
$badge = intval($array['badge']) ?: 0; // 角标数(iOS)
|
||||
//
|
||||
switch ($platform) {
|
||||
case 'ios':
|
||||
if (!isset($config['iOS'])) {
|
||||
return false;
|
||||
}
|
||||
$ios = new IOS($config);
|
||||
return $ios->send([
|
||||
'description' => $description,
|
||||
'payload' => array_merge([
|
||||
'aps' => [
|
||||
'alert' => [
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'body' => $body,
|
||||
],
|
||||
'sound' => 'default',
|
||||
'badge' => $badge,
|
||||
],
|
||||
], $extra),
|
||||
'type' => 'customizedcast',
|
||||
'alias_type' => 'userid',
|
||||
'alias' => $alias,
|
||||
'policy' => [
|
||||
'expire_time' => Carbon::now()->addSeconds($seconds)->toDateTimeString(),
|
||||
],
|
||||
]);
|
||||
|
||||
case 'android':
|
||||
if (!isset($config['Android'])) {
|
||||
return false;
|
||||
}
|
||||
$android = new Android($config);
|
||||
return $android->send([
|
||||
'description' => $description,
|
||||
'payload' => array_merge([
|
||||
'display_type' => 'notification',
|
||||
'body' => [
|
||||
'ticker' => $title,
|
||||
'text' => $body,
|
||||
'title' => $title,
|
||||
'after_open' => 'go_app',
|
||||
'play_sound' => true,
|
||||
],
|
||||
], $extra),
|
||||
'type' => 'customizedcast',
|
||||
'alias_type' => 'userid',
|
||||
'alias' => $alias,
|
||||
'mipush' => true,
|
||||
'mi_activity' => 'app.eeui.umeng.activity.MfrMessageActivity',
|
||||
'policy' => [
|
||||
'expire_time' => Carbon::now()->addSeconds($seconds)->toDateTimeString(),
|
||||
]
|
||||
]);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送给指定会员
|
||||
* @param array|int $userid
|
||||
* @param array $array
|
||||
* @return void
|
||||
*/
|
||||
public static function pushMsgToUserid($userid, $array)
|
||||
{
|
||||
$builder = self::select(['id', 'platform', 'alias', 'userid'])->where('updated_at', '>', Carbon::now()->subMonth());
|
||||
if (is_array($userid)) {
|
||||
$builder->whereIn('userid', $userid);
|
||||
} elseif (Base::isNumber($userid)) {
|
||||
$builder->whereUserid($userid);
|
||||
}
|
||||
$builder
|
||||
->orderByDesc('updated_at')
|
||||
->chunkById(100, function ($datas) use ($array) {
|
||||
$uids = $datas->groupBy('userid');
|
||||
foreach ($uids as $uid => $rows) {
|
||||
$array['badge'] = WebSocketDialogMsgRead::whereUserid($uid)->whereSilence(0)->whereReadAt(null)->count();
|
||||
$lists = $rows->take(5)->groupBy('platform'); // 每个会员最多推送5个别名
|
||||
foreach ($lists as $platform => $list) {
|
||||
$alias = $list->pluck('alias')->implode(',');
|
||||
self::pushMsgToAlias($alias, $platform, $array);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,20 @@ namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Class User
|
||||
* App\Models\User
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $userid
|
||||
* @property array $identity 身份
|
||||
* @property array $department 所属部门
|
||||
* @property string|null $az A-Z
|
||||
* @property string|null $pinyin 拼音(主要用于搜索)
|
||||
* @property string|null $email 邮箱
|
||||
* @property string|null $tel 联系电话
|
||||
* @property string $nickname 昵称
|
||||
* @property string|null $profession 职位/职称
|
||||
* @property string $userimg 头像
|
||||
@@ -29,16 +32,24 @@ use Carbon\Carbon;
|
||||
* @property string|null $line_at 最后在线时间(接口)
|
||||
* @property int|null $task_dialog_id 最后打开的任务会话ID
|
||||
* @property string|null $created_ip 注册IP
|
||||
* @property string|null $disable_at 禁用时间(离职时间)
|
||||
* @property int|null $email_verity 邮箱是否已验证
|
||||
* @property int|null $bot 是否机器人
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Database\Factories\UserFactory factory(...$parameters)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereAz($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereBot($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereChangepass($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedIp($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereDepartment($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereDisableAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerity($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereEncrypt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereIdentity($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereLastAt($value)
|
||||
@@ -48,8 +59,10 @@ use Carbon\Carbon;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereLoginNum($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereNickname($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User wherePassword($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User wherePinyin($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereProfession($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereTaskDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereTel($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|User whereUserimg($value)
|
||||
@@ -63,18 +76,11 @@ class User extends AbstractModel
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* 更新数据校验
|
||||
* @param array $param
|
||||
*/
|
||||
public function updateInstance(array $param)
|
||||
{
|
||||
parent::updateInstance($param);
|
||||
//
|
||||
if (isset($param['line_at']) && $this->userid) {
|
||||
Cache::put("User::online:" . $this->userid, time(), Carbon::now()->addSeconds(30));
|
||||
}
|
||||
}
|
||||
// 默认头像类型:auto自动生成,system系统默认
|
||||
public static $defaultAvatarMode = 'auto';
|
||||
|
||||
// 基本信息的字段
|
||||
public static $basicField = ['userid', 'email', 'nickname', 'profession', 'department', 'userimg', 'bot', 'az', 'pinyin', 'line_at', 'disable_at'];
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
@@ -93,11 +99,7 @@ class User extends AbstractModel
|
||||
*/
|
||||
public function getUserimgAttribute($value)
|
||||
{
|
||||
if ($value) {
|
||||
return Base::fillUrl($value);
|
||||
}
|
||||
$name = ($this->userid - 1) % 21 + 1;
|
||||
return url("images/avatar/default_{$name}.png");
|
||||
return self::getAvatar($this->userid, $value, $this->email, $this->nickname);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,13 +115,71 @@ class User extends AbstractModel
|
||||
return array_filter(is_array($value) ? $value : explode(",", trim($value, ",")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门
|
||||
* @param $value
|
||||
* @return array
|
||||
*/
|
||||
public function getDepartmentAttribute($value)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return [];
|
||||
}
|
||||
return array_filter(is_array($value) ? $value : Base::explodeInt($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所属部门名称
|
||||
* @return string
|
||||
*/
|
||||
public function getDepartmentName()
|
||||
{
|
||||
if (empty($this->department)) {
|
||||
return "";
|
||||
}
|
||||
$key = "UserDepartment::" . md5(Cache::get("UserDepartment::rand") . '-' . implode(',' , $this->department));
|
||||
$list = Cache::remember($key, now()->addMonth(), function() {
|
||||
$list = UserDepartment::select(['id', 'owner_userid', 'name'])->whereIn('id', $this->department)->take(10)->get();
|
||||
return $list->toArray();
|
||||
});
|
||||
$array = [];
|
||||
foreach ($list as $item) {
|
||||
$array[] = $item['name'] . ($item['owner_userid'] === $this->userid ? '(M)' : '');
|
||||
}
|
||||
return implode(', ', $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为部门负责人
|
||||
*/
|
||||
public function isDepartmentOwner()
|
||||
{
|
||||
return UserDepartment::where('owner_userid', $this->userid)->exists();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取机器人所有者
|
||||
* @return int|mixed
|
||||
*/
|
||||
public function getBotOwner()
|
||||
{
|
||||
if (!$this->bot) {
|
||||
return 0;
|
||||
}
|
||||
$key = "userBotOwner::" . $this->userid;
|
||||
return Cache::remember($key, now()->addMonth(), function() {
|
||||
return intval(UserBot::whereBotId($this->userid)->value('userid')) ?: $this->userid;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在线
|
||||
* @return bool
|
||||
*/
|
||||
public function getOnlineStatus()
|
||||
{
|
||||
$online = intval(Cache::get("User::online:" . $this->userid, 0));
|
||||
$online = $this->bot || Cache::get("User::online:" . $this->userid) === "on";
|
||||
if ($online) {
|
||||
return true;
|
||||
}
|
||||
@@ -127,9 +187,45 @@ class User extends AbstractModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否管理员
|
||||
* 返回是否LDAP用户
|
||||
* @return bool
|
||||
*/
|
||||
public function isLdap()
|
||||
{
|
||||
return in_array('ldap', $this->identity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否临时帐号
|
||||
* @return bool
|
||||
*/
|
||||
public function isTemp()
|
||||
{
|
||||
return in_array('temp', $this->identity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否禁用帐号(离职)
|
||||
* @return bool
|
||||
*/
|
||||
public function isDisable()
|
||||
{
|
||||
return in_array('disable', $this->identity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否管理员
|
||||
* @return bool
|
||||
*/
|
||||
public function isAdmin()
|
||||
{
|
||||
return in_array('admin', $this->identity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否管理员
|
||||
*/
|
||||
public function checkAdmin()
|
||||
{
|
||||
$this->identity('admin');
|
||||
}
|
||||
@@ -145,6 +241,54 @@ class User extends AbstractModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查环境是否允许
|
||||
* @param null $onlyUserid 仅指定会员
|
||||
*/
|
||||
public function checkSystem($onlyUserid = null)
|
||||
{
|
||||
if ($onlyUserid && $onlyUserid != $this->userid) {
|
||||
return;
|
||||
}
|
||||
if (env("PASSWORD_ADMIN") == 'disabled') {
|
||||
if ($this->userid == 1) {
|
||||
throw new ApiException('当前环境禁止此操作');
|
||||
}
|
||||
}
|
||||
if (env("PASSWORD_OWNER") == 'disabled') {
|
||||
throw new ApiException('当前环境禁止此操作');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会员
|
||||
* @param $reason
|
||||
* @return bool|null
|
||||
*/
|
||||
public function deleteUser($reason)
|
||||
{
|
||||
return AbstractModel::transaction(function () use ($reason) {
|
||||
// 删除原因
|
||||
$userDelete = UserDelete::createInstance([
|
||||
'operator' => User::userid(),
|
||||
'userid' => $this->userid,
|
||||
'email' => $this->email,
|
||||
'reason' => $reason,
|
||||
'cache' => array_merge($this->getRawOriginal(), [
|
||||
'department_name' => $this->getDepartmentName()
|
||||
])
|
||||
]);
|
||||
$userDelete->save();
|
||||
// 删除未读
|
||||
WebSocketDialogMsgRead::whereUserid($this->userid)->delete();
|
||||
// 删除待办
|
||||
WebSocketDialogMsgTodo::whereUserid($this->userid)->delete();
|
||||
// 删除邮箱验证记录
|
||||
UserEmailVerification::whereEmail($this->email)->delete();
|
||||
//
|
||||
return $this->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/** ***************************************************************************************** */
|
||||
/** ***************************************************************************************** */
|
||||
@@ -159,103 +303,47 @@ class User extends AbstractModel
|
||||
*/
|
||||
public static function reg($email, $password, $other = [])
|
||||
{
|
||||
//邮箱
|
||||
if (!Base::isMail($email)) {
|
||||
// 邮箱
|
||||
if (!Base::isEmail($email)) {
|
||||
throw new ApiException('请输入正确的邮箱地址');
|
||||
}
|
||||
if (User::email2userid($email) > 0) {
|
||||
$user = self::whereEmail($email)->first();
|
||||
if ($user) {
|
||||
$isRegVerify = Base::settingFind('emailSetting', 'reg_verify') === 'open';
|
||||
if ($isRegVerify && $user->email_verity === 0) {
|
||||
UserEmailVerification::userEmailSend($user);
|
||||
throw new ApiException('您的帐号已注册过,请验证邮箱', ['code' => 'email']);
|
||||
}
|
||||
throw new ApiException('邮箱地址已存在');
|
||||
}
|
||||
//密码
|
||||
if (strlen($password) < 6) {
|
||||
throw new ApiException('密码设置不能小于6位数');
|
||||
} elseif (strlen($password) > 32) {
|
||||
throw new ApiException('密码最多只能设置32位数');
|
||||
}
|
||||
//开始注册
|
||||
$encrypt = Base::generatePassword(6);
|
||||
$inArray = [
|
||||
'encrypt' => $encrypt,
|
||||
'email' => $email,
|
||||
'password' => Base::md52($password, $encrypt),
|
||||
'created_ip' => Base::getIp(),
|
||||
];
|
||||
// 密码
|
||||
self::passwordPolicy($password);
|
||||
// 开始注册
|
||||
$user = Doo::userCreate($email, $password);
|
||||
if ($other) {
|
||||
$inArray = array_merge($inArray, $other);
|
||||
$user->updateInstance($other);
|
||||
}
|
||||
$user->az = Base::getFirstCharter($user->nickname);
|
||||
$user->pinyin = Base::cn2pinyin($user->nickname);
|
||||
$user->created_ip = Base::getIp();
|
||||
if ($user->save()) {
|
||||
$setting = Base::setting('system');
|
||||
$reg_identity = $setting['reg_identity'] ?: 'normal';
|
||||
$all_group_autoin = $setting['all_group_autoin'] ?: 'yes';
|
||||
// 注册临时身份
|
||||
if ($reg_identity === 'temp') {
|
||||
$user->identity = Base::arrayImplode(array_merge(array_diff($user->identity, ['temp']), ['temp']));
|
||||
$user->save();
|
||||
}
|
||||
// 加入全员群组
|
||||
if ($all_group_autoin === 'yes') {
|
||||
$dialog = WebSocketDialog::whereGroupType('all')->orderByDesc('id')->first();
|
||||
$dialog?->joinGroup($user->userid, 0);
|
||||
}
|
||||
}
|
||||
$user = User::createInstance($inArray);
|
||||
$user->save();
|
||||
User::AZUpdate($user->userid);
|
||||
return $user->find($user->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮箱获取userid
|
||||
* @param $email
|
||||
* @return int
|
||||
*/
|
||||
public static function email2userid($email)
|
||||
{
|
||||
if (empty($email)) {
|
||||
return 0;
|
||||
}
|
||||
return intval(self::whereEmail($email)->value('userid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* token获取会员userid
|
||||
* @return int
|
||||
*/
|
||||
public static function token2userid()
|
||||
{
|
||||
return self::authFind('userid', Base::getToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* token获取会员邮箱
|
||||
* @return int
|
||||
*/
|
||||
public static function token2email()
|
||||
{
|
||||
return self::authFind('email', Base::getToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* token获取encrypt
|
||||
* @return mixed|string
|
||||
*/
|
||||
public static function token2encrypt()
|
||||
{
|
||||
return self::authFind('encrypt', Base::getToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token身份信息
|
||||
* @param $find
|
||||
* @param null $token
|
||||
* @return array|mixed|string
|
||||
*/
|
||||
public static function authFind($find, $token = null)
|
||||
{
|
||||
if ($token === null) {
|
||||
$token = Base::getToken();
|
||||
}
|
||||
list($userid, $email, $encrypt, $timestamp) = explode("#$", base64_decode($token) . "#$#$#$#$");
|
||||
$array = [
|
||||
'userid' => intval($userid),
|
||||
'email' => $email ?: '',
|
||||
'encrypt' => $encrypt ?: '',
|
||||
'timestamp' => intval($timestamp),
|
||||
];
|
||||
if (isset($array[$find])) {
|
||||
return $array[$find];
|
||||
}
|
||||
if ($find == 'all') {
|
||||
return $array;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取我的ID
|
||||
* @return int
|
||||
@@ -291,8 +379,7 @@ class User extends AbstractModel
|
||||
{
|
||||
$user = self::authInfo();
|
||||
if (!$user) {
|
||||
$authorization = Base::getToken();
|
||||
if ($authorization) {
|
||||
if (Base::token()) {
|
||||
throw new ApiException('身份已失效,请重新登录', [], -1);
|
||||
} else {
|
||||
throw new ApiException('请登录后继续...', [], -1);
|
||||
@@ -317,57 +404,49 @@ class User extends AbstractModel
|
||||
if (isset($_A["__static_auth"])) {
|
||||
return $_A["__static_auth"];
|
||||
}
|
||||
$authorization = Base::getToken();
|
||||
if ($authorization) {
|
||||
$authInfo = self::authFind('all', $authorization);
|
||||
if ($authInfo['userid'] > 0) {
|
||||
$loginValid = floatval(Base::settingFind('system', 'loginValid')) ?: 720;
|
||||
$loginValid *= 3600;
|
||||
if ($authInfo['timestamp'] + $loginValid > time()) {
|
||||
$row = self::whereUserid($authInfo['userid'])->whereEmail($authInfo['email'])->whereEncrypt($authInfo['encrypt'])->first();
|
||||
if ($row) {
|
||||
$upArray = [];
|
||||
if (Base::getIp() && $row->line_ip != Base::getIp()) {
|
||||
$upArray['line_ip'] = Base::getIp();
|
||||
}
|
||||
if (Carbon::parse($row->line_at)->addSeconds(30)->lt(Carbon::now())) {
|
||||
$upArray['line_at'] = Carbon::now();
|
||||
}
|
||||
if ($upArray) {
|
||||
$row->updateInstance($upArray);
|
||||
$row->save();
|
||||
}
|
||||
return $_A["__static_auth"] = $row;
|
||||
}
|
||||
}
|
||||
if (Doo::userId() > 0
|
||||
&& !Doo::userExpired()
|
||||
&& $user = self::whereUserid(Doo::userId())->whereEmail(Doo::userEmail())->whereEncrypt(Doo::userEncrypt())->first()) {
|
||||
$upArray = [];
|
||||
if (Base::getIp() && $user->line_ip != Base::getIp()) {
|
||||
$upArray['line_ip'] = Base::getIp();
|
||||
}
|
||||
if (Carbon::parse($user->line_at)->addSeconds(30)->lt(Carbon::now())) {
|
||||
$upArray['line_at'] = Carbon::now();
|
||||
}
|
||||
if ($upArray) {
|
||||
$user->updateInstance($upArray);
|
||||
$user->save();
|
||||
}
|
||||
return $_A["__static_auth"] = $user;
|
||||
}
|
||||
return $_A["__static_auth"] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成token
|
||||
* 生成 token
|
||||
* @param self $userinfo
|
||||
* @param bool $refresh 获取新的token
|
||||
* @return string
|
||||
*/
|
||||
public static function token($userinfo)
|
||||
public static function generateToken($userinfo, $refresh = false)
|
||||
{
|
||||
$userinfo->token = base64_encode($userinfo->userid . '#$' . $userinfo->email . '#$' . $userinfo->encrypt . '#$' . time() . '#$' . Base::generatePassword(6));
|
||||
if (!$refresh) {
|
||||
if (Doo::userId() != $userinfo->userid
|
||||
|| Doo::userEmail() != $userinfo->email
|
||||
|| Doo::userEncrypt() != $userinfo->encrypt) {
|
||||
$refresh = true;
|
||||
}
|
||||
}
|
||||
if ($refresh) {
|
||||
$days = $userinfo->bot ? 0 : max(1, intval(Base::settingFind('system', 'token_valid_days', 30)));
|
||||
$token = Doo::tokenEncode($userinfo->userid, $userinfo->email, $userinfo->encrypt, $days);
|
||||
} else {
|
||||
$token = Doo::userToken();
|
||||
}
|
||||
unset($userinfo->encrypt);
|
||||
unset($userinfo->password);
|
||||
return $userinfo->token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户权限(身份)
|
||||
* @param $identity
|
||||
* @param $userIdentity
|
||||
* @return bool
|
||||
*/
|
||||
public static function identityRaw($identity, $userIdentity)
|
||||
{
|
||||
$userIdentity = is_array($userIdentity) ? $userIdentity : explode(",", trim($userIdentity, ","));
|
||||
return $identity && in_array($identity, $userIdentity);
|
||||
return $userinfo->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -385,10 +464,10 @@ class User extends AbstractModel
|
||||
if (isset($_A["__static_userid2basic_" . $userid])) {
|
||||
return $_A["__static_userid2basic_" . $userid];
|
||||
}
|
||||
$fields = ['userid', 'email', 'nickname', 'profession', 'userimg'];
|
||||
$userInfo = self::whereUserid($userid)->select($fields)->first();
|
||||
$userInfo = self::whereUserid($userid)->select(User::$basicField)->first();
|
||||
if ($userInfo) {
|
||||
$userInfo->online = $userInfo->getOnlineStatus();
|
||||
$userInfo->department_name = $userInfo->getDepartmentName();
|
||||
}
|
||||
return $_A["__static_userid2basic_" . $userid] = ($userInfo ?: []);
|
||||
}
|
||||
@@ -401,21 +480,7 @@ class User extends AbstractModel
|
||||
*/
|
||||
public static function userid2nickname($userid)
|
||||
{
|
||||
$basic = self::userid2basic($userid);
|
||||
return $basic ? $basic->nickname : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新首字母
|
||||
* @param $userid
|
||||
*/
|
||||
public static function AZUpdate($userid)
|
||||
{
|
||||
$row = self::whereUserid($userid)->first();
|
||||
if ($row) {
|
||||
$row->az = Base::getFirstCharter($row->nickname);
|
||||
$row->save();
|
||||
}
|
||||
return self::userid2basic($userid)?->nickname ?: '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -441,4 +506,121 @@ class User extends AbstractModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取头像
|
||||
* @param $userid
|
||||
* @param $userimg
|
||||
* @param $email
|
||||
* @param $nickname
|
||||
* @return string
|
||||
*/
|
||||
public static function getAvatar($userid, $userimg, $email, $nickname)
|
||||
{
|
||||
// 自定义头像
|
||||
if ($userimg && !str_contains($userimg, 'avatar/')) {
|
||||
return Base::fillUrl($userimg);
|
||||
}
|
||||
// 机器人头像
|
||||
switch ($email) {
|
||||
case 'system-msg@bot.system':
|
||||
return url("images/avatar/default_system.png");
|
||||
case 'task-alert@bot.system':
|
||||
return url("images/avatar/default_task.png");
|
||||
case 'check-in@bot.system':
|
||||
return url("images/avatar/default_checkin.png");
|
||||
case 'anon-msg@bot.system':
|
||||
return url("images/avatar/default_anon.png");
|
||||
case 'approval-alert@bot.system':
|
||||
return url("images/avatar/default_approval.png");
|
||||
case 'okr-alert@bot.system':
|
||||
return url("images/avatar/default_okr.png");
|
||||
case 'ai-openai@bot.system':
|
||||
return url("images/avatar/default_openai.png");
|
||||
case 'ai-claude@bot.system':
|
||||
return url("images/avatar/default_claude.png");
|
||||
case 'bot-manager@bot.system':
|
||||
return url("images/avatar/default_bot.png");
|
||||
case 'meeting-alert@bot.system':
|
||||
return url("images/avatar/default_meeting.png");
|
||||
}
|
||||
// 生成文字头像
|
||||
if (self::$defaultAvatarMode === 'auto') {
|
||||
return url("avatar/" . urlencode($nickname) . ".png");
|
||||
}
|
||||
// 系统默认头像
|
||||
$name = ($userid - 1) % 21 + 1;
|
||||
return url("images/avatar/default_{$name}.png");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测密码策略是否符合
|
||||
* @param $password
|
||||
* @return void
|
||||
*/
|
||||
public static function passwordPolicy($password)
|
||||
{
|
||||
if (strlen($password) < 6) {
|
||||
throw new ApiException('密码设置不能小于6位数');
|
||||
}
|
||||
if (strlen($password) > 32) {
|
||||
throw new ApiException('密码最多只能设置32位数');
|
||||
}
|
||||
// 复杂密码
|
||||
$password_policy = Base::settingFind('system', 'password_policy');
|
||||
if ($password_policy == 'complex') {
|
||||
if (preg_match("/^[0-9]+$/", $password)) {
|
||||
throw new ApiException('密码不能全是数字,请包含数字,字母大小写或者特殊字符');
|
||||
}
|
||||
if (preg_match("/^[a-zA-Z]+$/", $password)) {
|
||||
throw new ApiException('密码不能全是字母,请包含数字,字母大小写或者特殊字符');
|
||||
}
|
||||
if (preg_match("/^[0-9A-Z]+$/", $password)) {
|
||||
throw new ApiException('密码不能全是数字+大写字母,密码包含数字,字母大小写或者特殊字符');
|
||||
}
|
||||
if (preg_match("/^[0-9a-z]+$/", $password)) {
|
||||
throw new ApiException('密码不能全是数字+小写字母,密码包含数字,字母大小写或者特殊字符');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取机器人或创建
|
||||
* @param $key
|
||||
* @param $update
|
||||
* @param $userid
|
||||
* @return self|null
|
||||
*/
|
||||
public static function botGetOrCreate($key, $update = [], $userid = 0)
|
||||
{
|
||||
$email = "{$key}@bot.system";
|
||||
$botUser = self::whereEmail($email)->first();
|
||||
if (empty($botUser)) {
|
||||
$botUser = Doo::userCreate($email, Base::generatePassword(32));
|
||||
if (empty($botUser)) {
|
||||
return null;
|
||||
}
|
||||
$botUser->updateInstance([
|
||||
'created_ip' => Base::getIp(),
|
||||
]);
|
||||
$botUser->save();
|
||||
if ($userid > 0) {
|
||||
UserBot::createInstance([
|
||||
'userid' => $userid,
|
||||
'bot_id' => $botUser->userid,
|
||||
])->save();
|
||||
}
|
||||
//
|
||||
$update['nickname'] = UserBot::systemBotName($email);
|
||||
}
|
||||
if ($update) {
|
||||
$botUser->updateInstance($update);
|
||||
if (isset($update['nickname'])) {
|
||||
$botUser->az = Base::getFirstCharter($botUser->nickname);
|
||||
$botUser->pinyin = Base::cn2pinyin($botUser->nickname);
|
||||
}
|
||||
$botUser->save();
|
||||
}
|
||||
return $botUser;
|
||||
}
|
||||
}
|
||||
|
||||
292
app/Models/UserBot.php
Normal file
292
app/Models/UserBot.php
Normal file
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Extranet;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* App\Models\UserBot
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 所属人ID
|
||||
* @property int|null $bot_id 机器人ID
|
||||
* @property int|null $clear_day 消息自动清理天数
|
||||
* @property string|null $clear_at 下一次清理时间
|
||||
* @property string|null $webhook_url 消息webhook地址
|
||||
* @property int|null $webhook_num 消息webhook请求次数
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereBotId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereClearAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereClearDay($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereUserid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereWebhookNum($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserBot whereWebhookUrl($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserBot extends AbstractModel
|
||||
{
|
||||
|
||||
/**
|
||||
* 系统机器人名称
|
||||
* @param $name string 邮箱 或 邮箱前缀
|
||||
* @return string
|
||||
*/
|
||||
public static function systemBotName($name)
|
||||
{
|
||||
if (str_contains($name, "@")) {
|
||||
$name = explode("@", $name)[0];
|
||||
}
|
||||
return match ($name) {
|
||||
'system-msg' => '系统消息',
|
||||
'task-alert' => '任务提醒',
|
||||
'check-in' => '签到打卡',
|
||||
'anon-msg' => '匿名消息',
|
||||
'approval-alert' => '审批',
|
||||
'ai-openai' => 'ChatGPT',
|
||||
'ai-claude' => 'Claude',
|
||||
'ai-wenxin' => '文心一言',
|
||||
'ai-qianwen' => '通义千问',
|
||||
'bot-manager' => '机器人管理',
|
||||
'meeting-alert' => '会议通知',
|
||||
'okr-alert' => 'OKR提醒',
|
||||
default => '', // 不是系统机器人时返回空(也可以拿来判断是否是系统机器人)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人菜单
|
||||
* @param $email
|
||||
* @return array|array[]
|
||||
*/
|
||||
public static function quickMsgs($email)
|
||||
{
|
||||
return match ($email) {
|
||||
'check-in@bot.system' => [
|
||||
[
|
||||
'key' => 'checkin',
|
||||
'label' => Doo::translate('我要打卡')
|
||||
], [
|
||||
'key' => 'it',
|
||||
'label' => Doo::translate('IT资讯')
|
||||
], [
|
||||
'key' => '36ke',
|
||||
'label' => Doo::translate('36氪')
|
||||
], [
|
||||
'key' => '60s',
|
||||
'label' => Doo::translate('60s读世界')
|
||||
], [
|
||||
'key' => 'joke',
|
||||
'label' => Doo::translate('开心笑话')
|
||||
], [
|
||||
'key' => 'soup',
|
||||
'label' => Doo::translate('心灵鸡汤')
|
||||
]
|
||||
],
|
||||
'anon-msg@bot.system' => [
|
||||
[
|
||||
'key' => 'help',
|
||||
'label' => Doo::translate('使用说明')
|
||||
], [
|
||||
'key' => 'privacy',
|
||||
'label' => Doo::translate('隐私说明')
|
||||
],
|
||||
],
|
||||
'bot-manager@bot.system' => [
|
||||
[
|
||||
'key' => '/help',
|
||||
'label' => Doo::translate('帮助指令')
|
||||
], [
|
||||
'key' => '/api',
|
||||
'label' => Doo::translate('Api接口文档')
|
||||
], [
|
||||
'key' => '/list',
|
||||
'label' => Doo::translate('我的机器人')
|
||||
],
|
||||
],
|
||||
'ai-openai@bot.system',
|
||||
'ai-claude@bot.system',
|
||||
'ai-wenxin@bot.system',
|
||||
'ai-qianwen@bot.system' => [
|
||||
[
|
||||
'key' => '%3A.clear',
|
||||
'label' => Doo::translate('清空上下文')
|
||||
]
|
||||
],
|
||||
default => [],
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到机器人
|
||||
* @param $command
|
||||
* @param $userid
|
||||
* @return string
|
||||
*/
|
||||
public static function checkinBotQuickMsg($command, $userid)
|
||||
{
|
||||
if (Cache::get("UserBot::checkinBotQuickMsg:{$userid}") === "yes") {
|
||||
return "操作频繁!";
|
||||
}
|
||||
Cache::put("UserBot::checkinBotQuickMsg:{$userid}", "yes", Carbon::now()->addSecond());
|
||||
//
|
||||
if ($command === 'checkin') {
|
||||
$setting = Base::setting('checkinSetting');
|
||||
if ($setting['open'] !== 'open') {
|
||||
return '暂未开启签到功能。';
|
||||
}
|
||||
if (!in_array('manual', $setting['modes'])) {
|
||||
return '暂未开放手动签到。';
|
||||
}
|
||||
if ($error = UserBot::checkinBotCheckin($userid, Base::time(), true)) {
|
||||
return $error;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
return Extranet::checkinBotQuickMsg($command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到机器人签到
|
||||
* @param $mac
|
||||
* @param $time
|
||||
* @param bool $alreadyTip 签到过是否提示
|
||||
* @return string|null 返回string表示错误信息,返回null表示签到成功
|
||||
*/
|
||||
public static function checkinBotCheckin($mac, $time, $alreadyTip = false)
|
||||
{
|
||||
$setting = Base::setting('checkinSetting');
|
||||
$times = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
|
||||
$advance = (intval($setting['advance']) ?: 120) * 60;
|
||||
$delay = (intval($setting['delay']) ?: 120) * 60;
|
||||
//
|
||||
$nowDate = date("Y-m-d");
|
||||
$nowTime = date("H:i:s");
|
||||
//
|
||||
$timeStart = strtotime("{$nowDate} {$times[0]}");
|
||||
$timeEnd = strtotime("{$nowDate} {$times[1]}");
|
||||
$timeAdvance = max($timeStart - $advance, strtotime($nowDate));
|
||||
$timeDelay = min($timeEnd + $delay, strtotime("{$nowDate} 23:59:59"));
|
||||
if (Base::time() < $timeAdvance || $timeDelay < Base::time()) {
|
||||
return "不在有效时间内,有效时间为:" . date("H:i", $timeAdvance) . "-" . date("H:i", $timeDelay);
|
||||
}
|
||||
//
|
||||
$macs = explode(",", $mac);
|
||||
$checkins = [];
|
||||
foreach ($macs as $mac) {
|
||||
$mac = strtoupper($mac);
|
||||
$array = [];
|
||||
if (Base::isMac($mac)) {
|
||||
if ($UserCheckinMac = UserCheckinMac::whereMac($mac)->first()) {
|
||||
$array = [
|
||||
'userid' => $UserCheckinMac->userid,
|
||||
'mac' => $UserCheckinMac->mac,
|
||||
'date' => $nowDate,
|
||||
];
|
||||
$checkins[] = [
|
||||
'userid' => $UserCheckinMac->userid,
|
||||
'remark' => $UserCheckinMac->remark,
|
||||
];
|
||||
}
|
||||
} elseif (Base::isNumber($mac)) {
|
||||
if ($UserInfo = User::whereUserid($mac)->whereBot(0)->first()) {
|
||||
$array = [
|
||||
'userid' => $UserInfo->userid,
|
||||
'mac' => '00:00:00:00:00:00',
|
||||
'date' => $nowDate,
|
||||
];
|
||||
$checkins[] = [
|
||||
'userid' => $UserInfo->userid,
|
||||
'remark' => '手动签到',
|
||||
];
|
||||
}
|
||||
}
|
||||
if ($array) {
|
||||
$record = UserCheckinRecord::where($array)->first();
|
||||
if (empty($record)) {
|
||||
$record = UserCheckinRecord::createInstance($array);
|
||||
}
|
||||
$record->times = Base::array2json(array_merge($record->times, [$nowTime]));
|
||||
$record->report_time = $time;
|
||||
$record->save();
|
||||
}
|
||||
}
|
||||
//
|
||||
if ($checkins && $botUser = User::botGetOrCreate('check-in')) {
|
||||
$getJokeSoup = function($type) {
|
||||
$pre = $type == "up" ? "每日开心:" : "心灵鸡汤:";
|
||||
$key = $type == "up" ? "JokeSoupTask:jokes" : "JokeSoupTask:soups";
|
||||
$array = Base::json2array(Cache::get($key));
|
||||
if ($array) {
|
||||
$item = $array[array_rand($array)];
|
||||
if ($item) {
|
||||
return $pre . $item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
$sendMsg = function($type, $checkin) use ($alreadyTip, $getJokeSoup, $botUser, $nowDate) {
|
||||
$cacheKey = "Checkin::sendMsg-{$nowDate}-{$type}:" . $checkin['userid'];
|
||||
$typeDesc = $type == "up" ? "上班" : "下班";
|
||||
if (Cache::get($cacheKey) === "yes") {
|
||||
if ($alreadyTip && $dialog = WebSocketDialog::checkUserDialog($botUser, $checkin['userid'])) {
|
||||
$text = "<p>今日已{$typeDesc}打卡,无需重复打卡。</p>";
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, $type != "up");
|
||||
}
|
||||
return;
|
||||
}
|
||||
Cache::put($cacheKey, "yes", Carbon::now()->addDay());
|
||||
//
|
||||
if ($dialog = WebSocketDialog::checkUserDialog($botUser, $checkin['userid'])) {
|
||||
$hi = date("H:i");
|
||||
$remark = $checkin['remark'] ? " ({$checkin['remark']})": "";
|
||||
$text = "<p>{$typeDesc}打卡成功,打卡时间: {$hi}{$remark}</p>";
|
||||
$suff = $getJokeSoup($type);
|
||||
if ($suff) {
|
||||
$text = "{$text}<p>----------</p><p>{$suff}</p>";
|
||||
}
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid, false, false, $type != "up");
|
||||
}
|
||||
};
|
||||
if ($timeAdvance <= Base::time() && Base::time() < $timeEnd) {
|
||||
// 上班打卡通知(从最早打卡时间 到 下班打卡时间)
|
||||
foreach ($checkins as $checkin) {
|
||||
$sendMsg('up', $checkin);
|
||||
}
|
||||
}
|
||||
if ($timeEnd <= Base::time() && Base::time() <= $timeDelay) {
|
||||
// 下班打卡通知(下班打卡时间 到 最晚打卡时间)
|
||||
foreach ($checkins as $checkin) {
|
||||
$sendMsg('down', $checkin);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐私机器人
|
||||
* @param $command
|
||||
* @return string
|
||||
*/
|
||||
public static function anonBotQuickMsg($command)
|
||||
{
|
||||
return match ($command) {
|
||||
"help" => "使用说明:打开你想要发匿名消息的个人对话,点击输入框右边的 ⊕ 号,选择 <u>匿名消息</u> 即可输入你想要发送的匿名消息内容。",
|
||||
"privacy" => "匿名消息将通过 <u>匿名消息(机器人)</u> 发送给对方,不会记录你的身份信息。",
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
}
|
||||
66
app/Models/UserCheckinMac.php
Normal file
66
app/Models/UserCheckinMac.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\UserCheckinMac
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 会员id
|
||||
* @property string|null $mac MAC地址
|
||||
* @property string|null $remark 备注
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereMac($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereRemark($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinMac whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserCheckinMac extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* 保存mac地址
|
||||
* @param $userid
|
||||
* @param $array
|
||||
* @return mixed
|
||||
*/
|
||||
public static function saveMac($userid, $array)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($array, $userid) {
|
||||
$ids = [];
|
||||
$list = [];
|
||||
foreach ($array as $item) {
|
||||
if (self::whereMac($item['mac'])->where('userid', '!=', $userid)->exists()) {
|
||||
throw new ApiException("{$item['mac']} 已被其他成员设置");
|
||||
}
|
||||
$update = [];
|
||||
if ($item['remark']) {
|
||||
$update = [
|
||||
'remark' => $item['remark']
|
||||
];
|
||||
}
|
||||
$row = self::updateInsert([
|
||||
'userid' => $userid,
|
||||
'mac' => $item['mac']
|
||||
], $update);
|
||||
if ($row) {
|
||||
$ids[] = $row->id;
|
||||
$list[] = $row;
|
||||
}
|
||||
}
|
||||
self::whereUserid($userid)->whereNotIn('id', $ids)->delete();
|
||||
//
|
||||
return Base::retSuccess('修改成功', $list);
|
||||
});
|
||||
}
|
||||
}
|
||||
134
app/Models/UserCheckinRecord.php
Normal file
134
app/Models/UserCheckinRecord.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\UserCheckinRecord
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 会员id
|
||||
* @property string|null $mac MAC地址
|
||||
* @property string|null $date 签到日期
|
||||
* @property array $times 签到时间
|
||||
* @property int|null $report_time 上报的时间戳
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereMac($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereReportTime($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereTimes($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserCheckinRecord whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserCheckinRecord extends AbstractModel
|
||||
{
|
||||
|
||||
/**
|
||||
* 签到记录
|
||||
* @param $value
|
||||
* @return array
|
||||
*/
|
||||
public function getTimesAttribute($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
return Base::json2array($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签到时间
|
||||
* @param int $userid
|
||||
* @param array $betweenTimes
|
||||
* @return array
|
||||
*/
|
||||
public static function getTimes(int $userid, array $betweenTimes)
|
||||
{
|
||||
$array = [];
|
||||
$records = self::whereUserid($userid)->whereBetween('created_at', $betweenTimes)->orderBy('id')->get();
|
||||
/** @var self $record */
|
||||
foreach ($records as $record) {
|
||||
$times = array_map(function ($time) {
|
||||
return preg_replace("/(\d+):(\d+):\d+$/", "$1:$2", $time);
|
||||
}, $record->times);
|
||||
if (isset($array[$record->date])) {
|
||||
$array[$record->date] = array_merge($array[$record->date], $times);
|
||||
} else {
|
||||
$array[$record->date] = $times;
|
||||
}
|
||||
}
|
||||
//
|
||||
foreach ($array as $date => $times) {
|
||||
$times = array_values(array_filter(array_unique($times)));
|
||||
$inOrder = [];
|
||||
foreach ($times as $key => $time) {
|
||||
$inOrder[$key] = strtotime("2022-01-01 {$time}");
|
||||
}
|
||||
array_multisort($inOrder, SORT_ASC, $times);
|
||||
$array[$date] = $times;
|
||||
}
|
||||
//
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间收集
|
||||
* @param string $data
|
||||
* @param array $times
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public static function atCollect($data, $times)
|
||||
{
|
||||
$sameTimes = array_map(function($time) use ($data) {
|
||||
return [
|
||||
"datetime" => "{$data} {$time}",
|
||||
"timestamp" => strtotime("{$data} {$time}")
|
||||
];
|
||||
}, $times);
|
||||
return collect($sameTimes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到时段
|
||||
* @param array $times
|
||||
* @param int $diff 多长未签到算失效(秒)
|
||||
* @return array
|
||||
*/
|
||||
public static function atSection($times, $diff = 3600)
|
||||
{
|
||||
$start = "";
|
||||
$end = "";
|
||||
$array = [];
|
||||
foreach ($times as $time) {
|
||||
$time = preg_replace("/(\d+):(\d+):\d+$/", "$1:$2", $time);
|
||||
if (empty($start)) {
|
||||
$start = $time;
|
||||
continue;
|
||||
}
|
||||
if (empty($end)) {
|
||||
$end = $time;
|
||||
continue;
|
||||
}
|
||||
if (strtotime("2022-01-01 {$time}") - strtotime("2022-01-01 {$end}") > $diff) {
|
||||
$array[] = [$start, $end];
|
||||
$start = $time;
|
||||
$end = "";
|
||||
continue;
|
||||
}
|
||||
$end = $time;
|
||||
}
|
||||
if ($start) {
|
||||
$array[] = [$start, $end];
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
71
app/Models/UserDelete.php
Normal file
71
app/Models/UserDelete.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\UserDelete
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $operator 操作人员
|
||||
* @property int|null $userid 用户id
|
||||
* @property string|null $email 邮箱帐号
|
||||
* @property string|null $reason 注销原因
|
||||
* @property string $cache 会员资料缓存
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereCache($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereOperator($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereReason($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDelete whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserDelete extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* 昵称
|
||||
* @param $value
|
||||
* @return string
|
||||
*/
|
||||
public function getCacheAttribute($value)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
$value = Base::json2array($value);
|
||||
// 昵称
|
||||
if (!$value['nickname']) {
|
||||
$value['nickname'] = Base::cardFormat($value['email']);
|
||||
}
|
||||
// 头像
|
||||
$value['userimg'] = User::getAvatar($value['userid'], $value['userimg'], $value['email'], $value['nickname']);
|
||||
// 部门
|
||||
$value['department'] = array_filter(is_array($value['department']) ? $value['department'] : Base::explodeInt($value['department']));
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* userid 获取 基础信息
|
||||
* @param int $userid 会员ID
|
||||
* @return array|null
|
||||
*/
|
||||
public static function userid2basic($userid)
|
||||
{
|
||||
$row = self::whereUserid($userid)->first();
|
||||
if (empty($row) || empty($row->cache)) {
|
||||
return null;
|
||||
}
|
||||
$cache = $row->cache;
|
||||
$cache = array_intersect_key($cache, array_flip(array_merge(User::$basicField, ['department_name'])));
|
||||
$cache['delete_at'] = $row->created_at->format($row->dateFormat ?: 'Y-m-d H:i:s');
|
||||
return $cache;
|
||||
}
|
||||
}
|
||||
152
app/Models/UserDepartment.php
Normal file
152
app/Models/UserDepartment.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
|
||||
/**
|
||||
* App\Models\UserDepartment
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $name 部门名称
|
||||
* @property int|null $dialog_id 聊天会话ID
|
||||
* @property int|null $parent_id 上级部门
|
||||
* @property int|null $owner_userid 部门负责人
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereOwnerUserid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereParentId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserDepartment whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserDepartment extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* 保存部门
|
||||
* @param $data
|
||||
* @param $dialogUseid
|
||||
*/
|
||||
public function saveDepartment($data = [], $dialogUseid = 0) {
|
||||
AbstractModel::transaction(function () use ($dialogUseid, $data) {
|
||||
$oldUser = null;
|
||||
$newUser = null;
|
||||
if ($data['owner_userid'] !== $this->owner_userid) {
|
||||
$oldUser = User::find($this->owner_userid);
|
||||
$newUser = User::find($data['owner_userid']);
|
||||
}
|
||||
$this->updateInstance($data);
|
||||
//
|
||||
if ($this->dialog_id > 0) {
|
||||
// 已有群
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
if ($dialog) {
|
||||
$dialog->name = $this->name;
|
||||
$dialog->owner_id = $this->owner_userid;
|
||||
if ($dialog->save()) {
|
||||
$dialog->joinGroup($this->owner_userid, 0, true);
|
||||
$dialog->pushMsg("groupUpdate", [
|
||||
'id' => $dialog->id,
|
||||
'name' => $dialog->name,
|
||||
'owner_id' => $dialog->owner_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
} elseif ($dialogUseid > 0) {
|
||||
// 使用现有群
|
||||
$dialog = WebSocketDialog::whereType('group')->whereGroupType('user')->find($dialogUseid);
|
||||
if (empty($dialog)) {
|
||||
throw new ApiException("选择现有聊天群不存在");
|
||||
}
|
||||
$dialog->name = $this->name;
|
||||
$dialog->owner_id = $this->owner_userid;
|
||||
$dialog->group_type = 'department';
|
||||
if ($dialog->save()) {
|
||||
$dialog->joinGroup($this->owner_userid, 0, true);
|
||||
$dialog->pushMsg("groupUpdate", [
|
||||
'id' => $dialog->id,
|
||||
'name' => $dialog->name,
|
||||
'owner_id' => $dialog->owner_id,
|
||||
'group_type' => $dialog->group_type,
|
||||
]);
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'notice', [
|
||||
'notice' => User::nickname() . " 将此群改为部门群"
|
||||
], User::userid(), true, true);
|
||||
}
|
||||
$this->dialog_id = $dialog->id;
|
||||
} else {
|
||||
// 创建群
|
||||
$dialog = WebSocketDialog::createGroup($this->name, [$this->owner_userid], 'department', $this->owner_userid);
|
||||
if (empty($dialog)) {
|
||||
throw new ApiException("创建群组失败");
|
||||
}
|
||||
$this->dialog_id = $dialog->id;
|
||||
}
|
||||
$this->save();
|
||||
//
|
||||
if ($oldUser) {
|
||||
$oldUser->department = array_diff($oldUser->department, [$this->id]);
|
||||
$oldUser->department = "," . implode(",", $oldUser->department) . ",";
|
||||
$oldUser->save();
|
||||
}
|
||||
if ($newUser) {
|
||||
$newUser->department = array_diff($newUser->department, [$this->id]);
|
||||
$newUser->department = array_merge($newUser->department, [$this->id]);
|
||||
$newUser->department = "," . implode(",", $newUser->department) . ",";
|
||||
$newUser->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
* @return void
|
||||
*/
|
||||
public function deleteDepartment() {
|
||||
// 删除子部门
|
||||
$list = self::whereParentId($this->id)->get();
|
||||
foreach ($list as $item) {
|
||||
$item->deleteDepartment();
|
||||
}
|
||||
// 移出成员
|
||||
User::where("department", "like", "%,{$this->id},%")->chunk(100, function($items) {
|
||||
/** @var User $user */
|
||||
foreach ($items as $user) {
|
||||
$user->department = array_diff($user->department, [$this->id]);
|
||||
$user->department = "," . implode(",", $user->department) . ",";
|
||||
$user->save();
|
||||
}
|
||||
});
|
||||
// 解散群组
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
if ($dialog) {
|
||||
$dialog->deleteDialog();
|
||||
}
|
||||
//
|
||||
$this->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移交部门身份
|
||||
* @param $originalUserid
|
||||
* @param $newUserid
|
||||
* @return void
|
||||
*/
|
||||
public static function transfer($originalUserid, $newUserid)
|
||||
{
|
||||
self::whereOwnerUserid($originalUserid)->chunkById(100, function ($list) use ($originalUserid, $newUserid) {
|
||||
/** @var self $item */
|
||||
foreach ($list as $item) {
|
||||
$item->saveDepartment([
|
||||
'owner_userid' => $newUserid,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
133
app/Models/UserEmailVerification.php
Normal file
133
app/Models/UserEmailVerification.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
|
||||
/**
|
||||
* App\Models\UserEmailVerification
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 用户id
|
||||
* @property string|null $code 验证参数
|
||||
* @property string|null $email 电子邮箱
|
||||
* @property int|null $status 0-未验证,1-已验证
|
||||
* @property int|null $type 邮件类型:1-邮箱认证,2-修改邮箱
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereCode($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserEmailVerification whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserEmailVerification extends AbstractModel
|
||||
{
|
||||
|
||||
/**
|
||||
* 发验证邮箱
|
||||
* @param User $user
|
||||
* @param int $type
|
||||
* @param null $email
|
||||
*/
|
||||
public static function userEmailSend(User $user, $type = 1, $email = null)
|
||||
{
|
||||
$email = $type == 1 ? $user->email : $email;
|
||||
$res = self::whereEmail($email)->where('created_at', '>', Carbon::now()->subMinutes(30))->whereType($type)->first();
|
||||
if ($res && $type == 1) return;
|
||||
//删除
|
||||
self::whereUserid($email)->delete();
|
||||
$code = $type == 1 ? Base::generatePassword(64) : rand(100000, 999999);
|
||||
$row = self::createInstance([
|
||||
'userid' => $user->userid,
|
||||
'email' => $email,
|
||||
'code' => $code,
|
||||
'status' => 0,
|
||||
'type' => $type
|
||||
]);
|
||||
$row->save();
|
||||
$setting = Base::setting('emailSetting');
|
||||
try {
|
||||
if (!Base::isEmail($email)) {
|
||||
throw new \Exception("User email '{$email}' address error");
|
||||
}
|
||||
switch ($type) {
|
||||
case 2:
|
||||
$subject = env('APP_NAME') . "修改邮箱验证";
|
||||
$content = "<p>{$user->nickname} 您好,您正在修改 " . env('APP_NAME') . " 的邮箱,验证码如下。请在30分钟内输入验证码</p><p style='color: #0000DD;'><u>$code</u></p><p>如果不是本人操作,您的帐号可能存在风险,请及时修改密码!</p>";
|
||||
break;
|
||||
case 3:
|
||||
$subject = env('APP_NAME') . "注销帐号验证";
|
||||
$content = "<p>{$user->nickname} 您好,您正在注销 " . env('APP_NAME') . " 的帐号,验证码如下。请在30分钟内输入验证码</p><p style='color: #0000DD;'><u>$code</u></p><p>如果不是本人操作,您的帐号可能存在风险,请及时修改密码!</p>";
|
||||
break;
|
||||
default:
|
||||
$url = Base::fillUrl('single/valid/email') . '?code=' . $row->code;
|
||||
$subject = env('APP_NAME') . "绑定邮箱验证";
|
||||
$content = "<p>{$user->nickname} 您好,您正在绑定 " . env('APP_NAME') . " 的邮箱,请于30分钟之内点击以下链接完成验证 :</p><p style='display: flex; justify-content: center;'><a href='{$url}' target='_blank'>{$url}</a></p>";
|
||||
break;
|
||||
}
|
||||
Factory::mailer()
|
||||
->setDsn("smtp://{$setting['account']}:{$setting['password']}@{$setting['smtp_server']}:{$setting['port']}?verify_peer=0")
|
||||
->setMessage(EmailMessage::create()
|
||||
->from(env('APP_NAME', 'Task') . " <{$setting['account']}>")
|
||||
->to($email)
|
||||
->subject($subject)
|
||||
->html($content))
|
||||
->send();
|
||||
} catch (\Throwable $e) {
|
||||
if (str_contains($e->getMessage(), "Timed Out")) {
|
||||
throw new ApiException("language.TimedOut");
|
||||
} elseif ($e->getCode() === 550) {
|
||||
throw new ApiException('邮件内容被拒绝,请检查邮箱是否开启接收功能');
|
||||
} else {
|
||||
throw new ApiException($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
* @param $email
|
||||
* @param $code
|
||||
* @param int $type
|
||||
* @return bool
|
||||
*/
|
||||
public static function verify($email, $code, $type = 1)
|
||||
{
|
||||
if (!$code) {
|
||||
throw new ApiException('请输入验证码');
|
||||
}
|
||||
/** @var UserEmailVerification $emailVerify */
|
||||
$emailVerify = self::whereEmail($email)->whereType($type)->orderByDesc('id')->first();
|
||||
|
||||
if (empty($emailVerify) || $emailVerify->code != $code) {
|
||||
throw new ApiException('验证码错误');
|
||||
}
|
||||
|
||||
$oldTime = Carbon::parse($emailVerify->created_at)->timestamp;
|
||||
$time = Base::Time();
|
||||
|
||||
// 30分钟失效
|
||||
if (abs($time - $oldTime) > 1800) {
|
||||
throw new ApiException('验证码已失效');
|
||||
}
|
||||
|
||||
self::whereEmail($email)->whereCode($code)->whereType($type)->update([
|
||||
'status' => 1
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
71
app/Models/UserTransfer.php
Normal file
71
app/Models/UserTransfer.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
|
||||
/**
|
||||
* App\Models\UserTransfer
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $original_userid 原作者
|
||||
* @property int|null $new_userid 交接人
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereNewUserid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereOriginalUserid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|UserTransfer whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserTransfer extends AbstractModel
|
||||
{
|
||||
|
||||
/**
|
||||
* 开始移交
|
||||
* @return void
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
// 移交部门
|
||||
UserDepartment::transfer($this->original_userid, $this->new_userid);
|
||||
// 移交项目身份
|
||||
ProjectUser::transfer($this->original_userid, $this->new_userid);
|
||||
// 移交任务身份
|
||||
ProjectTaskUser::transfer($this->original_userid, $this->new_userid);
|
||||
// 移交文件
|
||||
File::transfer($this->original_userid, $this->new_userid);
|
||||
// 离职移出群组
|
||||
WebSocketDialog::select(['web_socket_dialogs.*'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->where('web_socket_dialogs.type', 'group')
|
||||
->where('u.userid', $this->original_userid)
|
||||
->orderByDesc('web_socket_dialogs.id')
|
||||
->chunk(100, function($list) {
|
||||
/** @var WebSocketDialog $dialog */
|
||||
foreach ($list as $dialog) {
|
||||
// 离职员工退出群
|
||||
$dialog->exitGroup($this->original_userid, 'remove', false, false);
|
||||
if ($dialog->owner_id === $this->original_userid) {
|
||||
// 如果是群主则把交接人设为群主
|
||||
$dialog->owner_id = $this->new_userid;
|
||||
if ($dialog->save()) {
|
||||
$dialog->joinGroup($this->new_userid, 0);
|
||||
$dialog->pushMsg("groupUpdate", [
|
||||
'id' => $dialog->id,
|
||||
'owner_id' => $dialog->owner_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,13 @@ namespace App\Models;
|
||||
|
||||
|
||||
/**
|
||||
* Class WebSocket
|
||||
* App\Models\WebSocket
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property string $key
|
||||
* @property string|null $fd
|
||||
* @property int|null $userid
|
||||
* @property string|null $path
|
||||
* @property int|null $userid
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocket newModelQuery()
|
||||
|
||||
@@ -3,43 +3,63 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Tasks\PushTask;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Class WebSocketDialog
|
||||
* App\Models\WebSocketDialog
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property string|null $type 对话类型
|
||||
* @property string|null $group_type 聊天室类型
|
||||
* @property string|null $name 对话名称
|
||||
* @property string $avatar 头像(群)
|
||||
* @property string|null $last_at 最后消息时间
|
||||
* @property int|null $owner_id 群主用户ID
|
||||
* @property int|null $link_id 关联id
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\WebSocketDialogUser[] $dialogUser
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\WebSocketDialogUser> $dialogUser
|
||||
* @property-read int|null $dialog_user_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereAvatar($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereGroupType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereLastAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereLinkId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereOwnerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|WebSocketDialog withoutTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog withTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class WebSocketDialog extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* 头像地址
|
||||
* @param $value
|
||||
* @return string
|
||||
*/
|
||||
public function getAvatarAttribute($value)
|
||||
{
|
||||
return $value ? Base::fillUrl($value) : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
@@ -48,6 +68,272 @@ class WebSocketDialog extends AbstractModel
|
||||
return $this->hasMany(WebSocketDialogUser::class, 'dialog_id', 'id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取对话列表
|
||||
* @param int $userid 会员ID
|
||||
* @param $updated
|
||||
* @param $deleted
|
||||
* @return array
|
||||
*/
|
||||
public function getDialogList($userid, $updated = "", $deleted = "")
|
||||
{
|
||||
$builder = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->where('u.userid', $userid);
|
||||
if ($updated) {
|
||||
$builder->where('u.updated_at', '>', $updated);
|
||||
}
|
||||
$list = $builder
|
||||
->orderByDesc('u.top_at')
|
||||
->orderByDesc('web_socket_dialogs.last_at')
|
||||
->paginate(Base::getPaginate(100, 50));
|
||||
$list->transform(function (WebSocketDialog $item) use ($userid) {
|
||||
return $item->formatData($userid);
|
||||
});
|
||||
//
|
||||
$data = $list->toArray();
|
||||
if ($list->currentPage() === 1) {
|
||||
$data['deleted_id'] = Deleted::ids('dialog', $userid, $deleted);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化对话
|
||||
* @param int $userid 会员ID
|
||||
* @param bool $hasData
|
||||
* @return $this
|
||||
*/
|
||||
public function formatData($userid, $hasData = false)
|
||||
{
|
||||
$dialogUserFun = function ($key, $default = null) use ($userid) {
|
||||
$data = Cache::remember("Dialog::formatData-{$this->id}-{$userid}", now()->addSeconds(10), function () use ($userid) {
|
||||
return WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->first()?->toArray();
|
||||
});
|
||||
return $data[$key] ?? $default;
|
||||
};
|
||||
//
|
||||
$time = Carbon::parse($this->user_at ?? $dialogUserFun('updated_at'));
|
||||
$this->top_at = $this->top_at ?? $dialogUserFun('top_at');
|
||||
$this->user_at = $time->toDateTimeString('millisecond');
|
||||
$this->user_ms = $time->valueOf();
|
||||
//
|
||||
if (isset($this->search_msg_id)) {
|
||||
// 最后消息 (搜索预览消息)
|
||||
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->find($this->search_msg_id);
|
||||
$this->last_at = $this->last_msg?->created_at;
|
||||
} else {
|
||||
// 未读信息
|
||||
$this->generateUnread($userid, $hasData);
|
||||
// 未读标记
|
||||
$this->mark_unread = $this->mark_unread ?? $dialogUserFun('mark_unread');
|
||||
// 是否免打扰
|
||||
$this->silence = $this->silence ?? $dialogUserFun('silence');
|
||||
// 对话人数
|
||||
$this->people = WebSocketDialogUser::whereDialogId($this->id)->count();
|
||||
// 有待办
|
||||
$this->todo_num = WebSocketDialogMsgTodo::whereDialogId($this->id)->whereUserid($userid)->whereDoneAt(null)->count();
|
||||
// 最后消息
|
||||
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
|
||||
}
|
||||
// 对方信息
|
||||
$this->pinyin = Base::cn2pinyin($this->name);
|
||||
$this->quick_msgs = [];
|
||||
$this->dialog_user = null;
|
||||
$this->group_info = null;
|
||||
$this->bot = 0;
|
||||
switch ($this->type) {
|
||||
case "user":
|
||||
$dialog_user = WebSocketDialogUser::whereDialogId($this->id)->where('userid', '!=', $userid)->first();
|
||||
if ($dialog_user->userid === 0) {
|
||||
$dialog_user->userid = $userid;
|
||||
}
|
||||
$basic = User::userid2basic($dialog_user->userid);
|
||||
if ($basic) {
|
||||
$this->name = $basic->nickname;
|
||||
$this->email = $basic->email;
|
||||
$this->userimg = $basic->userimg;
|
||||
$this->bot = $basic->getBotOwner();
|
||||
$this->quick_msgs = UserBot::quickMsgs($basic->email);
|
||||
} else {
|
||||
$this->name = 'non-existent';
|
||||
$this->dialog_delete = 1;
|
||||
}
|
||||
$this->dialog_user = $dialog_user;
|
||||
break;
|
||||
case "group":
|
||||
switch ($this->group_type) {
|
||||
case 'project':
|
||||
$this->group_info = Project::withTrashed()->select(['id', 'name', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
|
||||
if ($this->group_info) {
|
||||
$this->name = $this->group_info->name;
|
||||
} else {
|
||||
$this->name = '[Delete]';
|
||||
$this->dialog_delete = 1;
|
||||
}
|
||||
break;
|
||||
case 'task':
|
||||
$this->group_info = ProjectTask::withTrashed()->select(['id', 'name', 'complete_at', 'archived_at', 'deleted_at'])->whereDialogId($this->id)->first()?->cancelAppend()->cancelHidden();
|
||||
if ($this->group_info) {
|
||||
$this->name = $this->group_info->name;
|
||||
} else {
|
||||
$this->name = '[Delete]';
|
||||
$this->dialog_delete = 1;
|
||||
}
|
||||
break;
|
||||
case 'all':
|
||||
$this->name = Doo::translate('全体成员');
|
||||
$this->all_group_mute = Base::settingFind('system', 'all_group_mute');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ($hasData === true) {
|
||||
$msgBuilder = WebSocketDialogMsg::whereDialogId($this->id);
|
||||
$this->has_tag = $msgBuilder->clone()->where('tag', '>', 0)->exists();
|
||||
$this->has_image = $msgBuilder->clone()->whereMtype('image')->exists();
|
||||
$this->has_file = $msgBuilder->clone()->whereMtype('file')->exists();
|
||||
$this->has_link = $msgBuilder->clone()->whereLink(1)->exists();
|
||||
$this->has_todo = $msgBuilder->clone()->where('todo', '>', 0)->exists();
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成未读数据
|
||||
* @param $userid
|
||||
* @param $positionData
|
||||
* @return $this
|
||||
*/
|
||||
public function generateUnread($userid, $positionData = false)
|
||||
{
|
||||
$builder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
|
||||
$this->unread = $builder->count();
|
||||
$this->mention = $this->unread > 0 ? $builder->clone()->whereMention(1)->count() : 0;
|
||||
if ($positionData) {
|
||||
$array = [];
|
||||
// @我的消息
|
||||
if ($this->mention > 0) {
|
||||
$list = $builder->clone()->whereMention(1)->orderByDesc('msg_id')->take(20)->get();
|
||||
foreach ($list as $item) {
|
||||
$array[] = [
|
||||
'msg_id' => $item->msg_id,
|
||||
'label' => Doo::translate('@我的消息'),
|
||||
];
|
||||
}
|
||||
}
|
||||
// 最早一条未读消息
|
||||
if ($this->unread > 0
|
||||
&& $first_id = intval($builder->clone()->orderBy('msg_id')->value('msg_id'))) {
|
||||
$array[] = [
|
||||
'msg_id' => $first_id,
|
||||
'label' => '{UNREAD}'
|
||||
];
|
||||
}
|
||||
//
|
||||
$this->position_msgs = $array;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入聊天室
|
||||
* @param int|array $userid 加入的会员ID或会员ID组
|
||||
* @param int $inviter 邀请人
|
||||
* @param bool|null $important 重要人员(null不修改、bool修改)
|
||||
* @return bool
|
||||
*/
|
||||
public function joinGroup($userid, $inviter, $important = null)
|
||||
{
|
||||
AbstractModel::transaction(function () use ($important, $inviter, $userid) {
|
||||
foreach (is_array($userid) ? $userid : [$userid] as $value) {
|
||||
if ($value > 0) {
|
||||
$updateData = [
|
||||
'inviter' => $inviter,
|
||||
];
|
||||
if (is_bool($important)) {
|
||||
$updateData['important'] = $important ? 1 : 0;
|
||||
}
|
||||
$isInsert = false;
|
||||
WebSocketDialogUser::updateInsert([
|
||||
'dialog_id' => $this->id,
|
||||
'userid' => $value,
|
||||
], $updateData, [], $isInsert);
|
||||
if ($isInsert) {
|
||||
WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [
|
||||
'notice' => User::userid2nickname($value) . " 已加入群组"
|
||||
], $inviter, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
$this->pushMsg("groupUpdate", [
|
||||
'id' => $this->id,
|
||||
'people' => WebSocketDialogUser::whereDialogId($this->id)->count()
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出聊天室
|
||||
* @param int|array $userid 退出的会员ID或会员ID组
|
||||
* @param string $type exit|remove
|
||||
* @param bool $checkDelete 是否检查删除
|
||||
* @param bool $pushMsg 是否推送消息
|
||||
*/
|
||||
public function exitGroup($userid, $type = 'exit', $checkDelete = true, $pushMsg = true)
|
||||
{
|
||||
$typeDesc = $type === 'remove' ? '移出' : '退出';
|
||||
AbstractModel::transaction(function () use ($pushMsg, $checkDelete, $typeDesc, $type, $userid) {
|
||||
$builder = WebSocketDialogUser::whereDialogId($this->id);
|
||||
if (is_array($userid)) {
|
||||
$builder->whereIn('userid', $userid);
|
||||
} else {
|
||||
$builder->whereUserid($userid);
|
||||
}
|
||||
$builder->chunkById(100, function($list) use ($pushMsg, $checkDelete, $typeDesc, $type) {
|
||||
/** @var WebSocketDialogUser $item */
|
||||
foreach ($list as $item) {
|
||||
if ($checkDelete) {
|
||||
if ($type === 'remove') {
|
||||
// 移出时:如果是全员群仅允许管理员操作,其他群仅群主或邀请人可以操作
|
||||
if ($this->group_type === 'all') {
|
||||
User::auth("admin");
|
||||
} elseif (!in_array(User::userid(), [$this->owner_id, $item->inviter])) {
|
||||
throw new ApiException('只有群主或邀请人可以移出成员');
|
||||
}
|
||||
}
|
||||
if ($item->userid == $this->owner_id) {
|
||||
throw new ApiException('群主不可' . $typeDesc);
|
||||
}
|
||||
if ($item->important) {
|
||||
throw new ApiException('部门成员、项目人员或任务人员不可' . $typeDesc);
|
||||
}
|
||||
}
|
||||
//
|
||||
$item->delete();
|
||||
//
|
||||
if ($pushMsg) {
|
||||
if ($type === 'remove') {
|
||||
$notice = User::nickname() . " 将 " . User::userid2nickname($item->userid) . " 移出群组";
|
||||
} else {
|
||||
$notice = User::userid2nickname($item->userid) . " 退出群组";
|
||||
}
|
||||
WebSocketDialogMsg::sendMsg(null, $this->id, 'notice', [
|
||||
'notice' => $notice
|
||||
], User::userid(), true, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
//
|
||||
$this->pushMsg("groupUpdate", [
|
||||
'id' => $this->id,
|
||||
'people' => WebSocketDialogUser::whereDialogId($this->id)->count()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会话
|
||||
* @return bool
|
||||
@@ -55,28 +341,157 @@ class WebSocketDialog extends AbstractModel
|
||||
public function deleteDialog()
|
||||
{
|
||||
AbstractModel::transaction(function () {
|
||||
WebSocketDialogMsgRead::whereDialogId($this->id)->whereNull('read_at')->update([
|
||||
'read_at' => Carbon::now()
|
||||
]);
|
||||
WebSocketDialogMsgRead::whereDialogId($this->id)
|
||||
->whereNull('read_at')
|
||||
->chunkById(100, function ($list) {
|
||||
WebSocketDialogMsgRead::onlyMarkRead($list);
|
||||
});
|
||||
$this->delete();
|
||||
});
|
||||
$this->pushMsg("groupDelete");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原会话
|
||||
* @return bool
|
||||
*/
|
||||
public function restoreDialog()
|
||||
{
|
||||
$this->restore();
|
||||
$this->pushMsg("groupRestore");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查群组类型
|
||||
* @param string|array|null $groupType
|
||||
* @return void
|
||||
*/
|
||||
public function checkGroup($groupType = null)
|
||||
{
|
||||
if ($this->type !== 'group') {
|
||||
throw new ApiException('仅限群组操作');
|
||||
}
|
||||
if ($groupType) {
|
||||
$groupTypes = is_array($groupType) ? $groupType : [$groupType];
|
||||
if (!in_array($this->group_type, $groupTypes)) {
|
||||
throw new ApiException('操作的群组类型错误');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查禁言
|
||||
* @param $userid
|
||||
* @return void
|
||||
*/
|
||||
public function checkMute($userid)
|
||||
{
|
||||
if ($this->group_type === 'all') {
|
||||
$allGroupMute = Base::settingFind('system', 'all_group_mute');
|
||||
switch ($allGroupMute) {
|
||||
case 'all':
|
||||
throw new ApiException('当前会话全员禁言');
|
||||
case 'user':
|
||||
if (!User::find($userid)?->isAdmin()) {
|
||||
throw new ApiException('当前会话禁言');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群组名称
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
public function getGroupName()
|
||||
{
|
||||
if (!isset($this->appendattrs['groupName'])) {
|
||||
$name = $this->name;
|
||||
if ($this->type == "group") {
|
||||
switch ($this->group_type) {
|
||||
case 'project':
|
||||
$name = \DB::table('projects')->where('dialog_id', $this->id)->value('name');
|
||||
break;
|
||||
case 'task':
|
||||
$name = \DB::table('project_tasks')->where('dialog_id', $this->id)->value('name');
|
||||
break;
|
||||
case 'all':
|
||||
$name = Doo::translate('全体成员');
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->appendattrs['groupName'] = $name;
|
||||
}
|
||||
return $this->appendattrs['groupName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送消息
|
||||
* @param $action
|
||||
* @param array $data 发送内容,默认为[id=>会话ID]
|
||||
* @param array $userid 指定会员,默认为群组所有成员
|
||||
* @return void
|
||||
*/
|
||||
public function pushMsg($action, $data = null, $userid = null)
|
||||
{
|
||||
if ($data === null) {
|
||||
$data = ['id' => $this->id];
|
||||
}
|
||||
//
|
||||
if ($userid === null) {
|
||||
$userid = $this->dialogUser->pluck('userid')->toArray();
|
||||
}
|
||||
//
|
||||
$params = [
|
||||
'userid' => $userid,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => $action,
|
||||
'data' => $data,
|
||||
]
|
||||
];
|
||||
$task = new PushTask($params, false);
|
||||
Task::deliver($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新对话最后消息时间
|
||||
* @return WebSocketDialogMsg|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|\Illuminate\Database\Query\Builder|object|null
|
||||
*/
|
||||
public function updateMsgLastAt()
|
||||
{
|
||||
$lastMsg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
|
||||
if ($lastMsg) {
|
||||
$this->last_at = $lastMsg->created_at;
|
||||
$this->save();
|
||||
}
|
||||
return $lastMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话(同时检验对话身份)
|
||||
* @param $id
|
||||
* @param $dialog_id
|
||||
* @param bool|string $checkOwner 是否校验群组身份,'auto'时有群主为true无群主为false
|
||||
* @return self
|
||||
*/
|
||||
public static function checkDialog($id)
|
||||
public static function checkDialog($dialog_id, $checkOwner = false)
|
||||
{
|
||||
$dialog = WebSocketDialog::whereId($id)->first();
|
||||
$dialog = WebSocketDialog::find($dialog_id);
|
||||
if (empty($dialog)) {
|
||||
throw new ApiException('对话不存在或已被删除');
|
||||
throw new ApiException('对话不存在或已被删除', ['dialog_id' => $dialog_id], -4003);
|
||||
}
|
||||
//
|
||||
$userid = User::userid();
|
||||
if ($dialog->type === 'group' && $dialog->group_type === 'task') {
|
||||
if ($checkOwner === 'auto') {
|
||||
$checkOwner = $dialog->owner_id > 0;
|
||||
}
|
||||
if ($checkOwner === true && $dialog->owner_id != $userid) {
|
||||
throw new ApiException('仅限群主操作');
|
||||
}
|
||||
//
|
||||
if ($dialog->group_type === 'task') {
|
||||
// 任务群对话校验是否在项目内
|
||||
$project_id = intval(ProjectTask::whereDialogId($dialog->id)->value('project_id'));
|
||||
if ($project_id > 0) {
|
||||
@@ -85,49 +500,11 @@ class WebSocketDialog extends AbstractModel
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($dialog->group_type == 'okr') {
|
||||
return $dialog;
|
||||
}
|
||||
if (!WebSocketDialogUser::whereDialogId($dialog->id)->whereUserid($userid)->exists()) {
|
||||
throw new ApiException('不在成员列表内');
|
||||
}
|
||||
return $dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化对话
|
||||
* @param WebSocketDialog $dialog
|
||||
* @param int $userid 会员ID
|
||||
* @return self|null
|
||||
*/
|
||||
public static function formatData(WebSocketDialog $dialog, $userid)
|
||||
{
|
||||
if (empty($dialog)) {
|
||||
return null;
|
||||
}
|
||||
// 最后消息
|
||||
$last_msg = WebSocketDialogMsg::whereDialogId($dialog->id)->orderByDesc('id')->first();
|
||||
$dialog->last_msg = $last_msg;
|
||||
// 未读信息
|
||||
$dialog->unread = WebSocketDialogMsgRead::whereDialogId($dialog->id)->whereUserid($userid)->whereReadAt(null)->count();
|
||||
// 对话人数
|
||||
$builder = WebSocketDialogUser::whereDialogId($dialog->id);
|
||||
$dialog->people = $builder->count();
|
||||
// 对方信息
|
||||
$dialog->dialog_user = null;
|
||||
$dialog->group_info = null;
|
||||
switch ($dialog->type) {
|
||||
case "user":
|
||||
$dialog_user = $builder->where('userid', '!=', $userid)->first();
|
||||
$dialog->name = User::userid2nickname($dialog_user->userid);
|
||||
$dialog->dialog_user = $dialog_user;
|
||||
break;
|
||||
case "group":
|
||||
if ($dialog->group_type === 'project') {
|
||||
$dialog->group_info = Project::withTrashed()->select(['id', 'name'])->whereDialogId($dialog->id)->first();
|
||||
$dialog->name = $dialog->group_info ? $dialog->group_info->name : '';
|
||||
} elseif ($dialog->group_type === 'task') {
|
||||
$dialog->group_info = ProjectTask::withTrashed()->select(['id', 'name'])->whereDialogId($dialog->id)->first();
|
||||
$dialog->name = $dialog->group_info ? $dialog->group_info->name : '';
|
||||
}
|
||||
break;
|
||||
throw new ApiException('不在成员列表内', ['dialog_id' => $dialog_id], -4003);
|
||||
}
|
||||
return $dialog;
|
||||
}
|
||||
@@ -135,17 +512,20 @@ class WebSocketDialog extends AbstractModel
|
||||
/**
|
||||
* 创建聊天室
|
||||
* @param string $name 聊天室名称
|
||||
* @param int|array $userid 加入的会员ID或会员ID组
|
||||
* @param int|array $userid 加入的会员ID(组)
|
||||
* @param string $group_type 聊天室类型
|
||||
* @param int $owner_id 群主会员ID
|
||||
* @return self|null
|
||||
*/
|
||||
public static function createGroup($name, $userid, $group_type = '')
|
||||
public static function createGroup($name, $userid, $group_type = '', $owner_id = 0)
|
||||
{
|
||||
return AbstractModel::transaction(function () use ($userid, $group_type, $name) {
|
||||
return AbstractModel::transaction(function () use ($owner_id, $userid, $group_type, $name) {
|
||||
$dialog = self::createInstance([
|
||||
'type' => 'group',
|
||||
'name' => $name ?: '',
|
||||
'group_type' => $group_type,
|
||||
'owner_id' => $owner_id,
|
||||
'last_at' => in_array($group_type, ['user', 'department', 'all']) ? Carbon::now() : null,
|
||||
]);
|
||||
$dialog->save();
|
||||
foreach (is_array($userid) ? $userid : [$userid] as $value) {
|
||||
@@ -153,6 +533,7 @@ class WebSocketDialog extends AbstractModel
|
||||
WebSocketDialogUser::createInstance([
|
||||
'dialog_id' => $dialog->id,
|
||||
'userid' => $value,
|
||||
'important' => !in_array($group_type, ['user', 'all'])
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
@@ -160,80 +541,124 @@ class WebSocketDialog extends AbstractModel
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入聊天室
|
||||
* @param int $dialog_id 会话ID(即 聊天室ID)
|
||||
* @param int|array $userid 加入的会员ID或会员ID组
|
||||
* @return bool
|
||||
*/
|
||||
public static function joinGroup($dialog_id, $userid)
|
||||
{
|
||||
$dialog = self::whereId($dialog_id)->whereType('group')->first();
|
||||
if (empty($dialog)) {
|
||||
return false;
|
||||
}
|
||||
AbstractModel::transaction(function () use ($dialog, $userid) {
|
||||
foreach (is_array($userid) ? $userid : [$userid] as $value) {
|
||||
if ($value > 0) {
|
||||
WebSocketDialogUser::createInstance([
|
||||
'dialog_id' => $dialog->id,
|
||||
'userid' => $value,
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出聊天室
|
||||
* @param int $dialog_id 会话ID(即 聊天室ID)
|
||||
* @param int|array $userid 加入的会员ID或会员ID组
|
||||
* @return bool
|
||||
*/
|
||||
public static function exitGroup($dialog_id, $userid)
|
||||
{
|
||||
if (is_array($userid)) {
|
||||
WebSocketDialogUser::whereDialogId($dialog_id)->whereIn('userid', $userid)->delete();
|
||||
} else {
|
||||
WebSocketDialogUser::whereDialogId($dialog_id)->whereUserid($userid)->delete();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员对话(没有自动创建)
|
||||
* @param int $userid 会员ID
|
||||
* @param int $userid2 另一个会员ID
|
||||
* @param User $user 发起会话的会员
|
||||
* @param int $receiver 另一个会员ID
|
||||
* @return self|null
|
||||
*/
|
||||
public static function checkUserDialog($userid, $userid2)
|
||||
public static function checkUserDialog($user, $receiver)
|
||||
{
|
||||
if ($user->userid == $receiver) {
|
||||
$receiver = 0;
|
||||
}
|
||||
$dialogUser = self::select(['web_socket_dialogs.*'])
|
||||
->join('web_socket_dialog_users as u1', 'web_socket_dialogs.id', '=', 'u1.dialog_id')
|
||||
->join('web_socket_dialog_users as u2', 'web_socket_dialogs.id', '=', 'u2.dialog_id')
|
||||
->where('u1.userid', $userid)
|
||||
->where('u2.userid', $userid2)
|
||||
->where('u1.userid', $user->userid)
|
||||
->where('u2.userid', $receiver)
|
||||
->where('web_socket_dialogs.type', 'user')
|
||||
->first();
|
||||
if ($dialogUser) {
|
||||
return $dialogUser;
|
||||
}
|
||||
return AbstractModel::transaction(function () use ($userid2, $userid) {
|
||||
if ($receiver > 0 && $user->isTemp() && !User::whereUserid($receiver)->whereBot(1)->exists() ) {
|
||||
throw new ApiException('无法发起会话,请联系管理员。');
|
||||
}
|
||||
return AbstractModel::transaction(function () use ($receiver, $user) {
|
||||
$dialog = self::createInstance([
|
||||
'type' => 'user',
|
||||
]);
|
||||
$dialog->save();
|
||||
WebSocketDialogUser::createInstance([
|
||||
'dialog_id' => $dialog->id,
|
||||
'userid' => $userid,
|
||||
'userid' => $user->userid,
|
||||
])->save();
|
||||
WebSocketDialogUser::createInstance([
|
||||
'dialog_id' => $dialog->id,
|
||||
'userid' => $userid2,
|
||||
'userid' => $receiver,
|
||||
])->save();
|
||||
return $dialog;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息文件
|
||||
*
|
||||
* @param User $user 发起会话的会员
|
||||
* @param array $dialogIds 对话id
|
||||
* @param file $files 文件对象
|
||||
* @param string $image64 base64文件
|
||||
* @param string $fileName 文件名称
|
||||
* @param int $replyId 恢复id
|
||||
* @param int $imageAttachment
|
||||
* @return array
|
||||
*/
|
||||
public static function sendMsgFiles($user, $dialogIds, $files, $image64, $fileName, $replyId, $imageAttachment)
|
||||
{
|
||||
$filePath = '';
|
||||
$result = [];
|
||||
foreach ($dialogIds as $dialog_id) {
|
||||
$dialog = WebSocketDialog::checkDialog($dialog_id);
|
||||
//
|
||||
$action = $replyId > 0 ? "reply-$replyId" : "";
|
||||
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
|
||||
if ($image64) {
|
||||
$data = Base::image64save([
|
||||
"image64" => $image64,
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
]);
|
||||
} else if ($filePath) {
|
||||
Base::makeDir(public_path($path));
|
||||
copy($filePath, public_path($path) . basename($filePath));
|
||||
} else {
|
||||
$setting = Base::setting('system');
|
||||
$data = Base::upload([
|
||||
"file" => $files,
|
||||
"type" => 'more',
|
||||
"path" => $path,
|
||||
"fileName" => $fileName,
|
||||
"size" => ($setting['file_upload_limit'] ?: 0) * 1024
|
||||
]);
|
||||
}
|
||||
//
|
||||
if (Base::isError($data)) {
|
||||
throw new ApiException($data['msg']);
|
||||
} else {
|
||||
$fileData = $data['data'];
|
||||
$filePath = $fileData['file'];
|
||||
$fileName = $fileData['name'];
|
||||
$fileData['thumb'] = Base::unFillUrl($fileData['thumb']);
|
||||
$fileData['size'] *= 1024;
|
||||
//
|
||||
if ($dialog->type === 'group' && $dialog->group_type === 'task') { // 任务群组保存文件
|
||||
if ($imageAttachment || !in_array($fileData['ext'], File::imageExt)) { // 如果是图片不保存
|
||||
$task = ProjectTask::whereDialogId($dialog->id)->first();
|
||||
if ($task) {
|
||||
$file = ProjectTaskFile::createInstance([
|
||||
'project_id' => $task->project_id,
|
||||
'task_id' => $task->id,
|
||||
'name' => $fileData['name'],
|
||||
'size' => $fileData['size'],
|
||||
'ext' => $fileData['ext'],
|
||||
'path' => $fileData['path'],
|
||||
'thumb' => $fileData['thumb'],
|
||||
'userid' => $user->userid,
|
||||
]);
|
||||
$file->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
$result = WebSocketDialogMsg::sendMsg($action, $dialog_id, 'file', $fileData, $user->userid);
|
||||
if (Base::isSuccess($result)) {
|
||||
if (isset($task)) {
|
||||
$result['data']['task_id'] = $task->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,51 +2,92 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\PushTask;
|
||||
use App\Tasks\WebSocketDialogMsgTask;
|
||||
use Carbon\Carbon;
|
||||
use App\Module\Base;
|
||||
use App\Module\Image;
|
||||
use App\Tasks\PushTask;
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Tasks\WebSocketDialogMsgTask;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Class WebSocketDialogMsg
|
||||
* App\Models\WebSocketDialogMsg
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property string|null $dialog_type 对话类型
|
||||
* @property int|null $userid 发送会员ID
|
||||
* @property string|null $type 消息类型
|
||||
* @property string|null $mtype 消息类型(用于搜索)
|
||||
* @property array|mixed $msg 详细消息
|
||||
* @property array|mixed $emoji emoji回复
|
||||
* @property string|null $key 搜索关键词
|
||||
* @property int|null $read 已阅数量
|
||||
* @property int|null $send 发送数量
|
||||
* @property int|null $tag 标注会员ID
|
||||
* @property int|null $todo 设为待办会员ID
|
||||
* @property int|null $link 是否存在链接
|
||||
* @property int|null $modify 是否编辑
|
||||
* @property int|null $reply_num 有多少条回复
|
||||
* @property int|null $reply_id 回复ID
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property-read int|mixed $percentage
|
||||
* @property-read \App\Models\WebSocketDialogMsg|null $reply_data
|
||||
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereEmoji($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereKey($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereLink($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereModify($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMtype($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereRead($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereReplyId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereReplyNum($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereSend($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereTag($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereTodo($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUserid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg withTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class WebSocketDialogMsg extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $appends = [
|
||||
'percentage',
|
||||
'reply_data',
|
||||
'forward_data',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'key',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function webSocketDialog(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(WebSocketDialog::class, 'id', 'dialog_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 阅读占比
|
||||
* @return int|mixed
|
||||
@@ -54,15 +95,41 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
public function getPercentageAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['percentage'])) {
|
||||
if ($this->read > $this->send || empty($this->send)) {
|
||||
$this->appendattrs['percentage'] = 100;
|
||||
} else {
|
||||
$this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
|
||||
}
|
||||
$this->generatePercentage();
|
||||
}
|
||||
return $this->appendattrs['percentage'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 回复消息详情
|
||||
* @return WebSocketDialogMsg|null
|
||||
*/
|
||||
public function getReplyDataAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['reply_data'])) {
|
||||
$this->appendattrs['reply_data'] = null;
|
||||
if ($this->reply_id > 0) {
|
||||
$this->appendattrs['reply_data'] = self::find($this->reply_id, ['id', 'userid', 'type', 'msg'])?->cancelAppend() ?: null;
|
||||
}
|
||||
}
|
||||
return $this->appendattrs['reply_data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 转发消息详情
|
||||
* @return WebSocketDialogMsg|null
|
||||
*/
|
||||
public function getForwardDataAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['forward_data'])) {
|
||||
$this->appendattrs['forward_data'] = null;
|
||||
if ($this->forward_id > 0) {
|
||||
$this->appendattrs['forward_data'] = self::find($this->forward_id, ['id', 'userid', 'type', 'msg'])?->cancelAppend() ?: null;
|
||||
}
|
||||
}
|
||||
return $this->appendattrs['forward_data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息格式化
|
||||
* @param $value
|
||||
@@ -75,13 +142,44 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
}
|
||||
$value = Base::json2array($value);
|
||||
if ($this->type === 'file') {
|
||||
$value['type'] = in_array($value['ext'], ['jpg', 'jpeg', 'png', 'gif']) ? 'img' : 'file';
|
||||
$value['type'] = in_array($value['ext'], ['jpg', 'jpeg', 'webp', 'png', 'gif']) ? 'img' : 'file';
|
||||
$value['path'] = Base::fillUrl($value['path']);
|
||||
$value['thumb'] = Base::fillUrl($value['thumb'] ?: Base::extIcon($value['ext']));
|
||||
} else if ($this->type === 'record') {
|
||||
$value['path'] = Base::fillUrl($value['path']);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* emoji回复格式化
|
||||
* @param $value
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function getEmojiAttribute($value)
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
return Base::json2array($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取占比
|
||||
* @param bool|int $increment 是否新增阅读数
|
||||
* @return int
|
||||
*/
|
||||
public function generatePercentage($increment = false) {
|
||||
if ($increment) {
|
||||
$this->increment('read', is_bool($increment) ? 1 : $increment);
|
||||
}
|
||||
if ($this->read > $this->send || empty($this->send)) {
|
||||
return $this->appendattrs['percentage'] = 100;
|
||||
} else {
|
||||
return $this->appendattrs['percentage'] = intval($this->read / $this->send * 100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记已送达 同时 告诉发送人已送达
|
||||
* @param $userid
|
||||
@@ -111,13 +209,17 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
if (!$msgRead->read_at) {
|
||||
$msgRead->read_at = Carbon::now();
|
||||
$msgRead->save();
|
||||
$this->increment('read');
|
||||
$this->generatePercentage(true);
|
||||
PushTask::push([
|
||||
'userid' => $this->userid,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'update',
|
||||
'data' => $this->toArray(),
|
||||
'mode' => 'readed',
|
||||
'data' => [
|
||||
'id' => $this->id,
|
||||
'read' => $this->read,
|
||||
'percentage' => $this->percentage,
|
||||
],
|
||||
]
|
||||
]);
|
||||
}
|
||||
@@ -126,33 +228,746 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param int $dialog_id 会话ID(即 聊天室ID)
|
||||
* @param string $type 消息类型
|
||||
* @param array $msg 发送的消息
|
||||
* @param int $sender 发送的会员ID(默认自己,0为系统)
|
||||
* emoji回复
|
||||
* @param $symbol
|
||||
* @param int $sender 发送的会员ID
|
||||
* @return mixed
|
||||
*/
|
||||
public function emojiMsg($symbol, $sender)
|
||||
{
|
||||
$exist = false;
|
||||
$array = $this->emoji;
|
||||
foreach ($array as $index => &$item) {
|
||||
if ($item['symbol'] === $symbol) {
|
||||
if (in_array($sender, $item['userids'])) {
|
||||
// 已存在 去除
|
||||
$item['userids'] = array_values(array_diff($item['userids'], [$sender]));
|
||||
if (empty($item['userids'])) {
|
||||
unset($array[$index]);
|
||||
$array = array_values($array);
|
||||
}
|
||||
} else {
|
||||
// 未存在 添加
|
||||
array_unshift($item['userids'], $sender);
|
||||
}
|
||||
$exist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$exist) {
|
||||
array_unshift($array, [
|
||||
'symbol' => $symbol,
|
||||
'userids' => [$sender]
|
||||
]);
|
||||
}
|
||||
//
|
||||
$this->emoji = Base::array2json($array);
|
||||
$this->save();
|
||||
$resData = [
|
||||
'id' => $this->id,
|
||||
'emoji' => $array,
|
||||
];
|
||||
//
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
$dialog?->pushMsg('update', $resData);
|
||||
//
|
||||
return Base::retSuccess('success', $resData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标注、取消标注
|
||||
* @param int $sender 标注的会员ID
|
||||
* @return mixed
|
||||
*/
|
||||
public function toggleTagMsg($sender)
|
||||
{
|
||||
if (in_array($this->type, ['tag', 'todo', 'notice'])) {
|
||||
return Base::retError('此消息不支持标注');
|
||||
}
|
||||
$before = $this->tag;
|
||||
$this->tag = $before ? 0 : $sender;
|
||||
$this->save();
|
||||
$resData = [
|
||||
'id' => $this->id,
|
||||
'tag' => $this->tag,
|
||||
];
|
||||
//
|
||||
$data = [
|
||||
'update' => $resData
|
||||
];
|
||||
$res = self::sendMsg(null, $this->dialog_id, 'tag', [
|
||||
'action' => $this->tag ? 'add' : 'remove',
|
||||
'data' => [
|
||||
'id' => $this->id,
|
||||
'type' => $this->type,
|
||||
'msg' => $this->quoteTextMsg(),
|
||||
]
|
||||
], $sender);
|
||||
if (Base::isSuccess($res)) {
|
||||
$data['add'] = $res['data'];
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
$dialog->pushMsg('update', $resData);
|
||||
} else {
|
||||
$this->tag = $before;
|
||||
$this->save();
|
||||
}
|
||||
//
|
||||
return Base::retSuccess($this->tag ? '标注成功' : '取消成功', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设待办、取消待办
|
||||
* @param int $sender 设待办的会员ID
|
||||
* @param array $userids 设置给指定会员
|
||||
* @return mixed
|
||||
*/
|
||||
public function toggleTodoMsg($sender, $userids = [])
|
||||
{
|
||||
if (in_array($this->type, ['tag', 'todo', 'notice'])) {
|
||||
return Base::retError('此消息不支持设待办');
|
||||
}
|
||||
if ($this->todo && $this->todo != $sender) {
|
||||
return Base::retError('仅支持设此待办人员【' . User::userid2nickname($this->todo) . '】取消');
|
||||
}
|
||||
$before = $this->todo;
|
||||
$this->todo = $before ? 0 : $sender;
|
||||
$this->save();
|
||||
$resData = [
|
||||
'id' => $this->id,
|
||||
'todo' => $this->todo,
|
||||
];
|
||||
//
|
||||
$data = [
|
||||
'update' => $resData
|
||||
];
|
||||
$res = self::sendMsg(null, $this->dialog_id, 'todo', [
|
||||
'action' => $this->todo ? 'add' : 'remove',
|
||||
'data' => [
|
||||
'id' => $this->id,
|
||||
'type' => $this->type,
|
||||
'msg' => $this->quoteTextMsg(),
|
||||
'userids' => implode(",", $userids),
|
||||
]
|
||||
], $sender);
|
||||
if (Base::isSuccess($res)) {
|
||||
$data['add'] = $res['data'];
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
$dialog->pushMsg('update', array_merge($resData, ['dialog_id' => $this->dialog_id]));
|
||||
//
|
||||
if ($this->todo) {
|
||||
$useridList = $dialog->dialogUser->pluck('userid')->toArray();
|
||||
foreach ($useridList as $userid) {
|
||||
if ($userids && !in_array($userid, $userids)) {
|
||||
continue;
|
||||
}
|
||||
if (empty($userid)) {
|
||||
continue;
|
||||
}
|
||||
WebSocketDialogMsgTodo::createInstance([
|
||||
'dialog_id' => $this->dialog_id,
|
||||
'msg_id' => $this->id,
|
||||
'userid' => $userid,
|
||||
])->saveOrIgnore();
|
||||
}
|
||||
} else {
|
||||
WebSocketDialogMsgTodo::whereMsgId($this->id)->delete();
|
||||
}
|
||||
} else {
|
||||
$this->todo = $before;
|
||||
$this->save();
|
||||
}
|
||||
//
|
||||
return Base::retSuccess($this->todo ? '设置成功' : '取消成功', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转发消息
|
||||
* @param array|int $dialogids
|
||||
* @param array|int $userids
|
||||
* @param User $user 发送的会员
|
||||
* @param int $showSource 是否显示原发送者信息
|
||||
* @param string $leaveMessage 转发留言
|
||||
* @return mixed
|
||||
*/
|
||||
public function forwardMsg($dialogids, $userids, $user, $showSource = 1, $leaveMessage = '')
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($dialogids, $user, $userids, $showSource, $leaveMessage) {
|
||||
$originalMsg = Base::json2array($this->getRawOriginal('msg'));
|
||||
$msgs = [];
|
||||
$already = [];
|
||||
if ($dialogids) {
|
||||
if (!is_array($dialogids)) {
|
||||
$dialogids = [$dialogids];
|
||||
}
|
||||
foreach ($dialogids as $dialogid) {
|
||||
$res = self::sendMsg('forward-'.( $showSource ? 1 : 0).'-'.($this->forward_id ?: $this->id), $dialogid, $this->type, $originalMsg, $user->userid);
|
||||
if (Base::isSuccess($res)) {
|
||||
$msgs[] = $res['data'];
|
||||
$already[] = $dialogid;
|
||||
}
|
||||
if ($leaveMessage) {
|
||||
self::sendMsg(null, $dialogid, 'text', ['text' => $leaveMessage], $user->userid);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($userids) {
|
||||
if (!is_array($userids)) {
|
||||
$userids = [$userids];
|
||||
}
|
||||
foreach ($userids as $userid) {
|
||||
if (!User::whereUserid($userid)->exists()) {
|
||||
continue;
|
||||
}
|
||||
$dialog = WebSocketDialog::checkUserDialog($user, $userid);
|
||||
if ($dialog && !in_array($dialog->id, $already)) {
|
||||
$res = self::sendMsg('forward-'.( $showSource ? 1 : 0).'-'.($this->forward_id ?: $this->id), $dialog->id, $this->type, $originalMsg, $user->userid);
|
||||
if (Base::isSuccess($res)) {
|
||||
$msgs[] = $res['data'];
|
||||
}
|
||||
if ($leaveMessage) {
|
||||
self::sendMsg(null, $dialog->id, 'text', ['text' => $leaveMessage], $user->userid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('转发成功', [
|
||||
'msgs' => $msgs
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
* @param array|int $ids
|
||||
* @return void
|
||||
*/
|
||||
public static function deleteMsgs($ids) {
|
||||
$ids = Base::arrayRetainInt(is_array($ids) ? $ids : [$ids], true);
|
||||
AbstractModel::transaction(function() use ($ids) {
|
||||
$dialogIds = WebSocketDialogMsg::select('dialog_id')->whereIn("id", $ids)->distinct()->get()->pluck('dialog_id');
|
||||
$replyIds = WebSocketDialogMsg::select('reply_id')->whereIn("id", $ids)->distinct()->get()->pluck('reply_id');
|
||||
//
|
||||
WebSocketDialogMsgRead::whereIn('msg_id', $ids)->whereNull('read_at')->delete(); // 未阅读记录不需要软删除,直接删除即可
|
||||
WebSocketDialogMsgTodo::whereIn('msg_id', $ids)->delete();
|
||||
self::whereIn('id', $ids)->delete();
|
||||
//
|
||||
$dialogDatas = WebSocketDialog::whereIn('id', $dialogIds)->get();
|
||||
foreach ($dialogDatas as $dialogData) {
|
||||
$dialogData->updateMsgLastAt();
|
||||
}
|
||||
foreach ($replyIds as $id) {
|
||||
self::whereId($id)->update(['reply_num' => self::whereReplyId($id)->count()]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回消息
|
||||
* @return void
|
||||
*/
|
||||
public function withdrawMsg()
|
||||
{
|
||||
$send_dt = Carbon::parse($this->created_at)->addDay();
|
||||
if ($send_dt->lt(Carbon::now())) {
|
||||
throw new ApiException('已超过24小时,此消息不能撤回');
|
||||
}
|
||||
AbstractModel::transaction(function() {
|
||||
$deleteRead = WebSocketDialogMsgRead::whereMsgId($this->id)->whereNull('read_at')->delete(); // 未阅读记录不需要软删除,直接删除即可
|
||||
$this->delete();
|
||||
//
|
||||
if ($this->reply_id > 0) {
|
||||
self::whereId($this->reply_id)->decrement('reply_num');
|
||||
}
|
||||
//
|
||||
$dialogData = $this->webSocketDialog;
|
||||
if ($dialogData) {
|
||||
foreach ($dialogData->dialogUser as $dialogUser) {
|
||||
$dialogUser->updated_at = Carbon::now();
|
||||
$dialogUser->save();
|
||||
}
|
||||
$userids = $dialogData->dialogUser->pluck('userid')->toArray();
|
||||
PushTask::push([
|
||||
'userid' => $userids,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'delete',
|
||||
'data' => [
|
||||
'id' => $this->id,
|
||||
'dialog_id' => $this->dialog_id,
|
||||
'last_msg' => $dialogData->updateMsgLastAt(),
|
||||
'update_read' => $deleteRead ? 1 : 0
|
||||
],
|
||||
]
|
||||
]);
|
||||
}
|
||||
//
|
||||
WebSocketDialogMsgTodo::whereMsgId($this->id)->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览消息
|
||||
* @param bool $preserveHtml 保留html格式
|
||||
* @param null|array $data
|
||||
* @return string
|
||||
*/
|
||||
public function previewMsg($preserveHtml = false, $data = null)
|
||||
{
|
||||
if ($data === null) {
|
||||
$data = [
|
||||
'type' => $this->type,
|
||||
'msg' => $this->msg,
|
||||
];
|
||||
}
|
||||
switch ($data['type']) {
|
||||
case 'text':
|
||||
case 'word-chain':
|
||||
case 'vote':
|
||||
return $this->previewTextMsg($data['msg']['text'], $preserveHtml);
|
||||
case 'record':
|
||||
return "[语音]";
|
||||
case 'meeting':
|
||||
return "[会议] ${$data['msg']['name']}";
|
||||
case 'file':
|
||||
if ($data['msg']['type'] == 'img') {
|
||||
return "[图片]";
|
||||
}
|
||||
return "[文件] {$data['msg']['name']}";
|
||||
case 'tag':
|
||||
$action = $data['msg']['action'] === 'remove' ? '取消标注' : '标注';
|
||||
return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}";
|
||||
case 'todo':
|
||||
$action = $data['msg']['action'] === 'remove' ? '取消待办' : ($data['msg']['action'] === 'done' ? '完成' : '设待办');
|
||||
return "[{$action}] {$this->previewMsg(false, $data['msg']['data'])}";
|
||||
case 'notice':
|
||||
return $data['msg']['notice'];
|
||||
default:
|
||||
return "[未知的消息]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成关键词
|
||||
* @return string
|
||||
*/
|
||||
public function generateMsgKey()
|
||||
{
|
||||
return match ($this->type) {
|
||||
'text' => str_replace(" ", " ", strip_tags($this->msg['text'])),
|
||||
'meeting', 'file' => $this->msg['name'],
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回引用消息(如果是文本消息则截取)
|
||||
* @param int $strlen
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function quoteTextMsg($strlen = 30)
|
||||
{
|
||||
$msg = $this->msg;
|
||||
if ($this->type === 'text') {
|
||||
$msg['text'] = $this->previewTextMsg($msg['text']);
|
||||
if (mb_strlen($msg['text']) > $strlen) {
|
||||
$msg['text'] = mb_substr($msg['text'], 0, $strlen - 3) . "...";
|
||||
}
|
||||
}
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文本预览消息
|
||||
* @param $text
|
||||
* @param bool $preserveHtml 保留html格式
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
private function previewTextMsg($text, $preserveHtml = false)
|
||||
{
|
||||
if (!$text) return '';
|
||||
$text = preg_replace("/<img\s+class=\"emoticon\"[^>]*?alt=\"(\S+)\"[^>]*?>/", "[$1]", $text);
|
||||
$text = preg_replace("/<img\s+class=\"emoticon\"[^>]*?>/", "[动画表情]", $text);
|
||||
$text = preg_replace("/<img\s+class=\"browse\"[^>]*?>/", "[图片]", $text);
|
||||
if (!$preserveHtml) {
|
||||
$text = strip_tags($text);
|
||||
$text = str_replace([" ", "&", "<", ">"], [" ", "&", "<", ">"], $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文本消息内容,用于发送前
|
||||
* @param $text
|
||||
* @param $dialog_id
|
||||
* @return mixed|string|string[]
|
||||
*/
|
||||
public static function formatMsg($text, $dialog_id)
|
||||
{
|
||||
@ini_set("pcre.backtrack_limit", 999999999);
|
||||
// 基础处理
|
||||
$text = preg_replace("/<(\/[a-zA-Z]+)\s*>/s", "<$1>", $text);
|
||||
// 图片 [:IMAGE:className:width:height:src:alt:]
|
||||
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg|webp|gif);base64,(.*?)\"(.*?)>(<\/img>)*/s", $text, $matchs);
|
||||
foreach ($matchs[2] as $key => $base64) {
|
||||
$imagePath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
|
||||
Base::makeDir(public_path($imagePath));
|
||||
$imagePath .= md5s($base64) . "." . $matchs[1][$key];
|
||||
if (Base::saveContentImage(public_path($imagePath), base64_decode($base64))) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
if ($extension = Image::thumbImage(public_path($imagePath), public_path($imagePath) . "_thumb.{*}", 320, 0)) {
|
||||
$imagePath .= "_thumb.{$extension}";
|
||||
}
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
|
||||
}
|
||||
}
|
||||
// 表情图片
|
||||
preg_match_all("/<img\s+class=\"emoticon\"(.*?)>/s", $text, $matchs);
|
||||
foreach ($matchs[1] as $key => $str) {
|
||||
preg_match("/data-asset=\"(.*?)\"/", $str, $matchAsset);
|
||||
preg_match("/data-name=\"(.*?)\"/", $str, $matchName);
|
||||
$imageSize = null;
|
||||
$imagePath = "";
|
||||
$imageName = "";
|
||||
if ($matchAsset[1] === "emosearch") {
|
||||
preg_match("/src=\"(.*?)\"/", $str, $matchSrc);
|
||||
if ($matchSrc) {
|
||||
$srcMd5 = md5($matchSrc[1]);
|
||||
$imagePath = "uploads/emosearch/" . substr($srcMd5, 0, 2) . "/" . substr($srcMd5, 32 - 2) . "/";
|
||||
Base::makeDir(public_path($imagePath));
|
||||
$imagePath .= md5s($matchSrc[1]);
|
||||
if (file_exists(public_path($imagePath))) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
} else {
|
||||
$image = file_get_contents($matchSrc[1]);
|
||||
if ($image && file_put_contents(public_path($imagePath), $image)) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
// 添加后缀
|
||||
if ($imageSize && !str_contains($imagePath, '.')) {
|
||||
preg_match("/^image\/(png|jpg|jpeg|webp|gif)$/", $imageSize['mime'], $matchMine);
|
||||
if ($matchMine) {
|
||||
$imageNewPath = $imagePath . "." . $matchMine[1];
|
||||
if (rename(public_path($imagePath), public_path($imageNewPath))) {
|
||||
$imagePath = $imageNewPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (file_exists(public_path($matchAsset[1]))) {
|
||||
$imagePath = $matchAsset[1];
|
||||
$imageName = $matchName[1];
|
||||
$imageSize = getimagesize(public_path($matchAsset[1]));
|
||||
}
|
||||
if ($imageSize) {
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:emoticon:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}:{$imageName}:]", $text);
|
||||
} else {
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:90:90:images/other/imgerr.jpg::]", $text);
|
||||
}
|
||||
}
|
||||
// 其他网络图片
|
||||
$imageSaveLocal = Base::settingFind("system", "image_save_local");
|
||||
preg_match_all("/<img[^>]*?src=([\"'])(.*?(png|jpg|jpeg|webp|gif).*?)\\1[^>]*?>/is", $text, $matchs);
|
||||
foreach ($matchs[2] as $key => $str) {
|
||||
if ($imageSaveLocal === 'close') {
|
||||
$imageSize = getimagesize($str);
|
||||
if ($imageSize === false) {
|
||||
$imageSize = ["auto", "auto"];
|
||||
}
|
||||
$imagePath = "base64-" . base64_encode($str);
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
|
||||
continue;
|
||||
}
|
||||
if (str_starts_with($str, "{{RemoteURL}}")) {
|
||||
$imagePath = Base::leftDelete($str, "{{RemoteURL}}");
|
||||
$imagePath = Base::thumbRestore($imagePath);
|
||||
} else {
|
||||
$imagePath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
|
||||
Base::makeDir(public_path($imagePath));
|
||||
$imagePath .= md5s($str) . "." . $matchs[3][$key];
|
||||
}
|
||||
if (file_exists(public_path($imagePath))) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
if ($extension = Image::thumbImage(public_path($imagePath), public_path($imagePath) . "_thumb.{*}", 320, 0)) {
|
||||
$imagePath .= "_thumb.{$extension}";
|
||||
}
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
|
||||
} else {
|
||||
$image = file_get_contents($str);
|
||||
if (empty($image)) {
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:90:90:images/other/imgerr.jpg::]", $text);
|
||||
} else if (Base::saveContentImage(public_path($imagePath), $image)) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
if ($extension = Image::thumbImage(public_path($imagePath), public_path($imagePath) . "_thumb.{*}", 320, 0)) {
|
||||
$imagePath .= "_thumb.{$extension}";
|
||||
}
|
||||
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imageSize[0]}:{$imageSize[1]}:{$imagePath}::]", $text);
|
||||
}
|
||||
}
|
||||
}
|
||||
// @成员、#任务、~文件
|
||||
preg_match_all("/<span\s+class=\"mention\"(.*?)>.*?<\/span>.*?<\/span>.*?<\/span>/s", $text, $matchs);
|
||||
foreach ($matchs[1] as $key => $str) {
|
||||
preg_match("/data-denotation-char=\"(.*?)\"/", $str, $matchChar);
|
||||
preg_match("/data-id=\"(.*?)\"/", $str, $matchId);
|
||||
preg_match("/data-value=\"(.*?)\"/s", $str, $matchValye);
|
||||
$keyId = $matchId[1];
|
||||
if ($matchChar[1] === "~") {
|
||||
if (Base::isNumber($keyId)) {
|
||||
$file = File::permissionFind($keyId, User::auth());
|
||||
if ($file->type == 'folder') {
|
||||
throw new ApiException('文件夹不支持分享');
|
||||
}
|
||||
$fileLink = $file->getShareLink(User::userid());
|
||||
$keyId = $fileLink['code'];
|
||||
} else {
|
||||
preg_match("/\/single\/file\/(.*?)$/i", $keyId, $match);
|
||||
if ($match && strlen($match[1]) >= 8) {
|
||||
$keyId = $match[1];
|
||||
} else {
|
||||
throw new ApiException('文件分享错误');
|
||||
}
|
||||
}
|
||||
}
|
||||
$text = str_replace($matchs[0][$key], "[:{$matchChar[1]}:{$keyId}:{$matchValye[1]}:]", $text);
|
||||
}
|
||||
// 处理快捷消息
|
||||
preg_match_all("/<span[^>]*?data-quick-key=([\"'])(.*?)\\1[^>]*?>(.*?)<\/span>/is", $text, $matchs);
|
||||
foreach ($matchs[0] as $key => $str) {
|
||||
$quickKey = $matchs[2][$key];
|
||||
$quickLabel = $matchs[3][$key];
|
||||
if ($quickKey && $quickLabel) {
|
||||
$quickKey = str_replace(":", "", $quickKey);
|
||||
$quickLabel = str_replace(":", "", $quickLabel);
|
||||
$text = str_replace($str, "[:QUICK:{$quickKey}:{$quickLabel}:]", $text);
|
||||
}
|
||||
}
|
||||
// 处理链接标签
|
||||
preg_match_all("/<a[^>]*?href=([\"'])(.*?)\\1[^>]*?>(.*?)<\/a>/is", $text, $matchs);
|
||||
foreach ($matchs[0] as $key => $str) {
|
||||
$herf = $matchs[2][$key];
|
||||
$title = $matchs[3][$key] ?: $herf;
|
||||
preg_match("/\/single\/file\/(.*?)$/i", strip_tags($title), $match);
|
||||
if ($match && strlen($match[1]) >= 8) {
|
||||
$file = File::select(['files.id', 'files.name', 'files.ext'])->join('file_links as L', 'files.id', '=', 'L.file_id')->where('L.code', $match[1])->first();
|
||||
if ($file && $file->name) {
|
||||
$name = $file->ext ? "{$file->name}.{$file->ext}" : $file->name;
|
||||
$text = str_replace($str, "[:~:{$match[1]}:{$name}:]", $text);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$herf = base64_encode($herf);
|
||||
$title = base64_encode($title);
|
||||
$text = str_replace($str, "[:LINK:{$herf}:{$title}:]", $text);
|
||||
}
|
||||
// 文件分享链接
|
||||
preg_match_all("/(https*:\/\/)((\w|=|\?|\.|\/|&|-|:|\+|%|;|#|@|,|!)+)/i", $text, $matchs);
|
||||
if ($matchs) {
|
||||
foreach ($matchs[0] as $str) {
|
||||
preg_match("/\/single\/file\/(.*?)$/i", $str, $match);
|
||||
if ($match && strlen($match[1]) >= 8) {
|
||||
$file = File::select(['files.id', 'files.name', 'files.ext'])->join('file_links as L', 'files.id', '=', 'L.file_id')->where('L.code', $match[1])->first();
|
||||
if ($file && $file->name) {
|
||||
$name = $file->ext ? "{$file->name}.{$file->ext}" : $file->name;
|
||||
$text = str_replace($str, "[:~:{$match[1]}:{$name}:]", $text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 过滤标签
|
||||
$text = strip_tags($text, '<blockquote> <strong> <pre> <ol> <ul> <li> <em> <p> <s> <u> <a>');
|
||||
$text = preg_replace("/\<(blockquote|strong|pre|ol|ul|li|em|p|s|u).*?\>/is", "<$1>", $text); // 不用去除a标签,上面已经处理过了
|
||||
$text = preg_replace_callback("/\[:LINK:(.*?):(.*?):\]/i", function (array $match) {
|
||||
return "<a href=\"" . base64_decode($match[1]) . "\" target=\"_blank\">" . base64_decode($match[2]) . "</a>";
|
||||
}, $text);
|
||||
$text = preg_replace_callback("/\[:IMAGE:(.*?):(.*?):(.*?):(.*?):(.*?):\]/i", function (array $match) {
|
||||
$wh = $match[2] === 'auto' ? "" : " width=\"{$match[2]}\" height=\"{$match[3]}\"";
|
||||
$src = str_starts_with($match[4], "base64-") ? base64_decode(substr($match[4], 7)) : "{{RemoteURL}}{$match[4]}";
|
||||
return "<img class=\"{$match[1]}\"{$wh} src=\"{$src}\" alt=\"{$match[5]}\"/>";
|
||||
}, $text);
|
||||
$text = preg_replace("/\[:@:(.*?):(.*?):\]/i", "<span class=\"mention user\" data-id=\"$1\">@$2</span>", $text);
|
||||
$text = preg_replace("/\[:#:(.*?):(.*?):\]/is", "<span class=\"mention task\" data-id=\"$1\">#$2</span>", $text);
|
||||
$text = preg_replace("/\[:~:(.*?):(.*?):\]/i", "<a class=\"mention file\" href=\"{{RemoteURL}}single/file/$1\" target=\"_blank\">~$2</a>", $text);
|
||||
$text = preg_replace("/\[:QUICK:(.*?):(.*?):\]/i", "<span data-quick-key=\"$1\">$2</span>", $text);
|
||||
return preg_replace("/^(<p><\/p>)+|(<p><\/p>)+$/i", "", $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息、修改消息
|
||||
* @param string $action 动作
|
||||
* - reply-98:回复消息ID=98
|
||||
* - update-99:更新消息ID=99(标记修改)
|
||||
* - change-99:更新消息ID=99(不标记修改)
|
||||
* - forward-99:转发消息ID=99
|
||||
* @param int $dialog_id 会话ID(即 聊天室ID)
|
||||
* @param string $type 消息类型
|
||||
* @param array $msg 发送的消息
|
||||
* @param int|null $sender 发送的会员ID(默认自己,0为系统)
|
||||
* @param bool $push_self 推送-是否推给自己
|
||||
* @param bool $push_retry 推送-失败后重试1次(有时候在事务里执行,数据还没生成时会出现找不到消息的情况)
|
||||
* @param bool|null $push_silence 推送-静默
|
||||
* - type = [text|file|record|meeting] 默认为:false
|
||||
* @return array
|
||||
*/
|
||||
public static function sendMsg($dialog_id, $type, $msg, $sender = 0)
|
||||
public static function sendMsg($action, $dialog_id, $type, $msg, $sender = null, $push_self = false, $push_retry = false, $push_silence = null)
|
||||
{
|
||||
$dialogMsg = self::createInstance([
|
||||
'userid' => $sender ?: User::userid(),
|
||||
'type' => $type,
|
||||
'msg' => $msg,
|
||||
'read' => 0,
|
||||
]);
|
||||
AbstractModel::transaction(function () use ($dialog_id, $msg, $dialogMsg) {
|
||||
$dialog = WebSocketDialog::find($dialog_id);
|
||||
if (empty($dialog)) {
|
||||
throw new ApiException('获取会话失败');
|
||||
$link = 0;
|
||||
$mtype = $type;
|
||||
if ($type === 'text') {
|
||||
if (str_contains($msg['text'], '<a ') || preg_match("/https*:\/\//", $msg['text'])) {
|
||||
$link = 1;
|
||||
}
|
||||
$dialog->last_at = Carbon::now();
|
||||
$dialog->save();
|
||||
$dialogMsg->send = 1;
|
||||
$dialogMsg->dialog_id = $dialog->id;
|
||||
if (str_contains($msg['text'], '<img ')) {
|
||||
$mtype = str_contains($msg['text'], '"emoticon"') ? 'emoticon' : 'image';
|
||||
}
|
||||
preg_match_all("/@([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,6})/i", $msg['text'], $matchs);
|
||||
foreach($matchs[0] as $key => $item) {
|
||||
$aiUser = User::whereEmail($matchs[1][$key])->whereDisableAt(null)->first();
|
||||
if ($aiUser) {
|
||||
$msg['text'] = str_replace($item, "<span class=\"mention user\" data-id=\"{$aiUser->userid}\">@{$aiUser->nickname}</span>", $msg['text']);
|
||||
}
|
||||
}
|
||||
} elseif ($type === 'file') {
|
||||
if (in_array($msg['ext'], ['jpg', 'jpeg', 'webp', 'png', 'gif'])) {
|
||||
$mtype = 'image';
|
||||
}
|
||||
}
|
||||
if ($push_silence === null) {
|
||||
$push_silence = !in_array($type, ["text", "file", "record", "meeting"]);
|
||||
}
|
||||
//
|
||||
$update_id = preg_match("/^update-(\d+)$/", $action, $match) ? $match[1] : 0;
|
||||
$change_id = preg_match("/^change-(\d+)$/", $action, $match) ? $match[1] : 0;
|
||||
$reply_id = preg_match("/^reply-(\d+)$/", $action, $match) ? $match[1] : 0;
|
||||
$forward_id = preg_match("/^forward-(\d+)-(\d+)$/", $action, $match) ? $match[2] : 0;
|
||||
$sender = $sender === null ? User::userid() : $sender;
|
||||
//
|
||||
$dialog = WebSocketDialog::find($dialog_id);
|
||||
if (empty($dialog)) {
|
||||
throw new ApiException('获取会话失败');
|
||||
}
|
||||
if ($sender > 0) {
|
||||
$dialog->checkMute($sender);
|
||||
}
|
||||
//
|
||||
$modify = 1;
|
||||
if ($change_id) {
|
||||
$modify = 0;
|
||||
$update_id = $change_id;
|
||||
}
|
||||
if ($update_id) {
|
||||
// 修改
|
||||
$dialogMsg = self::whereId($update_id)->whereDialogId($dialog_id)->first();
|
||||
if (empty($dialogMsg)) {
|
||||
throw new ApiException('消息不存在');
|
||||
}
|
||||
if ($dialogMsg->type !== 'text' && $dialogMsg->type !== 'vote') {
|
||||
throw new ApiException('此消息不支持此操作');
|
||||
}
|
||||
if ($dialogMsg->userid != $sender && $dialogMsg->type !== 'vote') {
|
||||
throw new ApiException('仅支持修改自己的消息');
|
||||
}
|
||||
//
|
||||
$updateData = [
|
||||
'mtype' => $mtype,
|
||||
'link' => $link,
|
||||
'msg' => $msg,
|
||||
'modify' => $modify,
|
||||
];
|
||||
$dialogMsg->updateInstance($updateData);
|
||||
$dialogMsg->key = $dialogMsg->generateMsgKey();
|
||||
$dialogMsg->save();
|
||||
});
|
||||
Task::deliver(new WebSocketDialogMsgTask($dialogMsg->id));
|
||||
return Base::retSuccess('发送成功', $dialogMsg);
|
||||
//
|
||||
$dialogMsg->msgJoinGroup($dialog);
|
||||
//
|
||||
$dialog->pushMsg('update', array_merge($updateData, [
|
||||
'id' => $dialogMsg->id
|
||||
]));
|
||||
//
|
||||
return Base::retSuccess('修改成功', $dialogMsg);
|
||||
} else {
|
||||
// 发送
|
||||
if ($reply_id && !self::whereId($reply_id)->increment('reply_num')) {
|
||||
throw new ApiException('回复的消息不存在');
|
||||
}
|
||||
// 转发
|
||||
if ($forward_id && !self::whereId($forward_id)->increment('forward_num')) {
|
||||
throw new ApiException('转发的消息不存在');
|
||||
}
|
||||
//
|
||||
$dialogMsg = self::createInstance([
|
||||
'dialog_id' => $dialog_id,
|
||||
'dialog_type' => $dialog->type,
|
||||
'reply_id' => $reply_id,
|
||||
'userid' => $sender,
|
||||
'type' => $type,
|
||||
'mtype' => $mtype,
|
||||
'link' => $link,
|
||||
'msg' => $msg,
|
||||
'read' => 0,
|
||||
'forward_id' => $forward_id,
|
||||
'forward_show' => $forward_id ? $match[1] : 1,
|
||||
]);
|
||||
AbstractModel::transaction(function () use ($dialog, $dialogMsg) {
|
||||
$dialog->last_at = Carbon::now();
|
||||
$dialog->save();
|
||||
$dialogMsg->send = 1;
|
||||
$dialogMsg->key = $dialogMsg->generateMsgKey();
|
||||
$dialogMsg->save();
|
||||
WebSocketDialogUser::whereDialogId($dialog->id)->change(['updated_at' => Carbon::now()->toDateTimeString('millisecond')]);
|
||||
});
|
||||
//
|
||||
$task = new WebSocketDialogMsgTask($dialogMsg->id);
|
||||
if ($push_self) {
|
||||
$task->setIgnoreFd(null);
|
||||
}
|
||||
if ($push_retry) {
|
||||
$task->setMsgNotExistRetry(true);
|
||||
}
|
||||
if ($push_silence) {
|
||||
$task->setSilence($push_silence);
|
||||
}
|
||||
Task::deliver($task);
|
||||
//
|
||||
return Base::retSuccess('发送成功', $dialogMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将被@的人加入群
|
||||
* @param WebSocketDialog $dialog 对话
|
||||
* @return array
|
||||
*/
|
||||
public function msgJoinGroup(WebSocketDialog $dialog)
|
||||
{
|
||||
$updateds = [];
|
||||
$silences = [];
|
||||
foreach ($dialog->dialogUser as $dialogUser) {
|
||||
$updateds[$dialogUser->userid] = $dialogUser->updated_at;
|
||||
$silences[$dialogUser->userid] = $dialogUser->silence;
|
||||
}
|
||||
$userids = array_keys($silences);
|
||||
|
||||
// 提及会员
|
||||
$mentions = [];
|
||||
if ($this->type === 'text') {
|
||||
preg_match_all("/<span class=\"mention user\" data-id=\"(\d+)\">/", $this->msg['text'], $matchs);
|
||||
if ($matchs) {
|
||||
$mentions = array_values(array_filter(array_unique($matchs[1])));
|
||||
}
|
||||
}
|
||||
|
||||
// 将会话以外的成员加入会话内
|
||||
$diffids = array_values(array_diff($mentions, $userids));
|
||||
if ($diffids) {
|
||||
// 仅(群聊)且(是群主或没有群主)才可以@成员以外的人
|
||||
if ($dialog->type === 'group' && in_array($dialog->owner_id, [0, $this->userid])) {
|
||||
$dialog->joinGroup($diffids, $this->userid);
|
||||
$dialog->pushMsg("groupJoin", null, $diffids);
|
||||
$userids = array_values(array_unique(array_merge($mentions, $userids)));
|
||||
}
|
||||
}
|
||||
|
||||
return compact('updateds', 'silences', 'userids', 'mentions');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,32 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Class WebSocketDialogMsgRead
|
||||
* App\Models\WebSocketDialogMsgRead
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $msg_id 消息ID
|
||||
* @property int|null $userid 发送会员ID
|
||||
* @property int|null $userid 接收会员ID
|
||||
* @property int|null $mention 是否提及(被@)
|
||||
* @property int|null $silence 是否免打扰:0否,1是
|
||||
* @property int|null $email 是否发了邮件
|
||||
* @property int|null $after 在阅读之后才添加的记录
|
||||
* @property string|null $read_at 阅读时间
|
||||
* @property-read \App\Models\WebSocketDialogMsg|null $webSocketDialogMsg
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereAfter($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereMention($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereMsgId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereReadAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereSilence($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgRead whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
@@ -30,4 +38,38 @@ class WebSocketDialogMsgRead extends AbstractModel
|
||||
parent::__construct($attributes);
|
||||
$this->timestamps = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function webSocketDialogMsg(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(WebSocketDialogMsg::class, 'id', 'msg_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅标记成阅读
|
||||
* @param $list
|
||||
* @return void
|
||||
*/
|
||||
public static function onlyMarkRead($list)
|
||||
{
|
||||
$dialogMsg = [];
|
||||
/** @var WebSocketDialogMsgRead $item */
|
||||
foreach ($list as $item) {
|
||||
$item->read_at = Carbon::now();
|
||||
$item->save();
|
||||
if (isset($dialogMsg[$item->msg_id])) {
|
||||
$dialogMsg[$item->msg_id]['readNum']++;
|
||||
} else {
|
||||
$dialogMsg[$item->msg_id] = [
|
||||
'dialogMsg' => $item->webSocketDialogMsg,
|
||||
'readNum' => 1
|
||||
];
|
||||
}
|
||||
}
|
||||
foreach ($dialogMsg as $item) {
|
||||
$item['dialogMsg']?->generatePercentage($item['readNum']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
app/Models/WebSocketDialogMsgTodo.php
Normal file
47
app/Models/WebSocketDialogMsgTodo.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* App\Models\WebSocketDialogMsgTodo
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $msg_id 消息ID
|
||||
* @property int|null $userid 接收会员ID
|
||||
* @property string|null $done_at 完成时间
|
||||
* @property-read array|mixed $msg_data
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereDoneAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereMsgId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsgTodo whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class WebSocketDialogMsgTodo extends AbstractModel
|
||||
{
|
||||
protected $appends = [
|
||||
'msg_data',
|
||||
];
|
||||
|
||||
function __construct(array $attributes = [])
|
||||
{
|
||||
parent::__construct($attributes);
|
||||
$this->timestamps = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息详情
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function getMsgDataAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['msgData'])) {
|
||||
$this->appendattrs['msgData'] = WebSocketDialogMsg::select(['id', 'type', 'msg'])->whereId($this->msg_id)->first()?->cancelAppend();
|
||||
}
|
||||
return $this->appendattrs['msgData'];
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,48 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Class WebSocketDialogUser
|
||||
* App\Models\WebSocketDialogUser
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property string|null $top_at 置顶时间
|
||||
* @property int|null $mark_unread 是否标记为未读:0否,1是
|
||||
* @property int|null $silence 是否免打扰:0否,1是
|
||||
* @property int|null $inviter 邀请人
|
||||
* @property int|null $important 是否不可移出(项目、任务、部门人员)
|
||||
* @property string|null $color 颜色
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\WebSocketDialog|null $webSocketDialog
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereColor($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereImportant($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereInviter($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereMarkUnread($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereSilence($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereTopAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class WebSocketDialogUser extends AbstractModel
|
||||
{
|
||||
protected $dateFormat = 'Y-m-d H:i:s.v';
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function webSocketDialog(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(WebSocketDialog::class, 'id', 'dialog_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* Class WebSocketTmpMsg
|
||||
* App\Models\WebSocketTmpMsg
|
||||
*
|
||||
* @package App\Models
|
||||
* @property int $id
|
||||
* @property string|null $md5 MD5(会员ID-消息)
|
||||
* @property string|null $msg 详细消息
|
||||
|
||||
29
app/Models/clearHelper.php
Executable file
29
app/Models/clearHelper.php
Executable file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 清除模型class注释
|
||||
*/
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
// 判断是否通过cmd命令执行的
|
||||
if (function_exists('info')){
|
||||
echo "Success \n";
|
||||
return;
|
||||
}
|
||||
|
||||
$path = dirname(__FILE__). '/';
|
||||
$lists = scandir($path);
|
||||
//
|
||||
foreach ($lists AS $item) {
|
||||
$fillPath = $path . $item;
|
||||
if (!in_array($item, ['AbstractModel.php', 'clearHelper.php']) && str_ends_with($item, '.php')) {
|
||||
$content = file_get_contents($fillPath);
|
||||
preg_match("/\/\*\*([\s\S]*?)class\s*(.*?)\s*extends\s*AbstractModel/i", $content, $matchs);
|
||||
if ($matchs[0]) {
|
||||
$content = str_replace($matchs[0], 'class ' . $matchs[2] . ' extends AbstractModel', $content);
|
||||
file_put_contents($fillPath, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
echo "Success \n";
|
||||
193
app/Module/AgoraIO/AccessToken.php
Normal file
193
app/Module/AgoraIO/AccessToken.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* @Description :
|
||||
*
|
||||
* @Date : 2019-03-14 13:22
|
||||
* @Author : hmy940118@gmail.com
|
||||
*/
|
||||
|
||||
namespace App\Module\AgoraIO;
|
||||
|
||||
class AccessToken
|
||||
{
|
||||
|
||||
const Privileges = array(
|
||||
"kJoinChannel" => 1,
|
||||
"kPublishAudioStream" => 2,
|
||||
"kPublishVideoStream" => 3,
|
||||
"kPublishDataStream" => 4,
|
||||
"kPublishAudioCdn" => 5,
|
||||
"kPublishVideoCdn" => 6,
|
||||
"kRequestPublishAudioStream" => 7,
|
||||
"kRequestPublishVideoStream" => 8,
|
||||
"kRequestPublishDataStream" => 9,
|
||||
"kInvitePublishAudioStream" => 10,
|
||||
"kInvitePublishVideoStream" => 11,
|
||||
"kInvitePublishDataStream" => 12,
|
||||
"kAdministrateChannel" => 101
|
||||
);
|
||||
|
||||
public $appID, $appCertificate, $channelName, $uid;
|
||||
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* AccessToken constructor.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->message = new Message();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $uid
|
||||
*/
|
||||
public function setUid($uid)
|
||||
{
|
||||
if ($uid === 0) {
|
||||
$this->uid = "";
|
||||
} else {
|
||||
$this->uid = $uid . '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $str
|
||||
* @return bool
|
||||
*/
|
||||
public function is_nonempty_string($name, $str)
|
||||
{
|
||||
if (is_string($str) && $str !== "") {
|
||||
return true;
|
||||
}
|
||||
echo $name . " check failed, should be a non-empty string";
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $appID
|
||||
* @param $appCertificate
|
||||
* @param $channelName
|
||||
* @param $uid
|
||||
* @return AccessToken|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function init($appID, $appCertificate, $channelName, $uid)
|
||||
{
|
||||
$accessToken = new AccessToken();
|
||||
if (!$accessToken->is_nonempty_string("appID", $appID) ||
|
||||
!$accessToken->is_nonempty_string("appCertificate", $appCertificate) ||
|
||||
!$accessToken->is_nonempty_string("channelName", $channelName)) {
|
||||
return null;
|
||||
}
|
||||
$accessToken->appID = $appID;
|
||||
$accessToken->appCertificate = $appCertificate;
|
||||
$accessToken->channelName = $channelName;
|
||||
$accessToken->setUid($uid);
|
||||
$accessToken->message = new Message();
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token
|
||||
* @param $appCertificate
|
||||
* @param $channel
|
||||
* @param $uid
|
||||
* @return AccessToken|null
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function initWithToken($token, $appCertificate, $channel, $uid)
|
||||
{
|
||||
$accessToken = new AccessToken();
|
||||
if (!$accessToken->extract($token, $appCertificate, $channel, $uid)) {
|
||||
return null;
|
||||
}
|
||||
return $accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @param $expireTimestamp
|
||||
* @return $this
|
||||
*/
|
||||
public function addPrivilege($key, $expireTimestamp)
|
||||
{
|
||||
$this->message->privileges[$key] = $expireTimestamp;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token
|
||||
* @param $appCertificate
|
||||
* @param $channelName
|
||||
* @param $uid
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function extract($token, $appCertificate, $channelName, $uid)
|
||||
{
|
||||
$ver_len = 3;
|
||||
$appid_len = 32;
|
||||
$version = substr($token, 0, $ver_len);
|
||||
if ($version !== "006") {
|
||||
echo 'invalid version ' . $version;
|
||||
return false;
|
||||
}
|
||||
if (!$this->is_nonempty_string("token", $token) ||
|
||||
!$this->is_nonempty_string("appCertificate", $appCertificate) ||
|
||||
!$this->is_nonempty_string("channelName", $channelName)) {
|
||||
return false;
|
||||
}
|
||||
$appid = substr($token, $ver_len, $appid_len);
|
||||
$content = (base64_decode(substr($token, $ver_len + $appid_len, strlen($token) - ($ver_len + $appid_len))));
|
||||
$pos = 0;
|
||||
$len = unpack("v", $content . substr($pos, 2))[1];
|
||||
$pos += 2;
|
||||
$sig = substr($content, $pos, $len);
|
||||
$pos += $len;
|
||||
$crc_channel = unpack("V", substr($content, $pos, 4))[1];
|
||||
$pos += 4;
|
||||
$crc_uid = unpack("V", substr($content, $pos, 4))[1];
|
||||
$pos += 4;
|
||||
$msgLen = unpack("v", substr($content, $pos, 2))[1];
|
||||
$pos += 2;
|
||||
$msg = substr($content, $pos, $msgLen);
|
||||
$this->appID = $appid;
|
||||
$message = new Message();
|
||||
$message->unpackContent($msg);
|
||||
$this->message = $message;
|
||||
//non reversable values
|
||||
$this->appCertificate = $appCertificate;
|
||||
$this->channelName = $channelName;
|
||||
$this->setUid($uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$msg = $this->message->packContent();
|
||||
$val = array_merge(unpack("C*", $this->appID), unpack("C*", $this->channelName), unpack("C*", $this->uid), $msg);
|
||||
|
||||
$sig = hash_hmac('sha256', implode(array_map("chr", $val)), $this->appCertificate, true);
|
||||
$crc_channel_name = crc32($this->channelName) & 0xffffffff;
|
||||
$crc_uid = crc32($this->uid) & 0xffffffff;
|
||||
$content = array_merge(unpack("C*", $this->packString($sig)), unpack("C*", pack("V", $crc_channel_name)), unpack("C*", pack("V", $crc_uid)), unpack("C*", pack("v", count($msg))), $msg);
|
||||
$version = "006";
|
||||
$ret = $version . $this->appID . base64_encode(implode(array_map("chr", $content)));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return string
|
||||
*/
|
||||
public function packString($value)
|
||||
{
|
||||
return pack("v", strlen($value)) . $value;
|
||||
}
|
||||
}
|
||||
122
app/Module/AgoraIO/AgoraTokenGenerator.php
Normal file
122
app/Module/AgoraIO/AgoraTokenGenerator.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* @Description :
|
||||
*
|
||||
* @Date : 2019-03-14 13:20
|
||||
* @Author : hmy940118@gmail.com
|
||||
*/
|
||||
|
||||
namespace App\Module\AgoraIO;
|
||||
|
||||
|
||||
class AgoraTokenGenerator
|
||||
{
|
||||
const AttendeePrivileges = array(
|
||||
AccessToken::Privileges["kJoinChannel"] => 0,
|
||||
AccessToken::Privileges["kPublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kPublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kPublishDataStream"] => 0
|
||||
);
|
||||
|
||||
|
||||
const PublisherPrivileges = array(
|
||||
AccessToken::Privileges["kJoinChannel"] => 0,
|
||||
AccessToken::Privileges["kPublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kPublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kPublishDataStream"] => 0,
|
||||
AccessToken::Privileges["kPublishAudioCdn"] => 0,
|
||||
AccessToken::Privileges["kPublishVideoCdn"] => 0,
|
||||
AccessToken::Privileges["kInvitePublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kInvitePublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kInvitePublishDataStream"] => 0
|
||||
);
|
||||
|
||||
const SubscriberPrivileges = array(
|
||||
AccessToken::Privileges["kJoinChannel"] => 0,
|
||||
AccessToken::Privileges["kRequestPublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kRequestPublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kRequestPublishDataStream"] => 0
|
||||
);
|
||||
|
||||
const AdminPrivileges = array(
|
||||
AccessToken::Privileges["kJoinChannel"] => 0,
|
||||
AccessToken::Privileges["kPublishAudioStream"] => 0,
|
||||
AccessToken::Privileges["kPublishVideoStream"] => 0,
|
||||
AccessToken::Privileges["kPublishDataStream"] => 0,
|
||||
AccessToken::Privileges["kAdministrateChannel"] => 0
|
||||
);
|
||||
const Role = array(
|
||||
"kRoleAttendee" => 0, // for communication
|
||||
"kRolePublisher" => 1, // for live broadcast
|
||||
"kRoleSubscriber" => 2, // for live broadcast
|
||||
"kRoleAdmin" => 101
|
||||
);
|
||||
|
||||
const RolePrivileges = array(
|
||||
self::Role["kRoleAttendee"] => self::AttendeePrivileges,
|
||||
self::Role["kRolePublisher"] => self::PublisherPrivileges,
|
||||
self::Role["kRoleSubscriber"] => self::SubscriberPrivileges,
|
||||
self::Role["kRoleAdmin"] => self::AdminPrivileges
|
||||
);
|
||||
|
||||
public $token;
|
||||
|
||||
/**
|
||||
* AgoraTokenGenerator constructor.
|
||||
* @param $appID
|
||||
* @param $appCertificate
|
||||
* @param $channelName
|
||||
* @param $uid
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($appID, $appCertificate, $channelName, $uid){
|
||||
$this->token = new AccessToken();
|
||||
$this->token->appID = $appID;
|
||||
$this->token->appCertificate = $appCertificate;
|
||||
$this->token->channelName = $channelName;
|
||||
$this->token->setUid($uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token
|
||||
* @param $appCertificate
|
||||
* @param $channel
|
||||
* @param $uid
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function initWithToken($token, $appCertificate, $channel, $uid){
|
||||
$this->token = AccessToken::initWithToken($token, $appCertificate, $channel, $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $role
|
||||
*/
|
||||
public function initPrivilege($role){
|
||||
$p = self::RolePrivileges[$role];
|
||||
foreach($p as $key => $value){
|
||||
$this->setPrivilege($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $privilege
|
||||
* @param $expireTimestamp
|
||||
*/
|
||||
public function setPrivilege($privilege, $expireTimestamp){
|
||||
$this->token->addPrivilege($privilege, $expireTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $privilege
|
||||
*/
|
||||
public function removePrivilege($privilege){
|
||||
unset($this->token->message->privileges[$privilege]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function buildToken(){
|
||||
return $this->token->build();
|
||||
}
|
||||
}
|
||||
70
app/Module/AgoraIO/Message.php
Normal file
70
app/Module/AgoraIO/Message.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* @Description :
|
||||
*
|
||||
* @Date : 2019-03-14 13:27
|
||||
* @Author : hmy940118@gmail.com
|
||||
*/
|
||||
|
||||
namespace App\Module\AgoraIO;
|
||||
|
||||
class Message
|
||||
{
|
||||
public $salt;
|
||||
|
||||
public $ts;
|
||||
|
||||
public $privileges;
|
||||
|
||||
/**
|
||||
* Message constructor.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->salt = rand(0, 100000);
|
||||
$date = new \DateTime("now", new \DateTimeZone('UTC'));
|
||||
$this->ts = $date->getTimestamp() + 168 * 3600; // 7天时间
|
||||
$this->privileges = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function packContent()
|
||||
{
|
||||
$buffer = unpack("C*", pack("V", $this->salt));
|
||||
$buffer = array_merge($buffer, unpack("C*", pack("V", $this->ts)));
|
||||
$buffer = array_merge($buffer, unpack("C*", pack("v", sizeof($this->privileges))));
|
||||
foreach ($this->privileges as $key => $value) {
|
||||
$buffer = array_merge($buffer, unpack("C*", pack("v", $key)));
|
||||
$buffer = array_merge($buffer, unpack("C*", pack("V", $value)));
|
||||
}
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $msg
|
||||
*/
|
||||
public function unpackContent($msg)
|
||||
{
|
||||
$pos = 0;
|
||||
$salt = unpack("V", substr($msg, $pos, 4))[1];
|
||||
$pos += 4;
|
||||
$ts = unpack("V", substr($msg, $pos, 4))[1];
|
||||
$pos += 4;
|
||||
$size = unpack("v", substr($msg, $pos, 2))[1];
|
||||
$pos += 2;
|
||||
$privileges = array();
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$key = unpack("v", substr($msg, $pos, 2));
|
||||
$pos += 2;
|
||||
$value = unpack("V", substr($msg, $pos, 4));
|
||||
$pos += 4;
|
||||
$privileges[$key[1]] = $value[1];
|
||||
}
|
||||
$this->salt = $salt;
|
||||
$this->ts = $ts;
|
||||
$this->privileges = $privileges;
|
||||
}
|
||||
}
|
||||
1119
app/Module/Base.php
1119
app/Module/Base.php
File diff suppressed because it is too large
Load Diff
152
app/Module/BillExport.php
Normal file
152
app/Module/BillExport.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use Excel;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithEvents;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use Maatwebsite\Excel\Events\AfterSheet;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Exception;
|
||||
|
||||
class BillExport implements WithHeadings, WithEvents, FromCollection, WithTitle, WithStrictNullComparison
|
||||
{
|
||||
protected $title;
|
||||
protected $headings = [];
|
||||
protected $data = [];
|
||||
protected $typeLists = [];
|
||||
protected $typeNumber = 0;
|
||||
protected $styles = [];
|
||||
|
||||
public function __construct($title, array $data)
|
||||
{
|
||||
$this->title = $title;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public static function create($data = [], $title = "Sheet1") {
|
||||
if (is_string($data)) {
|
||||
list($title, $data) = [$data, $title];
|
||||
}
|
||||
if (!is_array($data)) {
|
||||
$data = [];
|
||||
}
|
||||
return new BillExport($title, $data);
|
||||
}
|
||||
|
||||
public function setTitle($title) {
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setHeadings(array $headings) {
|
||||
$this->headings = $headings;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setData(array $data) {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTypeList(array $typeList, $number = 0) {
|
||||
$this->typeLists = $typeList;
|
||||
$this->typeNumber = $number;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStyles(array $styles)
|
||||
{
|
||||
$this->styles = $styles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function store($fileName = '') {
|
||||
if (empty($fileName)) {
|
||||
$fileName = date("YmdHis") . '.xls';
|
||||
}
|
||||
try {
|
||||
return Excel::store($this, $fileName);
|
||||
} catch (Exception|\PhpOffice\PhpSpreadsheet\Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public function download($fileName = '') {
|
||||
if (empty($fileName)) {
|
||||
$fileName = date("YmdHis") . '.xls';
|
||||
}
|
||||
try {
|
||||
return Excel::download($this, $fileName);
|
||||
} catch (Exception|\PhpOffice\PhpSpreadsheet\Exception $e) {
|
||||
return "导出错误:" . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出的文件标题
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标题行
|
||||
* @return array
|
||||
*/
|
||||
public function headings(): array
|
||||
{
|
||||
return $this->headings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出的内容
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return collect($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置单元格事件
|
||||
* @return array
|
||||
*/
|
||||
public function registerEvents(): array
|
||||
{
|
||||
return [
|
||||
AfterSheet::Class => function (AfterSheet $event) {
|
||||
if ($this->styles) {
|
||||
foreach ($this->styles as $cell => $style) {
|
||||
$event->sheet->getDelegate()->getStyle($cell)->applyFromArray($style);
|
||||
}
|
||||
}
|
||||
$count = count($this->data);
|
||||
foreach ($this->typeLists AS $cell => $typeList) {
|
||||
if ($cell && $typeList) {
|
||||
$p = $this->headings ? 1 : 0;
|
||||
for ($i = 1 + $p; $i <= max($count, $this->typeNumber) + $p; $i++) {
|
||||
$validation = $event->sheet->getDelegate()->getCell($cell . $i)->getDataValidation();
|
||||
$validation->setType(DataValidation::TYPE_LIST);
|
||||
$validation->setErrorStyle(DataValidation::STYLE_WARNING);
|
||||
$validation->setAllowBlank(false);
|
||||
$validation->setShowDropDown(true);
|
||||
$validation->setShowInputMessage(true);
|
||||
$validation->setShowErrorMessage(true);
|
||||
$validation->setErrorTitle('输入的值不合法');
|
||||
$validation->setError('选择的值不在列表中,请选择列表中的值');
|
||||
$validation->setPromptTitle('从列表中选择');
|
||||
$validation->setPrompt('请选择下拉列表中的值');
|
||||
$validation->setFormula1('"' . implode(',', $typeList) . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
16
app/Module/BillImport.php
Normal file
16
app/Module/BillImport.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
|
||||
use Maatwebsite\Excel\Concerns\ToArray;
|
||||
|
||||
class BillImport implements ToArray
|
||||
{
|
||||
public function Array(Array $tables)
|
||||
{
|
||||
return $tables;
|
||||
}
|
||||
|
||||
}
|
||||
24
app/Module/BillMultipleExport.php
Normal file
24
app/Module/BillMultipleExport.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
|
||||
use Maatwebsite\Excel\Concerns\Exportable;
|
||||
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
|
||||
|
||||
class BillMultipleExport implements WithMultipleSheets
|
||||
{
|
||||
use Exportable;
|
||||
|
||||
protected $data = [];
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function sheets(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
419
app/Module/Doo.php
Normal file
419
app/Module/Doo.php
Normal file
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Models\User;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use FFI;
|
||||
|
||||
class Doo
|
||||
{
|
||||
private static $doo;
|
||||
private static $passphrase = "LYHevk5n";
|
||||
|
||||
/**
|
||||
* char转为字符串
|
||||
* @param $text
|
||||
* @return string
|
||||
*/
|
||||
private static function string($text): string
|
||||
{
|
||||
return FFI::string($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 装载
|
||||
* @param $token
|
||||
* @param $language
|
||||
*/
|
||||
public static function load($token = null, $language = null)
|
||||
{
|
||||
self::$doo = FFI::cdef(<<<EOF
|
||||
void initialize(char* work, char* token, char* lang);
|
||||
char* license();
|
||||
char* licenseDecode(char* license);
|
||||
char* licenseSave(char* license);
|
||||
int userId();
|
||||
char* userExpiredAt();
|
||||
char* userEmail();
|
||||
char* userEncrypt();
|
||||
char* userToken();
|
||||
char* userCreate(char* email, char* password);
|
||||
char* tokenEncode(int userid, char* email, char* encrypt, int days);
|
||||
char* tokenDecode(char* val);
|
||||
char* translate(char* val, char* val);
|
||||
char* md5s(char* text, char* password);
|
||||
char* macs();
|
||||
char* dooSN();
|
||||
char* pgpGenerateKeyPair(char* name, char* email, char* passphrase);
|
||||
char* pgpEncrypt(char* plainText, char* publicKey);
|
||||
char* pgpDecrypt(char* cipherText, char* privateKey, char* passphrase);
|
||||
EOF, "/usr/lib/doo/doo.so");
|
||||
$token = $token ?: Base::token();
|
||||
$language = $language ?: Base::headerOrInput('language');
|
||||
self::$doo->initialize("/var/www", $token, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实例
|
||||
* @param $token
|
||||
* @param $language
|
||||
* @return mixed
|
||||
*/
|
||||
public static function doo($token = null, $language = null)
|
||||
{
|
||||
if (self::$doo == null) {
|
||||
self::load($token, $language);
|
||||
}
|
||||
return self::$doo;
|
||||
}
|
||||
|
||||
/**
|
||||
* License
|
||||
* @return array
|
||||
*/
|
||||
public static function license(): array
|
||||
{
|
||||
$array = Base::json2array(self::string(self::doo()->license()));
|
||||
|
||||
$ips = explode(",", $array['ip']);
|
||||
$array['ip'] = [];
|
||||
foreach ($ips as $ip) {
|
||||
if (Base::is_ipv4($ip)) {
|
||||
$array['ip'][] = $ip;
|
||||
}
|
||||
}
|
||||
|
||||
$domains = explode(",", $array['domain']);
|
||||
$array['domain'] = [];
|
||||
foreach ($domains as $domain) {
|
||||
if (Base::is_domain($domain)) {
|
||||
$array['domain'][] = $domain;
|
||||
}
|
||||
}
|
||||
|
||||
$macs = explode(",", $array['mac']);
|
||||
$array['mac'] = [];
|
||||
foreach ($macs as $mac) {
|
||||
if (Base::isMac($mac)) {
|
||||
$array['mac'][] = $mac;
|
||||
}
|
||||
}
|
||||
|
||||
$emails = explode(",", $array['email']);
|
||||
$array['email'] = [];
|
||||
foreach ($emails as $email) {
|
||||
if (Base::isEmail($email)) {
|
||||
$array['email'][] = $email;
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取License原文
|
||||
* @return string
|
||||
*/
|
||||
public static function licenseContent(): string
|
||||
{
|
||||
if (env("SYSTEM_LICENSE") == 'hidden') {
|
||||
return '';
|
||||
}
|
||||
$paths = [
|
||||
config_path("LICENSE"),
|
||||
config_path("license"),
|
||||
app_path("LICENSE"),
|
||||
app_path("license"),
|
||||
];
|
||||
$content = "";
|
||||
foreach ($paths as $path) {
|
||||
if (file_exists($path)) {
|
||||
$content = file_get_contents($path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析License
|
||||
* @param $license
|
||||
* @return array
|
||||
*/
|
||||
public static function licenseDecode($license): array
|
||||
{
|
||||
return Base::json2array(self::string(self::doo()->licenseDecode($license)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存License
|
||||
* @param $license
|
||||
*/
|
||||
public static function licenseSave($license): void
|
||||
{
|
||||
$res = self::string(self::doo()->licenseSave($license));
|
||||
if ($res != 'success') {
|
||||
throw new ApiException($res ?: 'LICENSE 保存失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前会员ID(来自请求的token)
|
||||
* @return int
|
||||
*/
|
||||
public static function userId(): int
|
||||
{
|
||||
return intval(self::doo()->userId());
|
||||
}
|
||||
|
||||
/**
|
||||
* token是否过期(来自请求的token)
|
||||
* @return bool
|
||||
*/
|
||||
public static function userExpired(): bool
|
||||
{
|
||||
$expiredAt = self::userExpiredAt();
|
||||
return $expiredAt && Carbon::parse($expiredAt)->isBefore(Carbon::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* token过期时间(来自请求的token)
|
||||
* @return string
|
||||
*/
|
||||
public static function userExpiredAt(): string
|
||||
{
|
||||
$expiredAt = self::string(self::doo()->userExpiredAt());
|
||||
return $expiredAt === 'forever' ? '' : $expiredAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前会员邮箱地址(来自请求的token)
|
||||
* @return string
|
||||
*/
|
||||
public static function userEmail(): string
|
||||
{
|
||||
return self::string(self::doo()->userEmail());
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前会员Encrypt(来自请求的token)
|
||||
* @return string
|
||||
*/
|
||||
public static function userEncrypt(): string
|
||||
{
|
||||
return self::string(self::doo()->userEncrypt());
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前会员token(来自请求的token)
|
||||
* @return string
|
||||
*/
|
||||
public static function userToken(): string
|
||||
{
|
||||
return self::string(self::doo()->userToken());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建帐号
|
||||
* @param $email
|
||||
* @param $password
|
||||
* @return User|null
|
||||
*/
|
||||
public static function userCreate($email, $password): User|null
|
||||
{
|
||||
$data = Base::json2array(self::string(self::doo()->userCreate($email, $password)));
|
||||
if (Base::isError($data)) {
|
||||
throw new ApiException($data['msg'] ?: '注册失败');
|
||||
}
|
||||
$user = User::whereEmail($email)->first();
|
||||
if (empty($user)) {
|
||||
throw new ApiException('注册失败');
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成token(编码token)
|
||||
* @param $userid
|
||||
* @param $email
|
||||
* @param $encrypt
|
||||
* @param int $days 有效时间(天)
|
||||
* @return string
|
||||
*/
|
||||
public static function tokenEncode($userid, $email, $encrypt, int $days = 15): string
|
||||
{
|
||||
return self::string(self::doo()->tokenEncode($userid, $email, $encrypt, $days));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码token
|
||||
* @param $token
|
||||
* @return array
|
||||
*/
|
||||
public static function tokenDecode($token): array
|
||||
{
|
||||
return Base::json2array(self::string(self::doo()->tokenDecode($token)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译
|
||||
* @param $text
|
||||
* @param string $type
|
||||
* @return string
|
||||
*/
|
||||
public static function translate($text, string $type = ""): string
|
||||
{
|
||||
return self::string(self::doo()->translate($text, $type));
|
||||
}
|
||||
|
||||
/**
|
||||
* md5防破解
|
||||
* @param $text
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
public static function md5s($text, string $password = ""): string
|
||||
{
|
||||
return self::string(self::doo()->md5s($text, $password));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取php容器mac地址组
|
||||
* @return array
|
||||
*/
|
||||
public static function macs(): array
|
||||
{
|
||||
$macs = explode(",", self::string(self::doo()->macs()));
|
||||
$array = [];
|
||||
foreach ($macs as $mac) {
|
||||
if (Base::isMac($mac)) {
|
||||
$array[] = $mac;
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前SN
|
||||
* @return string
|
||||
*/
|
||||
public static function dooSN(): string
|
||||
{
|
||||
return self::string(self::doo()->dooSN());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成PGP密钥对
|
||||
* @param $name
|
||||
* @param $email
|
||||
* @param string $passphrase
|
||||
* @return array
|
||||
*/
|
||||
public static function pgpGenerateKeyPair($name, $email, string $passphrase = ""): array
|
||||
{
|
||||
return Base::json2array(self::string(self::doo()->pgpGenerateKeyPair($name, $email, $passphrase)));
|
||||
}
|
||||
|
||||
/**
|
||||
* PGP加密
|
||||
* @param $plaintext
|
||||
* @param $publicKey
|
||||
* @return string
|
||||
*/
|
||||
public static function pgpEncrypt($plaintext, $publicKey): string
|
||||
{
|
||||
if (strlen($publicKey) < 50) {
|
||||
$keyCache = Base::json2array(Cache::get("KeyPair::" . $publicKey));
|
||||
$publicKey = $keyCache['public_key'];
|
||||
}
|
||||
return self::string(self::doo()->pgpEncrypt($plaintext, $publicKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* PGP解密
|
||||
* @param $encryptedText
|
||||
* @param $privateKey
|
||||
* @param null $passphrase
|
||||
* @return string
|
||||
*/
|
||||
public static function pgpDecrypt($encryptedText, $privateKey, $passphrase = null): string
|
||||
{
|
||||
if (strlen($privateKey) < 50) {
|
||||
$keyCache = Base::json2array(Cache::get("KeyPair::" . $privateKey));
|
||||
$privateKey = $keyCache['private_key'];
|
||||
$passphrase = $keyCache['passphrase'];
|
||||
}
|
||||
return self::string(self::doo()->pgpDecrypt($encryptedText, $privateKey, $passphrase));
|
||||
}
|
||||
|
||||
/**
|
||||
* PGP加密API
|
||||
* @param $plaintext
|
||||
* @param $publicKey
|
||||
* @return string
|
||||
*/
|
||||
public static function pgpEncryptApi($plaintext, $publicKey): string
|
||||
{
|
||||
$content = Base::array2json($plaintext);
|
||||
$content = self::pgpEncrypt($content, $publicKey);
|
||||
return preg_replace("/\s*-----(BEGIN|END) PGP MESSAGE-----\s*/i", "", $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* PGP解密API
|
||||
* @param $encryptedText
|
||||
* @param null $privateKey
|
||||
* @param null $passphrase
|
||||
* @return array
|
||||
*/
|
||||
public static function pgpDecryptApi($encryptedText, $privateKey, $passphrase = null): array
|
||||
{
|
||||
$content = "-----BEGIN PGP MESSAGE-----\n\n" . $encryptedText . "\n-----END PGP MESSAGE-----";
|
||||
$content = self::pgpDecrypt($content, $privateKey, $passphrase);
|
||||
return Base::json2array($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析PGP参数
|
||||
* @param $string
|
||||
* @return string[]
|
||||
*/
|
||||
public static function pgpParseStr($string): array
|
||||
{
|
||||
$array = [
|
||||
'encrypt_type' => '',
|
||||
'encrypt_id' => '',
|
||||
'client_type' => '',
|
||||
'client_key' => '',
|
||||
];
|
||||
$string = str_replace(";", "&", $string);
|
||||
parse_str($string, $params);
|
||||
foreach ($params as $key => $value) {
|
||||
$key = strtolower(trim($key));
|
||||
if ($key) {
|
||||
$array[$key] = trim($value);
|
||||
}
|
||||
}
|
||||
if ($array['client_type'] === 'pgp' && $array['client_key']) {
|
||||
$array['client_key'] = self::pgpPublicFormat($array['client_key']);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原公钥格式
|
||||
* @param $key
|
||||
* @return string
|
||||
*/
|
||||
public static function pgpPublicFormat($key): string
|
||||
{
|
||||
$key = str_replace(["-", "_", "$"], ["+", "/", "\n"], $key);
|
||||
if (!str_contains($key, '-----BEGIN PGP PUBLIC KEY BLOCK-----')) {
|
||||
$key = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n" . $key . "\n-----END PGP PUBLIC KEY BLOCK-----";
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
321
app/Module/Extranet.php
Normal file
321
app/Module/Extranet.php
Normal file
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* 外网资源请求
|
||||
*/
|
||||
class Extranet
|
||||
{
|
||||
/**
|
||||
* 获取IP地址经纬度
|
||||
* @param string $ip
|
||||
* @return array
|
||||
*/
|
||||
public static function getIpGcj02(string $ip = ''): array
|
||||
{
|
||||
if (empty($ip)) {
|
||||
$ip = Base::getIp();
|
||||
}
|
||||
$cacheKey = "getIpPoint::" . md5($ip);
|
||||
$result = Cache::rememberForever($cacheKey, function () use ($ip) {
|
||||
return Ihttp::ihttp_request("https://www.ifreesite.com/ipaddress/address.php?q=" . $ip, [], [], 12);
|
||||
});
|
||||
if (Base::isError($result)) {
|
||||
Cache::forget($cacheKey);
|
||||
return $result;
|
||||
}
|
||||
$data = $result['data'];
|
||||
$lastPos = strrpos($data, ',');
|
||||
$long = floatval(Base::getMiddle(substr($data, $lastPos + 1), null, ')'));
|
||||
$lat = floatval(Base::getMiddle(substr($data, strrpos(substr($data, 0, $lastPos), ',') + 1), null, ','));
|
||||
return Base::retSuccess("success", [
|
||||
'long' => $long,
|
||||
'lat' => $lat,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 百度接口:根据ip获取经纬度
|
||||
* @param string $ip
|
||||
* @return array
|
||||
*/
|
||||
public static function getIpGcj02ByBaidu(string $ip = ''): array
|
||||
{
|
||||
if (empty($ip)) {
|
||||
$ip = Base::getIp();
|
||||
}
|
||||
|
||||
$cacheKey = "getIpPoint::" . md5($ip);
|
||||
$result = Cache::rememberForever($cacheKey, function () use ($ip) {
|
||||
$ak = Config::get('app.baidu_app_key');
|
||||
$url = 'http://api.map.baidu.com/location/ip?ak=' . $ak . '&ip=' . $ip . '&coor=bd09ll';
|
||||
return Ihttp::ihttp_request($url, [], [], 12);
|
||||
});
|
||||
|
||||
if (Base::isError($result)) {
|
||||
Cache::forget($cacheKey);
|
||||
return $result;
|
||||
}
|
||||
$data = json_decode($result['data'], true);
|
||||
|
||||
// x坐标纬度, y坐标经度
|
||||
$long = Arr::get($data, 'content.point.x');
|
||||
$lat = Arr::get($data, 'content.point.y');
|
||||
return Base::retSuccess("success", [
|
||||
'long' => $long,
|
||||
'lat' => $lat,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取IP地址详情
|
||||
* @param string $ip
|
||||
* @return array
|
||||
*/
|
||||
public static function getIpInfo(string $ip = ''): array
|
||||
{
|
||||
if (empty($ip)) {
|
||||
$ip = Base::getIp();
|
||||
}
|
||||
$cacheKey = "getIpInfo::" . md5($ip);
|
||||
$result = Cache::rememberForever($cacheKey, function () use ($ip) {
|
||||
return Ihttp::ihttp_request("http://ip.taobao.com/service/getIpInfo.php?accessKey=alibaba-inc&ip=" . $ip, [], [], 12);
|
||||
});
|
||||
if (Base::isError($result)) {
|
||||
Cache::forget($cacheKey);
|
||||
return $result;
|
||||
}
|
||||
$data = json_decode($result['data'], true);
|
||||
if (!is_array($data) || intval($data['code']) != 0) {
|
||||
Cache::forget($cacheKey);
|
||||
return Base::retError("error ip: -1");
|
||||
}
|
||||
$data = $data['data'];
|
||||
if (!is_array($data) || !isset($data['country'])) {
|
||||
return Base::retError("error ip: -2");
|
||||
}
|
||||
$data['text'] = $data['country'];
|
||||
$data['textSmall'] = $data['country'];
|
||||
if ($data['region'] && $data['region'] != $data['country'] && $data['region'] != "XX") {
|
||||
$data['text'] .= " " . $data['region'];
|
||||
$data['textSmall'] = $data['region'];
|
||||
}
|
||||
if ($data['city'] && $data['city'] != $data['region'] && $data['city'] != "XX") {
|
||||
$data['text'] .= " " . $data['city'];
|
||||
$data['textSmall'] .= " " . $data['city'];
|
||||
}
|
||||
if ($data['county'] && $data['county'] != $data['city'] && $data['county'] != "XX") {
|
||||
$data['text'] .= " " . $data['county'];
|
||||
$data['textSmall'] .= " " . $data['county'];
|
||||
}
|
||||
return Base::retSuccess("success", $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否工作日
|
||||
* @param string $Ymd 年月日(如:20220102)
|
||||
* @return int
|
||||
* 0: 工作日
|
||||
* 1: 非工作日
|
||||
* 2: 获取不到远程数据的非工作日(周六、日)
|
||||
* 所以可以用>0来判断是否工作日
|
||||
*/
|
||||
public static function isHoliday(string $Ymd): int
|
||||
{
|
||||
$time = strtotime($Ymd . " 00:00:00");
|
||||
$holidayKey = "holiday::" . date("Ym", $time);
|
||||
$holidayData = Cache::remember($holidayKey, now()->addMonth(), function () use ($time) {
|
||||
$apiMonth = date("Ym", $time);
|
||||
$apiResult = Ihttp::ihttp_request("https://api.apihubs.cn/holiday/get?field=date&month={$apiMonth}&workday=2&size=31", [], [], 20);
|
||||
if (Base::isError($apiResult)) {
|
||||
info('[holiday] get error');
|
||||
return [];
|
||||
}
|
||||
$apiResult = Base::json2array($apiResult['data']);
|
||||
if ($apiResult['code'] !== 0) {
|
||||
info('[holiday] result error');
|
||||
return [];
|
||||
}
|
||||
return array_map(function ($item) {
|
||||
return $item['date'];
|
||||
}, $apiResult['data']['list']);
|
||||
});
|
||||
if (empty($holidayData)) {
|
||||
Cache::forget($holidayKey);
|
||||
return in_array(date("w", $time), [0, 6]) ? 2 : 0;
|
||||
}
|
||||
return in_array($Ymd, $holidayData) ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drawio 图标搜索
|
||||
* @param $query
|
||||
* @param $page
|
||||
* @param $size
|
||||
* @return array
|
||||
*/
|
||||
public static function drawioIconSearch($query, $page, $size): array
|
||||
{
|
||||
$result = self::curl("https://app.diagrams.net/iconSearch?q={$query}&p={$page}&c={$size}", 15 * 86400);
|
||||
if ($result = Base::json2array($result)) {
|
||||
return $result;
|
||||
}
|
||||
return [
|
||||
'icons' => [],
|
||||
'total_count' => 0
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机笑话接口
|
||||
* @return array
|
||||
*/
|
||||
public static function randJoke(): array
|
||||
{
|
||||
$jokeKey = env("JUKE_KEY_JOKE");
|
||||
if ($jokeKey) {
|
||||
$data = self::curl("http://v.juhe.cn/joke/randJoke.php?key=" . $jokeKey);
|
||||
$data = Base::json2array($data);
|
||||
if ($data['reason'] === 'success') {
|
||||
return $data['result'];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 心灵鸡汤
|
||||
* @return string
|
||||
*/
|
||||
public static function soups(): string
|
||||
{
|
||||
$soupKey = env("JUKE_KEY_SOUP");
|
||||
if ($soupKey) {
|
||||
$data = self::curl("https://apis.juhe.cn/fapig/soup/query?key=" . $soupKey);
|
||||
$data = Base::json2array($data);
|
||||
if ($data['reason'] === 'success' && $text = trim($data['result']['text'])) {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 签到机器人网络内容
|
||||
* @param $type
|
||||
* @return string
|
||||
*/
|
||||
public static function checkinBotQuickMsg($type): string
|
||||
{
|
||||
$text = "维护中...";
|
||||
switch ($type) {
|
||||
case "it":
|
||||
$data = self::curl('http://vvhan.api.hitosea.com/api/hotlist?type=itNews', 3600);
|
||||
if ($data = Base::json2array($data)) {
|
||||
$i = 1;
|
||||
$array = array_map(function ($item) use (&$i) {
|
||||
if ($item['title'] && $item['desc']) {
|
||||
return "<p>" . ($i++) . ". <strong><a href='{$item['mobilUrl']}' target='_blank'>{$item['title']}</a></strong></p><p>{$item['desc']}</p>";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, $data['data']);
|
||||
$array = array_values(array_filter($array));
|
||||
if ($array) {
|
||||
array_unshift($array, "<p><strong>{$data['title']}</strong>({$data['update_time']})</p>");
|
||||
$text = implode("<p> </p>", $array);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "36ke":
|
||||
$data = self::curl('http://vvhan.api.hitosea.com/api/hotlist?type=36Ke', 3600);
|
||||
if ($data = Base::json2array($data)) {
|
||||
$i = 1;
|
||||
$array = array_map(function ($item) use (&$i) {
|
||||
if ($item['title'] && $item['desc']) {
|
||||
return "<p>" . ($i++) . ". <strong><a href='{$item['mobilUrl']}' target='_blank'>{$item['title']}</a></strong></p><p>{$item['desc']}</p>";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, $data['data']);
|
||||
$array = array_values(array_filter($array));
|
||||
if ($array) {
|
||||
array_unshift($array, "<p><strong>{$data['title']}</strong>({$data['update_time']})</p>");
|
||||
$text = implode("<p> </p>", $array);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "60s":
|
||||
$data = self::curl('http://vvhan.api.hitosea.com/api/60s?type=json', 3600);
|
||||
if ($data = Base::json2array($data)) {
|
||||
$i = 1;
|
||||
$array = array_map(function ($item) use (&$i) {
|
||||
if ($item) {
|
||||
return "<p>" . ($i++) . ". {$item}</p>";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, $data['data']);
|
||||
$array = array_values(array_filter($array));
|
||||
if ($array) {
|
||||
array_unshift($array, "<p><strong>{$data['name']}</strong>({$data['time'][0]})</p>");
|
||||
$text = implode("<p> </p>", $array);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "joke":
|
||||
$text = "笑话被掏空";
|
||||
$data = self::curl('http://vvhan.api.hitosea.com/api/joke?type=json', 5);
|
||||
if ($data = Base::json2array($data)) {
|
||||
if ($data = trim($data['joke'])) {
|
||||
$text = "开心笑话:{$data}";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "soup":
|
||||
$text = "鸡汤分完了";
|
||||
$data = self::curl('https://api.ayfre.com/jt/?type=bot', 5);
|
||||
if ($data) {
|
||||
$text = "心灵鸡汤:{$data}";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $url
|
||||
* @param int $cacheSecond 缓存时间(秒),如果结果为空则缓存有效30秒
|
||||
* @param int $timeout
|
||||
* @return string
|
||||
*/
|
||||
private static function curl($url, int $cacheSecond = 0, int $timeout = 15): string
|
||||
{
|
||||
if ($cacheSecond > 0) {
|
||||
$key = "curlCache::" . md5($url);
|
||||
$content = Cache::remember($key, Carbon::now()->addSeconds($cacheSecond), function () use ($cacheSecond, $key, $timeout, $url) {
|
||||
$result = Ihttp::ihttp_request($url, [], [], $timeout);
|
||||
$content = Base::isSuccess($result) ? trim($result['data']) : '';
|
||||
if (empty($content) && $cacheSecond > 30) {
|
||||
Cache::put($key, "", Carbon::now()->addSeconds(30));
|
||||
}
|
||||
return $content;
|
||||
});
|
||||
} else {
|
||||
$result = Ihttp::ihttp_request($url, [], [], $timeout);
|
||||
$content = Base::isSuccess($result) ? trim($result['data']) : '';
|
||||
}
|
||||
//
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use Exception;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
class Ihttp
|
||||
@@ -88,7 +86,7 @@ class Ihttp
|
||||
return Base::retError($error);
|
||||
} else {
|
||||
if ($isGb2312) {
|
||||
try { $data = iconv('GB2312', 'UTF-8', $data); }catch (Exception $e) { }
|
||||
try { $data = iconv('GB2312', 'UTF-8', $data); }catch (\Throwable) { }
|
||||
}
|
||||
$response = self::ihttp_response_parse($data);
|
||||
Base::addLog([
|
||||
@@ -148,7 +146,7 @@ class Ihttp
|
||||
$content .= fgets($fp, 512);
|
||||
fclose($fp);
|
||||
if ($isGb2312) {
|
||||
try { $content = iconv('GB2312', 'UTF-8', $content); }catch (Exception $e) { }
|
||||
try { $content = iconv('GB2312', 'UTF-8', $content); }catch (\Throwable) { }
|
||||
}
|
||||
$response = self::ihttp_response_parse($content, true);
|
||||
Base::addLog([
|
||||
|
||||
235
app/Module/Image.php
Normal file
235
app/Module/Image.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use Imagick;
|
||||
|
||||
/**
|
||||
* 图片类:缩略图、调整大小、压缩
|
||||
*/
|
||||
class Image
|
||||
{
|
||||
private Imagick $image;
|
||||
|
||||
private string $path;
|
||||
|
||||
private int $height;
|
||||
|
||||
private int $width;
|
||||
|
||||
/**
|
||||
* @param $imagePath
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function __construct($imagePath) {
|
||||
$this->path = $imagePath;
|
||||
$this->image = new Imagick($this->path);
|
||||
$geo = $this->image->getImageGeometry();
|
||||
$this->height = $geo['height'];
|
||||
$this->width = $geo['width'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片宽度
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片高度
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建缩略图
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param string $mode = 'percentage|cover|contain' ($width 和 $height 都设置的情况下有效)
|
||||
* @return Image
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function thumb(int $width, int $height, string $mode = 'percentage'): static
|
||||
{
|
||||
if ($height === 0 && $width === 0) {
|
||||
return $this;
|
||||
}
|
||||
if ($width && $height) {
|
||||
if ($mode === 'cover') {
|
||||
// 铺满背景(超出被裁掉)
|
||||
$this->image->cropThumbnailImage($width, $height);
|
||||
} else {
|
||||
// 等比缩放
|
||||
if ($this->width >= $this->height) {
|
||||
$this->image->thumbnailImage($width, 0);
|
||||
} else {
|
||||
$this->image->thumbnailImage(0, $height);
|
||||
}
|
||||
if ($mode === 'contain') {
|
||||
// 显示完整的图
|
||||
$background = new Imagick();
|
||||
$background->newImage($width, $height, 'none', strtolower(pathinfo($this->path, PATHINFO_EXTENSION)));
|
||||
$bg_width = $background->getImageWidth();
|
||||
$bg_height = $background->getImageHeight();
|
||||
$img_width = $this->image->getImageWidth();
|
||||
$img_height = $this->image->getImageHeight();
|
||||
if ($img_width / $bg_width > $img_height / $bg_height) {
|
||||
$width = $bg_width;
|
||||
$height = intval($img_height / ($img_width / $width));
|
||||
} else {
|
||||
$height = $bg_height;
|
||||
$width = intval($img_width / ($img_height / $height));
|
||||
}
|
||||
$this->image->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
$x = intval(($bg_width - $width) / 2);
|
||||
$y = intval(($bg_height - $height) / 2);
|
||||
$background->compositeImage($this->image, Imagick::COMPOSITE_DEFAULT, $x, $y);
|
||||
$this->image->destroy();
|
||||
$this->image = $background;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->image->thumbnailImage($width, $height);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整图像大小
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @return Image
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function resize(int $width, int $height): static
|
||||
{
|
||||
if ($height === 0 && $width === 0) {
|
||||
return $this;
|
||||
}
|
||||
$this->image->adaptiveResizeImage($width, $height);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩图片
|
||||
* @param int $quality
|
||||
* @return $this
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function compress(int $quality = 100): static
|
||||
{
|
||||
$format = strtolower($this->image->getImageFormat());
|
||||
switch ($format) {
|
||||
case 'jpeg':
|
||||
case 'jpg':
|
||||
$this->image->setImageCompression(Imagick::COMPRESSION_JPEG);
|
||||
break;
|
||||
case 'png':
|
||||
$this->image->setImageCompression(Imagick::COMPRESSION_ZIP);
|
||||
break;
|
||||
case 'gif':
|
||||
$this->image->setImageCompression(Imagick::COMPRESSION_LZW);
|
||||
break;
|
||||
default:
|
||||
return $this;
|
||||
}
|
||||
if ($quality > 1) {
|
||||
$this->image->setImageCompressionQuality($quality);
|
||||
} elseif ($quality > 0) {
|
||||
$this->image->setImageCompressionQuality($quality * 100);
|
||||
} else {
|
||||
$this->image->setImageCompressionQuality(100);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存结果到文件
|
||||
* @param string $savePath Save path
|
||||
* @return void
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function saveTo(string $savePath): void
|
||||
{
|
||||
$this->image->writeImage($savePath);
|
||||
$this->image->destroy();
|
||||
}
|
||||
|
||||
/** ******************************************************************************/
|
||||
/** ******************************************************************************/
|
||||
/** ******************************************************************************/
|
||||
|
||||
/**
|
||||
* 生成缩略图
|
||||
* @param string $imagePath 图片路径
|
||||
* @param string $savePath 保存路径
|
||||
* @param int $width 宽度
|
||||
* @param int $height 高度
|
||||
* @param string $mode 模式(percentage|cover|contain)
|
||||
* @return string|null 成功返回图片后缀,失败返回 false
|
||||
*/
|
||||
public static function thumbImage(string $imagePath, string $savePath, int $width, int $height, string $mode = 'percentage'): ?string
|
||||
{
|
||||
if (!file_exists($imagePath)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
$extension = strtolower(pathinfo($imagePath, PATHINFO_EXTENSION));
|
||||
if (str_contains($savePath, '.{*}')) {
|
||||
$savePath = str_replace('.{*}', '.' . $extension, $savePath);
|
||||
}
|
||||
$image = new Image($imagePath);
|
||||
$image->thumb($width, $height, $mode);
|
||||
$image->saveTo($savePath);
|
||||
return $extension;
|
||||
} catch (\ImagickException) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩图片
|
||||
* @param string $imagePath 图片路径
|
||||
* @param string|null $savePath 保存路径(默认覆盖原图)
|
||||
* @param int $quality 压缩质量(0-100)
|
||||
* @param float $minSize 最小尺寸(单位:KB)
|
||||
* @return bool
|
||||
*/
|
||||
public static function compressImage(string $imagePath, string $savePath = null, int $quality = 100, float $minSize = 10): bool
|
||||
{
|
||||
if (Base::settingFind("system", "image_compress") === 'close') {
|
||||
return false;
|
||||
}
|
||||
if (!file_exists($imagePath)) {
|
||||
return false;
|
||||
}
|
||||
$imageSize = filesize($imagePath);
|
||||
if ($minSize > 0 && $imageSize < $minSize * 1024) {
|
||||
return false;
|
||||
}
|
||||
if (empty($savePath)) {
|
||||
$savePath = $imagePath;
|
||||
}
|
||||
$tmpPath = $imagePath . '.compress.tmp';
|
||||
try {
|
||||
$image = new Image($imagePath);
|
||||
$image->compress($quality);
|
||||
$image->saveTo($tmpPath);
|
||||
if (filesize($tmpPath) >= $imageSize) {
|
||||
copy($imagePath, $savePath);
|
||||
unlink($tmpPath);
|
||||
} else {
|
||||
rename($tmpPath, $savePath);
|
||||
}
|
||||
return true;
|
||||
} catch (\ImagickException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
390
app/Module/RandomColor.php
Normal file
390
app/Module/RandomColor.php
Normal file
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
/**
|
||||
* RandomColor 1.0.5
|
||||
*
|
||||
* PHP port of David Merfield JavaScript randomColor
|
||||
* https://github.com/davidmerfield/randomColor
|
||||
*
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2022 Damien "Mistic" Sorel
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
class RandomColor
|
||||
{
|
||||
static public $dictionary = null;
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
static public function one($options = array())
|
||||
{
|
||||
$h = self::_pickHue($options);
|
||||
$s = self::_pickSaturation($h, $options);
|
||||
$v = self::_pickBrightness($h, $s, $options);
|
||||
|
||||
return self::format(compact('h','s','v'), @$options['format']);
|
||||
}
|
||||
|
||||
static public function many($count, $options = array())
|
||||
{
|
||||
$colors = array();
|
||||
|
||||
for ($i = 0; $i < $count; $i++)
|
||||
{
|
||||
$colors[] = self::one($options);
|
||||
}
|
||||
|
||||
return $colors;
|
||||
}
|
||||
|
||||
static public function format($hsv, $format='hex')
|
||||
{
|
||||
switch ($format)
|
||||
{
|
||||
case 'hsv':
|
||||
return $hsv;
|
||||
|
||||
case 'hsl':
|
||||
return self::hsv2hsl($hsv);
|
||||
|
||||
case 'hslCss':
|
||||
$hsl = self::hsv2hsl($hsv);
|
||||
return 'hsl(' . $hsl['h'] . ',' . $hsl['s'] . '%,' . $hsl['l'] . '%)';
|
||||
|
||||
case 'rgb':
|
||||
return self::hsv2rgb($hsv);
|
||||
|
||||
case 'rgbCss':
|
||||
return 'rgb(' . implode(',', self::hsv2rgb($hsv)) . ')';
|
||||
|
||||
case 'hex':
|
||||
default:
|
||||
return self::hsv2hex($hsv);
|
||||
}
|
||||
}
|
||||
|
||||
static private function _pickHue($options)
|
||||
{
|
||||
$range = self::_getHueRange($options);
|
||||
|
||||
if (empty($range))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
$hue = self::_rand($range, $options);
|
||||
|
||||
// Instead of storing red as two separate ranges,
|
||||
// we group them, using negative numbers
|
||||
if ($hue < 0)
|
||||
{
|
||||
$hue = 360 + $hue;
|
||||
}
|
||||
|
||||
return $hue;
|
||||
}
|
||||
|
||||
static private function _pickSaturation($h, $options)
|
||||
{
|
||||
if (@$options['hue'] === 'monochrome')
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (@$options['luminosity'] === 'random')
|
||||
{
|
||||
return self::_rand(array(0, 100), $options);
|
||||
}
|
||||
|
||||
$colorInfo = self::_getColorInfo($h);
|
||||
$range = $colorInfo['s'];
|
||||
|
||||
switch (@$options['luminosity'])
|
||||
{
|
||||
case 'bright':
|
||||
$range[0] = 55;
|
||||
break;
|
||||
|
||||
case 'dark':
|
||||
$range[0] = $range[1] - 10;
|
||||
break;
|
||||
|
||||
case 'light':
|
||||
$range[1] = 55;
|
||||
break;
|
||||
}
|
||||
|
||||
return self::_rand($range, $options);
|
||||
}
|
||||
|
||||
static private function _pickBrightness($h, $s, $options)
|
||||
{
|
||||
if (@$options['luminosity'] === 'random')
|
||||
{
|
||||
$range = array(0, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
$range = array(
|
||||
self::_getMinimumBrightness($h, $s),
|
||||
100
|
||||
);
|
||||
|
||||
switch (@$options['luminosity'])
|
||||
{
|
||||
case 'dark':
|
||||
$range[1] = $range[0] + 20;
|
||||
break;
|
||||
|
||||
case 'light':
|
||||
$range[0] = round(($range[1] + $range[0]) / 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return self::_rand($range, $options);
|
||||
}
|
||||
|
||||
static private function _getHueRange($options)
|
||||
{
|
||||
$ranges = array();
|
||||
|
||||
if (isset($options['hue']))
|
||||
{
|
||||
if (!is_array($options['hue']))
|
||||
{
|
||||
$options['hue'] = array($options['hue']);
|
||||
}
|
||||
|
||||
foreach ($options['hue'] as $hue)
|
||||
{
|
||||
if ($hue === 'random')
|
||||
{
|
||||
$ranges[] = array(0, 360);
|
||||
}
|
||||
else if (isset(self::$dictionary[$hue]))
|
||||
{
|
||||
$ranges[] = self::$dictionary[$hue]['h'];
|
||||
}
|
||||
else if (is_numeric($hue))
|
||||
{
|
||||
$hue = intval($hue);
|
||||
|
||||
if ($hue <= 360 && $hue >= 0)
|
||||
{
|
||||
$ranges[] = array($hue, $hue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (($l = count($ranges)) === 0)
|
||||
{
|
||||
return array(0, 360);
|
||||
}
|
||||
else if ($l === 1)
|
||||
{
|
||||
return $ranges[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return $ranges[self::_rand(array(0, $l-1), $options)];
|
||||
}
|
||||
}
|
||||
|
||||
static private function _getMinimumBrightness($h, $s)
|
||||
{
|
||||
$colorInfo = self::_getColorInfo($h);
|
||||
$bounds = $colorInfo['bounds'];
|
||||
|
||||
for ($i = 0, $l = count($bounds); $i < $l - 1; $i++)
|
||||
{
|
||||
$s1 = $bounds[$i][0];
|
||||
$v1 = $bounds[$i][1];
|
||||
$s2 = $bounds[$i+1][0];
|
||||
$v2 = $bounds[$i+1][1];
|
||||
|
||||
if ($s >= $s1 && $s <= $s2)
|
||||
{
|
||||
$m = ($v2 - $v1) / ($s2 - $s1);
|
||||
$b = $v1 - $m * $s1;
|
||||
return round($m * $s + $b);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static private function _getColorInfo($h)
|
||||
{
|
||||
// Maps red colors to make picking hue easier
|
||||
if ($h >= 334 && $h <= 360)
|
||||
{
|
||||
$h-= 360;
|
||||
}
|
||||
|
||||
foreach (self::$dictionary as $color)
|
||||
{
|
||||
if ($color['h'] !== null && $h >= $color['h'][0] && $h <= $color['h'][1])
|
||||
{
|
||||
return $color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private function _rand($bounds, $options)
|
||||
{
|
||||
if (isset($options['prng']))
|
||||
{
|
||||
return $options['prng']($bounds[0], $bounds[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return mt_rand($bounds[0], $bounds[1]);
|
||||
}
|
||||
}
|
||||
|
||||
static public function hsv2hex($hsv)
|
||||
{
|
||||
$rgb = self::hsv2rgb($hsv);
|
||||
$hex = '#';
|
||||
|
||||
foreach ($rgb as $c)
|
||||
{
|
||||
$hex.= str_pad(dechex($c), 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
return $hex;
|
||||
}
|
||||
|
||||
static public function hsv2hsl($hsv)
|
||||
{
|
||||
extract($hsv);
|
||||
|
||||
$s/= 100;
|
||||
$v/= 100;
|
||||
$k = (2-$s)*$v;
|
||||
|
||||
return array(
|
||||
'h' => $h,
|
||||
's' => round($s*$v / ($k < 1 ? $k : 2-$k), 4) * 100,
|
||||
'l' => $k/2 * 100,
|
||||
);
|
||||
}
|
||||
|
||||
static public function hsv2rgb($hsv)
|
||||
{
|
||||
extract($hsv);
|
||||
|
||||
$h/= 360;
|
||||
$s/= 100;
|
||||
$v/= 100;
|
||||
|
||||
$i = floor($h * 6);
|
||||
$f = $h * 6 - $i;
|
||||
|
||||
$m = $v * (1 - $s);
|
||||
$n = $v * (1 - $s * $f);
|
||||
$k = $v * (1 - $s * (1 - $f));
|
||||
|
||||
$r = 1;
|
||||
$g = 1;
|
||||
$b = 1;
|
||||
|
||||
switch ($i)
|
||||
{
|
||||
case 0:
|
||||
list($r,$g,$b) = array($v,$k,$m);
|
||||
break;
|
||||
case 1:
|
||||
list($r,$g,$b) = array($n,$v,$m);
|
||||
break;
|
||||
case 2:
|
||||
list($r,$g,$b) = array($m,$v,$k);
|
||||
break;
|
||||
case 3:
|
||||
list($r,$g,$b) = array($m,$n,$v);
|
||||
break;
|
||||
case 4:
|
||||
list($r,$g,$b) = array($k,$m,$v);
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
list($r,$g,$b) = array($v,$m,$n);
|
||||
break;
|
||||
}
|
||||
|
||||
return array(
|
||||
'r' => floor($r*255),
|
||||
'g' => floor($g*255),
|
||||
'b' => floor($b*255),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* h=hueRange
|
||||
* s=saturationRange : bounds[0][0] ; bounds[-][0]
|
||||
*/
|
||||
RandomColor::$dictionary = array(
|
||||
'monochrome' => array(
|
||||
'bounds' => array(array(0,0), array(100,0)),
|
||||
'h' => NULL,
|
||||
's' => array(0,100)
|
||||
),
|
||||
'red' => array(
|
||||
'bounds' => array(array(20,100),array(30,92),array(40,89),array(50,85),array(60,78),array(70,70),array(80,60),array(90,55),array(100,50)),
|
||||
'h' => array(-26,18),
|
||||
's' => array(20,100)
|
||||
),
|
||||
'orange' => array(
|
||||
'bounds' => array(array(20,100),array(30,93),array(40,88),array(50,86),array(60,85),array(70,70),array(100,70)),
|
||||
'h' => array(19,46),
|
||||
's' => array(20,100)
|
||||
),
|
||||
'yellow' => array(
|
||||
'bounds' => array(array(25,100),array(40,94),array(50,89),array(60,86),array(70,84),array(80,82),array(90,80),array(100,75)),
|
||||
'h' => array(47,62),
|
||||
's' => array(25,100)
|
||||
),
|
||||
'green' => array(
|
||||
'bounds' => array(array(30,100),array(40,90),array(50,85),array(60,81),array(70,74),array(80,64),array(90,50),array(100,40)),
|
||||
'h' => array(63,178),
|
||||
's' => array(30,100)
|
||||
),
|
||||
'blue' => array(
|
||||
'bounds' => array(array(20,100),array(30,86),array(40,80),array(50,74),array(60,60),array(70,52),array(80,44),array(90,39),array(100,35)),
|
||||
'h' => array(179,257),
|
||||
's' => array(20,100)
|
||||
),
|
||||
'purple' => array(
|
||||
'bounds' => array(array(20,100),array(30,87),array(40,79),array(50,70),array(60,65),array(70,59),array(80,52),array(90,45),array(100,42)),
|
||||
'h' => array(258,282),
|
||||
's' => array(20,100)
|
||||
),
|
||||
'pink' => array(
|
||||
'bounds' => array(array(20,100),array(30,90),array(40,86),array(60,84),array(80,80),array(90,75),array(100,73)),
|
||||
'h' => array(283,334),
|
||||
's' => array(20,100)
|
||||
)
|
||||
);
|
||||
44
app/Module/TimeRange.php
Normal file
44
app/Module/TimeRange.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
class TimeRange
|
||||
{
|
||||
public ?Carbon $updated;
|
||||
public ?Carbon $deleted;
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
*/
|
||||
public function __construct($data)
|
||||
{
|
||||
if (is_array($data)) {
|
||||
$range = explode("-", str_replace([",", "|"], "-", $data['timerange']));
|
||||
if ($data['updated_at'] || $data['at_after']) {
|
||||
$range[0] = $data['updated_at'] ?: $data['at_after'];
|
||||
}
|
||||
if ($data['deleted_at']) {
|
||||
$range[1] = $data['deleted_at'];
|
||||
}
|
||||
} else {
|
||||
$range = explode("-", str_replace([",", "|"], "-", $data));
|
||||
}
|
||||
//
|
||||
$updated = Base::isNumber($range[0]) ? intval($range[0]) : trim($range[0]);
|
||||
$deleted = Base::isNumber($range[1]) ? intval($range[1]) : trim($range[1]);
|
||||
//
|
||||
$this->updated = $updated ? Carbon::parse($updated) : null;
|
||||
$this->deleted = $deleted ? Carbon::parse($deleted) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @return TimeRange
|
||||
*/
|
||||
public static function parse($data): TimeRange
|
||||
{
|
||||
return new self($data);
|
||||
}
|
||||
}
|
||||
81
app/Observers/ProjectObserver.php
Normal file
81
app/Observers/ProjectObserver.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Deleted;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectUser;
|
||||
|
||||
class ProjectObserver
|
||||
{
|
||||
/**
|
||||
* Handle the Project "created" event.
|
||||
*
|
||||
* @param \App\Models\Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function created(Project $project)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Project "updated" event.
|
||||
*
|
||||
* @param \App\Models\Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function updated(Project $project)
|
||||
{
|
||||
if ($project->isDirty('archived_at')) {
|
||||
$userids = $this->userids($project);
|
||||
if ($project->archived_at) {
|
||||
Deleted::record('project', $project->id, $userids);
|
||||
} else {
|
||||
Deleted::forget('project', $project->id, $userids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Project "deleted" event.
|
||||
*
|
||||
* @param \App\Models\Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(Project $project)
|
||||
{
|
||||
Deleted::record('project', $project->id, $this->userids($project));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Project "restored" event.
|
||||
*
|
||||
* @param \App\Models\Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function restored(Project $project)
|
||||
{
|
||||
Deleted::forget('project', $project->id, $this->userids($project));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Project "force deleted" event.
|
||||
*
|
||||
* @param \App\Models\Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(Project $project)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Project $project
|
||||
* @return array
|
||||
*/
|
||||
private function userids(Project $project)
|
||||
{
|
||||
return ProjectUser::whereProjectId($project->id)->pluck('userid')->toArray();
|
||||
}
|
||||
}
|
||||
80
app/Observers/ProjectTaskObserver.php
Normal file
80
app/Observers/ProjectTaskObserver.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Deleted;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectUser;
|
||||
|
||||
class ProjectTaskObserver
|
||||
{
|
||||
/**
|
||||
* Handle the ProjectTask "created" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTask $projectTask
|
||||
* @return void
|
||||
*/
|
||||
public function created(ProjectTask $projectTask)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectTask "updated" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTask $projectTask
|
||||
* @return void
|
||||
*/
|
||||
public function updated(ProjectTask $projectTask)
|
||||
{
|
||||
if ($projectTask->isDirty('archived_at')) {
|
||||
if ($projectTask->archived_at) {
|
||||
Deleted::record('projectTask', $projectTask->id, $this->userids($projectTask));
|
||||
} else {
|
||||
Deleted::forget('projectTask', $projectTask->id, $this->userids($projectTask));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectTask "deleted" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTask $projectTask
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(ProjectTask $projectTask)
|
||||
{
|
||||
Deleted::record('projectTask', $projectTask->id, $this->userids($projectTask));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectTask "restored" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTask $projectTask
|
||||
* @return void
|
||||
*/
|
||||
public function restored(ProjectTask $projectTask)
|
||||
{
|
||||
Deleted::forget('projectTask', $projectTask->id, $this->userids($projectTask));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectTask "force deleted" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTask $projectTask
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(ProjectTask $projectTask)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProjectTask $projectTask
|
||||
* @return array
|
||||
*/
|
||||
private function userids(ProjectTask $projectTask)
|
||||
{
|
||||
return ProjectUser::whereProjectId($projectTask->project_id)->pluck('userid')->toArray();
|
||||
}
|
||||
}
|
||||
67
app/Observers/ProjectTaskUserObserver.php
Normal file
67
app/Observers/ProjectTaskUserObserver.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Deleted;
|
||||
use App\Models\ProjectTaskUser;
|
||||
use App\Models\ProjectUser;
|
||||
|
||||
class ProjectTaskUserObserver
|
||||
{
|
||||
/**
|
||||
* Handle the ProjectTaskUser "created" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTaskUser $projectTaskUser
|
||||
* @return void
|
||||
*/
|
||||
public function created(ProjectTaskUser $projectTaskUser)
|
||||
{
|
||||
Deleted::forget('projectTask', $projectTaskUser->task_id, $projectTaskUser->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectTaskUser "updated" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTaskUser $projectTaskUser
|
||||
* @return void
|
||||
*/
|
||||
public function updated(ProjectTaskUser $projectTaskUser)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectTaskUser "deleted" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTaskUser $projectTaskUser
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(ProjectTaskUser $projectTaskUser)
|
||||
{
|
||||
if (!ProjectUser::whereProjectId($projectTaskUser->project_id)->whereUserid($projectTaskUser->userid)->exists()) {
|
||||
Deleted::record('projectTask', $projectTaskUser->task_id, $projectTaskUser->userid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectTaskUser "restored" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTaskUser $projectTaskUser
|
||||
* @return void
|
||||
*/
|
||||
public function restored(ProjectTaskUser $projectTaskUser)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectTaskUser "force deleted" event.
|
||||
*
|
||||
* @param \App\Models\ProjectTaskUser $projectTaskUser
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(ProjectTaskUser $projectTaskUser)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
64
app/Observers/ProjectUserObserver.php
Normal file
64
app/Observers/ProjectUserObserver.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Deleted;
|
||||
use App\Models\ProjectUser;
|
||||
|
||||
class ProjectUserObserver
|
||||
{
|
||||
/**
|
||||
* Handle the ProjectUser "created" event.
|
||||
*
|
||||
* @param \App\Models\ProjectUser $projectUser
|
||||
* @return void
|
||||
*/
|
||||
public function created(ProjectUser $projectUser)
|
||||
{
|
||||
Deleted::forget('project', $projectUser->project_id, $projectUser->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectUser "updated" event.
|
||||
*
|
||||
* @param \App\Models\ProjectUser $projectUser
|
||||
* @return void
|
||||
*/
|
||||
public function updated(ProjectUser $projectUser)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectUser "deleted" event.
|
||||
*
|
||||
* @param \App\Models\ProjectUser $projectUser
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(ProjectUser $projectUser)
|
||||
{
|
||||
Deleted::record('project', $projectUser->project_id, $projectUser->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectUser "restored" event.
|
||||
*
|
||||
* @param \App\Models\ProjectUser $projectUser
|
||||
* @return void
|
||||
*/
|
||||
public function restored(ProjectUser $projectUser)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the ProjectUser "force deleted" event.
|
||||
*
|
||||
* @param \App\Models\ProjectUser $projectUser
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(ProjectUser $projectUser)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
74
app/Observers/WebSocketDialogObserver.php
Normal file
74
app/Observers/WebSocketDialogObserver.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Deleted;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogUser;
|
||||
|
||||
class WebSocketDialogObserver
|
||||
{
|
||||
/**
|
||||
* Handle the WebSocketDialog "created" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialog $webSocketDialog
|
||||
* @return void
|
||||
*/
|
||||
public function created(WebSocketDialog $webSocketDialog)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the WebSocketDialog "updated" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialog $webSocketDialog
|
||||
* @return void
|
||||
*/
|
||||
public function updated(WebSocketDialog $webSocketDialog)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the WebSocketDialog "deleted" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialog $webSocketDialog
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(WebSocketDialog $webSocketDialog)
|
||||
{
|
||||
Deleted::record('dialog', $webSocketDialog->id, $this->userids($webSocketDialog));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the WebSocketDialog "restored" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialog $webSocketDialog
|
||||
* @return void
|
||||
*/
|
||||
public function restored(WebSocketDialog $webSocketDialog)
|
||||
{
|
||||
Deleted::forget('dialog', $webSocketDialog->id, $this->userids($webSocketDialog));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the WebSocketDialog "force deleted" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialog $webSocketDialog
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(WebSocketDialog $webSocketDialog)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WebSocketDialog $webSocketDialog
|
||||
* @return array
|
||||
*/
|
||||
private function userids(WebSocketDialog $webSocketDialog)
|
||||
{
|
||||
return WebSocketDialogUser::whereDialogId($webSocketDialog->id)->pluck('userid')->toArray();
|
||||
}
|
||||
}
|
||||
64
app/Observers/WebSocketDialogUserObserver.php
Normal file
64
app/Observers/WebSocketDialogUserObserver.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Deleted;
|
||||
use App\Models\WebSocketDialogUser;
|
||||
|
||||
class WebSocketDialogUserObserver
|
||||
{
|
||||
/**
|
||||
* Handle the WebSocketDialogUser "created" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialogUser $webSocketDialogUser
|
||||
* @return void
|
||||
*/
|
||||
public function created(WebSocketDialogUser $webSocketDialogUser)
|
||||
{
|
||||
Deleted::forget('dialog', $webSocketDialogUser->dialog_id, $webSocketDialogUser->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the WebSocketDialogUser "updated" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialogUser $webSocketDialogUser
|
||||
* @return void
|
||||
*/
|
||||
public function updated(WebSocketDialogUser $webSocketDialogUser)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the WebSocketDialogUser "deleted" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialogUser $webSocketDialogUser
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(WebSocketDialogUser $webSocketDialogUser)
|
||||
{
|
||||
Deleted::record('dialog', $webSocketDialogUser->dialog_id, $webSocketDialogUser->userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the WebSocketDialogUser "restored" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialogUser $webSocketDialogUser
|
||||
* @return void
|
||||
*/
|
||||
public function restored(WebSocketDialogUser $webSocketDialogUser)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the WebSocketDialogUser "force deleted" event.
|
||||
*
|
||||
* @param \App\Models\WebSocketDialogUser $webSocketDialogUser
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(WebSocketDialogUser $webSocketDialogUser)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,14 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
\Illuminate\Database\Query\Builder::macro('rawSql', function(){
|
||||
return array_reduce($this->getBindings(), function($sql, $binding){
|
||||
return preg_replace('/\?/', is_numeric($binding) ? $binding : "'".$binding."'" , $sql, 1);
|
||||
}, $this->toSql());
|
||||
});
|
||||
|
||||
\Illuminate\Database\Eloquent\Builder::macro('rawSql', function(){
|
||||
return ($this->getQuery()->rawSql());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,21 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskUser;
|
||||
use App\Models\ProjectUser;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogUser;
|
||||
use App\Observers\ProjectObserver;
|
||||
use App\Observers\ProjectTaskObserver;
|
||||
use App\Observers\ProjectTaskUserObserver;
|
||||
use App\Observers\ProjectUserObserver;
|
||||
use App\Observers\WebSocketDialogObserver;
|
||||
use App\Observers\WebSocketDialogUserObserver;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -27,6 +38,11 @@ class EventServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
Project::observe(ProjectObserver::class);
|
||||
ProjectTask::observe(ProjectTaskObserver::class);
|
||||
ProjectTaskUser::observe(ProjectTaskUserObserver::class);
|
||||
ProjectUser::observe(ProjectUserObserver::class);
|
||||
WebSocketDialog::observe(WebSocketDialogObserver::class);
|
||||
WebSocketDialogUser::observe(WebSocketDialogUserObserver::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\Models\User;
|
||||
use App\Models\WebSocket;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Tasks\LineTask;
|
||||
use App\Tasks\PushTask;
|
||||
use Cache;
|
||||
@@ -39,55 +40,45 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
*/
|
||||
public function onOpen(Server $server, Request $request)
|
||||
{
|
||||
global $_A;
|
||||
$_A = [
|
||||
'__static_langdata' => [],
|
||||
];
|
||||
$fd = $request->fd;
|
||||
$data = Base::newTrim($request->get);
|
||||
$action = $data['action'];
|
||||
$get = Base::newTrim($request->get);
|
||||
$action = $get['action'];
|
||||
Cache::forget("User::encrypt:" . $fd);
|
||||
switch ($action) {
|
||||
/**
|
||||
* 网页访问
|
||||
*/
|
||||
case 'web':
|
||||
{
|
||||
// 判断token参数
|
||||
$token = $data['token'];
|
||||
$cacheKey = "ws::token:" . md5($token);
|
||||
$userid = Cache::remember($cacheKey, now()->addSeconds(1), function () use ($token) {
|
||||
$authInfo = User::authFind('all', $token);
|
||||
if ($authInfo['userid'] > 0) {
|
||||
if (User::whereUserid($authInfo['userid'])->whereEmail($authInfo['email'])->whereEncrypt($authInfo['encrypt'])->exists()) {
|
||||
return $authInfo['userid'];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
if (empty($userid)) {
|
||||
Cache::forget($cacheKey);
|
||||
Doo::load($get['token'], $get['language']);
|
||||
//
|
||||
if (Doo::userId() > 0
|
||||
&& !Doo::userExpired()
|
||||
&& $user = User::whereUserid(Doo::userId())->whereEmail(Doo::userEmail())->whereEncrypt(Doo::userEncrypt())->first()) {
|
||||
// 保存用户
|
||||
$this->saveUser($fd, $user->userid);
|
||||
// 发送open事件
|
||||
$server->push($fd, Base::array2json([
|
||||
'type' => 'open',
|
||||
'data' => [
|
||||
'fd' => $fd,
|
||||
],
|
||||
]));
|
||||
// 通知上线
|
||||
Task::deliver(new LineTask($user->userid, true));
|
||||
// 推送离线时收到的消息
|
||||
Task::deliver(new PushTask("RETRY::" . $user->userid));
|
||||
} else {
|
||||
// 用户不存在
|
||||
$server->push($fd, Base::array2json([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'error' => '会员不存在'
|
||||
'error' => 'No member'
|
||||
],
|
||||
]));
|
||||
$server->close($fd);
|
||||
$this->deleteUser($fd);
|
||||
return;
|
||||
}
|
||||
// 保存用户、发送open事件
|
||||
$this->saveUser($fd, $userid);
|
||||
$server->push($fd, Base::array2json([
|
||||
'type' => 'open',
|
||||
'data' => [
|
||||
'fd' => $fd,
|
||||
],
|
||||
]));
|
||||
// 通知上线
|
||||
Task::deliver(new LineTask($userid, true));
|
||||
// 推送离线时收到的消息
|
||||
Task::deliver(new PushTask("RETRY::" . $userid));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -103,11 +94,6 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
*/
|
||||
public function onMessage(Server $server, Frame $frame)
|
||||
{
|
||||
global $_A;
|
||||
$_A = [
|
||||
'__static_langdata' => [],
|
||||
];
|
||||
//
|
||||
$msg = Base::json2array($frame->data);
|
||||
$type = $msg['type']; // 消息类型
|
||||
$msgId = $msg['msgId']; // 消息ID(用于回调)
|
||||
@@ -127,9 +113,11 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
case 'readMsg':
|
||||
$ids = is_array($data['id']) ? $data['id'] : [$data['id']];
|
||||
$userid = $this->getUserid($frame->fd);
|
||||
$list = WebSocketDialogMsg::whereIn('id', $ids)->get();
|
||||
$list->transform(function(WebSocketDialogMsg $item) use ($userid) {
|
||||
$item->readSuccess($userid);
|
||||
WebSocketDialogMsg::whereIn('id', $ids)->chunkById(20, function($list) use ($userid) {
|
||||
/** @var WebSocketDialogMsg $item */
|
||||
foreach ($list as $item) {
|
||||
$item->readSuccess($userid);
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
||||
@@ -143,14 +131,24 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
$pathOld = $row->path;
|
||||
$row->path = $pathNew;
|
||||
$row->save();
|
||||
if (preg_match("/^file\/content\/\d+$/", $pathOld)) {
|
||||
if (preg_match("/^\/single\/file\/\d+$/", $pathOld)) {
|
||||
$this->pushPath($pathOld);
|
||||
}
|
||||
if (preg_match("/^file\/content\/\d+$/", $pathNew)) {
|
||||
if (preg_match("/^\/single\/file\/\d+$/", $pathNew)) {
|
||||
$this->pushPath($pathNew);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
/**
|
||||
* 加密参数
|
||||
*/
|
||||
case 'encrypt':
|
||||
if ($data['type'] === 'pgp') {
|
||||
$data['key'] = Doo::pgpPublicFormat($data['key']);
|
||||
}
|
||||
Cache::put("User::encrypt:" . $frame->fd, Base::array2json($data), Carbon::now()->addDay());
|
||||
return;
|
||||
}
|
||||
//
|
||||
if ($msgId) {
|
||||
@@ -174,8 +172,11 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
*/
|
||||
public function onClose(Server $server, $fd, $reactorId)
|
||||
{
|
||||
Task::deliver(new LineTask($this->getUserid($fd), false)); // 通知离线
|
||||
$this->deleteUser($fd);
|
||||
$userid = $this->getUserid($fd);
|
||||
if($userid){
|
||||
Task::deliver(new LineTask($userid, false)); // 通知离线
|
||||
$this->deleteUser($fd);
|
||||
}
|
||||
}
|
||||
|
||||
/** ****************************************************************************** */
|
||||
@@ -189,13 +190,15 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
*/
|
||||
private function saveUser($fd, $userid)
|
||||
{
|
||||
Cache::put("User::fd:" . $fd, "on", Carbon::now()->addDay());
|
||||
Cache::put("User::online:" . $userid, "on", Carbon::now()->addDay());
|
||||
//
|
||||
WebSocket::updateInsert([
|
||||
'key' => md5($fd . '@' . $userid)
|
||||
], [
|
||||
'fd' => $fd,
|
||||
'userid' => $userid,
|
||||
]);
|
||||
Cache::put("User::online:" . $userid, time(), Carbon::now()->addSeconds(30));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,7 +207,28 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
*/
|
||||
private function deleteUser($fd)
|
||||
{
|
||||
WebSocket::whereFd($fd)->delete();
|
||||
Cache::forget("User::fd:" . $fd);
|
||||
//
|
||||
$array = [];
|
||||
WebSocket::whereFd($fd)->chunk(10, function($list) use (&$array) {
|
||||
/** @var WebSocket $item */
|
||||
foreach ($list as $item) {
|
||||
$item->delete();
|
||||
if ($item->userid) {
|
||||
// 离线时更新会员最后在线时间
|
||||
User::whereUserid($item->userid)->update([
|
||||
'line_at' => Carbon::now()
|
||||
]);
|
||||
Cache::forget("User::online:" . $item->userid);
|
||||
}
|
||||
if ($item->path && str_starts_with($item->path, "/single/file/")) {
|
||||
$array[$item->path] = $item->path;
|
||||
}
|
||||
}
|
||||
});
|
||||
foreach ($array as $path) {
|
||||
$this->pushPath($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\TaskWorker;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
|
||||
/**
|
||||
@@ -9,27 +12,18 @@ use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
*/
|
||||
abstract class AbstractTask extends Task
|
||||
{
|
||||
protected $newTask = [];
|
||||
protected int $twid = 0;
|
||||
|
||||
/**
|
||||
* 添加完成后执行的任务
|
||||
* @param $task
|
||||
*/
|
||||
final protected function addTask($task)
|
||||
public function __construct(...$params)
|
||||
{
|
||||
$this->newTask[] = $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装执行过程
|
||||
*/
|
||||
final public function handle()
|
||||
{
|
||||
try {
|
||||
$this->start();
|
||||
} catch (\Exception $e) {
|
||||
$this->info($e);
|
||||
$this->failed($e);
|
||||
$row = TaskWorker::createInstance([
|
||||
'args' => [
|
||||
'params' => $params,
|
||||
'class' => get_class($this)
|
||||
],
|
||||
]);
|
||||
if ($row->save()) {
|
||||
$this->twid = $row->id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,31 +35,53 @@ abstract class AbstractTask extends Task
|
||||
/**
|
||||
* 任务完成事件
|
||||
*/
|
||||
public function finish()
|
||||
abstract public function end();
|
||||
|
||||
/**
|
||||
* 重写执行过程
|
||||
*/
|
||||
final public function handle()
|
||||
{
|
||||
foreach ($this->newTask AS $task) {
|
||||
Task::deliver($task);
|
||||
TaskWorker::whereId($this->twid)->update(['start_at' => Carbon::now()]);
|
||||
//
|
||||
try {
|
||||
$this->start();
|
||||
} catch (\Throwable $e) {
|
||||
$this->failed("start", $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写完成事件
|
||||
*/
|
||||
final public function finish()
|
||||
{
|
||||
TaskWorker::whereId($this->twid)->update(['end_at' => Carbon::now()]);
|
||||
//
|
||||
try {
|
||||
$this->end();
|
||||
TaskWorker::whereId($this->twid)->delete();
|
||||
} catch (\Throwable $e) {
|
||||
$this->failed("end", $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务失败事件
|
||||
* @param $e
|
||||
* @param string $type
|
||||
* @param \Throwable $e
|
||||
*/
|
||||
public function failed($e)
|
||||
public function failed(string $type, \Throwable $e)
|
||||
{
|
||||
info($type);
|
||||
info($e);
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加日志
|
||||
* @param $var
|
||||
*/
|
||||
private function info($var)
|
||||
{
|
||||
if (!config('app.debug') || defined('DO_NOT_ADD_LOGS')) {
|
||||
return;
|
||||
}
|
||||
info($var);
|
||||
TaskWorker::whereId($this->twid)->update(['error' => Base::array2json([
|
||||
'time' => Carbon::now(),
|
||||
'type' => $type,
|
||||
'code' => $e->getCode(),
|
||||
'file' => $e->getFile(),
|
||||
'message' => $e->getMessage(),
|
||||
])]);
|
||||
}
|
||||
}
|
||||
|
||||
52
app/Tasks/AppPushTask.php
Normal file
52
app/Tasks/AppPushTask.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\ProjectTask;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
class AppPushTask extends AbstractTask
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$setting = Base::setting('appPushSetting');
|
||||
if ($setting['push'] !== 'open') {
|
||||
return;
|
||||
}
|
||||
ProjectTask::whereNull("complete_at")
|
||||
->whereNull("archived_at")
|
||||
->whereBetween("end_at", [
|
||||
Carbon::now()->addMinutes(60),
|
||||
Carbon::now()->addMinutes(60 + 10)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$task->taskPush(null, 1);
|
||||
}
|
||||
});
|
||||
ProjectTask::whereNull("complete_at")
|
||||
->whereNull("archived_at")
|
||||
->whereBetween("end_at", [
|
||||
Carbon::now()->subMinutes(60 + 10),
|
||||
Carbon::now()->subMinutes(60)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$task->taskPush(null, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
51
app/Tasks/AutoArchivedTask.php
Normal file
51
app/Tasks/AutoArchivedTask.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
namespace App\Tasks;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Models\AbstractModel;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* 完成的任务自动归档
|
||||
* Class AutoArchivedTask
|
||||
* @package App\Tasks
|
||||
*/
|
||||
class AutoArchivedTask extends AbstractTask
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$setting = Base::setting('system');
|
||||
if ($setting['auto_archived'] === 'open') {
|
||||
$archivedDay = floatval($setting['archived_day']);
|
||||
if ($archivedDay > 0) {
|
||||
$archivedDay = min(100, $archivedDay);
|
||||
$archivedTime = Carbon::now()->subDays($archivedDay);
|
||||
//获取已完成未归档的任务
|
||||
$taskLists = ProjectTask::whereNotNull('complete_at')
|
||||
->where('complete_at', '<=', $archivedTime)
|
||||
->where('archived_userid', 0)
|
||||
->whereNull('archived_at')
|
||||
->take(100)
|
||||
->get();
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($taskLists AS $task) {
|
||||
$task->archivedTask(Carbon::now(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
506
app/Tasks/BotReceiveMsgTask.php
Normal file
506
app/Tasks/BotReceiveMsgTask.php
Normal file
@@ -0,0 +1,506 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserBot;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\Base;
|
||||
use App\Module\Doo;
|
||||
use App\Module\Ihttp;
|
||||
use Carbon\Carbon;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
|
||||
/**
|
||||
* 推送会话消息
|
||||
* Class BotReceiveMsgTask
|
||||
* @package App\Tasks
|
||||
*/
|
||||
class BotReceiveMsgTask extends AbstractTask
|
||||
{
|
||||
protected $userid;
|
||||
protected $msgId;
|
||||
protected $mention;
|
||||
protected $client = [];
|
||||
|
||||
public function __construct($userid, $msgId, $mention, $client = [])
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->userid = $userid;
|
||||
$this->msgId = $msgId;
|
||||
$this->mention = $mention;
|
||||
$this->client = is_array($client) ? $client : [];
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$botUser = User::whereUserid($this->userid)->whereBot(1)->first();
|
||||
if (empty($botUser)) {
|
||||
return;
|
||||
}
|
||||
$msg = WebSocketDialogMsg::find($this->msgId);
|
||||
if (empty($msg)) {
|
||||
return;
|
||||
}
|
||||
$msg->readSuccess($botUser->userid);
|
||||
$this->botManagerReceive($msg, $botUser);
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人管理处理消息
|
||||
* @param WebSocketDialogMsg $msg
|
||||
* @param User $botUser
|
||||
* @return void
|
||||
*/
|
||||
private function botManagerReceive(WebSocketDialogMsg $msg, User $botUser)
|
||||
{
|
||||
if ($msg->type !== 'text') {
|
||||
return;
|
||||
}
|
||||
$original = $msg->msg['text'];
|
||||
if ($this->mention) {
|
||||
$original = preg_replace("/<span class=\"mention user\" data-id=\"(\d+)\">(.*?)<\/span>/", "", $original);
|
||||
}
|
||||
if (preg_match("/<span[^>]*?data-quick-key=([\"'])(.*?)\\1[^>]*?>(.*?)<\/span>/is", $original, $match)) {
|
||||
$command = $match[2];
|
||||
if (str_starts_with($command, '%3A.')) {
|
||||
$command = ":" . substr($command, 4);
|
||||
}
|
||||
} else {
|
||||
$command = trim(strip_tags($original));
|
||||
}
|
||||
//
|
||||
$dialog = WebSocketDialog::find($msg->dialog_id);
|
||||
if (empty($dialog)) {
|
||||
return;
|
||||
}
|
||||
// 推送Webhook
|
||||
if ($command
|
||||
&& !str_starts_with($command, '/')
|
||||
&& ($dialog->type === 'user' || $this->mention)) {
|
||||
$this->botManagerWebhook($command, $msg, $botUser, $dialog);
|
||||
}
|
||||
if ($dialog->type !== 'user') {
|
||||
return;
|
||||
}
|
||||
// 签到机器人
|
||||
if ($botUser->email === 'check-in@bot.system') {
|
||||
$text = UserBot::checkinBotQuickMsg($command, $msg->userid);
|
||||
if ($text) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
}
|
||||
// 隐私机器人
|
||||
if ($botUser->email === 'anon-msg@bot.system') {
|
||||
$text = UserBot::anonBotQuickMsg($command);
|
||||
if ($text) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
}
|
||||
// 管理机器人
|
||||
if (str_starts_with($command, '/')) {
|
||||
if ($botUser->email === 'bot-manager@bot.system') {
|
||||
$isManager = true;
|
||||
} elseif (UserBot::whereBotId($botUser->userid)->whereUserid($msg->userid)->exists()) {
|
||||
$isManager = false;
|
||||
} else {
|
||||
$text = "非常抱歉,我不是你的机器人,无法完成你的指令。";
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
return;
|
||||
}
|
||||
//
|
||||
$array = Base::newTrim(explode(" ", "{$command} "));
|
||||
$type = $array[0];
|
||||
$data = [];
|
||||
$notice = "";
|
||||
if (!$isManager && in_array($type, ['/list', '/newbot'])) {
|
||||
return; // 这些操作仅支持【机器人管理】机器人
|
||||
}
|
||||
switch ($type) {
|
||||
/**
|
||||
* 列表
|
||||
*/
|
||||
case '/list':
|
||||
$data = User::select([
|
||||
'users.*',
|
||||
'user_bots.clear_day',
|
||||
'user_bots.clear_at',
|
||||
'user_bots.webhook_url',
|
||||
'user_bots.webhook_num'
|
||||
])
|
||||
->join('user_bots', 'users.userid', '=', 'user_bots.bot_id')
|
||||
->where('users.bot', 1)
|
||||
->where('user_bots.userid', $msg->userid)
|
||||
->take(50)
|
||||
->orderByDesc('id')
|
||||
->get();
|
||||
if ($data->isEmpty()) {
|
||||
$type = "notice";
|
||||
$notice = "您没有创建机器人。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
case '/info':
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if (!$data) {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 创建
|
||||
*/
|
||||
case '/newbot':
|
||||
if (User::select(['users.*'])
|
||||
->join('user_bots', 'users.userid', '=', 'user_bots.bot_id')
|
||||
->where('users.bot', 1)
|
||||
->where('user_bots.userid', $msg->userid)
|
||||
->count() >= 50) {
|
||||
$type = "notice";
|
||||
$notice = "超过最大创建数量。";
|
||||
break;
|
||||
}
|
||||
if (strlen($array[1]) < 2 || strlen($array[1]) > 20) {
|
||||
$type = "notice";
|
||||
$notice = "机器人名称由2-20个字符组成。";
|
||||
break;
|
||||
}
|
||||
$data = User::botGetOrCreate("user-" . Base::generatePassword(), [
|
||||
'nickname' => $array[1]
|
||||
], $msg->userid);
|
||||
if (empty($data)) {
|
||||
$type = "notice";
|
||||
$notice = "创建失败。";
|
||||
break;
|
||||
}
|
||||
$dialog = WebSocketDialog::checkUserDialog($data, $msg->userid);
|
||||
if ($dialog) {
|
||||
$text = "<p>您好,我是机器人:{$data->nickname},我的机器人ID是:{$data->userid},</p><p>你可以发送 <u><b>/help</b></u> 查看我支持什么命令。</p>";
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $data->userid); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 修改名字
|
||||
*/
|
||||
case '/setname':
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$nameString = $isManager ? $array[2] : $array[1];
|
||||
if (strlen($nameString) < 2 || strlen($nameString) > 20) {
|
||||
$type = "notice";
|
||||
$notice = "机器人名称由2-20个字符组成。";
|
||||
break;
|
||||
}
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if ($data) {
|
||||
$data->nickname = $nameString;
|
||||
$data->az = Base::getFirstCharter($nameString);
|
||||
$data->pinyin = Base::cn2pinyin($nameString);
|
||||
$data->save();
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
case '/deletebot':
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if ($data) {
|
||||
$data->deleteUser('delete bot');
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 获取Token
|
||||
*/
|
||||
case '/token':
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if ($data) {
|
||||
User::generateToken($data);
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 更新Token
|
||||
*/
|
||||
case '/revoke':
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if ($data) {
|
||||
$data->encrypt = Base::generatePassword(6);
|
||||
$data->password = Doo::md5s(Base::generatePassword(32), $data->encrypt);
|
||||
$data->save();
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 设置保留消息时间
|
||||
*/
|
||||
case '/clearday':
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$clearDay = $isManager ? $array[2] : $array[1];
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if ($data) {
|
||||
$userBot = UserBot::whereBotId($botId)->whereUserid($msg->userid)->first();
|
||||
if ($userBot) {
|
||||
$userBot->clear_day = min(intval($clearDay) ?: 30, 999);
|
||||
$userBot->clear_at = Carbon::now()->addDays($userBot->clear_day);
|
||||
$userBot->save();
|
||||
}
|
||||
$data->clear_day = $userBot->clear_day;
|
||||
$data->clear_at = $userBot->clear_at; // 这两个参数只是作为输出,所以不保存
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 设置webhook
|
||||
*/
|
||||
case '/webhook':
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$webhookUrl = $isManager ? $array[2] : $array[1];
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if (strlen($webhookUrl) > 255) {
|
||||
$type = "notice";
|
||||
$notice = "webhook地址最长仅支持255个字符。";
|
||||
} elseif ($data) {
|
||||
$userBot = UserBot::whereBotId($botId)->whereUserid($msg->userid)->first();
|
||||
if ($userBot) {
|
||||
$userBot->webhook_url = $webhookUrl ?: "";
|
||||
$userBot->webhook_num = 0;
|
||||
$userBot->save();
|
||||
}
|
||||
$data->webhook_url = $userBot->webhook_url ?: '-';
|
||||
$data->webhook_num = $userBot->webhook_num; // 这两个参数只是作为输出,所以不保存
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 会话搜索
|
||||
*/
|
||||
case '/dialog':
|
||||
$botId = $isManager ? $array[1] : $botUser->userid;
|
||||
$nameKey = $isManager ? $array[2] : $array[1];
|
||||
$data = $this->botManagerOne($botId, $msg->userid);
|
||||
if ($data) {
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'u.silence', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->where('web_socket_dialogs.name', 'LIKE', "%{$nameKey}%")
|
||||
->where('u.userid', $data->userid)
|
||||
->orderByDesc('u.top_at')
|
||||
->orderByDesc('web_socket_dialogs.last_at')
|
||||
->take(20)
|
||||
->get();
|
||||
if ($list->isEmpty()) {
|
||||
$type = "notice";
|
||||
$notice = "没有搜索到相关会话。";
|
||||
} else {
|
||||
$list->transform(function (WebSocketDialog $item) use ($data) {
|
||||
return $item->formatData($data->userid);
|
||||
});
|
||||
$data->list = $list; // 这个参数只是作为输出,所以不保存
|
||||
}
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
}
|
||||
//
|
||||
$text = view('push.bot', [
|
||||
'type' => $type,
|
||||
'data' => $data,
|
||||
'notice' => $notice,
|
||||
'manager' => $isManager,
|
||||
'version' => Base::getVersion()
|
||||
])->render();
|
||||
if (!$isManager) {
|
||||
$text = preg_replace("/\s*\{机器人ID\}/", "", $text);
|
||||
}
|
||||
$text = preg_replace("/^\x20+/", "", $text);
|
||||
$text = preg_replace("/\n\x20+/", "\n", $text);
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人处理 Webhook
|
||||
* @param string $command
|
||||
* @param WebSocketDialogMsg $msg
|
||||
* @param User $botUser
|
||||
* @param WebSocketDialog $dialog
|
||||
* @return void
|
||||
*/
|
||||
private function botManagerWebhook(string $command, WebSocketDialogMsg $msg, User $botUser, WebSocketDialog $dialog)
|
||||
{
|
||||
$serverUrl = 'http://' . env('APP_IPPR') . '.3';
|
||||
$userBot = null;
|
||||
$extras = [];
|
||||
$error = null;
|
||||
switch ($botUser->email) {
|
||||
// ChatGPT 机器人
|
||||
case 'ai-openai@bot.system':
|
||||
$setting = Base::setting('aibotSetting');
|
||||
$webhookUrl = "{$serverUrl}/ai/openai/send";
|
||||
$extras = [
|
||||
'openai_key' => $setting['openai_key'],
|
||||
'openai_agency' => $setting['openai_agency'],
|
||||
'openai_model' => $setting['openai_model'],
|
||||
'server_url' => $serverUrl,
|
||||
];
|
||||
if (empty($extras['openai_key'])) {
|
||||
$error = 'Robot disabled.';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.11).';
|
||||
}
|
||||
break;
|
||||
// Claude 机器人
|
||||
case 'ai-claude@bot.system':
|
||||
$setting = Base::setting('aibotSetting');
|
||||
$webhookUrl = "{$serverUrl}/ai/claude/send";
|
||||
$extras = [
|
||||
'claude_token' => $setting['claude_token'],
|
||||
'claude_agency' => $setting['claude_agency'],
|
||||
'server_url' => $serverUrl,
|
||||
];
|
||||
if (empty($extras['claude_token'])) {
|
||||
$error = 'Robot disabled.';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.11).';
|
||||
}
|
||||
break;
|
||||
// Wenxin 机器人
|
||||
case 'ai-wenxin@bot.system':
|
||||
$setting = Base::setting('aibotSetting');
|
||||
$webhookUrl = "{$serverUrl}/ai/wenxin/send";
|
||||
$extras = [
|
||||
'wenxin_key' => $setting['wenxin_key'],
|
||||
'wenxin_secret' => $setting['wenxin_secret'],
|
||||
'wenxin_model' => $setting['wenxin_model'],
|
||||
'server_url' => $serverUrl,
|
||||
];
|
||||
if (empty($extras['wenxin_key'])) {
|
||||
$error = 'Robot disabled.';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.12).';
|
||||
}
|
||||
break;
|
||||
// QianWen 机器人
|
||||
case 'ai-qianwen@bot.system':
|
||||
$setting = Base::setting('aibotSetting');
|
||||
$webhookUrl = "{$serverUrl}/ai/qianwen/send";
|
||||
$extras = [
|
||||
'qianwen_key' => $setting['qianwen_key'],
|
||||
'qianwen_model' => $setting['qianwen_model'],
|
||||
'server_url' => $serverUrl,
|
||||
];
|
||||
if (empty($extras['qianwen_key'])) {
|
||||
$error = 'Robot disabled.';
|
||||
} elseif (in_array($this->client['platform'], ['win', 'mac', 'web'])
|
||||
&& !Base::judgeClientVersion("0.29.11", $this->client['version'])) {
|
||||
$error = 'The client version is low (required version ≥ v0.29.12).';
|
||||
}
|
||||
break;
|
||||
// 其他机器人
|
||||
default:
|
||||
$userBot = UserBot::whereBotId($botUser->userid)->first();
|
||||
$webhookUrl = $userBot?->webhook_url;
|
||||
break;
|
||||
}
|
||||
if ($error) {
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $error], $botUser->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
return;
|
||||
}
|
||||
if (!preg_match("/^https*:\/\//", $webhookUrl)) {
|
||||
return;
|
||||
}
|
||||
//
|
||||
try {
|
||||
$res = Ihttp::ihttp_post($webhookUrl, [
|
||||
'text' => $command,
|
||||
'token' => User::generateToken($botUser),
|
||||
'dialog_id' => $dialog->id,
|
||||
'dialog_type' => $dialog->type,
|
||||
'msg_id' => $msg->id,
|
||||
'msg_uid' => $msg->userid,
|
||||
'mention' => $this->mention ? 1 : 0,
|
||||
'bot_uid' => $botUser->userid,
|
||||
'version' => Base::getVersion(),
|
||||
'extras' => Base::array2json($extras)
|
||||
], 10);
|
||||
if ($userBot) {
|
||||
$userBot->webhook_num++;
|
||||
$userBot->save();
|
||||
}
|
||||
if($res['data'] && $data = json_decode($res['data'])){
|
||||
if($data['code'] != 200 && $data['message']){
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $res['data']['message']], $botUser->userid, false, false, true);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
//throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $botId
|
||||
* @param $userid
|
||||
* @return User
|
||||
*/
|
||||
private function botManagerOne($botId, $userid)
|
||||
{
|
||||
$botId = intval($botId);
|
||||
$userid = intval($userid);
|
||||
if ($botId > 0) {
|
||||
return User::select([
|
||||
'users.*',
|
||||
'user_bots.clear_day',
|
||||
'user_bots.clear_at',
|
||||
'user_bots.webhook_url',
|
||||
'user_bots.webhook_num'
|
||||
])
|
||||
->join('user_bots', 'users.userid', '=', 'user_bots.bot_id')
|
||||
->where('users.bot', 1)
|
||||
->where('user_bots.bot_id', $botId)
|
||||
->where('user_bots.userid', $userid)
|
||||
->first();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
95
app/Tasks/CheckinRemindTask.php
Normal file
95
app/Tasks/CheckinRemindTask.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserCheckinRecord;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\Base;
|
||||
use App\Module\Extranet;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
class CheckinRemindTask extends AbstractTask
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$setting = Base::setting('checkinSetting');
|
||||
if ($setting['open'] !== 'open') {
|
||||
return;
|
||||
}
|
||||
// 判断非工作日
|
||||
if (Extranet::isHoliday(date("Ymd")) > 0) {
|
||||
return;
|
||||
}
|
||||
//
|
||||
$times = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
|
||||
$remindin = (intval($setting['remindin']) ?: 5) * 60;
|
||||
$remindexceed = (intval($setting['remindexceed']) ?: 10) * 60;
|
||||
//
|
||||
$nowDate = date("Y-m-d");
|
||||
$timeStart = strtotime("{$nowDate} {$times[0]}");
|
||||
//
|
||||
if ($remindin > 0) {
|
||||
$timeRemindin = $timeStart - $remindin;
|
||||
if ($timeRemindin <= Base::time() && Base::time() <= $timeStart) {
|
||||
// 签到打卡提醒
|
||||
$this->remind('in');
|
||||
}
|
||||
}
|
||||
if ($remindexceed > 0) {
|
||||
$timeRemindexceed = $timeStart + $remindexceed;
|
||||
if ($timeRemindexceed <= Base::time() && Base::time() <= $timeRemindexceed + 300) {
|
||||
// 签到缺卡提醒
|
||||
$this->remind('exceed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private function remind($type)
|
||||
{
|
||||
if (Cache::get("CheckinRemindTask:remind-" . $type) == date("Ymd")) {
|
||||
return;
|
||||
}
|
||||
Cache::put("CheckinRemindTask:remind-" . $type, date("Ymd"), Carbon::now()->addDay());
|
||||
//
|
||||
$botUser = User::botGetOrCreate('check-in');
|
||||
if (!$botUser) {
|
||||
return;
|
||||
}
|
||||
// 提醒对象:3天内有签到的成员(在职)
|
||||
User::whereBot(0)->whereNull('disable_at')->chunk(100, function ($users) use ($type, $botUser) {
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
if (UserCheckinRecord::whereUserid($user->userid)->where('date', date("Y-m-d"))->exists()) {
|
||||
continue; // 已打卡
|
||||
}
|
||||
if (!UserCheckinRecord::whereUserid($user->userid)->where('created_at', '>', Carbon::now()->subDays(3))->exists()) {
|
||||
continue; // 3天内没有打卡
|
||||
}
|
||||
$dialog = WebSocketDialog::checkUserDialog($botUser, $user->userid);
|
||||
if ($dialog) {
|
||||
if ($type === 'exceed') {
|
||||
$text = "<p><strong style='color:red'>缺卡提醒:</strong>上班时间到了,你还没有打卡哦~</p>";
|
||||
} else {
|
||||
$text = "<p><strong>打卡提醒:</strong>快到上班时间了,别忘了打卡哦~</p>";
|
||||
}
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
44
app/Tasks/DeleteBotMsgTask.php
Normal file
44
app/Tasks/DeleteBotMsgTask.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Models\UserBot;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* 删除机器人消息
|
||||
*/
|
||||
class DeleteBotMsgTask extends AbstractTask
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$bots = UserBot::where('clear_at', '<=', Carbon::now())->take(100)->get();
|
||||
foreach ($bots as $bot) {
|
||||
WebSocketDialogMsg::whereUserid($bot->bot_id)
|
||||
->where('created_at', '<=', Carbon::now()->subDays($bot->clear_day))
|
||||
->orderBy('id')
|
||||
->chunk(1000, function ($msgs) {
|
||||
$ids = $msgs->pluck('id')->toArray();
|
||||
if ($ids) {
|
||||
WebSocketDialogMsg::deleteMsgs($ids);
|
||||
}
|
||||
});
|
||||
$bot->clear_at = Carbon::now()->addDays($bot->clear_day);
|
||||
$bot->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user