Compare commits
1833 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee4684055f | ||
|
|
fac503a188 | ||
|
|
4fa9ec346f | ||
|
|
5179903516 | ||
|
|
f2652d1ee4 | ||
|
|
9dd27e9796 | ||
|
|
6f2da3aa6d | ||
|
|
67780a1fdc | ||
|
|
16edfe18bc | ||
|
|
06815b4b8c | ||
|
|
954f1be44d | ||
|
|
81983026b4 | ||
|
|
f7d4f42551 | ||
|
|
b2d97bd5f1 | ||
|
|
47226e0654 | ||
|
|
9cfc736ba9 | ||
|
|
dc9a7c07e8 | ||
|
|
032b02c1ec | ||
|
|
f34a391aec | ||
|
|
e564be3fb5 | ||
|
|
c30c6114d5 | ||
|
|
cb33b9fc51 | ||
|
|
05b169c172 | ||
|
|
31c1466b42 | ||
|
|
bca28fb390 | ||
|
|
db3fc5f492 | ||
|
|
144fb5afba | ||
|
|
318effd1ec | ||
|
|
96b14911f6 | ||
|
|
248039f59e | ||
|
|
aacd52eec7 | ||
|
|
c699feef54 | ||
|
|
6cf7833ef7 | ||
|
|
d57245d3f0 | ||
|
|
893465fc38 | ||
|
|
19b55ff74c | ||
|
|
4c76b3e60c | ||
|
|
c27a15fce2 | ||
|
|
2322e9a1b6 | ||
|
|
aa781a51df | ||
|
|
5d5b1000fe | ||
|
|
c0fe4a2bcb | ||
|
|
5f1cb1492a | ||
|
|
67d0e26972 | ||
|
|
0dfeb4be4f | ||
|
|
3112cc55f3 | ||
|
|
10a2120fb8 | ||
|
|
61aa50f366 | ||
|
|
eee9045701 | ||
|
|
5c6fc6fe1a | ||
|
|
883cd94a5e | ||
|
|
1f7fca0c78 | ||
|
|
abe953f234 | ||
|
|
ba3eb7f27c | ||
|
|
20e236d429 | ||
|
|
b5059c13a3 | ||
|
|
0a84a8718e | ||
|
|
b1f5cae663 | ||
|
|
112b70dcad | ||
|
|
dca8eca669 | ||
|
|
e338711e19 | ||
|
|
1913e58758 | ||
|
|
947e5ed130 | ||
|
|
1b693d782b | ||
|
|
d63e1a4045 | ||
|
|
5aa31d8068 | ||
|
|
55519148f1 | ||
|
|
ca4a0eb0d3 | ||
|
|
b64b1cd654 | ||
|
|
81817fbfaf | ||
|
|
9a9d19e16c | ||
|
|
e500426f8f | ||
|
|
8f37c2f949 | ||
|
|
16ae0bd323 | ||
|
|
a21c3d640f | ||
|
|
f8644e7e39 | ||
|
|
50b0542e28 | ||
|
|
23cf984123 | ||
|
|
48654ca5db | ||
|
|
2b362659ef | ||
|
|
ce76f2d817 | ||
|
|
9a12184f9b | ||
|
|
bdbc394b4a | ||
|
|
60a04aa4a8 | ||
|
|
17f0d65b5c | ||
|
|
2dd7250616 | ||
|
|
fe8450d48a | ||
|
|
b9163af11a | ||
|
|
f9fdff54ac | ||
|
|
0c9b553591 | ||
|
|
18d57a41c4 | ||
|
|
914b19ea58 | ||
|
|
3a74fc6d4e | ||
|
|
1693d08d09 | ||
|
|
af2e68a97d | ||
|
|
b83010acdf | ||
|
|
5461071a87 | ||
|
|
d561d4f902 | ||
|
|
db5bcfb683 | ||
|
|
50a6b91a7e | ||
|
|
e8541f80ba | ||
|
|
9f7cdc34b4 | ||
|
|
ce7037d317 | ||
|
|
2b7ed69c21 | ||
|
|
9d5c5e0982 | ||
|
|
405e5aa03a | ||
|
|
68af686139 | ||
|
|
7f5261ad93 | ||
|
|
71a9b8f6ce | ||
|
|
efd25bc787 | ||
|
|
921ff4e5bf | ||
|
|
6e51b7f300 | ||
|
|
d9c036e359 | ||
|
|
3399ea73d0 | ||
|
|
2fca82d7c5 | ||
|
|
6ea3918a8b | ||
|
|
244c283556 | ||
|
|
44f3c76569 | ||
|
|
111b5a777f | ||
|
|
071ddfad0f | ||
|
|
8a83015118 | ||
|
|
1a5f472030 | ||
|
|
a8f26f8ba6 | ||
|
|
64f032a352 | ||
|
|
d3a50e7810 | ||
|
|
5f0b858baf | ||
|
|
7c4c03bbd4 | ||
|
|
79b5ca4db0 | ||
|
|
bf941b7ec4 | ||
|
|
aa5da2479c | ||
|
|
87d7be254a | ||
|
|
42e54d8a6e | ||
|
|
66f0bef271 | ||
|
|
a76e187c3a | ||
|
|
95431a7876 | ||
|
|
ed53234dc7 | ||
|
|
08e136bbca | ||
|
|
a610731897 | ||
|
|
3a41c7e57b | ||
|
|
8c1f408930 | ||
|
|
771d63b3ee | ||
|
|
b271105d1a | ||
|
|
6c173674f0 | ||
|
|
fedf8a3eda | ||
|
|
03170e3a9a | ||
|
|
16832c1a17 | ||
|
|
fcdd5e851b | ||
|
|
1eb17dd003 | ||
|
|
a7e82f906a | ||
|
|
150462354f | ||
|
|
1bc614abd2 | ||
|
|
3cf6246f4e | ||
|
|
9265080fcf | ||
|
|
094b75c4f7 | ||
|
|
a1e8da140e | ||
|
|
84864801aa | ||
|
|
30b511891e | ||
|
|
6a2c56da3e | ||
|
|
be8cd95ae4 | ||
|
|
d9562e6b33 | ||
|
|
84edd151bf | ||
|
|
e1456b72fb | ||
|
|
2307f36db0 | ||
|
|
9a8eae723f | ||
|
|
19f806e429 | ||
|
|
799d8330ba | ||
|
|
d1fb538e75 | ||
|
|
96e7913ae4 | ||
|
|
8a41730b63 | ||
|
|
5601261cbe | ||
|
|
ac123387fd | ||
|
|
e9b07dacce | ||
|
|
7fd0ef4ac6 | ||
|
|
f3e41f2ff4 | ||
|
|
49ac519a5e | ||
|
|
a3c0decaf0 | ||
|
|
d54e222ff2 | ||
|
|
0c2ff1a000 | ||
|
|
660851e81c | ||
|
|
e8e873f0da | ||
|
|
8b4244d237 | ||
|
|
472ece692d | ||
|
|
d5d9eb93a3 | ||
|
|
81e4220367 | ||
|
|
0c7adaf67a | ||
|
|
08a6be9499 | ||
|
|
63bb145224 | ||
|
|
b60b572494 | ||
|
|
c9e7fc14a1 | ||
|
|
1d46231f4d | ||
|
|
14827d8f25 | ||
|
|
4dee17983a | ||
|
|
ca4f45ce0e | ||
|
|
008908bf92 | ||
|
|
0f0da2ad27 | ||
|
|
2ebb963342 | ||
|
|
1b33f2fec7 | ||
|
|
8621ac16ce | ||
|
|
a0e84479f2 | ||
|
|
daa88a2cc2 | ||
|
|
1d5cf81d9e | ||
|
|
f578ac9e38 | ||
|
|
51b0aa2ed5 | ||
|
|
98889aa086 | ||
|
|
296a77df8a | ||
|
|
7c88fbdfeb | ||
|
|
2c99634c6f | ||
|
|
948cb55f89 | ||
|
|
4099d21d2c | ||
|
|
16ff7380a5 | ||
|
|
7109eb0238 | ||
|
|
fff929d2b8 | ||
|
|
ec2d9b1ca5 | ||
|
|
6f7f29118f | ||
|
|
d96e9f4daa | ||
|
|
b7ac923d36 | ||
|
|
88362cb1f9 | ||
|
|
92966ed516 | ||
|
|
e3877a11a6 | ||
|
|
eabbccb980 | ||
|
|
92314495d2 | ||
|
|
fd22cd3265 | ||
|
|
ffafe82e43 | ||
|
|
99453550b9 | ||
|
|
b7686df7e6 | ||
|
|
b2cbba08f2 | ||
|
|
01e2fa4694 | ||
|
|
e45804174c | ||
|
|
31ea53cc15 | ||
|
|
acfbcb41f1 | ||
|
|
844b2afaca | ||
|
|
9df56f9556 | ||
|
|
dcf5d3e910 | ||
|
|
78f197e7f9 | ||
|
|
c35454f4e1 | ||
|
|
72c991b62b | ||
|
|
8d89ed6f35 | ||
|
|
c21cb25eac | ||
|
|
10936bcdb5 | ||
|
|
3b346046ae | ||
|
|
98541007c4 | ||
|
|
786c059732 | ||
|
|
653ae3ec39 | ||
|
|
fa22554824 | ||
|
|
a9fc93882c | ||
|
|
907dc3c088 | ||
|
|
d1afd54ea1 | ||
|
|
c3abcf0469 | ||
|
|
3249501950 | ||
|
|
271d2c97fa | ||
|
|
283c03bb9b | ||
|
|
e84961f20f | ||
|
|
d89377a8fe | ||
|
|
4b5aa7fa2a | ||
|
|
5c6eb18d74 | ||
|
|
0c8a682fd0 | ||
|
|
f0d0ee69c8 | ||
|
|
e18fcd5c43 | ||
|
|
27558c10cd | ||
|
|
98b6466dd5 | ||
|
|
090ce1cf18 | ||
|
|
4ff23148e6 | ||
|
|
111c236fd0 | ||
|
|
c94c4fbbdd | ||
|
|
4efbc7db25 | ||
|
|
154b145e33 | ||
|
|
b2652e98fc | ||
|
|
1d90451b4e | ||
|
|
4f67329154 | ||
|
|
01266ef4c4 | ||
|
|
d5998efd91 | ||
|
|
f917b39432 | ||
|
|
95f9435ed1 | ||
|
|
6f4945ac05 | ||
|
|
6532382f43 | ||
|
|
ab7c4ea73d | ||
|
|
df7a4c8b8f | ||
|
|
2189337a1d | ||
|
|
3416f41116 | ||
|
|
945e8607b6 | ||
|
|
3e149ed3c2 | ||
|
|
8b406c594b | ||
|
|
b2bb2ddb1f | ||
|
|
6937bbace8 | ||
|
|
e3cdb20579 | ||
|
|
2c7f4837d5 | ||
|
|
3dc8269406 | ||
|
|
0077925fc0 | ||
|
|
529b8b682f | ||
|
|
1b23b4621a | ||
|
|
51d878469f | ||
|
|
8bbc1e8e4e | ||
|
|
93996d7378 | ||
|
|
9aac2c1549 | ||
|
|
3cd4c23ef7 | ||
|
|
675987d1f2 | ||
|
|
6f255189bd | ||
|
|
6bffc040b3 | ||
|
|
1513ebfe99 | ||
|
|
ea4bf4f87a | ||
|
|
68ec8fb905 | ||
|
|
36bfaf3b94 | ||
|
|
4b90086e5a | ||
|
|
f4a7b8e2bb | ||
|
|
42031982a3 | ||
|
|
4ef5deac69 | ||
|
|
a3725ca164 | ||
|
|
c4e360a64d | ||
|
|
764d252632 | ||
|
|
4adaebb461 | ||
|
|
ac6d4cd522 | ||
|
|
ca3bb6b8f2 | ||
|
|
a669e2b71b | ||
|
|
27f3591fb5 | ||
|
|
5627baa1cc | ||
|
|
19bcc8f3b6 | ||
|
|
53dbfaffc7 | ||
|
|
0c1ec83796 | ||
|
|
19fa164c75 | ||
|
|
1c090c1e5e | ||
|
|
fc4c44bbea | ||
|
|
453230d774 | ||
|
|
3fcb4ee61e | ||
|
|
f45ebb5dd2 | ||
|
|
39cca6ec99 | ||
|
|
65cbd108de | ||
|
|
038814400d | ||
|
|
9cf3503a84 | ||
|
|
3a3ecd920f | ||
|
|
bebeb49ae4 | ||
|
|
a925102881 | ||
|
|
b0d83f7858 | ||
|
|
6e5dea9cc7 | ||
|
|
7b9b59008d | ||
|
|
0f6c73699f | ||
|
|
c7ff189b96 | ||
|
|
8a6c956940 | ||
|
|
aad98623e8 | ||
|
|
a280c15c05 | ||
|
|
9f7151820f | ||
|
|
e5ee52ac7e | ||
|
|
471708cedf | ||
|
|
983b2e43e1 | ||
|
|
535648f918 | ||
|
|
3a69e1412d | ||
|
|
c323dab2d4 | ||
|
|
de5cbe994d | ||
|
|
65dedcd9ff | ||
|
|
8dd717f2c9 | ||
|
|
bc4b73cd2e | ||
|
|
82d4077e4d | ||
|
|
20dd624c77 | ||
|
|
151dfce5d9 | ||
|
|
69ced1a02f | ||
|
|
e6aef83bbe | ||
|
|
15d14fc24e | ||
|
|
e198670132 | ||
|
|
5c25bdfa91 | ||
|
|
ab24ca5b4f | ||
|
|
4a7cb9abc1 | ||
|
|
a63b867a5a | ||
|
|
4d5c5b052d | ||
|
|
f4d9949ce9 | ||
|
|
66c8a4db23 | ||
|
|
542e3b9a49 | ||
|
|
bbd19045bc | ||
|
|
0cc1469589 | ||
|
|
0e71efd714 | ||
|
|
623c32113d | ||
|
|
087b48f252 | ||
|
|
cd56e3ec6f | ||
|
|
c1275bac56 | ||
|
|
558d004e35 | ||
|
|
f963a00a05 | ||
|
|
1db5d3a3e0 | ||
|
|
c1381d5f67 | ||
|
|
374d3b85cf | ||
|
|
963474f32e | ||
|
|
c2d852eb3a | ||
|
|
df3b3bed98 | ||
|
|
8cdfe01afa | ||
|
|
f49b157d13 | ||
|
|
865ac80b99 | ||
|
|
c817e5815f | ||
|
|
cbadf25623 | ||
|
|
70ec88e57d | ||
|
|
21b6479dbe | ||
|
|
c1be894d35 | ||
|
|
99fc12e8c4 | ||
|
|
9ca6cb4fe4 | ||
|
|
05b05883fd | ||
|
|
c2d05fb846 | ||
|
|
57f4082cad | ||
|
|
a0174cdedd | ||
|
|
4e8053470d | ||
|
|
96a65866db | ||
|
|
b0cf3ef560 | ||
|
|
eacc3cc6ef | ||
|
|
efcaabbbd8 | ||
|
|
b556c3ba6c | ||
|
|
d02040e623 | ||
|
|
a5abc6d6f7 | ||
|
|
ed57411bbb | ||
|
|
ae32b3c525 | ||
|
|
31ef3a47e0 | ||
|
|
1ac069aba4 | ||
|
|
1ca397d19b | ||
|
|
bb4b09134f | ||
|
|
78ab3c591a | ||
|
|
b5302758da | ||
|
|
b6614b0974 | ||
|
|
6112d6950a | ||
|
|
440aa64fae | ||
|
|
9f8350da3f | ||
|
|
d50d32147b | ||
|
|
ec9544db0a | ||
|
|
798d62c3e2 | ||
|
|
ab11badc84 | ||
|
|
56867f942f | ||
|
|
19353d3846 | ||
|
|
1adb0c55c8 | ||
|
|
8ce7d3689f | ||
|
|
acd7193e55 | ||
|
|
7e74bfe7db | ||
|
|
ce162bf414 | ||
|
|
0d74e8f04b | ||
|
|
dd447de467 | ||
|
|
02e99db26e | ||
|
|
8e36b48476 | ||
|
|
38c4e611c3 | ||
|
|
10c68a7dc5 | ||
|
|
d164eb45d5 | ||
|
|
7ab858088b | ||
|
|
7ab20707c3 | ||
|
|
3b36d2039c | ||
|
|
dd2e921667 | ||
|
|
7d772542ee | ||
|
|
ef85282777 | ||
|
|
a4b719641b | ||
|
|
dcede463d7 | ||
|
|
8398248cb8 | ||
|
|
c421fc752b | ||
|
|
4bef2f5287 | ||
|
|
2693cf17ea | ||
|
|
c7b76e1009 | ||
|
|
6c81f828bd | ||
|
|
a5b0a4f752 | ||
|
|
ec73c3c785 | ||
|
|
e4b38e4121 | ||
|
|
9ae8b8dabb | ||
|
|
46b79a20e3 | ||
|
|
8508432297 | ||
|
|
757606ecc5 | ||
|
|
081aadf468 | ||
|
|
6c760f159b | ||
|
|
b2b2efc9a8 | ||
|
|
2347d7c7b7 | ||
|
|
03feb01af4 | ||
|
|
eb6fa0cf63 | ||
|
|
8c1cea6e6e | ||
|
|
473eaa040f | ||
|
|
b47b3b4b3a | ||
|
|
c185c8c22c | ||
|
|
86c19ce355 | ||
|
|
17e8dbb4d9 | ||
|
|
90fd2deba0 | ||
|
|
6b00aad904 | ||
|
|
d013ab1e3c | ||
|
|
9d14123a66 | ||
|
|
49744a3671 | ||
|
|
b73c931b8b | ||
|
|
f474f111dc | ||
|
|
c2fec1953b | ||
|
|
f7061d1a8d | ||
|
|
67e2ec4093 | ||
|
|
5f50fc7b3b | ||
|
|
3fa2814531 | ||
|
|
4c6b58cbd5 | ||
|
|
444afc30e4 | ||
|
|
51763d7857 | ||
|
|
108c82b353 | ||
|
|
ec1226a8ce | ||
|
|
127aaa30f4 | ||
|
|
2462d457a7 | ||
|
|
73ecfc50aa | ||
|
|
95c11f0504 | ||
|
|
3055eccb82 | ||
|
|
aedae961ff | ||
|
|
d38032bb2b | ||
|
|
bfd2917043 | ||
|
|
b4085fd1d3 | ||
|
|
40e258928e | ||
|
|
4b13e98d43 | ||
|
|
cfd3996af1 | ||
|
|
d565501e04 | ||
|
|
c7dafa6199 | ||
|
|
1c3de2e889 | ||
|
|
fa1c917b97 | ||
|
|
29ba6713f0 | ||
|
|
10041b1a30 | ||
|
|
3fe6ab0e79 | ||
|
|
0a99ecdd9b | ||
|
|
ed9b93c22f | ||
|
|
c8e518b4e6 | ||
|
|
8f42e2924d | ||
|
|
d23bff5a48 | ||
|
|
1a1c03b355 | ||
|
|
b20506cfa0 | ||
|
|
0835e5b069 | ||
|
|
26d0d07850 | ||
|
|
b24eca0c5b | ||
|
|
aba04e63a1 | ||
|
|
999f22d8ff | ||
|
|
438d0344eb | ||
|
|
c99edc20a2 | ||
|
|
132dd8134a | ||
|
|
16ac8d6b06 | ||
|
|
1a23fff2ce | ||
|
|
774353c5fe | ||
|
|
b6598a2b40 | ||
|
|
0273fe76ca | ||
|
|
e0ec15653c | ||
|
|
ed9c4f1413 | ||
|
|
cda9be878b | ||
|
|
64d34deb6b | ||
|
|
1ea5bfa807 | ||
|
|
5b9f85f442 | ||
|
|
8089085bb4 | ||
|
|
18b8ba5c5f | ||
|
|
d80943ab6b | ||
|
|
77ebb225c4 | ||
|
|
28850338dd | ||
|
|
1daba51286 | ||
|
|
2a2033ba21 | ||
|
|
367d7a0ae8 | ||
|
|
1ef33700ee | ||
|
|
0bf98ec9f8 | ||
|
|
fcbbd2b64c | ||
|
|
4844cc1ea1 | ||
|
|
0f076b4a16 | ||
|
|
8e9cd253e4 | ||
|
|
43a4e5741a | ||
|
|
4561efc3ce | ||
|
|
d0d6965c52 | ||
|
|
07d395e7b2 | ||
|
|
f61ad6ca5a | ||
|
|
82010e7225 | ||
|
|
36ea882869 | ||
|
|
a6a0133d37 | ||
|
|
bd6f6a7106 | ||
|
|
c3f7ea8ac7 | ||
|
|
c0f7d042c6 | ||
|
|
c3588b4ac3 | ||
|
|
13049f63c1 | ||
|
|
b26cea1aa3 | ||
|
|
7fb6135680 | ||
|
|
e60dd0a93e | ||
|
|
7c11e32b71 | ||
|
|
49a361bf66 | ||
|
|
e4e4224716 | ||
|
|
1542720bc4 | ||
|
|
a5d0cae884 | ||
|
|
6e03415847 | ||
|
|
f6b7eca0f5 | ||
|
|
82fad2c17d | ||
|
|
c13f8bbcc0 | ||
|
|
97d041076d | ||
|
|
d9696d2398 | ||
|
|
883e5c26d9 | ||
|
|
6412efd031 | ||
|
|
ef7400e9d2 | ||
|
|
c76e8d2677 | ||
|
|
e97f2271fa | ||
|
|
488259d442 | ||
|
|
5bbbbb0b02 | ||
|
|
6e186e9186 | ||
|
|
8ca6b53224 | ||
|
|
2af899cfea | ||
|
|
72a38f4b45 | ||
|
|
f24a8230c1 | ||
|
|
5651713032 | ||
|
|
3829ec1308 | ||
|
|
d085da07cd | ||
|
|
937d996458 | ||
|
|
6a58f07a91 | ||
|
|
2366e4df17 | ||
|
|
ed937f5f31 | ||
|
|
ca65741392 | ||
|
|
ef2b1dd321 | ||
|
|
8eb956f831 | ||
|
|
c3cccbb2c8 | ||
|
|
e861e8d878 | ||
|
|
1fb70a4456 | ||
|
|
277869c9d1 | ||
|
|
a13da976ae | ||
|
|
0f6886656e | ||
|
|
63cd4649dd | ||
|
|
673038bf63 | ||
|
|
625bb97c35 | ||
|
|
1f71cc827c | ||
|
|
50057894e8 | ||
|
|
824399bbf1 | ||
|
|
fb74ef4843 | ||
|
|
e5cbeb032e | ||
|
|
2cf68e96c3 | ||
|
|
92ea7eaab7 | ||
|
|
8ca22fc209 | ||
|
|
e80a38fa78 | ||
|
|
9c861ec58c | ||
|
|
6b9c20bf93 | ||
|
|
6cf9c1f4f2 | ||
|
|
b73e1c27eb | ||
|
|
69fb875421 | ||
|
|
aa7d93e97e | ||
|
|
bf136c9b2f | ||
|
|
256143fd21 | ||
|
|
26628b1c89 | ||
|
|
08883ce558 | ||
|
|
a9950a4365 | ||
|
|
42bc25381c | ||
|
|
830552c121 | ||
|
|
2728d3c093 | ||
|
|
81b07ea6e8 | ||
|
|
27441eb82e | ||
|
|
49f8e23aae | ||
|
|
a368e00b2f | ||
|
|
500ed3a4d7 | ||
|
|
83765c9d2d | ||
|
|
4c6a20601c | ||
|
|
d6b4cd78d0 | ||
|
|
460ebeb8c5 | ||
|
|
513bc28686 | ||
|
|
ef6b4e8149 | ||
|
|
644f415d46 | ||
|
|
575e569048 | ||
|
|
1a31939eaa | ||
|
|
d3dcce96fa | ||
|
|
8669acc663 | ||
|
|
a9c37cc5a6 | ||
|
|
d1f22ead93 | ||
|
|
305e67c454 | ||
|
|
86ceae9a1e | ||
|
|
f1dd5249ae | ||
|
|
47f3dadfd4 | ||
|
|
66b7a00b5c | ||
|
|
13ca1b18d6 | ||
|
|
688fcd3c77 | ||
|
|
58856c6620 | ||
|
|
17ce620abf | ||
|
|
5bb80b5f50 | ||
|
|
625908dc1b | ||
|
|
ad2b456f07 | ||
|
|
c6402c72f9 | ||
|
|
f34133f63e | ||
|
|
dfb6f23b60 | ||
|
|
0d158d2403 | ||
|
|
7a63f6d68a | ||
|
|
16bbab366e | ||
|
|
e63e025bda | ||
|
|
d4d3f1245b | ||
|
|
707c74264b | ||
|
|
23b91d37ab | ||
|
|
adf9225180 | ||
|
|
d386b4e7ac | ||
|
|
f13472f088 | ||
|
|
5ec56b4a9e | ||
|
|
567a77b0be | ||
|
|
a39802ea48 | ||
|
|
045d585df4 | ||
|
|
d0bc85346e | ||
|
|
dee12941c0 | ||
|
|
65018aab5b | ||
|
|
77761d9ec4 | ||
|
|
68eace11de | ||
|
|
db4678770d | ||
|
|
a798c61ded | ||
|
|
822bc3ea69 | ||
|
|
3aefe99bd9 | ||
|
|
bebef77e5c | ||
|
|
b5ae9c6a8a | ||
|
|
5f98cc93c7 | ||
|
|
c1d27399d9 | ||
|
|
25f244c5f1 | ||
|
|
561e205481 | ||
|
|
9dd22dba19 | ||
|
|
43b2b4dc3d | ||
|
|
ce5948d87a | ||
|
|
3a0d967800 | ||
|
|
093de52230 | ||
|
|
f54f86f2b5 | ||
|
|
d167a91a07 | ||
|
|
4384a2dd91 | ||
|
|
573a5c03e4 | ||
|
|
7c885cad5b | ||
|
|
ba65c21a0b | ||
|
|
073b1937f0 | ||
|
|
793f6152ff | ||
|
|
cf0a431fff | ||
|
|
ba2d81da50 | ||
|
|
c278806548 | ||
|
|
21ad0632b4 | ||
|
|
8365f69de6 | ||
|
|
db28811027 | ||
|
|
a69a4befc5 | ||
|
|
d7cc0064b2 | ||
|
|
b3c227d3cb | ||
|
|
09fd8aa1b0 | ||
|
|
58e8b7b0a0 | ||
|
|
9ea250278d | ||
|
|
17c9a8b3fa | ||
|
|
9ff2f24529 | ||
|
|
0ab321f184 | ||
|
|
500e666416 | ||
|
|
88316fda1c | ||
|
|
3bd2e40a98 | ||
|
|
d5c8b33678 | ||
|
|
f3e9ac2b56 | ||
|
|
3cc6a3317e | ||
|
|
3728b1d3a0 | ||
|
|
4c163a8092 | ||
|
|
bacbf2e3cb | ||
|
|
f211fe0ae4 | ||
|
|
e67dc0a637 | ||
|
|
3f959918af | ||
|
|
d9cd7a93cc | ||
|
|
4fcdeb0313 | ||
|
|
8839fb0738 | ||
|
|
26de1b1745 | ||
|
|
b1700ca4e6 | ||
|
|
f7f668a032 | ||
|
|
d8d707175c | ||
|
|
de45332543 | ||
|
|
dbba2a1b8f | ||
|
|
8b394819c4 | ||
|
|
74b1f65e0f | ||
|
|
8110930458 | ||
|
|
02b2e8cffb | ||
|
|
643ccc4880 | ||
|
|
4deed559c9 | ||
|
|
be3fcb55fb | ||
|
|
11308829a6 | ||
|
|
e2b2da8b46 | ||
|
|
b4bd6d17bf | ||
|
|
08f3925af4 | ||
|
|
c739448e3c | ||
|
|
7a3f817469 | ||
|
|
9d522d3c3e | ||
|
|
c46567782e | ||
|
|
e071209bb8 | ||
|
|
2efedf225f | ||
|
|
5a08c3dec4 | ||
|
|
734abe59ee | ||
|
|
6759e2b30c | ||
|
|
324d7ea487 | ||
|
|
4fd07e4c6e | ||
|
|
afea981a42 | ||
|
|
df9eaf6e0a | ||
|
|
caa0fc0a8a | ||
|
|
6bd8c590ab | ||
|
|
219fbc7fb5 | ||
|
|
cbaa5c3464 | ||
|
|
76e66ab433 | ||
|
|
0150b41e17 | ||
|
|
2dbdc3b780 | ||
|
|
e7cb48f0b1 | ||
|
|
cae7184a7e | ||
|
|
5ba63d951c | ||
|
|
b758133f63 | ||
|
|
4024aca002 | ||
|
|
7826cc8f68 | ||
|
|
bf9ecb78f8 | ||
|
|
8783b85b00 | ||
|
|
d72e9f678f | ||
|
|
fec3dcc65f | ||
|
|
300798981d | ||
|
|
58b5843dd9 | ||
|
|
6b97696593 | ||
|
|
18e41eba4d | ||
|
|
34c6addde3 | ||
|
|
5655c5440f | ||
|
|
dd25561338 | ||
|
|
eaabc5852c | ||
|
|
dbb1033911 | ||
|
|
7c287f6ae5 | ||
|
|
b9ddc0cf1f | ||
|
|
9a9ff64576 | ||
|
|
02f75726c5 | ||
|
|
b4c1d05d2e | ||
|
|
0035789951 | ||
|
|
a11eb11166 | ||
|
|
020be75f7b | ||
|
|
66961197f6 | ||
|
|
f05914f2a1 | ||
|
|
e161912496 | ||
|
|
abad2c2cc9 | ||
|
|
efac5401bf | ||
|
|
e463a844ff | ||
|
|
ddb7d1a6db | ||
|
|
c4faa0a316 | ||
|
|
eca066cbd0 | ||
|
|
e0bd225ac2 | ||
|
|
a5571ba1d4 | ||
|
|
a87f2261ce | ||
|
|
33db978e36 | ||
|
|
4c068f4a62 | ||
|
|
2e7f2bc316 | ||
|
|
54ebb327bd | ||
|
|
6875b1b8af | ||
|
|
a32faf1a99 | ||
|
|
94b3014051 | ||
|
|
c63fdb04c2 | ||
|
|
07a4196ed5 | ||
|
|
7f5eb59ac5 | ||
|
|
c9de0c2eba | ||
|
|
242932735a | ||
|
|
d0b2daa56b | ||
|
|
4bc05e258d | ||
|
|
902c0262f2 | ||
|
|
917f91249e | ||
|
|
d75ee02b4a | ||
|
|
c08e217fb8 | ||
|
|
244a2031a7 | ||
|
|
62b8b623ec | ||
|
|
c5c9e19f76 | ||
|
|
2f76e932b0 | ||
|
|
2c373c388c | ||
|
|
34a1c97e6b | ||
|
|
113c58a057 | ||
|
|
ce88fe426b | ||
|
|
ab24af6e79 | ||
|
|
b9b45e8e4f | ||
|
|
5c750f7a6f | ||
|
|
c2b754d682 | ||
|
|
2f649c0a9e | ||
|
|
d456ea68f7 | ||
|
|
7fa2c0d6e7 | ||
|
|
dace5b92c7 | ||
|
|
8be22e794d | ||
|
|
90c23a3fd1 | ||
|
|
2bd3ba189e | ||
|
|
8a49eb742e | ||
|
|
8358a10ce8 | ||
|
|
4ecb1995b8 | ||
|
|
ddbef0172f | ||
|
|
7a9ad7163b | ||
|
|
50f5b7fcf7 | ||
|
|
a494709177 | ||
|
|
7c40441f41 | ||
|
|
d735dbc72c | ||
|
|
3042ed2628 | ||
|
|
8a60916776 | ||
|
|
2ac7d0bc01 | ||
|
|
b8234adbc2 | ||
|
|
6eeeb733e2 | ||
|
|
d900355bfd | ||
|
|
e71c13e9ac | ||
|
|
4c2bf56cea | ||
|
|
47c815d966 | ||
|
|
6c7c17cd61 | ||
|
|
b855312cf9 | ||
|
|
b4c8a45bbf | ||
|
|
b79daedb94 | ||
|
|
8b4b5382b5 | ||
|
|
c08240db42 | ||
|
|
a2dcd69efd | ||
|
|
e15f7f6377 | ||
|
|
e6aa7de922 | ||
|
|
856f554a41 | ||
|
|
2e153a658f | ||
|
|
34e4d9f68c | ||
|
|
49082bc200 | ||
|
|
a10e9053f9 | ||
|
|
777b329181 | ||
|
|
fe3f6d2101 | ||
|
|
a1b6dc9e84 | ||
|
|
b4453cacc0 | ||
|
|
4cda99a704 | ||
|
|
f2a618d0c9 | ||
|
|
dbee06179c | ||
|
|
2b8e642aad | ||
|
|
b42721a2a3 | ||
|
|
e43123b9b4 | ||
|
|
357f0e4df4 | ||
|
|
bf59fae173 | ||
|
|
0f8cfa72b6 | ||
|
|
24ed87cc82 | ||
|
|
d47a514b09 | ||
|
|
1f8198b36b | ||
|
|
022499300e | ||
|
|
db077c8c9f | ||
|
|
e5aa387ef4 | ||
|
|
04ead7177a | ||
|
|
13501eb066 | ||
|
|
13a36fc471 | ||
|
|
afff590fa5 | ||
|
|
dea04429de | ||
|
|
cb2ad20e36 | ||
|
|
72f7cc927c | ||
|
|
9c3eeb59c0 | ||
|
|
77f6381eff | ||
|
|
7c1834107e | ||
|
|
6132c3fa02 | ||
|
|
9470ebb1ae | ||
|
|
062f0303b4 | ||
|
|
c0ca50edf6 | ||
|
|
461546e914 | ||
|
|
ba95f33592 | ||
|
|
cc1d535311 | ||
|
|
4853d5227b | ||
|
|
e750d2ae80 | ||
|
|
178b511a24 | ||
|
|
2dc5790a46 | ||
|
|
38b6a74e3d | ||
|
|
fb3bf48bbc | ||
|
|
c6ab79db30 | ||
|
|
06951bbc29 | ||
|
|
939da1278f | ||
|
|
75bc7f2a77 | ||
|
|
82d6f0b533 | ||
|
|
834bb28100 | ||
|
|
d4f3124054 | ||
|
|
85f95ff89c | ||
|
|
ca74ba1c15 | ||
|
|
44ff21ffcd | ||
|
|
4dd8658aff | ||
|
|
31b490916d | ||
|
|
66a5428ec5 | ||
|
|
965895de5c | ||
|
|
ac45ba633b | ||
|
|
173f5c84db | ||
|
|
1d8b4ba83f | ||
|
|
3ce2ec0a46 | ||
|
|
f9c01f0a7d | ||
|
|
ca860d551e | ||
|
|
675edef0d1 | ||
|
|
3f3e4a94ff | ||
|
|
6732cca479 | ||
|
|
dc8e5a370f | ||
|
|
0476bdea13 | ||
|
|
0911bc2dc9 | ||
|
|
c5a0e04242 | ||
|
|
844cdd734e | ||
|
|
fc5ce7c5db | ||
|
|
85a9e33de1 | ||
|
|
403462b204 | ||
|
|
12dda85ca5 | ||
|
|
e1ea5db49d | ||
|
|
edd8988961 | ||
|
|
a6425c2859 | ||
|
|
52464e232b | ||
|
|
d015118199 | ||
|
|
95ff6940f2 | ||
|
|
bd3b9315b2 | ||
|
|
46555fcfa5 | ||
|
|
883b13e0f3 | ||
|
|
82b2ef3a73 | ||
|
|
3331370d14 | ||
|
|
c53d7a5dd2 | ||
|
|
464d7bc635 | ||
|
|
4fd2a44742 | ||
|
|
2d805b775c | ||
|
|
7636420b3c | ||
|
|
fd52db7f71 | ||
|
|
9c72558878 | ||
|
|
98f4cf3644 | ||
|
|
2e6eb81648 | ||
|
|
972c01bd67 | ||
|
|
2c99033f1a | ||
|
|
ebf9686a05 | ||
|
|
b469069e92 | ||
|
|
7033f00a0e | ||
|
|
09ed329967 | ||
|
|
c23234964f | ||
|
|
df1d5d97c4 | ||
|
|
2ab12f74ee | ||
|
|
6a2f1f80f5 | ||
|
|
d0d6b8ce97 | ||
|
|
b749426389 | ||
|
|
a33d095296 | ||
|
|
44732f5849 | ||
|
|
a6150821e7 | ||
|
|
4f5dce282b | ||
|
|
68a7b55320 | ||
|
|
40ff85304b | ||
|
|
02de3a1680 | ||
|
|
7a8fcad27c | ||
|
|
35daeb443c | ||
|
|
22993782ed | ||
|
|
4c2b88a13f | ||
|
|
7a168977da | ||
|
|
2e03cee726 | ||
|
|
74b1194398 | ||
|
|
88163499fe | ||
|
|
c0ead0b891 | ||
|
|
6b6cb7473e | ||
|
|
e0f41f9330 | ||
|
|
95dcfc961f | ||
|
|
0178f4b8b5 | ||
|
|
85c4eae2a8 | ||
|
|
168e5cea45 | ||
|
|
0f5e393340 | ||
|
|
41faf72c37 | ||
|
|
6cf2279d21 | ||
|
|
6347454098 | ||
|
|
3f17c0689c | ||
|
|
e4524ff2c5 | ||
|
|
94e4054cd4 | ||
|
|
a4a7939eae | ||
|
|
63fa9eb9e4 | ||
|
|
9e449fa4ee | ||
|
|
ca7cd2372a | ||
|
|
f4502a5e9d | ||
|
|
040f1e8c86 | ||
|
|
a9c10a3e0b | ||
|
|
8522b4610d | ||
|
|
f9d7d4faec | ||
|
|
bc4477cd86 | ||
|
|
4d6fb6f2ca | ||
|
|
dafa00c895 | ||
|
|
e8dc71c71f | ||
|
|
3755402f37 | ||
|
|
8d876c60b2 | ||
|
|
157e89b5ea | ||
|
|
5fcc4c730f | ||
|
|
6e8ee60df0 | ||
|
|
6610a948c5 | ||
|
|
5ff1f57017 | ||
|
|
8a870a361d | ||
|
|
72e0f9b798 | ||
|
|
04bc5d7d17 | ||
|
|
3e19f3991c | ||
|
|
5e324c69b6 | ||
|
|
bba34ef55e | ||
|
|
99f9431794 | ||
|
|
279f05fd11 | ||
|
|
8f58797256 | ||
|
|
6dbbcff0b6 | ||
|
|
37578cf4a0 | ||
|
|
a13bc3f0bd | ||
|
|
1d4602db0b | ||
|
|
ece17b2aee | ||
|
|
117fd62c44 | ||
|
|
4da73dd144 | ||
|
|
05df135229 | ||
|
|
8e2a649d57 | ||
|
|
d2428e43b2 | ||
|
|
d03a6f7b00 | ||
|
|
c1b218c133 | ||
|
|
b3286c8f74 | ||
|
|
44b46c396b | ||
|
|
042fb28ccc | ||
|
|
2206e0f1e6 | ||
|
|
632f68660b | ||
|
|
43531d9b8d | ||
|
|
d28fb9a27a | ||
|
|
c0fadde527 | ||
|
|
663661c73c | ||
|
|
4304b66b06 | ||
|
|
99b789bc11 | ||
|
|
4bbd20b8c0 | ||
|
|
43c77726bb | ||
|
|
3ceb183d8b | ||
|
|
46787b187f | ||
|
|
7b8a6c2a4c | ||
|
|
7b757d68b4 | ||
|
|
092506e9ab | ||
|
|
529b4bec31 | ||
|
|
2e88e933c3 | ||
|
|
031dac36e6 | ||
|
|
dc3b666635 | ||
|
|
e45bcf34a3 | ||
|
|
0aa7679a71 | ||
|
|
a46ac38561 | ||
|
|
b1395377a1 | ||
|
|
41e60ee990 | ||
|
|
00f80e8db8 | ||
|
|
f70da2c4a2 | ||
|
|
ee485020d1 | ||
|
|
8cdbb969ff | ||
|
|
687b9ca8b1 | ||
|
|
f486477a41 | ||
|
|
6f7acc60b5 | ||
|
|
c748d8f5f6 | ||
|
|
5d1d108811 | ||
|
|
d8d49d6b5a | ||
|
|
ebb3ec6784 | ||
|
|
ab66b70485 | ||
|
|
a723c2a44a | ||
|
|
d0c7ee5e47 | ||
|
|
3c386eeaa9 | ||
|
|
e5c4faf6ef | ||
|
|
0274a7f6e6 | ||
|
|
92632cf294 | ||
|
|
864af174f7 | ||
|
|
b1cbf6f893 | ||
|
|
0b0e6951e5 | ||
|
|
52392e2e0a | ||
|
|
493d05f807 | ||
|
|
4ae7f22293 | ||
|
|
204bdcd2a9 | ||
|
|
c185b4c9c2 | ||
|
|
7d48f9bde5 | ||
|
|
a48a1210c8 | ||
|
|
6b2b072e16 | ||
|
|
8acf3b21c7 | ||
|
|
0ad32ff63d | ||
|
|
4c595affa5 | ||
|
|
4f267c6d89 | ||
|
|
fae46bc2b9 | ||
|
|
81f8a01cc1 | ||
|
|
a6be0dc079 | ||
|
|
0e4017f69a | ||
|
|
505f3b8b05 | ||
|
|
65f53e264c | ||
|
|
7c5e9f9b75 | ||
|
|
87933c821f | ||
|
|
b590bce3e1 | ||
|
|
cb3174f7d8 | ||
|
|
7b3d9d9402 | ||
|
|
5b66d32f25 | ||
|
|
2686bfbf5e | ||
|
|
9553e43fa8 | ||
|
|
b5a46ca63a | ||
|
|
7435fa69f2 | ||
|
|
ea7d889d99 | ||
|
|
b4bd635dbb | ||
|
|
a066fc7eae | ||
|
|
11ae7b091a | ||
|
|
8cbc3a9667 | ||
|
|
f5b663b900 | ||
|
|
572415d089 | ||
|
|
8893254664 | ||
|
|
d5ac6fc0c7 | ||
|
|
64e53b3dd0 | ||
|
|
1de42f5c72 | ||
|
|
5c3d8727e0 | ||
|
|
c6e693392c | ||
|
|
522ca02b36 | ||
|
|
8c05d8791d | ||
|
|
8df3611e7d | ||
|
|
428d2eb795 | ||
|
|
74c20fc57e | ||
|
|
6ca13bf263 | ||
|
|
fcd6b1ddec | ||
|
|
ac1e7bc186 | ||
|
|
8a0f53aaaf | ||
|
|
473dbb8361 | ||
|
|
7aa3989b11 | ||
|
|
04d121367a | ||
|
|
7f79795c55 | ||
|
|
f76016860f | ||
|
|
6cd3449571 | ||
|
|
c480be4e1d | ||
|
|
e3583df260 | ||
|
|
fd40ae6b67 | ||
|
|
003ddb4016 | ||
|
|
be755a6f4e | ||
|
|
91c62ee55e | ||
|
|
17dc434350 | ||
|
|
ad46fd1aae | ||
|
|
9ffa46a878 | ||
|
|
b1ee3fe3cd | ||
|
|
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 | ||
|
|
f0ce6cc28f | ||
|
|
ede298a142 | ||
|
|
e63c5c074c | ||
|
|
0d0b5dd552 | ||
|
|
d48ed18102 | ||
|
|
320f183b49 | ||
|
|
c8f11578d6 | ||
|
|
f33be18be3 | ||
|
|
513c7dbd85 | ||
|
|
02a9623310 | ||
|
|
58c1d52f52 | ||
|
|
b71c881b43 | ||
|
|
ffc2c7dea3 | ||
|
|
d7652a7a32 |
@@ -1,13 +1,14 @@
|
||||
APP_NAME=Dootask
|
||||
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,6 +54,9 @@ 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}"
|
||||
|
||||
|
||||
85
.github/workflows/electron-generic.yml
vendored
Normal file
85
.github/workflows/electron-generic.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Build Generic
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build-mac:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
id: [ "com.dootask.task", "com.hitosea.task" ]
|
||||
runs-on: macos-latest
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Create changelog text
|
||||
id: changelog
|
||||
uses: loopwerk/tag-changelog@v1
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
exclude_types: other,chore,build
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Build for MacOS
|
||||
env:
|
||||
APPID: ${{ matrix.id }}
|
||||
PROVIDER: "generic"
|
||||
APPLEID: ${{ secrets.APPLEID }}
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
RELEASE_BODY: ${{ steps.changelog.outputs.changes }}
|
||||
run: ./cmd electron build-mac
|
||||
|
||||
build-win:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
id: [ "com.dootask.task", "com.hitosea.task" ]
|
||||
runs-on: windows-latest
|
||||
environment: build
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Create changelog text
|
||||
id: changelog
|
||||
uses: loopwerk/tag-changelog@v1
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
exclude_types: other,chore,build
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Build for Windows
|
||||
shell: powershell
|
||||
env:
|
||||
APPID: ${{ matrix.id }}
|
||||
PROVIDER: "generic"
|
||||
RELEASE_BODY: ${{ steps.changelog.outputs.changes }}
|
||||
run: |
|
||||
npm install
|
||||
cd electron
|
||||
npm install
|
||||
cd ../
|
||||
mkdir -p ./electron/public
|
||||
cp ./electron/index.html ./electron/public/index.html
|
||||
npx mix --production -- --env --electron
|
||||
node ./electron/build.js build-win
|
||||
|
||||
97
.github/workflows/electron-github.yml
vendored
Normal file
97
.github/workflows/electron-github.yml
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
name: Build Github
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: ${{ (startsWith(github.event.ref, 'refs/tags/v')) && (github.repository == 'kuaifan/dootask') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Create changelog text
|
||||
id: changelog
|
||||
uses: loopwerk/tag-changelog@v1
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
exclude_types: other,chore,build
|
||||
|
||||
- name: Create release
|
||||
uses: actions/create-release@latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
body: ${{ steps.changelog.outputs.changes }}
|
||||
|
||||
build-mac:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
id: [ "com.dootask.task", "com.hitosea.task" ]
|
||||
runs-on: macos-latest
|
||||
environment: build
|
||||
|
||||
if: ${{ (startsWith(github.event.ref, 'refs/tags/v')) && (github.repository == 'kuaifan/dootask') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Build for MacOS
|
||||
env:
|
||||
APPID: ${{ matrix.id }}
|
||||
PROVIDER: "github"
|
||||
APPLEID: ${{ secrets.APPLEID }}
|
||||
APPLEIDPASS: ${{ secrets.APPLEIDPASS }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||
EP_PRE_RELEASE: true
|
||||
run: ./cmd electron build-mac
|
||||
|
||||
build-win:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
id: [ "com.dootask.task", "com.hitosea.task" ]
|
||||
runs-on: windows-latest
|
||||
environment: build
|
||||
|
||||
if: ${{ (startsWith(github.event.ref, 'refs/tags/v')) && (github.repository == 'kuaifan/dootask') }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Build for Windows
|
||||
shell: powershell
|
||||
env:
|
||||
APPID: ${{ matrix.id }}
|
||||
PROVIDER: "github"
|
||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||
EP_PRE_RELEASE: true
|
||||
run: |
|
||||
npm install
|
||||
cd electron
|
||||
npm install
|
||||
cd ../
|
||||
mkdir -p ./electron/public
|
||||
cp ./electron/index.html ./electron/public/index.html
|
||||
npx mix --production -- --env --electron
|
||||
node ./electron/build.js build-win
|
||||
|
||||
61
.github/workflows/electron.yml
vendored
61
.github/workflows/electron.yml
vendored
@@ -1,61 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Create changelog text
|
||||
id: changelog
|
||||
uses: loopwerk/tag-changelog@v1
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
exclude_types: other,chore
|
||||
|
||||
- name: Create release
|
||||
uses: actions/create-release@latest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
body: ${{ steps.changelog.outputs.changes }}
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: build
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-11]
|
||||
platform: [
|
||||
build-mac,
|
||||
build-mac-arm,
|
||||
build-win
|
||||
]
|
||||
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 14.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14.x
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||
EP_PRE_RELEASE: true
|
||||
run: ./cmd electron ${{ matrix.platform }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,7 +19,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
|
||||
1924
CHANGELOG.md
Normal file
1924
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
81
README.md
81
README.md
@@ -5,10 +5,15 @@ English | **[中文文档](./README_CN.md)**
|
||||
- [Screenshot Preview](README_PREVIEW.md)
|
||||
- [Demo site](http://www.dootask.com/)
|
||||
|
||||
**QQ Group**
|
||||
|
||||
Group No.: `546574618`
|
||||
|
||||
## Setup
|
||||
|
||||
> `Docker` & `Docker Compose v2.0+` must be installed
|
||||
|
||||
- `Docker` & `Docker Compose v2.0+` must be installed
|
||||
- System: `Centos/Debian/Ubuntu/macOS`
|
||||
- Hardware suggestion: 2 cores and above 4G memory
|
||||
|
||||
### Deployment project
|
||||
|
||||
@@ -16,9 +21,9 @@ English | **[中文文档](./README_CN.md)**
|
||||
# 1、Clone the repository
|
||||
|
||||
# Clone projects on github
|
||||
git clone https://github.com/kuaifan/dootask.git
|
||||
git clone --depth=1 https://github.com/kuaifan/dootask.git
|
||||
# Or you can use gitee
|
||||
git clone https://gitee.com/aipaw/dootask.git
|
||||
git clone --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、Enter directory
|
||||
cd dootask
|
||||
@@ -40,6 +45,16 @@ cd dootask
|
||||
./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
|
||||
|
||||
```bash
|
||||
@@ -49,18 +64,28 @@ cd dootask
|
||||
./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 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)
|
||||
./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 PROXY SSL
|
||||
@@ -71,7 +96,7 @@ 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、Enter directory and run command
|
||||
# 2、Running commands in a project
|
||||
./cmd https
|
||||
```
|
||||
|
||||
@@ -80,7 +105,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
**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
|
||||
@@ -89,16 +114,32 @@ git pull
|
||||
./cmd uninstall
|
||||
./cmd install
|
||||
./cmd mysql recovery
|
||||
./cmd artisan migrate
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Contact us
|
||||
|
||||
QQ Group: 546574618
|
||||
|
||||
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` 根据提示编译
|
||||
|
||||
|
||||
82
README_CN.md
82
README_CN.md
@@ -5,10 +5,15 @@
|
||||
- [截图预览](README_PREVIEW.md)
|
||||
- [演示站点](http://www.dootask.com/)
|
||||
|
||||
**QQ交流群**
|
||||
|
||||
- QQ群号: `546574618`
|
||||
|
||||
## 安装程序
|
||||
|
||||
> 必须安装 `Docker` 和 `Docker Compose v2.0+`
|
||||
|
||||
- 必须安装:`Docker` 和 `Docker Compose v2.0+`
|
||||
- 支持环境:`Centos/Debian/Ubuntu/macOS`
|
||||
- 硬件建议:2核4G以上
|
||||
|
||||
### 部署项目
|
||||
|
||||
@@ -16,9 +21,9 @@
|
||||
# 1、克隆项目到您的本地或服务器
|
||||
|
||||
# 通过github克隆项目
|
||||
git clone https://github.com/kuaifan/dootask.git
|
||||
git clone --depth=1 https://github.com/kuaifan/dootask.git
|
||||
# 或者你也可以使用gitee
|
||||
git clone https://gitee.com/aipaw/dootask.git
|
||||
git clone --depth=1 https://gitee.com/aipaw/dootask.git
|
||||
|
||||
# 2、进入目录
|
||||
cd dootask
|
||||
@@ -40,6 +45,16 @@ cd dootask
|
||||
./cmd port 2222
|
||||
```
|
||||
|
||||
### 更换URL
|
||||
|
||||
```bash
|
||||
# 此地址仅影响邮件回复功能
|
||||
./cmd url {域名地址}
|
||||
|
||||
# 例如:
|
||||
./cmd url https://domain.com
|
||||
```
|
||||
|
||||
### 停止服务
|
||||
|
||||
```bash
|
||||
@@ -49,18 +64,29 @@ cd dootask
|
||||
./cmd start
|
||||
```
|
||||
|
||||
### 开发编译
|
||||
|
||||
```bash
|
||||
# 开发模式,仅限macOS
|
||||
./cmd dev
|
||||
|
||||
# 编译项目,仅限macOS
|
||||
./cmd prod
|
||||
```
|
||||
|
||||
|
||||
### 运行命令的快捷方式
|
||||
|
||||
```bash
|
||||
# 你可以使用以下命令来执行
|
||||
./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: 还原数据库)
|
||||
./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: 还原数据库)
|
||||
```
|
||||
|
||||
### NGINX 代理 SSL
|
||||
@@ -71,7 +97,7 @@ 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、进入项目所在目录,运行以下命令
|
||||
# 2、在项目下运行命令
|
||||
./cmd https
|
||||
```
|
||||
|
||||
@@ -80,7 +106,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
**注意:在升级之前请备份好你的数据!**
|
||||
|
||||
```bash
|
||||
# 方法1:进入项目所在目录,运行以下命令
|
||||
# 方法1:在项目下运行命令
|
||||
./cmd update
|
||||
|
||||
# (或者)方法2:如果方法1失败请使用此方法
|
||||
@@ -89,16 +115,32 @@ git pull
|
||||
./cmd uninstall
|
||||
./cmd install
|
||||
./cmd mysql recovery
|
||||
./cmd artisan migrate
|
||||
```
|
||||
|
||||
如果升级后出现502请运行 `./cmd restart` 重启服务即可。
|
||||
|
||||
## 迁移项目
|
||||
|
||||
在新项目安装好之后按照以下步骤完成项目迁移:
|
||||
|
||||
1、备份原数据库
|
||||
|
||||
```bash
|
||||
# 在旧的项目下运行命令
|
||||
./cmd mysql backup
|
||||
```
|
||||
|
||||
2、将`数据库备份文件`及`public/uploads`目录拷贝至新项目
|
||||
|
||||
3、还原数据库至新项目
|
||||
```bash
|
||||
# 在新的项目下运行命令
|
||||
./cmd mysql recovery
|
||||
```
|
||||
|
||||
## 卸载项目
|
||||
|
||||
```bash
|
||||
# 进入项目所在目录,运行以下命令
|
||||
# 在项目下运行命令
|
||||
./cmd uninstall
|
||||
```
|
||||
|
||||
## 联系我们
|
||||
|
||||
QQ群号: 546574618
|
||||
|
||||
28
README_PUBLISH.md
Normal file
28
README_PUBLISH.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# 发布说明
|
||||
|
||||
## 发布前
|
||||
|
||||
1. 添加环境变量 `APPLEID`、`APPLEIDPASS`、`CSC_LINK`
|
||||
2. 发布GitHub还需要添加 `GH_PAT`
|
||||
|
||||
## 通过 GitHub Actions 发布
|
||||
|
||||
1. 执行 `./cmd prod` 编译
|
||||
2. 执行 `node ./version.js` 制作版本
|
||||
3. 执行 `git commit` 相关操作
|
||||
4. 制作标签
|
||||
5. 推送标签
|
||||
|
||||
## 本地发布
|
||||
|
||||
1. 执行 `./cmd prod` 编译
|
||||
2. 执行 `node ./version.js` 制作版本
|
||||
3. 执行 `git commit` 相关操作
|
||||
4. 制作标签
|
||||
5. 执行 `./cmd electron` 相关操作
|
||||
|
||||
|
||||
## 编译App
|
||||
|
||||
1. 执行 `./cmd appbuild` 或 `./cmd appbuild setting` 编译
|
||||
2. 进入 `resources/mobile` eeui框架内打包Android或iOS应用
|
||||
1443
_ide_helper.php
1443
_ide_helper.php
File diff suppressed because it is too large
Load Diff
@@ -15,3 +15,10 @@ if (!function_exists('seeders_at')) {
|
||||
return date("Y-m-d H:i:s", $time);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('md5s')) {
|
||||
function md5s($val, $len = 16)
|
||||
{
|
||||
return substr(md5($val), 32 - $len);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Models\AbstractModel;
|
||||
use App\Models\File;
|
||||
use App\Models\FileContent;
|
||||
@@ -11,7 +11,9 @@ use App\Models\FileUser;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Module\Ihttp;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Redirect;
|
||||
use Request;
|
||||
|
||||
/**
|
||||
@@ -43,11 +45,12 @@ class FileController extends AbstractController
|
||||
$pid = intval($data['pid']);
|
||||
//
|
||||
$permission = 1000;
|
||||
$userids = $user->isTemp() ? [$user->userid] : [0, $user->userid];
|
||||
$builder = File::wherePid($pid);
|
||||
if ($pid > 0) {
|
||||
File::permissionFind($pid, 0, $permission);
|
||||
$builder = File::wherePid($pid);
|
||||
File::permissionFind($pid, $userids, 0, $permission);
|
||||
} else {
|
||||
$builder = File::whereUserid($user->userid);
|
||||
$builder->whereUserid($user->userid);
|
||||
}
|
||||
//
|
||||
$array = $builder->take(500)->get()->toArray();
|
||||
@@ -64,9 +67,25 @@ class FileController extends AbstractController
|
||||
}
|
||||
$pid = $file->pid;
|
||||
$temp = $file->toArray();
|
||||
$temp['permission'] = $file->getPermission($user->userid);
|
||||
$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=''");
|
||||
@@ -74,10 +93,7 @@ class FileController extends AbstractController
|
||||
$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)
|
||||
->where(function ($query) use ($user) {
|
||||
$query->where('file_users.userid', 0);
|
||||
$query->orWhere('file_users.userid', $user->userid);
|
||||
})
|
||||
->whereIn('file_users.userid', $userids)
|
||||
->groupBy('files.id')
|
||||
->take(100)
|
||||
->get();
|
||||
@@ -89,6 +105,10 @@ class FileController extends AbstractController
|
||||
}
|
||||
}
|
||||
}
|
||||
// 图片直接返回预览地址
|
||||
foreach ($array as &$item) {
|
||||
$item = File::handleImageUrl($item);
|
||||
}
|
||||
return Base::retSuccess('success', $array);
|
||||
}
|
||||
|
||||
@@ -114,13 +134,18 @@ class FileController extends AbstractController
|
||||
//
|
||||
$permission = 0;
|
||||
if (Base::isNumber($id)) {
|
||||
User::auth();
|
||||
$file = File::permissionFind(intval($id), 0, $permission);
|
||||
$user = User::auth();
|
||||
$file = File::permissionFind(intval($id), $user, 0, $permission);
|
||||
} elseif ($id) {
|
||||
$fileLink = FileLink::whereCode($id)->first();
|
||||
$file = $fileLink?->file;
|
||||
if (empty($file)) {
|
||||
return Base::retError('链接不存在');
|
||||
$msg = '文件链接不存在';
|
||||
$data = File::code2IdName($id);
|
||||
if ($data) {
|
||||
$msg = "【{$data->name}】 {$msg}";
|
||||
}
|
||||
return Base::retError($msg, $data);
|
||||
}
|
||||
} else {
|
||||
return Base::retError('参数错误');
|
||||
@@ -139,6 +164,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName search
|
||||
*
|
||||
* @apiParam {String} [link] 通过分享地址搜索(如:https://t.hitosea.com/single/file/ODcwOCwzOSxpa0JBS2lmVQ==)
|
||||
* @apiParam {String} [key] 关键词
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@@ -149,15 +175,54 @@ class FileController extends AbstractController
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$link = trim(Request::input('link'));
|
||||
$key = trim(Request::input('key'));
|
||||
if (empty($key)) {
|
||||
return Base::retError('请输入关键词');
|
||||
$id = 0;
|
||||
$take = 50;
|
||||
if (preg_match("/\/single\/file\/(.*?)$/i", $link, $match)) {
|
||||
$id = intval(FileLink::whereCode($match[1])->value('file_id'));
|
||||
$take = 1;
|
||||
}
|
||||
// 搜索自己的
|
||||
$builder = File::whereUserid($user->userid);
|
||||
if ($id) {
|
||||
$builder->where("id", $id);
|
||||
}
|
||||
if ($key) {
|
||||
$builder->where("name", "like", "%{$key}%");
|
||||
}
|
||||
$array = $builder->take($take)->get()->toArray();
|
||||
// 搜索共享的
|
||||
$take = $take - count($array);
|
||||
if ($take > 0 && ($id || $key)) {
|
||||
$builder = File::whereIn('pshare', function ($queryA) use ($user) {
|
||||
$queryA->select('files.id')
|
||||
->from('files')
|
||||
->join('file_users', 'files.id', '=', 'file_users.file_id')
|
||||
->where('files.userid', '!=', $user->userid)
|
||||
->where(function ($queryB) use ($user) {
|
||||
$queryB->whereIn('file_users.userid', [0, $user->userid]);
|
||||
});
|
||||
});
|
||||
if ($id) {
|
||||
$builder->where("id", $id);
|
||||
}
|
||||
if ($key) {
|
||||
$builder->where("name", "like", "%{$key}%");
|
||||
}
|
||||
$list = $builder->take($take)->get();
|
||||
if ($list->isNotEmpty()) {
|
||||
foreach ($list as $file) {
|
||||
$temp = $file->toArray();
|
||||
if ($file->pshare === $file->id) {
|
||||
$temp['pid'] = 0;
|
||||
}
|
||||
$array[] = $temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
$builder = File::whereUserid($user->userid)->where("name", "like", "%{$key}%");
|
||||
$list = $builder->take(50)->get();
|
||||
//
|
||||
return Base::retSuccess('success', $list);
|
||||
return Base::retSuccess('success', $array);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,41 +258,54 @@ class FileController extends AbstractController
|
||||
//
|
||||
if ($id > 0) {
|
||||
// 修改
|
||||
$file = File::permissionFind($id, 1);
|
||||
$file = File::permissionFind($id, $user, 1);
|
||||
//
|
||||
$file->name = $name;
|
||||
$file->handleDuplicateName();
|
||||
$file->save();
|
||||
$file->pushMsg('update', $file);
|
||||
return Base::retSuccess('修改成功', $file);
|
||||
$data = [
|
||||
'id' => $file->id,
|
||||
'name' => $file->name,
|
||||
];
|
||||
$file->pushMsg('update', $data);
|
||||
return Base::retSuccess('修改成功', $data);
|
||||
} else {
|
||||
// 添加
|
||||
if (!in_array($type, [
|
||||
'folder',
|
||||
'document',
|
||||
'mind',
|
||||
'sheet',
|
||||
'flow',
|
||||
'drawio',
|
||||
'word',
|
||||
'excel',
|
||||
'ppt',
|
||||
])) {
|
||||
return Base::retError('类型错误');
|
||||
}
|
||||
$ext = '';
|
||||
if (in_array($type, [
|
||||
$ext = str_replace([
|
||||
'folder',
|
||||
'document',
|
||||
'mind',
|
||||
'drawio',
|
||||
'word',
|
||||
'excel',
|
||||
'ppt',
|
||||
])) {
|
||||
$ext = str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $type);
|
||||
}
|
||||
], [
|
||||
'',
|
||||
'md',
|
||||
'mind',
|
||||
'drawio',
|
||||
'docx',
|
||||
'xlsx',
|
||||
'pptx',
|
||||
], $type);
|
||||
//
|
||||
$userid = $user->userid;
|
||||
if ($pid > 0) {
|
||||
if (File::wherePid($pid)->count() >= 300) {
|
||||
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
|
||||
}
|
||||
$row = File::permissionFind($pid, 1);
|
||||
$row = File::permissionFind($pid, $user, 1);
|
||||
$userid = $row->userid;
|
||||
} else {
|
||||
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
|
||||
@@ -243,7 +321,8 @@ class FileController extends AbstractController
|
||||
'userid' => $userid,
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
$file->save();
|
||||
$file->handleDuplicateName();
|
||||
$file->saveBeforePP();
|
||||
//
|
||||
$data = File::find($file->id);
|
||||
$data->pushMsg('add', $data);
|
||||
@@ -271,7 +350,7 @@ class FileController extends AbstractController
|
||||
//
|
||||
$id = intval(Request::input('id'));
|
||||
//
|
||||
$row = File::permissionFind($id);
|
||||
$row = File::permissionFind($id, $user);
|
||||
//
|
||||
$userid = $user->userid;
|
||||
if ($row->pid > 0) {
|
||||
@@ -293,9 +372,20 @@ class FileController extends AbstractController
|
||||
'userid' => $userid,
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
$file->save();
|
||||
$file->handleDuplicateName();
|
||||
$data = AbstractModel::transaction(function() use ($file) {
|
||||
$content = FileContent::select(['content', 'text', 'size'])->whereFid($file->cid)->orderByDesc('id')->first();
|
||||
$file->size = $content?->size ?: 0;
|
||||
$file->saveBeforePP();
|
||||
if ($content) {
|
||||
$content = $content->toArray();
|
||||
$content['fid'] = $file->id;
|
||||
$content['userid'] = $file->userid;
|
||||
FileContent::createInstance($content)->save();
|
||||
}
|
||||
return File::find($file->id);
|
||||
});
|
||||
//
|
||||
$data = File::find($file->id);
|
||||
$data->pushMsg('add', $data);
|
||||
return Base::retSuccess('复制成功', $data);
|
||||
}
|
||||
@@ -308,7 +398,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName move
|
||||
*
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Numbers} ids 文件ID(格式:[id1, id2])
|
||||
* @apiParam {Number} pid 移动到的文件夹ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@@ -319,30 +409,60 @@ class FileController extends AbstractController
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$id = intval(Request::input('id'));
|
||||
$ids = Request::input('ids');
|
||||
$pid = intval(Request::input('pid'));
|
||||
//
|
||||
$file = File::permissionFind($id, 1000);
|
||||
//
|
||||
if (!is_array($ids) || empty($ids)) {
|
||||
return Base::retError('请选择移动的文件或文件夹');
|
||||
}
|
||||
if (count($ids) > 100) {
|
||||
return Base::retError('一次最多只能移动100个文件或文件夹');
|
||||
}
|
||||
$toShareFile = false;
|
||||
if ($pid > 0) {
|
||||
if (!File::whereUserid($user->userid)->whereId($pid)->exists()) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
$arr = [];
|
||||
$tid = $pid;
|
||||
while ($tid > 0) {
|
||||
$arr[] = $tid;
|
||||
$tid = intval(File::whereId($tid)->value('pid'));
|
||||
}
|
||||
if (in_array($id, $arr)) {
|
||||
return Base::retError('位置错误');
|
||||
}
|
||||
$tmpFile = File::permissionFind($pid, $user, 1);
|
||||
$toShareFile = $tmpFile->getShareInfo();
|
||||
}
|
||||
//
|
||||
$file->pid = $pid;
|
||||
$file->save();
|
||||
$file->pushMsg('update', $file);
|
||||
return Base::retSuccess('操作成功', $file);
|
||||
$files = [];
|
||||
AbstractModel::transaction(function() use ($user, $pid, $ids, $toShareFile, &$files) {
|
||||
foreach ($ids as $id) {
|
||||
$file = File::permissionFind($id, $user, 1000);
|
||||
//
|
||||
if ($pid > 0) {
|
||||
if ($toShareFile) {
|
||||
if ($file->share) {
|
||||
throw new ApiException("{$file->name} 当前正在共享,无法移动到另一个共享文件夹内");
|
||||
}
|
||||
if ($file->isSubShare()) {
|
||||
throw new ApiException("{$file->name} 内含有共享文件,无法移动到另一个共享文件夹内");
|
||||
}
|
||||
$file->userid = $toShareFile->userid;
|
||||
File::where('pids', 'LIKE', "%,{$file->id},%")->update(['userid' => $toShareFile->userid]);
|
||||
}
|
||||
//
|
||||
$tmpId = $pid;
|
||||
while ($tmpId > 0) {
|
||||
if ($id == $tmpId) {
|
||||
throw new ApiException('移动位置错误');
|
||||
}
|
||||
$tmpId = intval(File::whereId($tmpId)->value('pid'));
|
||||
}
|
||||
} else {
|
||||
$file->userid = $user->userid;
|
||||
File::where('pids', 'LIKE', "%,{$file->id},%")->update(['userid' => $user->userid]);
|
||||
}
|
||||
//
|
||||
$file->pid = $pid;
|
||||
$file->handleDuplicateName();
|
||||
$file->saveBeforePP();
|
||||
$files[] = $file;
|
||||
}
|
||||
});
|
||||
foreach ($files as $file) {
|
||||
$file->pushMsg('update', $file);
|
||||
}
|
||||
return Base::retSuccess('操作成功', $files);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,7 +473,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName remove
|
||||
*
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Numbers} ids 文件ID(格式:[id1, id2])
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -361,14 +481,27 @@ class FileController extends AbstractController
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
User::auth();
|
||||
$user = User::auth();
|
||||
//
|
||||
$id = intval(Request::input('id'));
|
||||
$ids = Request::input('ids');
|
||||
//
|
||||
$file = File::permissionFind($id, 1000);
|
||||
if (!is_array($ids) || empty($ids)) {
|
||||
return Base::retError('请选择删除的文件或文件夹');
|
||||
}
|
||||
if (count($ids) > 100) {
|
||||
return Base::retError('一次最多只能删除100个文件或文件夹');
|
||||
}
|
||||
//
|
||||
$file->deleteFile();
|
||||
return Base::retSuccess('删除成功', $file);
|
||||
$files = [];
|
||||
AbstractModel::transaction(function() use ($user, $ids, &$files) {
|
||||
foreach ($ids as $id) {
|
||||
$file = File::permissionFind($id, $user, 1000);
|
||||
$file->deleteFile();
|
||||
$files[] = $file;
|
||||
}
|
||||
});
|
||||
//
|
||||
return Base::retSuccess('删除成功', $files);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,8 +513,16 @@ class FileController extends AbstractController
|
||||
* @apiName content
|
||||
*
|
||||
* @apiParam {Number|String} id
|
||||
* - Number 文件ID(需要登录)
|
||||
* - String 链接码(不需要登录,用于预览)
|
||||
* - Number: 文件ID(需要登录)
|
||||
* - String: 链接码(不需要登录,用于预览)
|
||||
* @apiParam {String} only_update_at 仅获取update_at字段
|
||||
* - no (默认)
|
||||
* - yes
|
||||
* @apiParam {String} down 直接下载
|
||||
* - no: 浏览(默认)
|
||||
* - yes: 下载(office文件直接下载,除非是preview)
|
||||
* - preview: 转预览地址
|
||||
* @apiParam {Number} [history_id] 读取历史记录ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -390,22 +531,44 @@ class FileController extends AbstractController
|
||||
public function content()
|
||||
{
|
||||
$id = Request::input('id');
|
||||
$down = Request::input('down', 'no');
|
||||
$only_update_at = Request::input('only_update_at', 'no');
|
||||
$history_id = intval(Request::input('history_id'));
|
||||
//
|
||||
if (Base::isNumber($id)) {
|
||||
User::auth();
|
||||
$file = File::permissionFind(intval($id));
|
||||
$user = User::auth();
|
||||
$file = File::permissionFind(intval($id), $user);
|
||||
} elseif ($id) {
|
||||
$fileLink = FileLink::whereCode($id)->first();
|
||||
$file = $fileLink?->file;
|
||||
if (empty($file)) {
|
||||
return Base::retError('链接不存在');
|
||||
$msg = '文件链接不存在';
|
||||
$data = File::code2IdName($id);
|
||||
if ($data) {
|
||||
$msg = "【{$data->name}】 {$msg}";
|
||||
}
|
||||
return Base::retError($msg, $data);
|
||||
}
|
||||
} else {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
//
|
||||
$content = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||||
return FileContent::formatContent($file->type, $content ? $content->content : []);
|
||||
if ($only_update_at == 'yes') {
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $file->id,
|
||||
'update_at' => Carbon::parse($file->updated_at)->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
//
|
||||
$builder = FileContent::whereFid($file->id);
|
||||
if ($history_id > 0) {
|
||||
$builder->whereId($history_id);
|
||||
}
|
||||
$content = $builder->orderByDesc('id')->first();
|
||||
if ($down === 'preview') {
|
||||
return Redirect::to(FileContent::formatPreview($file, $content?->content));
|
||||
}
|
||||
return FileContent::formatContent($file, $content?->content, $down == 'yes');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -416,7 +579,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName content__save
|
||||
*
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Object} [D] Request Payload 提交
|
||||
* - content: 内容
|
||||
*
|
||||
@@ -431,20 +594,20 @@ class FileController extends AbstractController
|
||||
$id = Base::getPostInt('id');
|
||||
$content = Base::getPostValue('content');
|
||||
//
|
||||
$file = File::permissionFind($id, 1);
|
||||
$file = File::permissionFind($id, $user, 1);
|
||||
//
|
||||
$text = '';
|
||||
if ($file->type == 'document') {
|
||||
$data = Base::json2array($content);
|
||||
$isRep = false;
|
||||
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs);
|
||||
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"/s", $data['content'], $matchs);
|
||||
foreach ($matchs[2] as $key => $text) {
|
||||
$p = "uploads/files/document/" . $id . "/";
|
||||
Base::makeDir(public_path($p));
|
||||
$p.= md5($text) . "." . $matchs[1][$key];
|
||||
$r = file_put_contents(public_path($p), base64_decode($text));
|
||||
if ($r) {
|
||||
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($p) . '"', $data['content']);
|
||||
$tmpPath = "uploads/file/document/" . date("Ym") . "/" . $id . "/attached/";
|
||||
Base::makeDir(public_path($tmpPath));
|
||||
$tmpPath .= md5($text) . "." . $matchs[1][$key];
|
||||
if (file_put_contents(public_path($tmpPath), base64_decode($text))) {
|
||||
$paramet = getimagesize(public_path($tmpPath));
|
||||
$data['content'] = str_replace($matchs[0][$key], '<img src="' . Base::fillUrl($tmpPath) . '" original-width="' . $paramet[0] . '" original-height="' . $paramet[1] . '"', $data['content']);
|
||||
$isRep = true;
|
||||
}
|
||||
}
|
||||
@@ -454,11 +617,42 @@ class FileController extends AbstractController
|
||||
}
|
||||
}
|
||||
//
|
||||
switch ($file->type) {
|
||||
case 'document':
|
||||
$contentArray = Base::json2array($content);
|
||||
$contentString = $contentArray['content'];
|
||||
$file->ext = $contentArray['type'] == 'md' ? 'md' : 'text';
|
||||
break;
|
||||
case 'drawio':
|
||||
$contentArray = Base::json2array($content);
|
||||
$contentString = $contentArray['xml'];
|
||||
$file->ext = 'drawio';
|
||||
break;
|
||||
case 'mind':
|
||||
$contentString = $content;
|
||||
$file->ext = 'mind';
|
||||
break;
|
||||
case 'txt':
|
||||
case 'code':
|
||||
$contentArray = Base::json2array($content);
|
||||
$contentString = $contentArray['content'];
|
||||
break;
|
||||
default:
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
$path = "uploads/file/" . $file->type . "/" . date("Ym") . "/" . $id . "/" . md5($contentString);
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
file_put_contents($save, $contentString);
|
||||
//
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => $content,
|
||||
'content' => [
|
||||
'type' => $file->ext,
|
||||
'url' => $path
|
||||
],
|
||||
'text' => $text,
|
||||
'size' => strlen($content),
|
||||
'size' => filesize($save),
|
||||
'userid' => $user->userid,
|
||||
]);
|
||||
$content->save();
|
||||
@@ -493,12 +687,12 @@ class FileController extends AbstractController
|
||||
$key = Request::input('key');
|
||||
$url = Request::input('url');
|
||||
//
|
||||
$file = File::permissionFind($id, 1);
|
||||
$file = File::permissionFind($id, $user, 1);
|
||||
//
|
||||
if ($status === 2) {
|
||||
$parse = parse_url($url);
|
||||
$from = 'http://' . env('APP_IPPR') . '.3' . $parse['path'] . '?' . $parse['query'];
|
||||
$path = 'uploads/office/' . date("Ym") . '/' . $file->id . '/' . $user->userid . '-' . $key;
|
||||
$path = 'uploads/file/' . $file->type . '/' . date("Ym") . '/' . $file->id . '/' . $key;
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
$res = Ihttp::download($from, $save);
|
||||
@@ -516,6 +710,7 @@ class FileController extends AbstractController
|
||||
$content->save();
|
||||
//
|
||||
$file->size = $content->size;
|
||||
$file->updated_at = Carbon::now();
|
||||
$file->save();
|
||||
$file->pushMsg('update', $file);
|
||||
}
|
||||
@@ -531,7 +726,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName content__upload
|
||||
*
|
||||
* @apiParam {Number} [pid] 父级ID
|
||||
* @apiParam {Number} [pid] 父级ID
|
||||
* @apiParam {String} [files] 文件名
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@@ -543,13 +738,14 @@ class FileController extends AbstractController
|
||||
$user = User::auth();
|
||||
//
|
||||
$pid = intval(Request::input('pid'));
|
||||
$webkitRelativePath = Request::input('webkitRelativePath');
|
||||
//
|
||||
$userid = $user->userid;
|
||||
if ($pid > 0) {
|
||||
if (File::wherePid($pid)->count() >= 300) {
|
||||
return Base::retError('每个文件夹里最多只能创建300个文件或文件夹');
|
||||
}
|
||||
$row = File::permissionFind($pid, 1);
|
||||
$row = File::permissionFind($pid, $user, 1);
|
||||
$userid = $row->userid;
|
||||
} else {
|
||||
if (File::whereUserid($user->userid)->wherePid(0)->count() >= 300) {
|
||||
@@ -557,7 +753,38 @@ class FileController extends AbstractController
|
||||
}
|
||||
}
|
||||
//
|
||||
$path = 'uploads/office/' . date("Ym") . '/u' . $user->userid . '/';
|
||||
$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);
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
$path = 'uploads/tmp/' . date("Ym") . '/';
|
||||
$data = Base::upload([
|
||||
"file" => Request::file('files'),
|
||||
"type" => 'more',
|
||||
@@ -570,23 +797,36 @@ class FileController extends AbstractController
|
||||
$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', 'png', 'gif', 'bmp', 'ico', 'raw' => "picture",
|
||||
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z' => "archive",
|
||||
'jpg', 'jpeg', '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",
|
||||
'html', 'htm', 'asp', 'jsp', 'xml', 'json', 'properties', 'md', 'gitignore', 'log', 'java', 'py', 'c', 'cpp', 'sql', 'sh', 'bat', 'm', 'bas', 'prg', 'cmd',
|
||||
'php', 'go', 'python', 'js', 'ftl', 'css', 'lua', 'rb', 'yaml', 'yml', 'h', 'cs', 'aspx' => "code",
|
||||
'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']),
|
||||
@@ -595,35 +835,117 @@ class FileController extends AbstractController
|
||||
'userid' => $userid,
|
||||
'created_id' => $user->userid,
|
||||
]);
|
||||
$file->handleDuplicateName();
|
||||
// 开始创建
|
||||
return AbstractModel::transaction(function () use ($type, $user, $data, $file) {
|
||||
$file->save();
|
||||
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' => [
|
||||
'from' => '',
|
||||
'type' => $type,
|
||||
'ext' => $data['ext'],
|
||||
'url' => $data['path']
|
||||
],
|
||||
'content' => $content,
|
||||
'text' => '',
|
||||
'size' => $data['size'] * 1024,
|
||||
'size' => $file->size,
|
||||
'userid' => $user->userid,
|
||||
]);
|
||||
$content->save();
|
||||
//
|
||||
$file->size = $content->size;
|
||||
$file->save();
|
||||
$tmpRow = File::find($file->id);
|
||||
$tmpRow->pushMsg('add', $tmpRow);
|
||||
//
|
||||
$data = File::find($file->id);
|
||||
$data->pushMsg('add', $data);
|
||||
return Base::retSuccess($data['name'] . ' 上传成功', $data);
|
||||
$data = File::handleImageUrl($tmpRow->toArray());
|
||||
$data['full_name'] = $webkitRelativePath ?: $data['name'];
|
||||
//
|
||||
$addItem[] = $data;
|
||||
return Base::retSuccess($data['name'] . ' 上传成功', $addItem);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/file/share 12. 获取共享信息
|
||||
* @api {get} api/file/content/history 12. 获取内容历史
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup file
|
||||
* @apiName content__history
|
||||
*
|
||||
* @apiParam {Number} id 文件ID
|
||||
*
|
||||
* @apiParam {Number} [page] 当前页,默认:1
|
||||
* @apiParam {Number} [pagesize] 每页显示数量,默认:20,最大:100
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function content__history()
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$id = Request::input('id');
|
||||
//
|
||||
$file = File::permissionFind(intval($id), $user);
|
||||
//
|
||||
$data = FileContent::select(['id', 'size', 'userid', 'created_at'])
|
||||
->whereFid($file->id)
|
||||
->orderByDesc('id')
|
||||
->paginate(Base::getPaginate(100, 20));
|
||||
return Base::retSuccess('success', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/file/content/restore 13. 恢复文件历史
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup file
|
||||
* @apiName content__restore
|
||||
*
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Number} history_id 历史数据ID
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function content__restore()
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$id = intval(Request::input('id'));
|
||||
$history_id = intval(Request::input('history_id'));
|
||||
//
|
||||
$file = File::permissionFind($id, $user);
|
||||
//
|
||||
$history = FileContent::whereFid($file->id)->whereId($history_id)->first();
|
||||
if (empty($history)) {
|
||||
return Base::retError('历史数据不存在或已被删除');
|
||||
}
|
||||
//
|
||||
$content = $history->replicate();
|
||||
$content->userid = $user->userid;
|
||||
$content->save();
|
||||
//
|
||||
$file->size = $content->size;
|
||||
$file->save();
|
||||
$file->pushMsg('content');
|
||||
//
|
||||
return Base::retSuccess('还原成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/file/share 14. 获取共享信息
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -659,7 +981,7 @@ class FileController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/file/share/update 13. 设置共享
|
||||
* @api {get} api/file/share/update 15. 设置共享
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -672,6 +994,9 @@ class FileController extends AbstractController
|
||||
* - 0:只读
|
||||
* - 1:读写
|
||||
* - -1: 删除
|
||||
* @apiParam {Number} [force] 设置共享时是否忽略提醒
|
||||
* - 0:如果子文件夹已存在共享则ret返回-3001(默认)
|
||||
* - 1:忽略提醒
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -684,6 +1009,7 @@ class FileController extends AbstractController
|
||||
$id = intval(Request::input('id'));
|
||||
$userids = Request::input('userids');
|
||||
$permission = intval(Request::input('permission'));
|
||||
$force = intval(Request::input('force'));
|
||||
//
|
||||
if (!in_array($permission, [-1, 0, 1])) {
|
||||
return Base::retError('参数错误');
|
||||
@@ -697,8 +1023,10 @@ class FileController extends AbstractController
|
||||
return Base::retError('仅限所有者操作');
|
||||
}
|
||||
//
|
||||
if ($file->isNnShare()) {
|
||||
return Base::retError('已经处于共享文件夹中');
|
||||
$share = $file->isNnShare();
|
||||
if ($share) {
|
||||
$typeCn = $file->type === 'folder' ? '文件夹' : '文件';
|
||||
return Base::retError("此{$typeCn}已经处于【{$share->name}】共享文件夹中,无法重复共享");
|
||||
}
|
||||
//
|
||||
if (!is_array($userids) || empty($userids)) {
|
||||
@@ -710,16 +1038,18 @@ class FileController extends AbstractController
|
||||
// 取消共享
|
||||
$action = "delete";
|
||||
foreach ($userids as $userid) {
|
||||
if (FileUser::where([
|
||||
'file_id' => $file->id,
|
||||
'userid' => $userid,
|
||||
])->delete()) {
|
||||
if (FileUser::deleteFileUser($file->id, $userid)) {
|
||||
$array[] = $userid;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 设置共享
|
||||
$action = "update";
|
||||
if ($force === 0) {
|
||||
if ($file->isSubShare()) {
|
||||
return Base::retError('此文件夹内已有共享文件夹', [], -3001);
|
||||
}
|
||||
}
|
||||
if (FileUser::whereFileId($file->id)->count() + count($userids) > 100) {
|
||||
return Base::retError('共享人数上限100个成员');
|
||||
}
|
||||
@@ -735,13 +1065,13 @@ class FileController extends AbstractController
|
||||
}
|
||||
}
|
||||
//
|
||||
$file->setShare();
|
||||
$file->updataShare();
|
||||
$file->pushMsg($action, $action == "delete" ? null : $file, $array);
|
||||
return Base::retSuccess($action == "delete" ? "删除成功" : "设置成功", $file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/file/share/out 14. 退出共享
|
||||
* @api {get} api/file/share/out 16. 退出共享
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -760,28 +1090,22 @@ class FileController extends AbstractController
|
||||
//
|
||||
$id = intval(Request::input('id'));
|
||||
//
|
||||
$file = File::permissionFind($id);
|
||||
$file = File::permissionFind($id, $user);
|
||||
//
|
||||
if ($file->userid == $user->userid) {
|
||||
return Base::retError('不能退出自己共享的文件');
|
||||
}
|
||||
if (FileUser::where([
|
||||
'file_id' => $file->id,
|
||||
'userid' => 0,
|
||||
])->exists()) {
|
||||
if (FileUser::whereFileId($file->id)->whereUserid(0)->exists()) {
|
||||
return Base::retError('无法退出共享所有人的文件或文件夹');
|
||||
}
|
||||
FileUser::where([
|
||||
'file_id' => $file->id,
|
||||
'userid' => $user->userid,
|
||||
])->delete();
|
||||
FileUser::deleteFileUser($file->id, $user->userid);
|
||||
//
|
||||
$file->setShare();
|
||||
$file->updataShare();
|
||||
return Base::retSuccess("退出成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/file/link 15. 获取链接
|
||||
* @api {get} api/file/link 17. 获取链接
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -799,33 +1123,14 @@ class FileController extends AbstractController
|
||||
*/
|
||||
public function link()
|
||||
{
|
||||
User::auth();
|
||||
$user = User::auth();
|
||||
//
|
||||
$id = intval(Request::input('id'));
|
||||
$refresh = Request::input('refresh', 'no');
|
||||
//
|
||||
$file = File::permissionFind($id, 1000);
|
||||
if ($file->type == 'folder') {
|
||||
return Base::retError('文件夹暂不支持此功能');
|
||||
}
|
||||
$file = File::permissionFind($id, $user);
|
||||
$fileLink = $file->getShareLink($user->userid, $refresh == 'yes');
|
||||
//
|
||||
$fileLink = FileLink::whereFileId($file->id)->first();
|
||||
if (empty($fileLink)) {
|
||||
$fileLink = FileLink::createInstance([
|
||||
'file_id' => $file->id,
|
||||
'code' => Base::generatePassword(64),
|
||||
]);
|
||||
$fileLink->save();
|
||||
} else {
|
||||
if ($refresh == 'yes') {
|
||||
$fileLink->code = Base::generatePassword(64);
|
||||
$fileLink->save();
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', [
|
||||
'id' => $file->id,
|
||||
'url' => Base::fillUrl('single/file/' . $fileLink->code),
|
||||
'num' => $fileLink->num
|
||||
]);
|
||||
return Base::retSuccess('success', $fileLink);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
179
app/Http/Controllers/Api/PublicController.php
Executable file
179
app/Http/Controllers/Api/PublicController.php
Executable file
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\User;
|
||||
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 ($key != $setting['key']) {
|
||||
return 'key error';
|
||||
}
|
||||
$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 "not in valid time, valid time is " . date("H:i", $timeAdvance) . "-" . date("H:i", $timeDelay);
|
||||
}
|
||||
//
|
||||
$macs = explode(",", $mac);
|
||||
$checkins = [];
|
||||
foreach ($macs as $mac) {
|
||||
$mac = strtoupper($mac);
|
||||
if (Base::isMac($mac) && $UserCheckinMac = UserCheckinMac::whereMac($mac)->first()) {
|
||||
$checkins[] = $UserCheckinMac;
|
||||
$array = [
|
||||
'userid' => $UserCheckinMac->userid,
|
||||
'mac' => $UserCheckinMac->mac,
|
||||
'date' => $nowDate,
|
||||
];
|
||||
$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, UserCheckinMac $checkin) use ($getJokeSoup, $botUser, $nowDate) {
|
||||
$cacheKey = "Checkin::sendMsg-{$nowDate}-{$type}:" . $checkin->userid;
|
||||
if (Cache::get($cacheKey) === "yes") {
|
||||
return;
|
||||
}
|
||||
Cache::put($cacheKey, "yes", Carbon::now()->addDay());
|
||||
//
|
||||
$dialog = WebSocketDialog::checkUserDialog($botUser, $checkin->userid);
|
||||
if ($dialog) {
|
||||
$hi = date("H:i");
|
||||
$pre = $type == "up" ? "上班" : "下班";
|
||||
$remark = $checkin->remark ? " ({$checkin->remark})": "";
|
||||
$text = "<p>{$pre}打卡成功,打卡时间: {$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);
|
||||
}
|
||||
};
|
||||
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 'success';
|
||||
}
|
||||
}
|
||||
523
app/Http/Controllers/Api/ReportController.php
Executable file
523
app/Http/Controllers/Api/ReportController.php
Executable file
@@ -0,0 +1,523 @@
|
||||
<?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\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" => Base::getPostValue("id", 0),
|
||||
"sign" => Base::getPostValue("sign"),
|
||||
"title" => Base::getPostValue("title"),
|
||||
"type" => Base::getPostValue("type"),
|
||||
"content" => Base::getPostValue("content"),
|
||||
"receive" => Base::getPostValue("receive"),
|
||||
// 以当前日期为基础的周期偏移量。例如选择了上一周那么就是 -1,上一天同理。
|
||||
"offset" => Base::getPostValue("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>[' . Base::Lang('周' . ['日', '一', '二', '三', '四', '五', '六'][$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;">[' . Base::Lang('超期') . ']</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>' . Base::Lang('已完成工作') . '</h2><ol>' .
|
||||
$completeContent . '</ol><h2>' .
|
||||
Base::Lang('未完成的工作') . '</h2><ol>' .
|
||||
$unfinishedContent . '</ol>';
|
||||
if ($type === Report::WEEKLY) {
|
||||
$content .= "<h2>" . Base::Lang("下周拟定计划") . "[" . $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
|
||||
*
|
||||
* @apiParam {Number} [userid] 用户id
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function unread(): array
|
||||
{
|
||||
$userid = intval(trim(Request::input("userid")));
|
||||
$user = empty($userid) ? User::auth() : User::find($userid);
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,23 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Models\UserCheckinRecord;
|
||||
use App\Module\Base;
|
||||
use App\Module\BillExport;
|
||||
use App\Module\BillMultipleExport;
|
||||
use App\Module\Extranet;
|
||||
use Arr;
|
||||
use Carbon\Carbon;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
use LdapRecord\Container;
|
||||
use LdapRecord\LdapRecordException;
|
||||
use Madzipper;
|
||||
use Request;
|
||||
use Response;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* @apiDefine system
|
||||
@@ -25,7 +38,7 @@ class SystemController extends AbstractController
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - all: 获取所有(需要管理员权限)
|
||||
* - save: 保存设置(参数:reg、reg_invite、login_code、password_policy、project_invite、chat_nickname、auto_archived、archived_day)
|
||||
* - save: 保存设置(参数:['reg', 'reg_identity', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'anon_message', 'auto_archived', 'archived_day', 'all_group_mute', 'all_group_autoin', 'start_home', 'home_footer'])
|
||||
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -41,7 +54,22 @@ class SystemController extends AbstractController
|
||||
User::auth('admin');
|
||||
$all = Request::input();
|
||||
foreach ($all AS $key => $value) {
|
||||
if (!in_array($key, ['reg', 'reg_invite', 'login_code', 'password_policy', 'project_invite', 'chat_nickname', 'auto_archived', 'archived_day'])) {
|
||||
if (!in_array($key, [
|
||||
'reg',
|
||||
'reg_identity',
|
||||
'reg_invite',
|
||||
'login_code',
|
||||
'password_policy',
|
||||
'project_invite',
|
||||
'chat_information',
|
||||
'anon_message',
|
||||
'auto_archived',
|
||||
'archived_day',
|
||||
'all_group_mute',
|
||||
'all_group_autoin',
|
||||
'start_home',
|
||||
'home_footer'
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
@@ -66,18 +94,306 @@ class SystemController extends AbstractController
|
||||
}
|
||||
//
|
||||
$setting['reg'] = $setting['reg'] ?: 'open';
|
||||
$setting['reg_identity'] = $setting['reg_identity'] ?: 'normal';
|
||||
$setting['login_code'] = $setting['login_code'] ?: 'auto';
|
||||
$setting['password_policy'] = $setting['password_policy'] ?: 'simple';
|
||||
$setting['project_invite'] = $setting['project_invite'] ?: 'open';
|
||||
$setting['chat_nickname'] = $setting['chat_nickname'] ?: 'optional';
|
||||
$setting['chat_information'] = $setting['chat_information'] ?: 'optional';
|
||||
$setting['anon_message'] = $setting['anon_message'] ?: 'open';
|
||||
$setting['auto_archived'] = $setting['auto_archived'] ?: 'close';
|
||||
$setting['archived_day'] = floatval($setting['archived_day']) ?: 7;
|
||||
$setting['all_group_mute'] = $setting['all_group_mute'] ?: 'open';
|
||||
$setting['all_group_autoin'] = $setting['all_group_autoin'] ?: 'yes';
|
||||
$setting['start_home'] = $setting['start_home'] ?: 'close';
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/demo 02. 获取演示账号
|
||||
* @api {get} api/system/setting/email 02. 获取邮箱设置、保存邮箱设置(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName setting__email
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存设置(参数:['smtp_server', 'port', 'account', 'password', 'reg_verify', 'notice_msg', 'msg_unread_user_minute', 'msg_unread_group_minute', 'ignore_addr'])
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function setting__email()
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'save') {
|
||||
if (env("SYSTEM_SETTING") == 'disabled') {
|
||||
return Base::retError('当前环境禁止修改');
|
||||
}
|
||||
$user->identity('admin');
|
||||
$all = Request::input();
|
||||
foreach ($all as $key => $value) {
|
||||
if (!in_array($key, [
|
||||
'smtp_server',
|
||||
'port',
|
||||
'account',
|
||||
'password',
|
||||
'reg_verify',
|
||||
'notice_msg',
|
||||
'msg_unread_user_minute',
|
||||
'msg_unread_group_minute',
|
||||
'ignore_addr'
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
$setting = Base::setting('emailSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('emailSetting');
|
||||
}
|
||||
//
|
||||
$setting['smtp_server'] = $setting['smtp_server'] ?: '';
|
||||
$setting['port'] = $setting['port'] ?: '';
|
||||
$setting['account'] = $setting['account'] ?: '';
|
||||
$setting['password'] = $setting['password'] ?: '';
|
||||
$setting['reg_verify'] = $setting['reg_verify'] ?: 'close';
|
||||
$setting['notice_msg'] = $setting['notice_msg'] ?: 'close';
|
||||
$setting['msg_unread_user_minute'] = intval($setting['msg_unread_user_minute'] ?? -1);
|
||||
$setting['msg_unread_group_minute'] = intval($setting['msg_unread_group_minute'] ?? -1);
|
||||
$setting['ignore_addr'] = $setting['ignore_addr'] ?: '';
|
||||
//
|
||||
if ($type != 'save' && !in_array('admin', $user->identity)) {
|
||||
$setting = array_intersect_key($setting, array_flip(['reg_verify']));
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/setting/meeting 03. 获取会议设置、保存会议设置(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName setting__meeting
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存设置(参数:['open', 'appid', 'app_certificate'])
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function setting__meeting()
|
||||
{
|
||||
User::auth('admin');
|
||||
//
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'save') {
|
||||
if (env("SYSTEM_SETTING") == 'disabled') {
|
||||
return Base::retError('当前环境禁止修改');
|
||||
}
|
||||
$all = Request::input();
|
||||
foreach ($all as $key => $value) {
|
||||
if (!in_array($key, [
|
||||
'open',
|
||||
'appid',
|
||||
'app_certificate',
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
$setting = Base::setting('meetingSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('meetingSetting');
|
||||
}
|
||||
//
|
||||
$setting['open'] = $setting['open'] ?: 'close';
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/setting/checkin 04. 获取签到设置、保存签到设置(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName setting__checkin
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存设置(参数:['open', 'time', 'advance', 'delay', 'remindin', 'remindexceed', 'edit', 'key'])
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function setting__checkin()
|
||||
{
|
||||
User::auth('admin');
|
||||
//
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'save') {
|
||||
if (env("SYSTEM_SETTING") == 'disabled') {
|
||||
return Base::retError('当前环境禁止修改');
|
||||
}
|
||||
$all = Request::input();
|
||||
foreach ($all as $key => $value) {
|
||||
if (!in_array($key, [
|
||||
'open',
|
||||
'time',
|
||||
'advance',
|
||||
'delay',
|
||||
'remindin',
|
||||
'remindexceed',
|
||||
'edit',
|
||||
'key',
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
if ($all['open'] === 'close') {
|
||||
$all['key'] = md5(Base::generatePassword(32));
|
||||
}
|
||||
$setting = Base::setting('checkinSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('checkinSetting');
|
||||
}
|
||||
//
|
||||
if (empty($setting['key'])) {
|
||||
$setting['key'] = md5(Base::generatePassword(32));
|
||||
Base::setting('checkinSetting', $setting);
|
||||
}
|
||||
//
|
||||
$setting['open'] = $setting['open'] ?: 'close';
|
||||
$setting['time'] = $setting['time'] ? Base::json2array($setting['time']) : ['09:00', '18:00'];
|
||||
$setting['advance'] = intval($setting['advance']) ?: 120;
|
||||
$setting['delay'] = intval($setting['delay']) ?: 120;
|
||||
$setting['remindin'] = intval($setting['remindin']) ?: 5;
|
||||
$setting['remindexceed'] = intval($setting['remindexceed']) ?: 10;
|
||||
$setting['edit'] = $setting['edit'] ?: 'close';
|
||||
$setting['cmd'] = "curl -sSL '" . Base::fillUrl("api/public/checkin/install?key={$setting['key']}") . "' | sh";
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/setting/apppush 05. 获取APP推送设置、保存APP推送设置(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName setting__apppush
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存设置(参数:['push', 'ios_key', 'ios_secret', 'android_key', 'android_secret'])
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function setting__apppush()
|
||||
{
|
||||
User::auth('admin');
|
||||
//
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'save') {
|
||||
if (env("SYSTEM_SETTING") == 'disabled') {
|
||||
return Base::retError('当前环境禁止修改');
|
||||
}
|
||||
$all = Request::input();
|
||||
foreach ($all as $key => $value) {
|
||||
if (!in_array($key, [
|
||||
'push',
|
||||
'ios_key',
|
||||
'ios_secret',
|
||||
'android_key',
|
||||
'android_secret',
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
$setting = Base::setting('appPushSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('appPushSetting');
|
||||
}
|
||||
//
|
||||
$setting['push'] = $setting['push'] ?: 'close';
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/setting/thirdaccess 06. 第三方帐号(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName setting__thirdaccess
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存设置(参数:['ldap_open', 'ldap_host', 'ldap_port', 'ldap_password', 'ldap_user_dn', 'ldap_base_dn', 'ldap_sync_local'])
|
||||
* - testldap: 测试ldap连接
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function setting__thirdaccess()
|
||||
{
|
||||
User::auth('admin');
|
||||
//
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'testldap') {
|
||||
$all = Base::newTrim(Request::input());
|
||||
$connection = Container::getDefaultConnection();
|
||||
try {
|
||||
$connection->setConfiguration([
|
||||
"hosts" => [$all['ldap_host']],
|
||||
"port" => intval($all['ldap_port']),
|
||||
"password" => $all['ldap_password'],
|
||||
"username" => $all['ldap_user_dn'],
|
||||
"base_dn" => $all['ldap_base_dn'],
|
||||
]);
|
||||
if ($connection->auth()->attempt($all['ldap_user_dn'], $all['ldap_password'])) {
|
||||
return Base::retSuccess('验证通过');
|
||||
} else {
|
||||
return Base::retError('验证失败');
|
||||
}
|
||||
} catch (LdapRecordException $e) {
|
||||
return Base::retError($e->getMessage() ?: "验证失败:未知错误", config("ldap.connections.default"));
|
||||
}
|
||||
} elseif ($type == 'save') {
|
||||
if (env("SYSTEM_SETTING") == 'disabled') {
|
||||
return Base::retError('当前环境禁止修改');
|
||||
}
|
||||
$all = Base::newTrim(Request::input());
|
||||
foreach ($all as $key => $value) {
|
||||
if (!in_array($key, [
|
||||
'ldap_open',
|
||||
'ldap_host',
|
||||
'ldap_port',
|
||||
'ldap_password',
|
||||
'ldap_user_dn',
|
||||
'ldap_base_dn',
|
||||
'ldap_sync_local'
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
$all['ldap_port'] = intval($all['ldap_port']) ?: 389;
|
||||
$setting = Base::setting('thirdAccessSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('thirdAccessSetting');
|
||||
}
|
||||
//
|
||||
$setting['ldap_open'] = $setting['ldap_open'] ?: 'close';
|
||||
$setting['ldap_port'] = intval($setting['ldap_port']) ?: 389;
|
||||
$setting['ldap_sync_local'] = $setting['ldap_sync_local'] ?: 'close';
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/demo 07. 获取演示帐号
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -101,12 +417,16 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/priority 03. 获取优先级、保存优先级
|
||||
* @api {post} api/system/priority 08. 任务优先级
|
||||
*
|
||||
* @apiDescription 获取任务优先级、保存任务优先级
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName priority
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存(限管理员)
|
||||
* @apiParam {Array} list 优先级数据,格式:[{name,color,days,priority}]
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
@@ -146,7 +466,54 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/info 04. 获取终端详细信息
|
||||
* @api {post} api/system/column/template 09. 创建项目模板
|
||||
*
|
||||
* @apiDescription 获取创建项目模板、保存创建项目模板
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName column__template
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存(限管理员)
|
||||
* @apiParam {Array} list 优先级数据,格式:[{name,columns}]
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function column__template()
|
||||
{
|
||||
$type = trim(Request::input('type'));
|
||||
if ($type == 'save') {
|
||||
User::auth('admin');
|
||||
$list = Base::getPostValue('list');
|
||||
$array = [];
|
||||
if (empty($list) || !is_array($list)) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
foreach ($list AS $item) {
|
||||
if (empty($item['name']) || empty($item['columns'])) {
|
||||
continue;
|
||||
}
|
||||
$array[] = [
|
||||
'name' => $item['name'],
|
||||
'columns' => array_values(array_filter(array_unique(explode(",", $item['columns']))))
|
||||
];
|
||||
}
|
||||
if (empty($array)) {
|
||||
return Base::retError('参数为空');
|
||||
}
|
||||
$setting = Base::setting('columnTemplate', $array);
|
||||
} else {
|
||||
$setting = Base::setting('columnTemplate');
|
||||
}
|
||||
//
|
||||
return Base::retSuccess('success', $setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/info 10. 获取终端详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -165,8 +532,8 @@ class SystemController extends AbstractController
|
||||
}
|
||||
return Base::retSuccess('success', [
|
||||
'ip' => Base::getIp(),
|
||||
'ip-info' => Base::getIpInfo(Base::getIp()),
|
||||
'ip-gcj02' => Base::getIpGcj02(Base::getIp()),
|
||||
'ip-info' => Extranet::getIpInfo(Base::getIp()),
|
||||
'ip-gcj02' => Extranet::getIpGcj02(Base::getIp()),
|
||||
'ip-iscn' => Base::isCnIp(Base::getIp()),
|
||||
'header' => Request::header(),
|
||||
'token' => Base::getToken(),
|
||||
@@ -175,7 +542,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ip 05. 获取IP地址
|
||||
* @api {get} api/system/get/ip 11. 获取IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -190,7 +557,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/cnip 06. 是否中国IP地址
|
||||
* @api {get} api/system/get/cnip 12. 是否中国IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -207,7 +574,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipgcj02 07. 获取IP地址经纬度
|
||||
* @api {get} api/system/get/ipgcj02 13. 获取IP地址经纬度
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -220,11 +587,11 @@ class SystemController extends AbstractController
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function get__ipgcj02() {
|
||||
return Base::getIpGcj02(Request::input("ip"));
|
||||
return Extranet::getIpGcj02(Request::input("ip"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipinfo 08. 获取IP地址详细信息
|
||||
* @api {get} api/system/get/ipinfo 14. 获取IP地址详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -237,19 +604,26 @@ class SystemController extends AbstractController
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function get__ipinfo() {
|
||||
return Base::getIpInfo(Request::input("ip"));
|
||||
return Extranet::getIpInfo(Request::input("ip"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/imgupload 09. 上传图片
|
||||
* @api {post} api/system/imgupload 15. 上传图片
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName imgupload
|
||||
*
|
||||
* @apiParam {String} image64 图片base64
|
||||
* @apiParam {String} filename 文件名
|
||||
* @apiParam {File} image post-图片对象
|
||||
* @apiParam {String} [image64] post-图片base64(与'image'二选一)
|
||||
* @apiParam {String} filename post-文件名
|
||||
* @apiParam {Number} [width] 压缩图片宽(默认0)
|
||||
* @apiParam {Number} [height] 压缩图片高(默认0)
|
||||
* @apiParam {String} [whcut] 压缩方式
|
||||
* - 1:裁切(默认,宽、高非0有效)
|
||||
* - 0:缩放
|
||||
* - -1或'auto':保持等比裁切
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -260,11 +634,14 @@ class SystemController extends AbstractController
|
||||
if (User::userid() === 0) {
|
||||
return Base::retError('身份失效,等重新登录');
|
||||
}
|
||||
$scale = [intval(Request::input('width')), intval(Request::input('height'))];
|
||||
if (!$scale[0] && !$scale[1]) {
|
||||
$scale = [2160, 4160, -1];
|
||||
$width = intval(Request::input('width'));
|
||||
$height = intval(Request::input('height'));
|
||||
$whcut = intval(Request::input('whcut', 1));
|
||||
$scale = [2160, 4160, -1];
|
||||
if ($width > 0 || $height > 0) {
|
||||
$scale = [$width, $height, $whcut];
|
||||
}
|
||||
$path = "uploads/picture/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$path = "uploads/user/picture/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$image64 = trim(Base::getPostValue('image64'));
|
||||
$fileName = trim(Base::getPostValue('filename'));
|
||||
if ($image64) {
|
||||
@@ -291,7 +668,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/imgview 10. 浏览图片空间
|
||||
* @api {get} api/system/get/imgview 16. 浏览图片空间
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -309,7 +686,7 @@ class SystemController extends AbstractController
|
||||
if (User::userid() === 0) {
|
||||
return Base::retError('身份失效,等重新登录');
|
||||
}
|
||||
$publicPath = "uploads/picture/" . User::userid() . "/";
|
||||
$publicPath = "uploads/user/picture/" . User::userid() . "/";
|
||||
$dirPath = public_path($publicPath);
|
||||
$dirs = $files = [];
|
||||
//
|
||||
@@ -387,7 +764,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/fileupload 11. 上传文件
|
||||
* @api {post} api/system/fileupload 17. 上传文件
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -407,7 +784,7 @@ class SystemController extends AbstractController
|
||||
if (User::userid() === 0) {
|
||||
return Base::retError('身份失效,等重新登录');
|
||||
}
|
||||
$path = "uploads/files/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$path = "uploads/user/file/" . User::userid() . "/" . date("Ym") . "/";
|
||||
$image64 = trim(Base::getPostValue('image64'));
|
||||
$fileName = trim(Base::getPostValue('filename'));
|
||||
if ($image64) {
|
||||
@@ -427,4 +804,324 @@ class SystemController extends AbstractController
|
||||
//
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/showitem 18. 首页显示ITEM
|
||||
*
|
||||
* @apiDescription 用于判断首页是否显示:pro、github、更新日志...
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName get__showitem
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function get__showitem()
|
||||
{
|
||||
$logPath = base_path('CHANGELOG.md');
|
||||
$logContent = "";
|
||||
$logVersion = "";
|
||||
if (file_exists($logPath)) {
|
||||
$logContent = file_get_contents($logPath);
|
||||
preg_match("/## \[(.*?)\]/", $logContent, $matchs);
|
||||
if ($matchs) {
|
||||
$logVersion = $matchs[1] === "Unreleased" ? $matchs[1] : "v{$matchs[1]}";
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', [
|
||||
'pro' => str_contains(Request::getHost(), "dootask.com") || str_contains(Request::getHost(), "127.0.0.1"),
|
||||
'github' => env('GITHUB_URL') ?: false,
|
||||
'updateLog' => $logContent ?: false,
|
||||
'updateVer' => $logVersion,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/starthome 19. 启动首页设置信息
|
||||
*
|
||||
* @apiDescription 用于判断注册是否需要启动首页
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName get__starthome
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function get__starthome()
|
||||
{
|
||||
return Base::retSuccess('success', [
|
||||
'need_start' => Base::settingFind('system', 'start_home') == 'open',
|
||||
'home_footer' => Base::settingFind('system', 'home_footer')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/email/check 20. 邮件发送测试(限管理员)
|
||||
*
|
||||
* @apiDescription 测试配置邮箱是否能发送邮件
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName email__check
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function email__check()
|
||||
{
|
||||
User::auth('admin');
|
||||
//
|
||||
$all = Request::input();
|
||||
if (!Base::isEmail($all['to'])) {
|
||||
return Base::retError('请输入正确的收件人地址');
|
||||
}
|
||||
try {
|
||||
Setting::validateAddr($all['to'], function($to) use ($all) {
|
||||
Factory::mailer()
|
||||
->setDsn("smtp://{$all['account']}:{$all['password']}@{$all['smtp_server']}:{$all['port']}?verify_peer=0")
|
||||
->setMessage(EmailMessage::create()
|
||||
->from(env('APP_NAME', 'Task') . " <{$all['account']}>")
|
||||
->to($to)
|
||||
->subject('Mail sending test')
|
||||
->html('<p>收到此电子邮件意味着您的邮箱配置正确。</p><p>Receiving this email means that your mailbox is configured correctly.</p>'))
|
||||
->send();
|
||||
}, function () {
|
||||
throw new \Exception("收件人地址错误或已被忽略");
|
||||
});
|
||||
return Base::retSuccess('成功发送');
|
||||
} catch (\Throwable $e) {
|
||||
// 一般是请求超时
|
||||
if (str_contains($e->getMessage(), "Timed Out")) {
|
||||
return Base::retError("language.TimedOut");
|
||||
} elseif ($e->getCode() === 550) {
|
||||
return Base::retError('邮件内容被拒绝,请检查邮箱是否开启接收功能');
|
||||
} else {
|
||||
return Base::retError($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/checkin/export 21. 导出签到数据(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName checkin__export
|
||||
*
|
||||
* @apiParam {Array} [userid] 指定会员,如:[1, 2]
|
||||
* @apiParam {Array} [date] 指定日期范围,如:['2020-12-12', '2020-12-30']
|
||||
* @apiParam {Array} [time] 指定时间范围,如:['09:00', '18:00']
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function checkin__export()
|
||||
{
|
||||
User::auth('admin');
|
||||
//
|
||||
$setting = Base::setting('checkinSetting');
|
||||
if ($setting['open'] !== 'open') {
|
||||
return Base::retError('此功能未开启,请前往系统设置开启');
|
||||
}
|
||||
//
|
||||
$userid = Base::arrayRetainInt(Request::input('userid'), true);
|
||||
$date = Request::input('date');
|
||||
$time = Request::input('time');
|
||||
//
|
||||
if (empty($userid) || empty($date) || empty($time)) {
|
||||
return Base::retError('参数错误');
|
||||
}
|
||||
if (count($userid) > 100) {
|
||||
return Base::retError('导出成员限制最多100个');
|
||||
}
|
||||
if (!(is_array($date) && Base::isDate($date[0]) && Base::isDate($date[1]))) {
|
||||
return Base::retError('日期选择错误');
|
||||
}
|
||||
if (Carbon::parse($date[1])->timestamp - Carbon::parse($date[0])->timestamp > 35 * 86400) {
|
||||
return Base::retError('日期范围限制最大35天');
|
||||
}
|
||||
if (!(is_array($time) && Base::isTime($time[0]) && Base::isTime($time[1]))) {
|
||||
return Base::retError('时间选择错误');
|
||||
}
|
||||
//
|
||||
$secondStart = strtotime("2000-01-01 {$time[0]}") - strtotime("2000-01-01 00:00:00");
|
||||
$secondEnd = strtotime("2000-01-01 {$time[1]}") - strtotime("2000-01-01 00:00:00");
|
||||
//
|
||||
$headings = [];
|
||||
$headings[] = '签到人';
|
||||
$headings[] = '签到日期';
|
||||
$headings[] = '班次时间';
|
||||
$headings[] = '首次签到时间';
|
||||
$headings[] = '首次签到结果';
|
||||
$headings[] = '最后签到时间';
|
||||
$headings[] = '最后签到结果';
|
||||
$headings[] = '参数数据';
|
||||
//
|
||||
$sheets = [];
|
||||
$startD = Carbon::parse($date[0])->startOfDay();
|
||||
$endD = Carbon::parse($date[1])->endOfDay();
|
||||
$users = User::whereIn('userid', $userid)->take(100)->get();
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$recordTimes = UserCheckinRecord::getTimes($user->userid, [$startD, $endD]);
|
||||
//
|
||||
$nickname = Base::filterEmoji($user->nickname);
|
||||
$styles = ["A1:H1" => ["font" => ["bold" => true]]];
|
||||
$datas = [];
|
||||
$startT = $startD->timestamp;
|
||||
$endT = $endD->timestamp;
|
||||
$index = 1;
|
||||
while ($startT < $endT) {
|
||||
$index++;
|
||||
$sameDate = date("Y-m-d", $startT);
|
||||
$sameTimes = $recordTimes[$sameDate] ?? [];
|
||||
$sameCollect = UserCheckinRecord::atCollect($sameDate, $sameTimes);
|
||||
$firstBetween = [Carbon::createFromTimestamp($startT), Carbon::createFromTimestamp($startT + $secondEnd - 1)];
|
||||
$lastBetween = [Carbon::createFromTimestamp($startT + $secondStart + 1), Carbon::createFromTimestamp($startT + 86400)];
|
||||
$firstRecord = $sameCollect?->whereBetween("datetime", $firstBetween)->first();
|
||||
$lastRecord = $sameCollect?->whereBetween("datetime", $lastBetween)->last();
|
||||
$firstTimestamp = $firstRecord['timestamp'] ?: 0;
|
||||
$lastTimestamp = $lastRecord['timestamp'] ?: 0;
|
||||
if (Base::time() < $startT + $secondStart) {
|
||||
$firstResult = "-";
|
||||
} else {
|
||||
$firstResult = "正常";
|
||||
if (empty($firstTimestamp)) {
|
||||
$firstResult = "缺卡";
|
||||
$styles["E{$index}"] = ["font" => ["color" => ["rgb" => "ff0000"]]];
|
||||
} elseif ($firstTimestamp > $startT + $secondStart) {
|
||||
$firstResult = "迟到";
|
||||
$styles["E{$index}"] = ["font" => ["color" => ["rgb" => "436FF6"]]];
|
||||
}
|
||||
}
|
||||
if (Base::time() < $startT + $secondEnd) {
|
||||
$lastResult = "-";
|
||||
$lastTimestamp = 0;
|
||||
} else {
|
||||
$lastResult = "正常";
|
||||
if (empty($lastTimestamp) || $lastTimestamp === $firstTimestamp) {
|
||||
$lastResult = "缺卡";
|
||||
$styles["G{$index}"] = ["font" => ["color" => ["rgb" => "ff0000"]]];
|
||||
} elseif ($lastTimestamp < $startT + $secondEnd) {
|
||||
$lastResult = "早退";
|
||||
$styles["G{$index}"] = ["font" => ["color" => ["rgb" => "436FF6"]]];
|
||||
}
|
||||
}
|
||||
$firstTimestamp = $firstTimestamp ? date("H:i", $firstTimestamp) : "-";
|
||||
$lastTimestamp = $lastTimestamp ? date("H:i", $lastTimestamp) : "-";
|
||||
$section = array_map(function($item) {
|
||||
return $item[0] . "-" . ($item[1] ?: "None");
|
||||
}, UserCheckinRecord::atSection($sameTimes));
|
||||
$datas[] = [
|
||||
"{$nickname} (ID: {$user->userid})",
|
||||
$sameDate,
|
||||
implode("-", $time),
|
||||
$firstTimestamp,
|
||||
$firstResult,
|
||||
$lastTimestamp,
|
||||
$lastResult,
|
||||
implode(", ", $section),
|
||||
];
|
||||
$startT += 86400;
|
||||
}
|
||||
$title = (count($sheets) + 1) . "." . ($nickname ?: $user->userid);
|
||||
$sheets[] = BillExport::create()->setTitle($title)->setHeadings($headings)->setData($datas)->setStyles($styles);
|
||||
}
|
||||
if (empty($sheets)) {
|
||||
return Base::retError('没有任何数据');
|
||||
}
|
||||
//
|
||||
$fileName = $users[0]->nickname;
|
||||
if (count($users) > 1) {
|
||||
$fileName .= "等" . count($userid) . "位成员";
|
||||
}
|
||||
$fileName .= '签到记录_' . Base::time() . '.xls';
|
||||
$filePath = "temp/checkin/export/" . date("Ym", Base::time());
|
||||
$export = new BillMultipleExport($sheets);
|
||||
$res = $export->store($filePath . "/" . $fileName);
|
||||
if ($res != 1) {
|
||||
return Base::retError('导出失败,' . $fileName . '!');
|
||||
}
|
||||
$xlsPath = storage_path("app/" . $filePath . "/" . $fileName);
|
||||
$zipFile = "app/" . $filePath . "/" . Base::rightDelete($fileName, '.xls') . ".zip";
|
||||
$zipPath = storage_path($zipFile);
|
||||
if (file_exists($zipPath)) {
|
||||
Base::deleteDirAndFile($zipPath, true);
|
||||
}
|
||||
try {
|
||||
Madzipper::make($zipPath)->add($xlsPath)->close();
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
//
|
||||
if (file_exists($zipPath)) {
|
||||
$base64 = base64_encode(Base::array2string([
|
||||
'file' => $zipFile,
|
||||
]));
|
||||
Session::put('checkin::export:userid', $user->userid);
|
||||
return Base::retSuccess('success', [
|
||||
'size' => Base::twoFloat(filesize($zipPath) / 1024, true),
|
||||
'url' => Base::fillUrl('api/system/checkin/down?key=' . urlencode($base64)),
|
||||
]);
|
||||
} else {
|
||||
return Base::retError('打包失败,请稍后再试...');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/checkin/down 22. 下载导出的签到数据
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName checkin__down
|
||||
*
|
||||
* @apiParam {String} key 通过export接口得到的下载钥匙
|
||||
*
|
||||
* @apiSuccess {File} data 返回数据(直接下载文件)
|
||||
*/
|
||||
public function checkin__down()
|
||||
{
|
||||
$userid = Session::get('checkin::export:userid');
|
||||
if (empty($userid)) {
|
||||
return Base::ajaxError("请求已过期,请重新导出!", [], 0, 502);
|
||||
}
|
||||
//
|
||||
$array = Base::string2array(base64_decode(urldecode(Request::input('key'))));
|
||||
$file = $array['file'];
|
||||
if (empty($file) || !file_exists(storage_path($file))) {
|
||||
return Base::ajaxError("文件不存在!", [], 0, 502);
|
||||
}
|
||||
return Response::download(storage_path($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/version 23. 获取版本号
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName version
|
||||
*
|
||||
* @apiSuccess {String} version
|
||||
* @apiSuccess {String} publish
|
||||
*/
|
||||
public function version()
|
||||
{
|
||||
$url = url('');
|
||||
$package = Base::getPackage();
|
||||
$array = [
|
||||
'version' => Base::getVersion(),
|
||||
'publish' => Arr::get($package, 'app.0.publish'),
|
||||
];
|
||||
if (is_array($package['app'])) {
|
||||
foreach ($package['app'] as $item) {
|
||||
$urls = $item['urls'] && is_array($item['urls']) ? $item['urls'] : $item['url'];
|
||||
if (is_array($item['publish']) && Base::hostContrast($url, $urls)) {
|
||||
$array['publish'] = $item['publish'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,25 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\File;
|
||||
use App\Module\Base;
|
||||
use App\Module\Extranet;
|
||||
use App\Module\RandomColor;
|
||||
use App\Tasks\AppPushTask;
|
||||
use App\Tasks\AutoArchivedTask;
|
||||
use App\Tasks\CheckinRemindTask;
|
||||
use App\Tasks\DeleteBotMsgTask;
|
||||
use App\Tasks\DeleteTmpTask;
|
||||
use App\Tasks\EmailNoticeTask;
|
||||
use App\Tasks\JokeSoupTask;
|
||||
use App\Tasks\LoopTask;
|
||||
use Arr;
|
||||
use Cache;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use LasseRafn\InitialAvatarGenerator\InitialAvatar;
|
||||
use Redirect;
|
||||
use Request;
|
||||
use Response;
|
||||
|
||||
|
||||
/**
|
||||
@@ -22,6 +36,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';
|
||||
}
|
||||
@@ -30,11 +48,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.3.2-8/web-apps/vendor/requirejs/require.js",
|
||||
"office/7.3.2-8/web-apps/apps/api/documents/api.js",
|
||||
"office/7.3.2-8/sdkjs/common/AllFonts.js",
|
||||
"office/7.3.2-8/web-apps/vendor/xregexp/xregexp-all-min.js",
|
||||
"office/7.3.2-8/web-apps/vendor/sockjs/sockjs.min.js",
|
||||
"office/7.3.2-8/web-apps/vendor/jszip/jszip.min.js",
|
||||
"office/7.3.2-8/web-apps/vendor/jszip-utils/jszip-utils.min.js",
|
||||
"office/7.3.2-8/sdkjs/common/libfont/wasm/fonts.js",
|
||||
"office/7.3.2-8/sdkjs/common/Charts/ChartStyles.js",
|
||||
"office/7.3.2-8/sdkjs/slide/themes//themes.js",
|
||||
|
||||
"office/7.3.2-8/web-apps/apps/presentationeditor/main/app.js",
|
||||
"office/7.3.2-8/sdkjs/slide/sdk-all-min.js",
|
||||
"office/7.3.2-8/sdkjs/slide/sdk-all.js",
|
||||
|
||||
"office/7.3.2-8/web-apps/apps/documenteditor/main/app.js",
|
||||
"office/7.3.2-8/sdkjs/word/sdk-all-min.js",
|
||||
"office/7.3.2-8/sdkjs/word/sdk-all.js",
|
||||
|
||||
"office/7.3.2-8/web-apps/apps/spreadsheeteditor/main/app.js",
|
||||
"office/7.3.2-8/sdkjs/cell/sdk-all-min.js",
|
||||
"office/7.3.2-8/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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,15 +184,175 @@ 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('task_worker', 12));
|
||||
Task::deliver(new DeleteTmpTask('tmp', 24));
|
||||
// 自动归档任务
|
||||
Task::deliver(new AutoArchivedTask());
|
||||
// 删除机器人消息
|
||||
Task::deliver(new DeleteBotMsgTask());
|
||||
// 周期任务
|
||||
Task::deliver(new LoopTask());
|
||||
// 签到提醒
|
||||
Task::deliver(new CheckinRemindTask());
|
||||
// 获取笑话/心灵鸡汤
|
||||
Task::deliver(new JokeSoupTask());
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
/**
|
||||
* 桌面客户端发布
|
||||
*/
|
||||
public function desktop__publish($name = '')
|
||||
{
|
||||
$genericVersion = Request::header('generic-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+$/", $genericVersion)) {
|
||||
if (version_compare($genericVersion, $latestVersion) > -1) { // 限制上传版本必须 ≥ 当前版本
|
||||
$genericPath = "uploads/desktop/{$genericVersion}/";
|
||||
$res = Base::upload([
|
||||
"file" => Request::file('file'),
|
||||
"type" => 'desktop',
|
||||
"path" => $genericPath,
|
||||
"fileName" => true
|
||||
]);
|
||||
if (Base::isSuccess($res)) {
|
||||
file_put_contents($latestFile, $genericVersion);
|
||||
}
|
||||
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", fileatime($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']) > fileatime($file)) {
|
||||
continue;
|
||||
}
|
||||
$fileName = Base::leftDelete($file, $dirPath);
|
||||
$apkFile = [
|
||||
'name' => substr($fileName, 1),
|
||||
'time' => date("Y-m-d H:i:s", fileatime($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)) {
|
||||
$genericVersion = file_get_contents($latestFile);
|
||||
if (preg_match("/^\d+\.\d+\.\d+$/", $genericVersion)) {
|
||||
$filePath = public_path("uploads/desktop/{$genericVersion}/{$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
|
||||
@@ -126,9 +414,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);
|
||||
|
||||
@@ -21,6 +21,9 @@ class VerifyCsrfToken extends Middleware
|
||||
// 保存任务优先级
|
||||
'api/system/priority/',
|
||||
|
||||
// 保存创建项目列表模板
|
||||
'api/system/column/template/',
|
||||
|
||||
// 添加任务
|
||||
'api/project/task/add/',
|
||||
|
||||
@@ -30,12 +33,18 @@ class VerifyCsrfToken extends Middleware
|
||||
// 修改任务
|
||||
'api/project/task/update/',
|
||||
|
||||
// 上传任务问题
|
||||
'api/project/task/upload/',
|
||||
// 聊天发文本
|
||||
'api/dialog/msg/sendtext/',
|
||||
|
||||
// 聊天发语音
|
||||
'api/dialog/msg/sendrecord/',
|
||||
|
||||
// 聊天发文件
|
||||
'api/dialog/msg/sendfile/',
|
||||
|
||||
// 聊天发匿名消息
|
||||
'api/dialog/msg/sendanon/',
|
||||
|
||||
// 保存文件内容
|
||||
'api/file/content/save/',
|
||||
|
||||
@@ -44,5 +53,17 @@ class VerifyCsrfToken extends Middleware
|
||||
|
||||
// 保存文件内容(上传)
|
||||
'api/file/content/upload/',
|
||||
|
||||
// 保存汇报
|
||||
'api/report/store/',
|
||||
|
||||
// 签到设置
|
||||
'api/users/checkin/save/',
|
||||
|
||||
// 签到上报
|
||||
'api/public/checkin/report/',
|
||||
|
||||
// 发布桌面端
|
||||
'desktop/publish/',
|
||||
];
|
||||
}
|
||||
|
||||
249
app/Ldap/LdapUser.php
Normal file
249
app/Ldap/LdapUser.php
Normal file
@@ -0,0 +1,249 @@
|
||||
<?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/";
|
||||
Base::makeDir(public_path($path));
|
||||
file_put_contents(public_path("{$path}{$user->userid}.jpeg"), $userimg);
|
||||
$user->userimg = "{$path}{$user->userid}.jpeg";
|
||||
}
|
||||
$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,7 +10,7 @@ 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()
|
||||
@@ -18,9 +18,13 @@ use Illuminate\Support\Facades\DB;
|
||||
* @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
|
||||
@@ -37,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
|
||||
@@ -45,7 +85,7 @@ class AbstractModel extends Model
|
||||
{
|
||||
try {
|
||||
return $this->save();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -72,6 +112,15 @@ class AbstractModel extends Model
|
||||
return $this->setAppends([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消隐藏值
|
||||
* @return static
|
||||
*/
|
||||
protected function scopeCancelHidden()
|
||||
{
|
||||
return $this->setHidden([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为数组 / JSON 序列化准备日期。
|
||||
* @param DateTimeInterface $date
|
||||
@@ -143,9 +192,10 @@ 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)) {
|
||||
@@ -155,8 +205,10 @@ class AbstractModel extends Model
|
||||
unset($array[$row->primaryKey]);
|
||||
}
|
||||
$row->updateInstance($array);
|
||||
$isInsert = true;
|
||||
} elseif ($update) {
|
||||
$row->updateInstance($update);
|
||||
$isInsert = false;
|
||||
}
|
||||
if (!$row->save()) {
|
||||
return null;
|
||||
|
||||
88
app/Models/Deleted.php
Normal file
88
app/Models/Deleted.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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
|
||||
{
|
||||
if (empty($time)) {
|
||||
return [];
|
||||
}
|
||||
return self::where([
|
||||
'type' => $type,
|
||||
'userid' => $userid
|
||||
])->where('created_at', '>=', Carbon::parse($time))->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) {
|
||||
self::updateInsert([
|
||||
'type' => $type,
|
||||
'did' => $id,
|
||||
'userid' => $value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use Request;
|
||||
*
|
||||
* @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 类型
|
||||
@@ -21,6 +22,7 @@ use Request;
|
||||
* @property int|null $size 大小(B)
|
||||
* @property int|null $userid 拥有者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
|
||||
@@ -37,6 +39,8 @@ use Request;
|
||||
* @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)
|
||||
@@ -51,22 +55,59 @@ class File extends AbstractModel
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* 是否有访问权限
|
||||
* @param $userid
|
||||
* @return int -1:没有权限,0:访问权限,1:读写权限,1000:所有者
|
||||
* 文件文件
|
||||
*/
|
||||
public function getPermission($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', 'png', 'gif', 'bmp'
|
||||
];
|
||||
|
||||
/**
|
||||
* 本地媒体文件
|
||||
*/
|
||||
const localExt = [
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
|
||||
'tif', 'tiff',
|
||||
'mp3', 'wav', 'mp4', 'flv',
|
||||
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
|
||||
];
|
||||
|
||||
/**
|
||||
* 是否有访问权限
|
||||
* @param array $userids
|
||||
* @return int -1:没有权限,0:访问权限,1:读写权限,1000:所有者或创建者
|
||||
*/
|
||||
public function getPermission(array $userids)
|
||||
{
|
||||
if ($userid == $this->userid) {
|
||||
// ① 自己的文件夹
|
||||
if (in_array($this->userid, $userids) || in_array($this->created_id, $userids)) {
|
||||
// ① 自己的文件夹 或 自己创建的文件夹
|
||||
return 1000;
|
||||
}
|
||||
$row = $this->getShareInfo();
|
||||
if ($row) {
|
||||
$fileUser = FileUser::whereFileId($row->id)->where(function ($query) use ($userid) {
|
||||
$query->where('userid', 0);
|
||||
$query->orWhere('userid', $userid);
|
||||
})->orderByDesc('permission')->first();
|
||||
$fileUser = FileUser::whereFileId($row->id)->whereIn('userid', $userids)->orderByDesc('permission')->first();
|
||||
if ($fileUser) {
|
||||
// ② 在指定共享成员内
|
||||
return $fileUser->permission;
|
||||
@@ -100,7 +141,7 @@ class File extends AbstractModel
|
||||
|
||||
/**
|
||||
* 是否处于共享文件夹内(不含自身)
|
||||
* @return bool
|
||||
* @return File|false
|
||||
*/
|
||||
public function isNnShare()
|
||||
{
|
||||
@@ -111,19 +152,28 @@ class File extends AbstractModel
|
||||
break;
|
||||
}
|
||||
if ($row->share) {
|
||||
return true;
|
||||
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 = null)
|
||||
public function updataShare($share = null)
|
||||
{
|
||||
if ($share === null) {
|
||||
$share = FileUser::whereFileId($this->id)->count() == 0 ? 0 : 1;
|
||||
@@ -132,10 +182,14 @@ class File extends AbstractModel
|
||||
AbstractModel::transaction(function () use ($share) {
|
||||
$this->share = $share;
|
||||
$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->setShare(0);
|
||||
$item->updataShare(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -143,6 +197,75 @@ class File extends AbstractModel
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历删除文件(夹)
|
||||
* @return bool
|
||||
@@ -152,8 +275,7 @@ class File extends AbstractModel
|
||||
AbstractModel::transaction(function () {
|
||||
$this->delete();
|
||||
$this->pushMsg('delete');
|
||||
FileLink::whereFileId($this->id)->delete();
|
||||
FileUser::whereFileId($this->id)->delete();
|
||||
FileUser::deleteFileAll($this->id);
|
||||
FileContent::whereFid($this->id)->delete();
|
||||
$list = self::wherePid($this->id)->get();
|
||||
if ($list->isNotEmpty()) {
|
||||
@@ -165,6 +287,29 @@ class File extends AbstractModel
|
||||
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
|
||||
@@ -179,19 +324,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;
|
||||
}
|
||||
@@ -215,28 +348,238 @@ class File extends AbstractModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件并检测权限
|
||||
* @param $id
|
||||
* @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者
|
||||
* @param $permission
|
||||
* 获取推送会员
|
||||
* @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 permissionFind($id, $limit = 0, &$permission = -1)
|
||||
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('文件不存在或已被删除');
|
||||
}
|
||||
//
|
||||
$permission = $file->getPermission(User::userid());
|
||||
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 => '没有访问权限',
|
||||
1000 => '仅限所有者或创建者操作',
|
||||
1 => '没有修改写入权限',
|
||||
default => '没有查看访问权限',
|
||||
};
|
||||
throw new ApiException($msg);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Response;
|
||||
|
||||
/**
|
||||
* App\Models\FileContent
|
||||
@@ -41,43 +40,99 @@ class FileContent extends AbstractModel
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* 获取格式内容
|
||||
* @param $type
|
||||
* @param $content
|
||||
* @return array|\Symfony\Component\HttpFoundation\BinaryFileResponse
|
||||
* 转预览地址
|
||||
* @param array $array
|
||||
* @return string
|
||||
*/
|
||||
public static function formatContent($type, $content)
|
||||
public static function toPreviewUrl($array)
|
||||
{
|
||||
$content = Base::json2array($content);
|
||||
if (in_array($type, ['word', 'excel', 'ppt'])) {
|
||||
$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)) {
|
||||
return Response::download(resource_path('assets/statics/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $type)));
|
||||
$filePath = 'assets/office/empty.' . str_replace(['word', 'excel', 'ppt'], ['docx', 'xlsx', 'pptx'], $file->type);
|
||||
}
|
||||
return Response::download(public_path($content['url']));
|
||||
}
|
||||
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(function() use ($filePath) {
|
||||
echo file_get_contents($filePath);
|
||||
}, $name);
|
||||
}
|
||||
if (empty($content)) {
|
||||
$content = match ($type) {
|
||||
$content = match ($file->type) {
|
||||
'document' => [
|
||||
"type" => "md",
|
||||
"type" => $file->ext,
|
||||
"content" => "",
|
||||
],
|
||||
'sheet' => [
|
||||
[
|
||||
"name" => "Sheet1",
|
||||
"config" => json_decode('{}'),
|
||||
]
|
||||
],
|
||||
default => json_decode('{}'),
|
||||
};
|
||||
if ($download) {
|
||||
abort(403, "This file is empty.");
|
||||
}
|
||||
} else {
|
||||
$content['preview'] = false;
|
||||
if ($content['ext'] && !in_array($content['ext'], ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'])) {
|
||||
$url = 'http://' . env('APP_IPPR') . '.3/' . $content['url'];
|
||||
if (in_array($type, ['picture', 'image', 'tif', 'media'])) {
|
||||
$url = Base::fillUrl($content['url']);
|
||||
$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(function() use ($filePath) {
|
||||
echo file_get_contents($filePath);
|
||||
}, $name);
|
||||
} else {
|
||||
abort(403, "This file not support download.");
|
||||
}
|
||||
$content['url'] = base64_encode($url);
|
||||
$content['preview'] = true;
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('success', [ 'content' => $content ]);
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\FileLink
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $file_id 项目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
|
||||
@@ -21,6 +24,7 @@ namespace App\Models;
|
||||
* @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
|
||||
@@ -32,4 +36,35 @@ class FileLink extends AbstractModel
|
||||
{
|
||||
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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,34 @@ namespace App\Models;
|
||||
*/
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
34
app/Models/Meeting.php
Normal file
34
app/Models/Meeting.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* App\Models\Meeting
|
||||
*
|
||||
* @property int $id
|
||||
* @property string|null $meetingid 会议ID,不是数字
|
||||
* @property string|null $name 会议主题
|
||||
* @property string|null $channel 频道
|
||||
* @property int|null $userid 创建人
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property string|null $end_at
|
||||
* @property \Illuminate\Support\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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
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;
|
||||
@@ -17,6 +19,8 @@ use Request;
|
||||
* @property string|null $name 名称
|
||||
* @property string|null $desc 描述、备注
|
||||
* @property int|null $userid 创建人
|
||||
* @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 归档会员
|
||||
@@ -44,7 +48,9 @@ use Request;
|
||||
* @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 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()
|
||||
@@ -112,6 +118,7 @@ class Project extends AbstractModel
|
||||
->select([
|
||||
'projects.*',
|
||||
'project_users.owner',
|
||||
'project_users.top_at',
|
||||
])
|
||||
->leftJoin('project_users', function ($leftJoin) use ($userid) {
|
||||
$leftJoin
|
||||
@@ -135,6 +142,7 @@ class Project extends AbstractModel
|
||||
->select([
|
||||
'projects.*',
|
||||
'project_users.owner',
|
||||
'project_users.top_at',
|
||||
])
|
||||
->join('project_users', 'projects.id', '=', 'project_users.project_id')
|
||||
->where('project_users.userid', $userid);
|
||||
@@ -199,9 +207,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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -211,7 +221,7 @@ class Project extends AbstractModel
|
||||
*/
|
||||
public function relationUserids()
|
||||
{
|
||||
return $this->projectUser->pluck('userid')->toArray();
|
||||
return ProjectUser::whereProjectId($this->id)->orderBy('id')->pluck('userid')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,8 +251,8 @@ class Project extends AbstractModel
|
||||
$this->archived_at = null;
|
||||
$this->archived_userid = User::userid();
|
||||
$this->addLog("项目取消归档");
|
||||
$this->pushMsg('add', $this);
|
||||
ProjectTask::whereProjectId($this->id)->whereArchivedFollow(1)->update([
|
||||
$this->pushMsg('recovery', $this);
|
||||
ProjectTask::whereProjectId($this->id)->whereArchivedFollow(1)->change([
|
||||
'archived_at' => null,
|
||||
'archived_follow' => 0
|
||||
]);
|
||||
@@ -252,7 +262,7 @@ class Project extends AbstractModel
|
||||
$this->archived_userid = User::userid();
|
||||
$this->addLog("项目归档");
|
||||
$this->pushMsg('archived');
|
||||
ProjectTask::whereProjectId($this->id)->whereArchivedAt(null)->update([
|
||||
ProjectTask::whereProjectId($this->id)->whereArchivedAt(null)->change([
|
||||
'archived_at' => $archived_at,
|
||||
'archived_follow' => 1
|
||||
]);
|
||||
@@ -350,6 +360,201 @@ class Project extends AbstractModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加工作流
|
||||
* @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']);
|
||||
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,
|
||||
], [], $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},{"id":-11,"name":"进行中","status":"progress","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-12,"name":"待测试","status":"test","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-13,"name":"已完成","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0},{"id":-14,"name":"已取消","status":"end","turns":[-10,-11,-12,-13,-14],"userids":[],"usertype":"add","userlimit":0}]'));
|
||||
}
|
||||
});
|
||||
//
|
||||
$data = Project::find($project->id);
|
||||
$data->addLog("创建项目");
|
||||
$data->pushMsg('add', $data);
|
||||
return Base::retSuccess('添加成功', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目信息(用于判断会员是否存在项目内)
|
||||
* @param int $project_id
|
||||
|
||||
@@ -13,7 +13,7 @@ use App\Module\Base;
|
||||
* @property string|null $name 名称
|
||||
* @property string|null $status 状态
|
||||
* @property array $turns 可流转
|
||||
* @property array $userids 自动负责人ID
|
||||
* @property array $userids 状态负责人ID
|
||||
* @property string|null $usertype 流转模式
|
||||
* @property int|null $userlimit 限制负责人
|
||||
* @property int|null $sort 排序
|
||||
@@ -81,7 +81,7 @@ class ProjectFlowItem extends AbstractModel
|
||||
*/
|
||||
public function deleteFlowItem()
|
||||
{
|
||||
ProjectTask::whereFlowItemId($this->id)->update([
|
||||
ProjectTask::whereFlowItemId($this->id)->change([
|
||||
'flow_item_id' => 0,
|
||||
'flow_item_name' => "",
|
||||
]);
|
||||
|
||||
@@ -10,7 +10,7 @@ use App\Module\Base;
|
||||
* @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 记录数据
|
||||
|
||||
@@ -8,7 +8,6 @@ use App\Tasks\PushTask;
|
||||
use Arr;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Exception;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Request;
|
||||
@@ -37,9 +36,12 @@ use Request;
|
||||
* @property string|null $p_name 优先级名称
|
||||
* @property string|null $p_color 优先级颜色
|
||||
* @property int|null $sort 排序(ASC)
|
||||
* @property string|null $loop 重复周期
|
||||
* @property string|null $loop_at 下一次重复时间
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
* @property int|null $deleted_userid 删除会员
|
||||
* @property-read \App\Models\ProjectTaskContent|null $content
|
||||
* @property-read int $file_num
|
||||
* @property-read int $msg_num
|
||||
@@ -58,7 +60,7 @@ use Request;
|
||||
* @property-read int|null $task_user_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask allData($userid = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask authData($userid = null, $owner = null)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask betweenTime($start, $end, $type = 'taskTime')
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectTask onlyTrashed()
|
||||
@@ -71,12 +73,15 @@ use Request;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereCompleteAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDeletedUserid($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDesc($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereDialogId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereEndAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereFlowItemName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoop($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereLoopAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask whereName($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePColor($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTask wherePLevel($value)
|
||||
@@ -235,7 +240,7 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function content(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(ProjectTaskContent::class, 'task_id', 'id');
|
||||
return $this->hasOne(ProjectTaskContent::class, 'task_id', 'id')->orderByDesc('id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,7 +248,7 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function taskFile(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(ProjectTaskFile::class, 'task_id', 'id')->orderBy('id');
|
||||
return $this->hasMany(ProjectTaskFile::class, 'task_id', 'id')->orderByDesc('id')->limit(50);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,6 +304,7 @@ class ProjectTask extends AbstractModel
|
||||
'project_tasks.*',
|
||||
'project_task_users.owner'
|
||||
])
|
||||
->selectRaw("1 AS assist")
|
||||
->join('project_task_users', 'project_tasks.id', '=', 'project_task_users.task_id')
|
||||
->where('project_task_users.userid', $userid);
|
||||
if ($owner !== null) {
|
||||
@@ -312,18 +318,33 @@ class ProjectTask extends AbstractModel
|
||||
* @param $query
|
||||
* @param $start
|
||||
* @param $end
|
||||
* @param $type
|
||||
* @return mixed
|
||||
*/
|
||||
public function scopeBetweenTime($query, $start, $end)
|
||||
public function scopeBetweenTime($query, $start, $end, $type = 'taskTime')
|
||||
{
|
||||
$query->where(function ($q1) use ($start, $end) {
|
||||
$q1->where(function ($q2) use ($start) {
|
||||
$q2->where('project_tasks.start_at', '<=', $start)->where('project_tasks.end_at', '>=', $start);
|
||||
})->orWhere(function ($q2) use ($end) {
|
||||
$q2->where('project_tasks.start_at', '<=', $end)->where('project_tasks.end_at', '>=', $end);
|
||||
})->orWhere(function ($q2) use ($start, $end) {
|
||||
$q2->where('project_tasks.start_at', '>', $start)->where('project_tasks.end_at', '<', $end);
|
||||
});
|
||||
$query->where(function ($q1) use ($start, $end, $type) {
|
||||
switch ($type) {
|
||||
case 'createdTime':
|
||||
$q1->where(function ($q2) use ($start) {
|
||||
$q2->where('project_tasks.created_at', '>=', $start);
|
||||
})->orWhere(function ($q2) use ($end) {
|
||||
$q2->where('project_tasks.created_at', '<=', $end);
|
||||
})->orWhere(function ($q2) use ($start, $end) {
|
||||
$q2->where('project_tasks.created_at', '>', $start)->where('project_tasks.created_at', '<', $end);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
$q1->where(function ($q2) use ($start) {
|
||||
$q2->where('project_tasks.start_at', '<=', $start)->where('project_tasks.end_at', '>=', $start);
|
||||
})->orWhere(function ($q2) use ($end) {
|
||||
$q2->where('project_tasks.start_at', '<=', $end)->where('project_tasks.end_at', '>=', $end);
|
||||
})->orWhere(function ($q2) use ($start, $end) {
|
||||
$q2->where('project_tasks.start_at', '>', $start)->where('project_tasks.end_at', '<', $end);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
return $query;
|
||||
}
|
||||
@@ -342,11 +363,13 @@ class ProjectTask extends AbstractModel
|
||||
$content = $data['content'];
|
||||
$times = $data['times'];
|
||||
$owner = $data['owner'];
|
||||
$add_assist = intval($data['add_assist']);
|
||||
$subtasks = $data['subtasks'];
|
||||
$p_level = intval($data['p_level']);
|
||||
$p_name = $data['p_name'];
|
||||
$p_color = $data['p_color'];
|
||||
$top = intval($data['top']);
|
||||
$userid = User::userid();
|
||||
//
|
||||
if (ProjectTask::whereProjectId($project_id)
|
||||
->whereNull('project_tasks.complete_at')
|
||||
@@ -411,8 +434,13 @@ class ProjectTask extends AbstractModel
|
||||
$tmpArray[] = $uid;
|
||||
}
|
||||
$owner = $tmpArray;
|
||||
// 协助人员
|
||||
$assist = [];
|
||||
if (!in_array($userid, $owner) && $add_assist) {
|
||||
$assist = [$userid];
|
||||
}
|
||||
// 创建人
|
||||
$task->userid = User::userid();
|
||||
$task->userid = $userid;
|
||||
// 排序位置
|
||||
if ($top) {
|
||||
$task->sort = intval(self::whereColumnId($task->column_id)->orderBy('sort')->value('sort')) - 1;
|
||||
@@ -434,7 +462,7 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
}
|
||||
//
|
||||
return AbstractModel::transaction(function() use ($times, $subtasks, $content, $owner, $task) {
|
||||
return AbstractModel::transaction(function() use ($assist, $times, $subtasks, $content, $owner, $task) {
|
||||
$task->save();
|
||||
$owner = array_values(array_unique($owner));
|
||||
foreach ($owner as $uid) {
|
||||
@@ -446,11 +474,23 @@ class ProjectTask extends AbstractModel
|
||||
'owner' => 1,
|
||||
])->save();
|
||||
}
|
||||
$assist = array_values(array_unique(array_diff($assist, $owner)));
|
||||
foreach ($assist as $uid) {
|
||||
ProjectTaskUser::createInstance([
|
||||
'project_id' => $task->project_id,
|
||||
'task_id' => $task->id,
|
||||
'task_pid' => $task->parent_id ?: $task->id,
|
||||
'userid' => $uid,
|
||||
'owner' => 0,
|
||||
])->save();
|
||||
}
|
||||
if ($content) {
|
||||
ProjectTaskContent::createInstance([
|
||||
'project_id' => $task->project_id,
|
||||
'task_id' => $task->id,
|
||||
'content' => $content,
|
||||
'content' => [
|
||||
'url' => ProjectTaskContent::saveContent($task->id, $content)
|
||||
],
|
||||
])->save();
|
||||
}
|
||||
if ($task->parent_id == 0 && $subtasks && is_array($subtasks)) {
|
||||
@@ -491,13 +531,15 @@ class ProjectTask extends AbstractModel
|
||||
{
|
||||
AbstractModel::transaction(function () use ($data, &$updateMarking) {
|
||||
// 判断版本
|
||||
if (version_compare(Base::getClientVersion(), '0.6.0', '<')) {
|
||||
throw new ApiException('当前版本过低');
|
||||
}
|
||||
Base::checkClientVersion('0.19.0');
|
||||
// 主任务
|
||||
$mainTask = $this->parent_id > 0 ? self::find($this->parent_id) : null;
|
||||
// 工作流
|
||||
if (Arr::exists($data, 'flow_item_id')) {
|
||||
$isProjectOwner = $this->useridInTheProject(User::userid()) === 2;
|
||||
if (!$isProjectOwner && !$this->isOwner()) {
|
||||
throw new ApiException('仅限项目或任务负责人修改任务状态');
|
||||
}
|
||||
if ($this->flow_item_id == $data['flow_item_id']) {
|
||||
throw new ApiException('任务状态未发生改变');
|
||||
}
|
||||
@@ -518,8 +560,7 @@ class ProjectTask extends AbstractModel
|
||||
throw new ApiException("当前状态[{$currentFlowItem->name}]不可流转到[{$newFlowItem->name}]");
|
||||
}
|
||||
if ($currentFlowItem->userlimit) {
|
||||
if (!in_array(User::userid(), $currentFlowItem->userids)
|
||||
&& !ProjectUser::whereProjectId($this->project_id)->whereOwner(1)->exists()) {
|
||||
if (!$isProjectOwner && !in_array(User::userid(), $currentFlowItem->userids)) {
|
||||
throw new ApiException("当前状态[{$currentFlowItem->name}]仅限状态负责人或项目负责人修改");
|
||||
}
|
||||
}
|
||||
@@ -541,13 +582,17 @@ class ProjectTask extends AbstractModel
|
||||
if ($newFlowItem->userids) {
|
||||
// 判断自动添加负责人
|
||||
$flowData['owner'] = $data['owner'] = $this->taskUser->where('owner', 1)->pluck('userid')->toArray();
|
||||
if ($newFlowItem->usertype == "replace") {
|
||||
// 流转模式
|
||||
if (in_array($newFlowItem->usertype, ["replace", "merge"])) {
|
||||
// 流转模式、剔除模式
|
||||
if ($this->parent_id === 0) {
|
||||
$flowData['assist'] = $data['assist'] = $this->taskUser->where('owner', 0)->pluck('userid')->toArray();
|
||||
$data['assist'] = array_merge($data['assist'], $data['owner']);
|
||||
}
|
||||
$data['owner'] = $newFlowItem->userids;
|
||||
// 判断剔除模式:保留操作状态的人员
|
||||
if ($newFlowItem->usertype == "merge") {
|
||||
$data['owner'][] = User::userid();
|
||||
}
|
||||
} else {
|
||||
// 添加模式
|
||||
$data['owner'] = array_merge($data['owner'], $newFlowItem->userids);
|
||||
@@ -563,6 +608,14 @@ class ProjectTask extends AbstractModel
|
||||
'flow' => $flowData,
|
||||
'change' => [$currentFlowItem?->name, $newFlowItem->name]
|
||||
]);
|
||||
ProjectTaskFlowChange::createInstance([
|
||||
'task_id' => $this->id,
|
||||
'userid' => User::userid(),
|
||||
'before_flow_item_id' => $flowData['flow_item_id'],
|
||||
'before_flow_item_name' => $flowData['flow_item_name'],
|
||||
'after_flow_item_id' => $this->flow_item_id,
|
||||
'after_flow_item_name' => $this->flow_item_name,
|
||||
])->save();
|
||||
}
|
||||
// 状态
|
||||
if (Arr::exists($data, 'complete_at')) {
|
||||
@@ -575,7 +628,7 @@ class ProjectTask extends AbstractModel
|
||||
if ($this->complete_at) {
|
||||
throw new ApiException('任务已完成');
|
||||
}
|
||||
$this->completeTask(Carbon::now());
|
||||
$this->completeTask(Carbon::now(), isset($newFlowItem) ? $newFlowItem->name : null);
|
||||
} else {
|
||||
// 标记未完成
|
||||
if (!$this->complete_at) {
|
||||
@@ -584,7 +637,6 @@ class ProjectTask extends AbstractModel
|
||||
$this->completeTask(null);
|
||||
}
|
||||
$updateMarking['is_update_project'] = true;
|
||||
return;
|
||||
}
|
||||
// 标题
|
||||
if (Arr::exists($data, 'name') && $this->name != $data['name']) {
|
||||
@@ -597,10 +649,13 @@ class ProjectTask extends AbstractModel
|
||||
'change' => [$this->name, $data['name']]
|
||||
]);
|
||||
$this->name = $data['name'];
|
||||
if ($this->dialog_id) {
|
||||
WebSocketDialog::updateData(['id' => $this->dialog_id], ['name' => $this->name]);
|
||||
}
|
||||
}
|
||||
// 负责人
|
||||
if (Arr::exists($data, 'owner')) {
|
||||
$count = $this->taskUser->where('owner', 1)->count();
|
||||
$older = $this->taskUser->where('owner', 1)->pluck('userid')->toArray();
|
||||
$array = [];
|
||||
$owner = is_array($data['owner']) ? $data['owner'] : [$data['owner']];
|
||||
if (count($owner) > 10) {
|
||||
@@ -619,20 +674,18 @@ class ProjectTask extends AbstractModel
|
||||
'owner' => 1,
|
||||
]);
|
||||
$array[] = $uid;
|
||||
if ($this->parent_id) {
|
||||
break; // 子任务只能是一个负责人
|
||||
}
|
||||
}
|
||||
if ($array) {
|
||||
if ($count == 0 && count($array) == 1 && $array[0] == User::userid()) {
|
||||
if (count($older) == 0 && count($array) == 1 && $array[0] == User::userid()) {
|
||||
$this->addLog("认领{任务}");
|
||||
} else {
|
||||
$this->addLog("修改{任务}负责人", ['userid' => $array]);
|
||||
}
|
||||
$this->taskPush(array_values(array_diff($array, $older)), 0);
|
||||
}
|
||||
$rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(1)->whereNotIn('userid', $array)->get();
|
||||
if ($rows->isNotEmpty()) {
|
||||
$this->addLog("删除{任务}负责人", ['userid' => $rows->implode('userid', ',')]);
|
||||
$this->addLog("删除{任务}负责人", ['userid' => $rows->pluck('userid')]);
|
||||
foreach ($rows as $row) {
|
||||
$row->delete();
|
||||
}
|
||||
@@ -643,10 +696,11 @@ class ProjectTask extends AbstractModel
|
||||
// 计划时间(原则:子任务时间在主任务时间内)
|
||||
if (Arr::exists($data, 'times')) {
|
||||
$oldAt = [Carbon::parse($this->start_at), Carbon::parse($this->end_at)];
|
||||
$oldStringAt = $this->start_at ? ($oldAt[0]->toDateTimeString() . '~' . $oldAt[1]->toDateTimeString()) : '';
|
||||
$this->start_at = null;
|
||||
$this->end_at = null;
|
||||
$times = $data['times'];
|
||||
list($start, $end) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []);
|
||||
list($start, $end, $desc) = is_string($times) ? explode(",", $times) : (is_array($times) ? $times : []);
|
||||
if (Base::isDate($start) && Base::isDate($end) && $start != $end) {
|
||||
$start_at = Carbon::parse($start);
|
||||
$end_at = Carbon::parse($end);
|
||||
@@ -686,14 +740,18 @@ class ProjectTask extends AbstractModel
|
||||
$start_at = Carbon::parse($subTask->start_at);
|
||||
$end_at = Carbon::parse($subTask->end_at);
|
||||
$isUp = false;
|
||||
if ($start_at->eq($oldAt[0]) || $start_at->lt(Carbon::parse($this->start_at))) {
|
||||
if (empty($subTask->start_at) || $start_at->eq($oldAt[0]) || $start_at->lt(Carbon::parse($this->start_at))) {
|
||||
$subTask->start_at = $this->start_at;
|
||||
$isUp = true;
|
||||
}
|
||||
if ($end_at->eq($oldAt[1]) || $end_at->gt(Carbon::parse($this->end_at))) {
|
||||
if (empty($subTask->end_at) || $end_at->eq($oldAt[1]) || $end_at->gt(Carbon::parse($this->end_at))) {
|
||||
$subTask->end_at = $this->end_at;
|
||||
$isUp = true;
|
||||
}
|
||||
if ($subTask->start_at && Carbon::parse($subTask->start_at)->gt($subTask->end_at)) {
|
||||
$subTask->start_at = $this->start_at;
|
||||
$isUp = true;
|
||||
}
|
||||
if ($isUp) {
|
||||
$updateMarking['is_update_subtask'] = true;
|
||||
$subTask->addLog("同步修改{任务}时间");
|
||||
@@ -702,10 +760,39 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
});
|
||||
}
|
||||
$this->addLog("修改{任务}时间");
|
||||
$newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
|
||||
$newDesc = $desc ? "(备注:{$desc})" : "";
|
||||
$this->addLog("修改{任务}时间" . $newDesc, [
|
||||
'change' => [$oldStringAt, $newStringAt]
|
||||
]);
|
||||
$this->taskPush(null, 3, $newDesc);
|
||||
}
|
||||
// 以下紧顶级任务可修改
|
||||
// 以下仅顶级任务可修改
|
||||
if ($this->parent_id === 0) {
|
||||
// 重复周期
|
||||
$loopAt = $this->loop_at;
|
||||
$loopDesc = $this->loopDesc();
|
||||
if (Arr::exists($data, 'loop')) {
|
||||
$this->loop = $data['loop'];
|
||||
if (!$this->refreshLoop()) {
|
||||
throw new ApiException('重复周期选择错误');
|
||||
}
|
||||
} elseif (Arr::exists($data, 'times')) {
|
||||
// 更新任务时间也要更新重复周期
|
||||
$this->refreshLoop();
|
||||
}
|
||||
$oldLoop = $loopAt ? Carbon::parse($loopAt)->toDateTimeString() : null;
|
||||
$newLoop = $this->loop_at ? Carbon::parse($this->loop_at)->toDateTimeString() : null;
|
||||
if ($oldLoop != $newLoop) {
|
||||
$this->addLog("修改{任务}下个周期", [
|
||||
'change' => [$oldLoop, $newLoop]
|
||||
]);
|
||||
}
|
||||
if ($loopDesc != $this->loopDesc()) {
|
||||
$this->addLog("修改{任务}重复周期", [
|
||||
'change' => [$loopDesc, $this->loopDesc()]
|
||||
]);
|
||||
}
|
||||
// 协助人员
|
||||
if (Arr::exists($data, 'assist')) {
|
||||
$array = [];
|
||||
@@ -732,7 +819,7 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
$rows = ProjectTaskUser::whereTaskId($this->id)->whereOwner(0)->whereNotIn('userid', $array)->get();
|
||||
if ($rows->isNotEmpty()) {
|
||||
$this->addLog("删除{任务}协助人员", ['userid' => $rows->implode('userid', ',')]);
|
||||
$this->addLog("删除{任务}协助人员", ['userid' => $rows->pluck('userid')]);
|
||||
foreach ($rows as $row) {
|
||||
$row->delete();
|
||||
}
|
||||
@@ -760,18 +847,20 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
// 内容
|
||||
if (Arr::exists($data, 'content')) {
|
||||
ProjectTaskContent::updateInsert([
|
||||
ProjectTaskContent::createInstance([
|
||||
'project_id' => $this->project_id,
|
||||
'task_id' => $this->id,
|
||||
], [
|
||||
'content' => $data['content'],
|
||||
]);
|
||||
'content' => [
|
||||
'url' => ProjectTaskContent::saveContent($this->id, $data['content'])
|
||||
],
|
||||
])->save();
|
||||
$this->desc = Base::getHtml($data['content'], 100);
|
||||
$this->addLog("修改{任务}详细描述");
|
||||
$updateMarking['is_update_content'] = true;
|
||||
}
|
||||
// 优先级
|
||||
$p = false;
|
||||
$oldPName = $this->p_name;
|
||||
if (Arr::exists($data, 'p_level') && $this->p_level != $data['p_level']) {
|
||||
$this->p_level = intval($data['p_level']);
|
||||
$p = true;
|
||||
@@ -785,7 +874,9 @@ class ProjectTask extends AbstractModel
|
||||
$p = true;
|
||||
}
|
||||
if ($p) {
|
||||
$this->addLog("修改{任务}优先级");
|
||||
$this->addLog("修改{任务}优先级", [
|
||||
'change' => [$oldPName, $this->p_name]
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->save();
|
||||
@@ -795,6 +886,141 @@ class ProjectTask extends AbstractModel
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新重复周期时间
|
||||
* @param bool $save 是否执行保存
|
||||
* @return bool
|
||||
*/
|
||||
public function refreshLoop($save = false)
|
||||
{
|
||||
$success = true;
|
||||
if ($this->start_at) {
|
||||
$base = Carbon::parse($this->start_at);
|
||||
if ($base->lt(Carbon::today())) {
|
||||
// 如果任务开始时间小于今天则基数时间为今天
|
||||
$base = Carbon::parse(date("Y-m-d {$base->toTimeString()}"));
|
||||
}
|
||||
} else {
|
||||
// 未设置任务时间时基数时间为今天
|
||||
$base = Carbon::today();
|
||||
}
|
||||
switch ($this->loop) {
|
||||
case "day":
|
||||
$this->loop_at = $base->addDay();
|
||||
break;
|
||||
case "weekdays":
|
||||
$this->loop_at = $base->addWeekday();
|
||||
break;
|
||||
case "week":
|
||||
$this->loop_at = $base->addWeek();
|
||||
break;
|
||||
case "twoweeks":
|
||||
$this->loop_at = $base->addWeeks(2);
|
||||
break;
|
||||
case "month":
|
||||
$this->loop_at = $base->addMonth();
|
||||
break;
|
||||
case "year":
|
||||
$this->loop_at = $base->addYear();
|
||||
break;
|
||||
case "never":
|
||||
$this->loop_at = null;
|
||||
break;
|
||||
default:
|
||||
if (Base::isNumber($this->loop)) {
|
||||
$this->loop_at = $base->addDays($this->loop);
|
||||
} else {
|
||||
$this->loop_at = null;
|
||||
$success = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ($success && $save) {
|
||||
$this->save();
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取周期描述
|
||||
* @return string
|
||||
*/
|
||||
public function loopDesc() {
|
||||
$loopDesc = "从不";
|
||||
switch ($this->loop) {
|
||||
case "day":
|
||||
$loopDesc = "每天";
|
||||
break;
|
||||
case "weekdays":
|
||||
$loopDesc = "每个工作日";
|
||||
break;
|
||||
case "week":
|
||||
$loopDesc = "每周";
|
||||
break;
|
||||
case "twoweeks":
|
||||
$loopDesc = "每两周";
|
||||
break;
|
||||
case "month":
|
||||
$loopDesc = "每月";
|
||||
break;
|
||||
case "year":
|
||||
$loopDesc = "每年";
|
||||
break;
|
||||
default:
|
||||
if (Base::isNumber($this->loop)) {
|
||||
$loopDesc = "每{$this->loop}天";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $loopDesc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制任务
|
||||
* @return self
|
||||
*/
|
||||
public function copyTask()
|
||||
{
|
||||
if ($this->parent_id > 0) {
|
||||
throw new ApiException('子任务禁止复制');
|
||||
}
|
||||
return AbstractModel::transaction(function() {
|
||||
// 复制任务
|
||||
$task = $this->replicate();
|
||||
$task->dialog_id = 0;
|
||||
$task->archived_at = null;
|
||||
$task->archived_userid = 0;
|
||||
$task->archived_follow = 0;
|
||||
$task->complete_at = null;
|
||||
$task->created_at = Carbon::now();
|
||||
$task->save();
|
||||
// 复制任务内容
|
||||
if ($this->content) {
|
||||
$tmp = $this->content->replicate();
|
||||
$tmp->task_id = $task->id;
|
||||
$tmp->created_at = Carbon::now();
|
||||
$tmp->save();
|
||||
}
|
||||
// 复制任务附件
|
||||
foreach ($this->taskFile as $taskFile) {
|
||||
$tmp = $taskFile->replicate();
|
||||
$tmp->task_id = $task->id;
|
||||
$tmp->created_at = Carbon::now();
|
||||
$tmp->save();
|
||||
}
|
||||
// 复制任务成员
|
||||
foreach ($this->taskUser as $taskUser) {
|
||||
$tmp = $taskUser->replicate();
|
||||
$tmp->task_id = $task->id;
|
||||
$tmp->task_pid = $task->id;
|
||||
$tmp->created_at = Carbon::now();
|
||||
$tmp->save();
|
||||
}
|
||||
//
|
||||
return $task;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步项目成员至聊天室
|
||||
*/
|
||||
@@ -814,9 +1040,11 @@ class ProjectTask 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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -826,7 +1054,7 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function relationUserids()
|
||||
{
|
||||
$userids = $this->taskUser->pluck('userid')->toArray();
|
||||
$userids = ProjectTaskUser::whereTaskId($this->id)->orderByDesc('owner')->orderByDesc('id')->pluck('userid')->toArray();
|
||||
$items = ProjectTask::with(['taskUser'])->where('parent_id', $this->id)->whereNull('archived_at')->get();
|
||||
foreach ($items as $item) {
|
||||
$userids = array_merge($userids, $item->taskUser->pluck('userid')->toArray());
|
||||
@@ -862,6 +1090,78 @@ class ProjectTask extends AbstractModel
|
||||
return $user->owner ? 2 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限版本
|
||||
* @param int $level
|
||||
* 1:负责人
|
||||
* 2:协助人/负责人
|
||||
* 3:创建人/协助人/负责人
|
||||
* 4:任务群聊成员/3
|
||||
* @return bool
|
||||
*/
|
||||
public function permission($level = 1)
|
||||
{
|
||||
if ($level >= 4) {
|
||||
return $this->permission(3) || $this->existDialogUser();
|
||||
}
|
||||
if ($level >= 3 && $this->isCreater()) {
|
||||
return true;
|
||||
}
|
||||
if ($level >= 2 && $this->isAssister()) {
|
||||
return true;
|
||||
}
|
||||
return $this->isOwner();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否在任务对话里
|
||||
* @return bool
|
||||
*/
|
||||
public function existDialogUser()
|
||||
{
|
||||
return $this->dialog_id && WebSocketDialogUser::whereDialogId($this->dialog_id)->whereUserid(User::userid())->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否创建者
|
||||
* @return bool
|
||||
*/
|
||||
public function isCreater()
|
||||
{
|
||||
return $this->userid == User::userid();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否协助人员
|
||||
* @return bool
|
||||
*/
|
||||
public function isAssister()
|
||||
{
|
||||
$row = $this;
|
||||
while ($row->parent_id > 0) {
|
||||
$row = self::find($row->parent_id);
|
||||
}
|
||||
return ProjectTaskUser::whereTaskId($row->id)->whereUserid(User::userid())->whereOwner(0)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否负责人(或者是主任务的负责人)
|
||||
* @return bool
|
||||
*/
|
||||
public function isOwner()
|
||||
{
|
||||
if ($this->owner) {
|
||||
return true;
|
||||
}
|
||||
if ($this->parent_id > 0) {
|
||||
$mainTask = self::allData()->find($this->parent_id);
|
||||
if ($mainTask->owner) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有负责人
|
||||
* @return bool
|
||||
@@ -877,15 +1177,22 @@ class ProjectTask extends AbstractModel
|
||||
/**
|
||||
* 标记已完成、未完成
|
||||
* @param Carbon|null $complete_at 完成时间
|
||||
* @param String $complete_name 已完成名称(留空为:已完成)
|
||||
* @return bool
|
||||
*/
|
||||
public function completeTask($complete_at)
|
||||
public function completeTask($complete_at, $complete_name = null)
|
||||
{
|
||||
AbstractModel::transaction(function () use ($complete_at) {
|
||||
AbstractModel::transaction(function () use ($complete_at, $complete_name) {
|
||||
$addMsg = $this->parent_id == 0 && $this->dialog_id > 0;
|
||||
if ($complete_at === null) {
|
||||
// 标记未完成
|
||||
$this->complete_at = null;
|
||||
$this->addLog("标记{任务}未完成");
|
||||
if ($addMsg) {
|
||||
WebSocketDialogMsg::sendMsg(null, $this->dialog_id, 'notice', [
|
||||
'notice' => "标记任务未完成"
|
||||
], 0, true, true);
|
||||
}
|
||||
} else {
|
||||
// 标记已完成
|
||||
if ($this->parent_id == 0) {
|
||||
@@ -896,8 +1203,16 @@ class ProjectTask extends AbstractModel
|
||||
if (!$this->hasOwner()) {
|
||||
throw new ApiException('请先领取任务');
|
||||
}
|
||||
if (empty($complete_name)) {
|
||||
$complete_name = '已完成';
|
||||
}
|
||||
$this->complete_at = $complete_at;
|
||||
$this->addLog("标记{任务}已完成");
|
||||
$this->addLog("标记{任务}{$complete_name}");
|
||||
if ($addMsg) {
|
||||
WebSocketDialogMsg::sendMsg(null, $this->dialog_id, 'notice', [
|
||||
'notice' => "标记任务{$complete_name}"
|
||||
], 0, true, true);
|
||||
}
|
||||
}
|
||||
$this->save();
|
||||
});
|
||||
@@ -911,6 +1226,16 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public function archivedTask($archived_at, $isAuto = false)
|
||||
{
|
||||
if (!$this->complete_at) {
|
||||
$flowItems = ProjectFlowItem::whereProjectId($this->project_id)->whereStatus('end')->pluck('name');
|
||||
if ($flowItems) {
|
||||
$flowItems = implode(",", array_values(array_unique($flowItems->toArray())));
|
||||
}
|
||||
if (empty($flowItems)) {
|
||||
$flowItems = "已完成";
|
||||
}
|
||||
throw new ApiException('仅限【' . $flowItems . '】状态的任务归档');
|
||||
}
|
||||
AbstractModel::transaction(function () use ($isAuto, $archived_at) {
|
||||
if ($archived_at === null) {
|
||||
// 取消归档
|
||||
@@ -918,7 +1243,6 @@ class ProjectTask extends AbstractModel
|
||||
$this->archived_userid = User::userid();
|
||||
$this->archived_follow = 0;
|
||||
$this->addLog("任务取消归档");
|
||||
$this->pushMsg('add', ProjectTask::oneTask($this->id));
|
||||
} else {
|
||||
// 归档任务
|
||||
if ($isAuto === true) {
|
||||
@@ -932,9 +1256,13 @@ class ProjectTask extends AbstractModel
|
||||
$this->archived_userid = $userid;
|
||||
$this->archived_follow = 0;
|
||||
$this->addLog($logText, [], $userid);
|
||||
$this->pushMsg('archived');
|
||||
}
|
||||
self::whereParentId($this->id)->update([
|
||||
$this->pushMsg($archived_at === null ? 'recovery' : 'archived', [
|
||||
'id' => $this->id,
|
||||
'archived_at' => $this->archived_at,
|
||||
'archived_userid' => $this->archived_userid,
|
||||
]);
|
||||
self::whereParentId($this->id)->change([
|
||||
'archived_at' => $this->archived_at,
|
||||
'archived_userid' => $this->archived_userid,
|
||||
'archived_follow' => $this->archived_follow,
|
||||
@@ -956,7 +1284,9 @@ class ProjectTask extends AbstractModel
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
$dialog?->deleteDialog();
|
||||
}
|
||||
self::whereParentId($this->id)->delete();
|
||||
self::whereParentId($this->id)->remove();
|
||||
$this->deleted_userid = User::userid();
|
||||
$this->save();
|
||||
$this->addLog("删除{任务}");
|
||||
$this->delete();
|
||||
});
|
||||
@@ -966,6 +1296,28 @@ class ProjectTask extends AbstractModel
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原任务
|
||||
* @param bool $pushMsg 是否推送
|
||||
* @return bool
|
||||
*/
|
||||
public function restoreTask($pushMsg = true)
|
||||
{
|
||||
AbstractModel::transaction(function () {
|
||||
if ($this->dialog_id) {
|
||||
$dialog = WebSocketDialog::withTrashed()->find($this->dialog_id);
|
||||
$dialog?->restoreDialog();
|
||||
}
|
||||
self::whereParentId($this->id)->withTrashed()->restore();
|
||||
$this->addLog("还原{任务}");
|
||||
$this->restore();
|
||||
});
|
||||
if ($pushMsg) {
|
||||
$this->pushMsg('restore');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务日志
|
||||
* @param string $detail
|
||||
@@ -1017,28 +1369,62 @@ class ProjectTask extends AbstractModel
|
||||
$data = $data->toArray();
|
||||
}
|
||||
//
|
||||
$array = [$userid, []];
|
||||
$userids = [];
|
||||
if ($userid === null) {
|
||||
$array[0] = $this->project->relationUserids();
|
||||
$userids = $this->project->relationUserids();
|
||||
} elseif (!is_array($userid)) {
|
||||
$array[0] = [$userid];
|
||||
$userids = [$userid];
|
||||
}
|
||||
//
|
||||
if (isset($data['owner'])) {
|
||||
$owners = ProjectTaskUser::whereTaskId($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;
|
||||
$array = [];
|
||||
if (empty($data['parent_id'])) {
|
||||
if (Arr::exists($data, 'owner') || Arr::exists($data, 'assist')) {
|
||||
$taskUser = ProjectTaskUser::select(['userid', 'owner'])->whereTaskId($data['id'])->get();
|
||||
// 负责人
|
||||
$owners = $taskUser->where('owner', 1)->pluck('userid')->toArray();
|
||||
$owners = array_intersect($userids, $owners);
|
||||
if ($owners) {
|
||||
$array[] = [
|
||||
'userid' => array_values($owners),
|
||||
'data' => array_merge($data, [
|
||||
'owner' => 1,
|
||||
'assist' => 1,
|
||||
])
|
||||
];
|
||||
}
|
||||
// 协助人
|
||||
$assists = $taskUser->pluck('userid')->toArray();
|
||||
$assists = array_intersect($userids, array_diff($assists, $owners));
|
||||
if ($assists) {
|
||||
$array[] = [
|
||||
'userid' => array_values($assists),
|
||||
'data' => array_merge($data, [
|
||||
'owner' => 0,
|
||||
'assist' => 1,
|
||||
])
|
||||
];
|
||||
}
|
||||
// 项目成员(其他人)
|
||||
$userids = array_diff($userids, $owners, $assists);
|
||||
$data = array_merge($data, [
|
||||
'owner' => 0,
|
||||
'assist' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$array[] = [
|
||||
'userid' => array_values($userids),
|
||||
'data' => $data
|
||||
];
|
||||
//
|
||||
foreach ($array as $item) {
|
||||
$params = [
|
||||
'ignoreFd' => Request::header('fd'),
|
||||
'userid' => array_values($item),
|
||||
'msg' => [
|
||||
'type' => 'projectTask',
|
||||
'action' => $action,
|
||||
'data' => $data,
|
||||
'data' => $item['data'],
|
||||
]
|
||||
];
|
||||
$task = new PushTask($params, false);
|
||||
@@ -1046,6 +1432,62 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务提醒
|
||||
* @param $userids
|
||||
* @param int $type 0-新任务、1-即将超时、2-已超时、3-修改时间
|
||||
* @param string $suffix 描述后缀
|
||||
* @return void
|
||||
*/
|
||||
public function taskPush($userids, int $type, string $suffix = "")
|
||||
{
|
||||
if ($userids === null) {
|
||||
$userids = $this->taskUser->pluck('userid')->toArray();
|
||||
}
|
||||
if (empty($userids)) {
|
||||
return;
|
||||
}
|
||||
$owners = $this->taskUser->pluck('owner', 'userid')->toArray();
|
||||
$receivers = User::whereIn('userid', $userids)->whereNull('disable_at')->get();
|
||||
if (empty($receivers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$botUser = User::botGetOrCreate('task-alert');
|
||||
if (empty($botUser)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$taskHtml = "<span class=\"mention task\" data-id=\"{$this->id}\">#{$this->name}</span>";
|
||||
$text = match ($type) {
|
||||
1 => "您的任务 {$taskHtml} 即将超时。",
|
||||
2 => "您的任务 {$taskHtml} 已经超时。",
|
||||
3 => "您的任务 {$taskHtml} 时间已修改。",
|
||||
default => "您有一个新任务 {$taskHtml}。",
|
||||
};
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($receivers as $receiver) {
|
||||
$data = [
|
||||
'type' => $type,
|
||||
'userid' => $receiver->userid,
|
||||
'task_id' => $this->id,
|
||||
];
|
||||
if (in_array($type, [1, 2]) && ProjectTaskPushLog::where($data)->exists()) {
|
||||
continue;
|
||||
}
|
||||
//
|
||||
$replace = $owners[$receiver->userid] ? "您负责的任务" : "您协助的任务";
|
||||
$dialog = WebSocketDialog::checkUserDialog($botUser, $receiver->userid);
|
||||
if ($dialog) {
|
||||
ProjectTaskPushLog::createInstance($data)->save();
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', [
|
||||
'text' => str_replace("您的任务", $replace, $text) . $suffix
|
||||
], $botUser->userid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务
|
||||
* @param $task_id
|
||||
@@ -1053,48 +1495,67 @@ class ProjectTask extends AbstractModel
|
||||
*/
|
||||
public static function oneTask($task_id)
|
||||
{
|
||||
return self::with(['taskUser', 'taskTag'])->allData()->where("project_tasks.id", intval($task_id))->first();
|
||||
$data = self::with(['taskUser', 'taskTag'])->allData()->where("project_tasks.id", intval($task_id))->first();
|
||||
if ($data && $data->parent_id === 0) {
|
||||
if ($data->owner || ProjectTaskUser::select(['owner'])->whereTaskId($data->id)->whereUserid(User::userid())->exists()) {
|
||||
$data->assist = 1;
|
||||
} else {
|
||||
$data->assist = 0;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务(会员有任务权限 或 会员存在项目内)
|
||||
* @param int $task_id
|
||||
* @param bool $archived true:仅限未归档, false:不限制, null:不限制
|
||||
* @param int|bool $mustOwner 0|false:不限制, 1|true:限制任务或项目负责人, 2:已有负责人才限制任务或项目负责人
|
||||
* @param bool $archived true:仅限未归档, false:仅限已归档, null:不限制
|
||||
* @param bool $trashed true:仅限未删除, false:仅限已删除, null:不限制
|
||||
* @param int|bool $permission
|
||||
* - 0|false 限制:项目成员、任务成员、任务群聊成员(任务成员 = 任务创建人+任务协助人+任务负责人)
|
||||
* - 1|true 限制:项目负责人、任务成员
|
||||
* - 2 已有负责人才限制true (子任务时如果是主任务负责人也可以)
|
||||
* @param array $with
|
||||
* @return self
|
||||
*/
|
||||
public static function userTask($task_id, $archived = true, $mustOwner = 0, $with = [])
|
||||
public static function userTask($task_id, $archived = true, $trashed = true, $permission = false, $with = [])
|
||||
{
|
||||
$task = self::with($with)->allData()->where("project_tasks.id", intval($task_id))->first();
|
||||
$builder = self::with($with)->allData()->where("project_tasks.id", intval($task_id));
|
||||
if ($trashed === false) {
|
||||
$builder->onlyTrashed();
|
||||
} elseif ($trashed === null) {
|
||||
$builder->withTrashed();
|
||||
}
|
||||
$task = $builder->first();
|
||||
//
|
||||
if (empty($task)) {
|
||||
throw new ApiException('任务不存在', [ 'task_id' => $task_id ], -4002);
|
||||
throw new ApiException('任务不存在', ['task_id' => $task_id], -4002);
|
||||
}
|
||||
if ($archived === true && $task->archived_at != null) {
|
||||
throw new ApiException('任务已归档', [ 'task_id' => $task_id ], -4002);
|
||||
throw new ApiException('任务已归档', ['task_id' => $task_id]);
|
||||
}
|
||||
if ($archived === false && $task->archived_at == null) {
|
||||
throw new ApiException('任务未归档', [ 'task_id' => $task_id ]);
|
||||
throw new ApiException('任务未归档', ['task_id' => $task_id]);
|
||||
}
|
||||
//
|
||||
try {
|
||||
$project = Project::userProject($task->project_id, $archived);
|
||||
} catch (Exception $e) {
|
||||
if ($task->owner === null) {
|
||||
$project = Project::userProject($task->project_id);
|
||||
} catch (\Throwable $e) {
|
||||
if ($task->owner !== null || (!$permission && $task->permission(4))) {
|
||||
$project = Project::find($task->project_id);
|
||||
if (empty($project)) {
|
||||
throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002);
|
||||
}
|
||||
} else {
|
||||
throw new ApiException($e->getMessage(), [ 'task_id' => $task_id ], -4002);
|
||||
}
|
||||
$project = Project::find($task->project_id);
|
||||
if (empty($project)) {
|
||||
throw new ApiException('项目不存在或已被删除', [ 'task_id' => $task_id ], -4002);
|
||||
}
|
||||
}
|
||||
//
|
||||
if ($mustOwner === 2) {
|
||||
$mustOwner = $task->hasOwner() ? 1 : 0;
|
||||
if ($permission >= 2) {
|
||||
$permission = $task->hasOwner() ? 1 : 0;
|
||||
}
|
||||
if (($mustOwner === 1 || $mustOwner === true) && !$task->owner && !$project->owner) {
|
||||
throw new ApiException('仅限项目或任务负责人操作');
|
||||
if ($permission && !$project->owner && !$task->permission(3)) {
|
||||
throw new ApiException('仅限项目负责人、任务负责人、协助人员或任务创建者操作');
|
||||
}
|
||||
//
|
||||
return $task;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectTaskContent
|
||||
*
|
||||
@@ -28,4 +30,55 @@ 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)
|
||||
{
|
||||
$path = 'uploads/task/content/' . date("Ym") . '/' . $task_id . '/';
|
||||
//
|
||||
preg_match_all("/<img\s+src=\"data:image\/(png|jpg|jpeg);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 (file_put_contents(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));
|
||||
file_put_contents($publicPath, $content);
|
||||
//
|
||||
return $filePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
use Cache;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectTaskFile
|
||||
@@ -19,6 +20,8 @@ use App\Module\Base;
|
||||
* @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()
|
||||
@@ -38,6 +41,11 @@ use App\Module\Base;
|
||||
*/
|
||||
class ProjectTaskFile extends AbstractModel
|
||||
{
|
||||
protected $appends = [
|
||||
'width',
|
||||
'height',
|
||||
];
|
||||
|
||||
/**
|
||||
* 地址
|
||||
* @param $value
|
||||
@@ -57,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', '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\Query\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\Query\Builder|ProjectTaskPushLog withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectTaskPushLog withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectTaskPushLog extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
}
|
||||
@@ -13,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()
|
||||
@@ -29,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,8 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectUser
|
||||
*
|
||||
@@ -11,6 +9,7 @@ use App\Module\Base;
|
||||
* @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
|
||||
@@ -21,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
|
||||
@@ -36,6 +36,41 @@ 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出项目
|
||||
*/
|
||||
@@ -45,15 +80,13 @@ class ProjectUser extends AbstractModel
|
||||
->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();
|
||||
}
|
||||
$item->delete();
|
||||
}
|
||||
$tasks = ProjectTask::whereIn('id', $tastIds)->get();
|
||||
foreach ($tasks as $task) {
|
||||
$task->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|\App\Models\ReportReceive[] $Receives
|
||||
* @property-read int|null $receives_count
|
||||
* @property-read mixed $receives
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\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,6 +2,8 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* App\Models\Setting
|
||||
*
|
||||
@@ -24,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\Query\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\Query\Builder|TaskWorker withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|TaskWorker withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class TaskWorker extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
}
|
||||
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('id')
|
||||
->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->groupBy('platform');
|
||||
foreach ($lists as $platform => $list) {
|
||||
$alias = $list->pluck('alias')->implode(',');
|
||||
self::pushMsgToAlias($alias, $platform, $array);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,11 @@ use Carbon\Carbon;
|
||||
*
|
||||
* @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 头像
|
||||
@@ -28,7 +31,9 @@ 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 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)
|
||||
@@ -36,11 +41,14 @@ use Carbon\Carbon;
|
||||
* @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)
|
||||
@@ -50,8 +58,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)
|
||||
@@ -62,22 +72,14 @@ class User extends AbstractModel
|
||||
protected $primaryKey = 'userid';
|
||||
|
||||
protected $hidden = [
|
||||
'disable_at',
|
||||
'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'];
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
@@ -96,11 +98,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,13 +114,47 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在线
|
||||
* @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;
|
||||
}
|
||||
@@ -130,9 +162,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');
|
||||
}
|
||||
@@ -167,6 +235,36 @@ class User extends AbstractModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会员
|
||||
* @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();
|
||||
});
|
||||
}
|
||||
|
||||
/** ***************************************************************************************** */
|
||||
/** ***************************************************************************************** */
|
||||
/** ***************************************************************************************** */
|
||||
@@ -180,16 +278,22 @@ 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) {
|
||||
$isRegVerify = Base::settingFind('emailSetting', 'reg_verify') === 'open';
|
||||
$user = self::whereUserid(User::email2userid($email))->first();
|
||||
if ($isRegVerify && $user->email_verity === 0) {
|
||||
UserEmailVerification::userEmailSend($user);
|
||||
throw new ApiException('您的帐号已注册过,请验证邮箱', ['code' => 'email']);
|
||||
}
|
||||
throw new ApiException('邮箱地址已存在');
|
||||
}
|
||||
//密码
|
||||
// 密码
|
||||
self::passwordPolicy($password);
|
||||
//开始注册
|
||||
// 开始注册
|
||||
$encrypt = Base::generatePassword(6);
|
||||
$inArray = [
|
||||
'encrypt' => $encrypt,
|
||||
@@ -201,8 +305,23 @@ class User extends AbstractModel
|
||||
$inArray = array_merge($inArray, $other);
|
||||
}
|
||||
$user = User::createInstance($inArray);
|
||||
$user->save();
|
||||
User::AZUpdate($user->userid);
|
||||
$user->az = Base::getFirstCharter($user->nickname);
|
||||
$user->pinyin = Base::cn2pinyin($user->nickname);
|
||||
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);
|
||||
}
|
||||
}
|
||||
return $user->find($user->userid);
|
||||
}
|
||||
|
||||
@@ -340,9 +459,12 @@ class User extends AbstractModel
|
||||
if ($authInfo['userid'] > 0) {
|
||||
$loginValid = floatval(Base::settingFind('system', 'loginValid')) ?: 720;
|
||||
$loginValid *= 3600;
|
||||
if ($authInfo['timestamp'] + $loginValid > time()) {
|
||||
if ($authInfo['timestamp'] + $loginValid > time() || $authInfo['timestamp'] === -1) {
|
||||
$row = self::whereUserid($authInfo['userid'])->whereEmail($authInfo['email'])->whereEncrypt($authInfo['encrypt'])->first();
|
||||
if ($row) {
|
||||
if (!$row->bot && $authInfo['timestamp'] === -1) {
|
||||
return $_A["__static_auth"] = false; // 非机器人token时间不允许-1
|
||||
}
|
||||
$upArray = [];
|
||||
if (Base::getIp() && $row->line_ip != Base::getIp()) {
|
||||
$upArray['line_ip'] = Base::getIp();
|
||||
@@ -369,7 +491,8 @@ class User extends AbstractModel
|
||||
*/
|
||||
public static function token($userinfo)
|
||||
{
|
||||
$userinfo->token = base64_encode($userinfo->userid . '#$' . $userinfo->email . '#$' . $userinfo->encrypt . '#$' . time() . '#$' . Base::generatePassword(6));
|
||||
$time = $userinfo->bot ? -1 : time();
|
||||
$userinfo->token = base64_encode($userinfo->userid . '#$' . $userinfo->email . '#$' . $userinfo->encrypt . '#$' . $time . '#$' . Base::generatePassword(6));
|
||||
unset($userinfo->encrypt);
|
||||
unset($userinfo->password);
|
||||
return $userinfo->token;
|
||||
@@ -402,10 +525,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 ?: []);
|
||||
}
|
||||
@@ -422,19 +545,6 @@ class User extends AbstractModel
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要验证码
|
||||
* @param $email
|
||||
@@ -459,6 +569,42 @@ 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 'bot-manager@bot.system':
|
||||
return url("images/avatar/default_bot.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
|
||||
@@ -489,4 +635,61 @@ class User extends AbstractModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取机器人或创建
|
||||
* @param $key
|
||||
* @param $update
|
||||
* @param $userid
|
||||
* @return self
|
||||
*/
|
||||
public static function botGetOrCreate($key, $update = [], $userid = 0)
|
||||
{
|
||||
$email = "{$key}@bot.system";
|
||||
$botUser = self::whereEmail($email)->first();
|
||||
if (empty($botUser)) {
|
||||
$encrypt = Base::generatePassword(6);
|
||||
$botUser = self::createInstance([
|
||||
'bot' => 1,
|
||||
'encrypt' => $encrypt,
|
||||
'email' => $email,
|
||||
'password' => Base::md52(Base::generatePassword(32), $encrypt),
|
||||
'created_ip' => Base::getIp(),
|
||||
]);
|
||||
$botUser->save();
|
||||
if ($userid > 0) {
|
||||
UserBot::createInstance([
|
||||
'userid' => $userid,
|
||||
'bot_id' => $botUser->userid,
|
||||
])->save();
|
||||
}
|
||||
//
|
||||
switch ($key) {
|
||||
case 'system-msg':
|
||||
$update['nickname'] = '系统消息';
|
||||
break;
|
||||
case 'task-alert':
|
||||
$update['nickname'] = '任务提醒';
|
||||
break;
|
||||
case 'check-in':
|
||||
$update['nickname'] = '签到打卡';
|
||||
break;
|
||||
case 'anon-msg':
|
||||
$update['nickname'] = '匿名消息';
|
||||
break;
|
||||
case 'bot-manager':
|
||||
$update['nickname'] = '机器人管理';
|
||||
break;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
127
app/Models/UserBot.php
Normal file
127
app/Models/UserBot.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Module\Base;
|
||||
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 $email
|
||||
* @return array|array[]
|
||||
*/
|
||||
public static function quickMsgs($email)
|
||||
{
|
||||
return match ($email) {
|
||||
'check-in@bot.system' => [
|
||||
[
|
||||
'key' => 'checkin',
|
||||
'label' => Base::Lang('我要签到')
|
||||
], [
|
||||
'key' => 'it',
|
||||
'label' => Base::Lang('IT资讯')
|
||||
], [
|
||||
'key' => '36ke',
|
||||
'label' => Base::Lang('36氪')
|
||||
], [
|
||||
'key' => '60s',
|
||||
'label' => Base::Lang('60s读世界')
|
||||
], [
|
||||
'key' => 'joke',
|
||||
'label' => Base::Lang('开心笑话')
|
||||
], [
|
||||
'key' => 'soup',
|
||||
'label' => Base::Lang('心灵鸡汤')
|
||||
]
|
||||
],
|
||||
'anon-msg@bot.system' => [
|
||||
[
|
||||
'key' => 'help',
|
||||
'label' => Base::Lang('使用说明')
|
||||
], [
|
||||
'key' => 'privacy',
|
||||
'label' => Base::Lang('隐私说明')
|
||||
],
|
||||
],
|
||||
'bot-manager@bot.system' => [
|
||||
[
|
||||
'key' => '/help',
|
||||
'label' => Base::Lang('帮助指令')
|
||||
], [
|
||||
'key' => '/api',
|
||||
'label' => Base::Lang('Api接口文档')
|
||||
], [
|
||||
'key' => '/list',
|
||||
'label' => Base::Lang('我的机器人')
|
||||
],
|
||||
],
|
||||
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());
|
||||
//
|
||||
$text = match ($command) {
|
||||
"checkin" => "暂未开放手动签到。",
|
||||
default => Extranet::checkinBotQuickMsg($command),
|
||||
};
|
||||
return $text ?: '维护中...';
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐私机器人
|
||||
* @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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,11 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\PushTask;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
@@ -13,7 +17,9 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @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 \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property \Illuminate\Support\Carbon|null $deleted_at
|
||||
@@ -23,12 +29,14 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialog newQuery()
|
||||
* @method static \Illuminate\Database\Query\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 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()
|
||||
@@ -39,6 +47,16 @@ 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
|
||||
*/
|
||||
@@ -47,6 +65,231 @@ class WebSocketDialog extends AbstractModel
|
||||
return $this->hasMany(WebSocketDialogUser::class, 'dialog_id', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化对话
|
||||
* @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->bot = $basic->bot;
|
||||
$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 = Base::Lang('全体成员');
|
||||
$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();
|
||||
}
|
||||
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
|
||||
&& $mention_id = intval($builder->clone()->whereMention(1)->orderByDesc('msg_id')->value('msg_id'))) {
|
||||
$array[] = [
|
||||
'msg_id' => $mention_id,
|
||||
'label' => Base::Lang('@我的消息'),
|
||||
];
|
||||
}
|
||||
// 最早一条未读消息
|
||||
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' && !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
|
||||
@@ -54,20 +297,142 @@ 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)?->checkAdmin()) {
|
||||
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 = Base::Lang('全体成员');
|
||||
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 $dialog_id
|
||||
* @param bool|string $checkOwner 是否校验群组身份,'auto'时有群主为true无群主为false
|
||||
* @return self
|
||||
*/
|
||||
public static function checkDialog($dialog_id)
|
||||
public static function checkDialog($dialog_id, $checkOwner = false)
|
||||
{
|
||||
$dialog = WebSocketDialog::find($dialog_id);
|
||||
if (empty($dialog)) {
|
||||
@@ -75,7 +440,14 @@ class WebSocketDialog extends AbstractModel
|
||||
}
|
||||
//
|
||||
$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,48 +457,7 @@ class WebSocketDialog extends AbstractModel
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -134,17 +465,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) {
|
||||
@@ -152,6 +486,7 @@ class WebSocketDialog extends AbstractModel
|
||||
WebSocketDialogUser::createInstance([
|
||||
'dialog_id' => $dialog->id,
|
||||
'userid' => $value,
|
||||
'important' => !in_array($group_type, ['user', 'all'])
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
@@ -159,80 +494,44 @@ 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()) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,50 +2,91 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use Carbon\Carbon;
|
||||
use App\Models\User;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\PushTask;
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Tasks\WebSocketDialogMsgTask;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* App\Models\WebSocketDialogMsg
|
||||
*
|
||||
* @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\Query\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\Query\Builder|WebSocketDialogMsg withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|WebSocketDialogMsg withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class WebSocketDialogMsg extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $appends = [
|
||||
'percentage',
|
||||
'reply_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
|
||||
@@ -53,15 +94,26 @@ 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'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息格式化
|
||||
* @param $value
|
||||
@@ -77,10 +129,41 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
$value['type'] = in_array($value['ext'], ['jpg', 'jpeg', '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
|
||||
@@ -110,13 +193,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,
|
||||
],
|
||||
]
|
||||
]);
|
||||
}
|
||||
@@ -125,33 +212,665 @@ 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 发送的会员
|
||||
* @return mixed
|
||||
*/
|
||||
public function forwardMsg($dialogids, $userids, $user)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($dialogids, $user, $userids) {
|
||||
$originalMsg = Base::json2array($this->getRawOriginal('msg'));
|
||||
$msgs = [];
|
||||
$already = [];
|
||||
if ($dialogids) {
|
||||
if (!is_array($dialogids)) {
|
||||
$dialogids = [$dialogids];
|
||||
}
|
||||
foreach ($dialogids as $dialogid) {
|
||||
$res = self::sendMsg(null, $dialogid, $this->type, $originalMsg, $user->userid);
|
||||
if (Base::isSuccess($res)) {
|
||||
$msgs[] = $res['data'];
|
||||
$already[] = $dialogid;
|
||||
}
|
||||
}
|
||||
}
|
||||
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(null, $dialog->id, $this->type, $originalMsg, $user->userid);
|
||||
if (Base::isSuccess($res)) {
|
||||
$msgs[] = $res['data'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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':
|
||||
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|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 (file_put_contents(public_path($imagePath), base64_decode($base64))) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
if (Base::imgThumb(public_path($imagePath), public_path($imagePath) . "_thumb.jpg", 320, 0)) {
|
||||
$imagePath .= "_thumb.jpg";
|
||||
}
|
||||
$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|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);
|
||||
}
|
||||
}
|
||||
// 其他网络图片
|
||||
preg_match_all("/<img[^>]*?src=([\"'])(.*?\.(png|jpg|jpeg|gif))\\1[^>]*?>/is", $text, $matchs);
|
||||
foreach ($matchs[2] as $key => $str) {
|
||||
if (str_starts_with($str, "{{RemoteURL}}")) {
|
||||
$imagePath = Base::leftDelete($str, "{{RemoteURL}}");
|
||||
$imagePath = Base::rightDelete($imagePath, "_thumb.jpg");
|
||||
} 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 (Base::imgThumb(public_path($imagePath), public_path($imagePath) . "_thumb.jpg", 320, 0)) {
|
||||
$imagePath .= "_thumb.jpg";
|
||||
}
|
||||
$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 (file_put_contents(public_path($imagePath), $image)) {
|
||||
$imageSize = getimagesize(public_path($imagePath));
|
||||
if (Base::imgThumb(public_path($imagePath), public_path($imagePath) . "_thumb.jpg", 320, 0)) {
|
||||
$imagePath .= "_thumb.jpg";
|
||||
}
|
||||
$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=\"(.*?)\"/", $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("/\[:IMAGE:(.*?):(.*?):(.*?):(.*?):(.*?):\]/i", "<img class=\"$1\" width=\"$2\" height=\"$3\" src=\"{{RemoteURL}}$4\" alt=\"$5\"/>", $text);
|
||||
$text = preg_replace("/\[:@:(.*?):(.*?):\]/i", "<span class=\"mention user\" data-id=\"$1\">@$2</span>", $text);
|
||||
$text = preg_replace("/\[:#:(.*?):(.*?):\]/i", "<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);
|
||||
$text = preg_replace_callback("/\[:LINK:(.*?):(.*?):\]/i", function (array $match) {
|
||||
return "<a href=\"" . base64_decode($match[1]) . "\" target=\"_blank\">" . base64_decode($match[2]) . "</a>";
|
||||
}, $text);
|
||||
return preg_replace("/^(<p><\/p>)+|(<p><\/p>)+$/i", "", $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息、修改消息
|
||||
* @param string $action 动作
|
||||
* - reply-98:回复消息ID=98
|
||||
* - update-99:更新消息ID=99
|
||||
* @param int $dialog_id 会话ID(即 聊天室ID)
|
||||
* @param string $type 消息类型
|
||||
* @param array $msg 发送的消息
|
||||
* @param int $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 = 0, $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', '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;
|
||||
$reply_id = preg_match("/^reply-(\d+)$/", $action, $match) ? $match[1] : 0;
|
||||
$sender = $sender ?: User::userid();
|
||||
//
|
||||
$dialog = WebSocketDialog::find($dialog_id);
|
||||
if (empty($dialog)) {
|
||||
throw new ApiException('获取会话失败');
|
||||
}
|
||||
$dialog->checkMute($sender);
|
||||
//
|
||||
if ($update_id) {
|
||||
// 修改
|
||||
$dialogMsg = self::whereId($update_id)->whereDialogId($dialog_id)->first();
|
||||
if (empty($dialogMsg)) {
|
||||
throw new ApiException('消息不存在');
|
||||
}
|
||||
if ($dialogMsg->type !== 'text') {
|
||||
throw new ApiException('此消息不支持此操作');
|
||||
}
|
||||
if ($dialogMsg->userid != $sender) {
|
||||
throw new ApiException('仅支持修改自己的消息');
|
||||
}
|
||||
//
|
||||
$updateData = [
|
||||
'mtype' => $mtype,
|
||||
'link' => $link,
|
||||
'msg' => $msg,
|
||||
'modify' => 1,
|
||||
];
|
||||
$dialogMsg->updateInstance($updateData);
|
||||
$dialogMsg->key = $dialogMsg->generateMsgKey();
|
||||
$dialogMsg->save();
|
||||
});
|
||||
Task::deliver(new WebSocketDialogMsgTask($dialogMsg->id));
|
||||
return Base::retSuccess('发送成功', $dialogMsg);
|
||||
//
|
||||
$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('回复的消息不存在');
|
||||
}
|
||||
//
|
||||
$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,
|
||||
]);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,32 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* App\Models\WebSocketDialogMsgRead
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@@ -29,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,25 +2,46 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* App\Models\WebSocketDialogUser
|
||||
*
|
||||
* @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 \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 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');
|
||||
}
|
||||
}
|
||||
|
||||
23
app/Models/clearHelper.php
Executable file
23
app/Models/clearHelper.php
Executable file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 清除模型class注释
|
||||
*/
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
306
app/Module/Extranet.php
Normal file
306
app/Module/Extranet.php
Normal file
@@ -0,0 +1,306 @@
|
||||
<?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('https://api.vvhan.com/api/hotlist?type=itNews', 3600);
|
||||
if ($data = Base::json2array($data)) {
|
||||
$array = array_map(function ($item, $key) {
|
||||
$content = "<p>" . ($key + 1) . ". <strong><a href='{$item['mobilUrl']}' target='_blank'>{$item['title']}</a></strong></p>";
|
||||
if ($item['desc']) {
|
||||
$content .= "<p>{$item['desc']}</p>";
|
||||
}
|
||||
return $content;
|
||||
}, $data['data'], array_keys($data['data']));
|
||||
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('https://api.vvhan.com/api/hotlist?type=36Ke', 3600);
|
||||
if ($data = Base::json2array($data)) {
|
||||
$array = array_map(function ($item, $key) {
|
||||
$content = "<p>" . ($key + 1) . ". <strong><a href='{$item['mobilUrl']}' target='_blank'>{$item['title']}</a></strong></p>";
|
||||
if ($item['desc']) {
|
||||
$content .= "<p>{$item['desc']}</p>";
|
||||
}
|
||||
return $content;
|
||||
}, $data['data'], array_keys($data['data']));
|
||||
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('https://api.vvhan.com/api/60s?type=json', 3600);
|
||||
if ($data = Base::json2array($data)) {
|
||||
$array = array_map(function ($item, $key) {
|
||||
return "<p>" . ($key + 1) . ". {$item}</p>";
|
||||
}, $data['data'], array_keys($data['data']));
|
||||
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('https://api.vvhan.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 = trim($data)) {
|
||||
$text = "心灵鸡汤:{$data}";
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $url
|
||||
* @param int $cacheSecond
|
||||
* @param int $timeout
|
||||
* @return string
|
||||
*/
|
||||
private static function curl($url, int $cacheSecond = 0, int $timeout = 15): string
|
||||
{
|
||||
if ($cacheSecond > 0) {
|
||||
$res = Cache::remember("curlCache::" . md5($url), Carbon::now()->addSeconds($cacheSecond), function () use ($timeout, $url) {
|
||||
return Ihttp::ihttp_request($url, [], [], $timeout);
|
||||
});
|
||||
} else {
|
||||
$res = Ihttp::ihttp_request($url, [], [], $timeout);
|
||||
}
|
||||
if (Base::isSuccess($res)) {
|
||||
return $res['data'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -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([
|
||||
|
||||
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)
|
||||
)
|
||||
);
|
||||
41
app/Module/TimeRange.php
Normal file
41
app/Module/TimeRange.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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 = Base::explodeInt($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 = Base::explodeInt($data);
|
||||
}
|
||||
//
|
||||
$this->updated = $range[0] ? Carbon::parse($range[0]) : null;
|
||||
$this->deleted = $range[1] ? Carbon::parse($range[1]) : 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();
|
||||
}
|
||||
}
|
||||
81
app/Observers/ProjectTaskObserver.php
Normal file
81
app/Observers/ProjectTaskObserver.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Deleted;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskUser;
|
||||
|
||||
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')) {
|
||||
$userids = $this->userids($projectTask);
|
||||
if ($projectTask->archived_at) {
|
||||
Deleted::record('projectTask', $projectTask->id, $userids);
|
||||
} else {
|
||||
Deleted::forget('projectTask', $projectTask->id, $userids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ProjectTaskUser::whereTaskId($projectTask->id)->pluck('userid')->toArray();
|
||||
}
|
||||
}
|
||||
64
app/Observers/ProjectTaskUserObserver.php
Normal file
64
app/Observers/ProjectTaskUserObserver.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Observers;
|
||||
|
||||
use App\Models\Deleted;
|
||||
use App\Models\ProjectTaskUser;
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
$server->push($fd, Base::array2json([
|
||||
'type' => 'error',
|
||||
'data' => [
|
||||
'error' => '会员不存在'
|
||||
'error' => '成员不存在'
|
||||
],
|
||||
]));
|
||||
$server->close($fd);
|
||||
@@ -127,9 +127,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,10 +145,10 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -189,13 +191,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 +208,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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class AutoArchivedTask extends AbstractTask
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function start()
|
||||
@@ -42,4 +42,9 @@ class AutoArchivedTask extends AbstractTask
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
385
app/Tasks/BotReceiveMsgTask.php
Normal file
385
app/Tasks/BotReceiveMsgTask.php
Normal file
@@ -0,0 +1,385 @@
|
||||
<?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\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;
|
||||
|
||||
public function __construct($userid, $msgId)
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->userid = $userid;
|
||||
$this->msgId = $msgId;
|
||||
}
|
||||
|
||||
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);
|
||||
//
|
||||
$dialog = WebSocketDialog::find($msg->dialog_id);
|
||||
if (empty($dialog)) {
|
||||
return;
|
||||
}
|
||||
if ($dialog->type !== 'user') {
|
||||
return;
|
||||
}
|
||||
$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 (preg_match("/<span[^>]*?data-quick-key=([\"'])(.*?)\\1[^>]*?>(.*?)<\/span>/is", $original, $match)) {
|
||||
$command = $match[2];
|
||||
} else {
|
||||
$command = trim(strip_tags($original));
|
||||
}
|
||||
// 签到机器人
|
||||
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::token($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 = Base::md52(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.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事件来发送任务
|
||||
return;
|
||||
}
|
||||
// 推送Webhook
|
||||
if ($command) {
|
||||
$userBot = UserBot::whereBotId($botUser->userid)->first();
|
||||
if ($userBot && preg_match("/^https*:\/\//", $userBot->webhook_url)) {
|
||||
Ihttp::ihttp_post($userBot->webhook_url, [
|
||||
'text' => $command,
|
||||
'token' => User::token($botUser),
|
||||
'dialog_id' => $msg->dialog_id,
|
||||
'msg_id' => $msg->id,
|
||||
'msg_uid' => $msg->userid,
|
||||
'bot_uid' => $botUser->userid,
|
||||
'version' => Base::getVersion(),
|
||||
], 10);
|
||||
$userBot->webhook_num++;
|
||||
$userBot->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\TaskWorker;
|
||||
use App\Models\Tmp;
|
||||
use App\Models\WebSocketTmpMsg;
|
||||
use Carbon\Carbon;
|
||||
@@ -18,6 +19,7 @@ class DeleteTmpTask extends AbstractTask
|
||||
|
||||
public function __construct(string $data, int $hours)
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->data = $data;
|
||||
$this->hours = $hours;
|
||||
}
|
||||
@@ -54,6 +56,23 @@ class DeleteTmpTask extends AbstractTask
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 表pre_task_worker
|
||||
*/
|
||||
case 'task_worker':
|
||||
{
|
||||
TaskWorker::onlyTrashed()
|
||||
->where('deleted_at', '<', Carbon::now()->subHours($this->hours)->toDateTimeString())
|
||||
->orderBy('id')
|
||||
->forceDelete();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
170
app/Tasks/EmailNoticeTask.php
Normal file
170
app/Tasks/EmailNoticeTask.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
class EmailNoticeTask extends AbstractTask
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
$setting = Base::setting('emailSetting');
|
||||
// 消息通知
|
||||
if ($setting['notice_msg'] === 'open') {
|
||||
$userMinute = intval($setting['msg_unread_user_minute']);
|
||||
$groupMinute = intval($setting['msg_unread_group_minute']);
|
||||
\DB::statement("SET SQL_MODE=''");
|
||||
$builder = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid'])
|
||||
->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id')
|
||||
->whereNull("r.read_at")
|
||||
->where("r.silence", 0)
|
||||
->where("r.email", 0);
|
||||
if ($userMinute > -1) {
|
||||
$builder->clone()
|
||||
->where("web_socket_dialog_msgs.dialog_type", "user")
|
||||
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
|
||||
->whereBetween("web_socket_dialog_msgs.created_at", [
|
||||
Carbon::now()->subMinutes($userMinute + 10),
|
||||
Carbon::now()->subMinutes($userMinute)
|
||||
])
|
||||
->groupBy('r_userid')
|
||||
->chunkById(100, function ($rows) {
|
||||
$this->unreadMsgEmail($rows, "user");
|
||||
});
|
||||
}
|
||||
if ($groupMinute > -1) {
|
||||
$builder->clone()
|
||||
->where("web_socket_dialog_msgs.dialog_type", "group")
|
||||
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
|
||||
->whereBetween("web_socket_dialog_msgs.created_at", [
|
||||
Carbon::now()->subMinutes($groupMinute + 10),
|
||||
Carbon::now()->subMinutes($groupMinute)
|
||||
])
|
||||
->groupBy('r_userid')
|
||||
->chunkById(100, function ($rows) {
|
||||
$this->unreadMsgEmail($rows, "group");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 未读消息通知
|
||||
* @param $rows
|
||||
* @param $dialogType
|
||||
* @return void
|
||||
*/
|
||||
private function unreadMsgEmail($rows, $dialogType)
|
||||
{
|
||||
$array = $rows->groupBy('r_userid');
|
||||
foreach ($array as $userid => $data) {
|
||||
$data = WebSocketDialogMsg::select(['web_socket_dialog_msgs.*', 'r.id as r_id', 'r.userid as r_userid'])
|
||||
->join('web_socket_dialog_msg_reads as r', 'web_socket_dialog_msgs.id', '=', 'r.msg_id')
|
||||
->whereNull("r.read_at")
|
||||
->where("r.silence", 0)
|
||||
->where("r.email", 0)
|
||||
->where("r.userid", $userid)
|
||||
->where("web_socket_dialog_msgs.dialog_type", $dialogType)
|
||||
->whereIn("web_socket_dialog_msgs.type", ["text", "file", "record", "meeting"])
|
||||
->take(100)
|
||||
->get();
|
||||
if (empty($data)) {
|
||||
continue;
|
||||
}
|
||||
$user = User::whereBot(0)->whereNull('disable_at')->find($userid);
|
||||
if (empty($user)) {
|
||||
continue;
|
||||
}
|
||||
if (!Base::isEmail($user->email)) {
|
||||
continue;
|
||||
}
|
||||
$setting = Base::setting('emailSetting');
|
||||
$msgType = $dialogType === "group" ? "群聊" : "成员";
|
||||
$subject = null;
|
||||
$content = view('email.unread', [
|
||||
'type' => 'head',
|
||||
'nickname' => $user->nickname,
|
||||
'msgType' => $msgType,
|
||||
'count' => count($data),
|
||||
])->render();
|
||||
$lists = $data->groupBy('dialog_id');
|
||||
/** @var WebSocketDialogMsg[] $items */
|
||||
foreach ($lists as $items) {
|
||||
$dialogId = 0;
|
||||
$dialogName = null;
|
||||
foreach ($items as $item) {
|
||||
$item->cancelAppend();
|
||||
$item->userInfo = User::userid2basic($item->userid);
|
||||
$item->preview = $item->previewMsg(true);
|
||||
$item->preview = str_replace('<p>', '<p style="margin:0;padding:0">', $item->preview);
|
||||
if (empty($dialogId)) {
|
||||
$dialogId = $item->dialog_id;
|
||||
}
|
||||
if ($dialogName === null) {
|
||||
if ($dialogType === "user" && $item->userInfo) {
|
||||
if ($item->userInfo->profession) {
|
||||
$dialogName = $item->userInfo->nickname . " ({$item->userInfo->profession})";
|
||||
} else {
|
||||
$dialogName = $item->userInfo->nickname;
|
||||
}
|
||||
} else {
|
||||
$dialogName = $item->webSocketDialog?->getGroupName();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($subject === null) {
|
||||
$count = count($lists);
|
||||
if ($count > 1) {
|
||||
$subject = "来自{$count}个{$msgType}未读消息提醒";
|
||||
} else {
|
||||
$subject = "来自{$dialogName}未读消息提醒";
|
||||
}
|
||||
}
|
||||
$content .= view('email.unread', [
|
||||
'type' => 'content',
|
||||
'dialogUrl' => config("app.url") . "/manage/messenger?dialog_id={$dialogId}",
|
||||
'dialogName' => $dialogName,
|
||||
'unread' => count($items),
|
||||
'items' => $items,
|
||||
])->render();
|
||||
$content = str_replace("{{RemoteURL}}", config("app.url") . "/", $content);
|
||||
}
|
||||
try {
|
||||
Setting::validateAddr($user->email, function($to) use ($content, $subject, $setting) {
|
||||
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($to)
|
||||
->subject($subject)
|
||||
->html($content))
|
||||
->send();
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
info("unreadMsgEmail: " . $e->getMessage());
|
||||
}
|
||||
WebSocketDialogMsgRead::whereIn('id', $data->pluck('r_id'))->update([
|
||||
'email' => 1
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ class IhttpTask extends AbstractTask
|
||||
protected $extra;
|
||||
protected $apiWebsocket;
|
||||
protected $apiUserid;
|
||||
protected $endPush = [];
|
||||
|
||||
/**
|
||||
* IhttpTask constructor.
|
||||
@@ -27,6 +28,7 @@ class IhttpTask extends AbstractTask
|
||||
*/
|
||||
public function __construct($url, $post = [], $extra = [])
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->url = $url;
|
||||
$this->post = $post;
|
||||
$this->extra = $extra;
|
||||
@@ -53,7 +55,7 @@ class IhttpTask extends AbstractTask
|
||||
$res = Ihttp::ihttp_request($this->url, $this->post, $this->extra);
|
||||
if ($this->apiWebsocket && $this->apiUserid) {
|
||||
$data = Base::isSuccess($res) ? Base::json2array($res['data']) : $res;
|
||||
PushTask::push([
|
||||
$this->endPush[] = [
|
||||
'userid' => $this->apiUserid,
|
||||
'msg' => [
|
||||
'type' => 'apiWebsocket',
|
||||
@@ -61,7 +63,13 @@ class IhttpTask extends AbstractTask
|
||||
'apiSuccess' => Base::isSuccess($res),
|
||||
'data' => $data,
|
||||
]
|
||||
]);
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
PushTask::push($this->endPush);
|
||||
}
|
||||
}
|
||||
|
||||
57
app/Tasks/JokeSoupTask.php
Normal file
57
app/Tasks/JokeSoupTask.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Module\Base;
|
||||
use App\Module\Extranet;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
|
||||
/**
|
||||
* 获取笑话、心灵鸡汤
|
||||
*
|
||||
* 在.env添加笑话 JUKE_KEY_JOKE
|
||||
* 在.env添加鸡汤 JUKE_KEY_SOUP
|
||||
*
|
||||
* 每日小时采集1次
|
||||
*/
|
||||
class JokeSoupTask extends AbstractTask
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
// 判断每小时执行一次
|
||||
if (Cache::get("JokeSoupTask:YmdH") == date("YmdH")) {
|
||||
return;
|
||||
}
|
||||
Cache::put("JokeSoupTask:YmdH", date("YmdH"), Carbon::now()->addDay());
|
||||
//
|
||||
$array = Base::json2array(Cache::get("JokeSoupTask:jokes"));
|
||||
$data = Extranet::randJoke();
|
||||
foreach ($data as $item) {
|
||||
if ($text = trim($item['content'])) {
|
||||
$array[] = $text;
|
||||
}
|
||||
}
|
||||
Cache::forever("JokeSoupTask:jokes", Base::array2json(array_slice($array, -100)));
|
||||
//
|
||||
$array = Base::json2array(Cache::get("JokeSoupTask:soups"));
|
||||
$data = Extranet::soups();
|
||||
if ($data) {
|
||||
$array[] = $data;
|
||||
}
|
||||
Cache::forever("JokeSoupTask:soups", Base::array2json(array_slice($array, -24)));
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ class LineTask extends AbstractTask
|
||||
{
|
||||
protected $userid;
|
||||
protected $online;
|
||||
protected $endPush = [];
|
||||
|
||||
/**
|
||||
* LineTask constructor.
|
||||
@@ -24,6 +25,7 @@ class LineTask extends AbstractTask
|
||||
*/
|
||||
public function __construct($userid, bool $online)
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->userid = $userid;
|
||||
$this->online = $online;
|
||||
}
|
||||
@@ -36,7 +38,7 @@ class LineTask extends AbstractTask
|
||||
$fd[] = $ws->fd;
|
||||
}
|
||||
if ($fd) {
|
||||
PushTask::push([
|
||||
$this->endPush[] = [
|
||||
'fd' => $fd,
|
||||
'msg' => [
|
||||
'type' => 'line',
|
||||
@@ -45,8 +47,13 @@ class LineTask extends AbstractTask
|
||||
'online' => $this->online,
|
||||
],
|
||||
]
|
||||
]);
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
PushTask::push($this->endPush);
|
||||
}
|
||||
}
|
||||
|
||||
84
app/Tasks/LoopTask.php
Normal file
84
app/Tasks/LoopTask.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\ProjectFlow;
|
||||
use App\Models\ProjectFlowItem;
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskUser;
|
||||
use Carbon\Carbon;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
|
||||
/**
|
||||
* 任务重复周期
|
||||
*/
|
||||
class LoopTask extends AbstractTask
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
ProjectTask::whereBetween('loop_at', [
|
||||
Carbon::now()->subMinutes(10),
|
||||
Carbon::now()
|
||||
])->chunkById(100, function ($list) {
|
||||
/** @var ProjectTask $item */
|
||||
foreach ($list as $item) {
|
||||
try {
|
||||
$task = $item->copyTask();
|
||||
// 工作流
|
||||
$projectFlow = ProjectFlow::whereProjectId($task->project_id)->orderByDesc('id')->first();
|
||||
if ($projectFlow) {
|
||||
$projectFlowItem = ProjectFlowItem::whereFlowId($projectFlow->id)->orderBy('sort')->get();
|
||||
// 赋一个开始状态
|
||||
foreach ($projectFlowItem as $flowItem) {
|
||||
if ($flowItem->status == 'start') {
|
||||
$task->flow_item_id = $flowItem->id;
|
||||
$task->flow_item_name = $flowItem->status . "|" . $flowItem->name;
|
||||
if ($flowItem->userids) {
|
||||
$userids = array_values(array_unique($flowItem->userids));
|
||||
foreach ($userids as $uid) {
|
||||
ProjectTaskUser::updateInsert([
|
||||
'task_id' => $task->id,
|
||||
'userid' => $uid,
|
||||
], [
|
||||
'project_id' => $task->project_id,
|
||||
'task_pid' => $task->id,
|
||||
'owner' => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 新任务时间、周期
|
||||
if ($task->start_at) {
|
||||
$diffSecond = Carbon::parse($task->start_at)->diffInSeconds(Carbon::parse($task->end_at), true);
|
||||
$task->start_at = Carbon::parse($task->loop_at);
|
||||
$task->end_at = $task->start_at->clone()->addSeconds($diffSecond);
|
||||
}
|
||||
$task->refreshLoop(true);
|
||||
$task->addLog("创建任务来自周期任务ID:{$item->id}", [], $task->userid);
|
||||
// 清空旧周期
|
||||
$item->loop = '';
|
||||
$item->loop_at = null;
|
||||
$item->save();
|
||||
$item->addLog("已创建新的周期任务ID:{$task->id},此任务关闭周期", [], $task->userid);
|
||||
} catch (\Throwable $e) {
|
||||
$item->addLog("生成重复任务失败:" . $e->getMessage(), [], $item->userid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ class PushTask extends AbstractTask
|
||||
{
|
||||
protected $params;
|
||||
protected $retryOffline = true;
|
||||
protected $endPush = [];
|
||||
|
||||
/**
|
||||
* PushTask constructor.
|
||||
@@ -26,6 +27,7 @@ class PushTask extends AbstractTask
|
||||
*/
|
||||
public function __construct($params = [], $retryOffline = true)
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->params = $params;
|
||||
$this->retryOffline = $retryOffline;
|
||||
}
|
||||
@@ -42,12 +44,41 @@ class PushTask extends AbstractTask
|
||||
}
|
||||
// 根据会员ID推送离线时收到的消息
|
||||
elseif (Base::leftExists($this->params, "RETRY::")) {
|
||||
self::sendTmpMsgForUserid(intval(Base::leftDelete($this->params, "RETRY::")));
|
||||
$this->sendTmpMsgForUserid(intval(Base::leftDelete($this->params, "RETRY::")));
|
||||
}
|
||||
}
|
||||
is_array($this->params) && self::push($this->params, $this->retryOffline);
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
self::push($this->endPush);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据会员ID推送离线时收到的消息
|
||||
* @param $userid
|
||||
*/
|
||||
private function sendTmpMsgForUserid($userid)
|
||||
{
|
||||
if (empty($userid)) {
|
||||
return;
|
||||
}
|
||||
WebSocketTmpMsg::whereCreateId($userid)
|
||||
->whereSend(0)
|
||||
->where('created_at', '>', Carbon::now()->subMinute()) // 1分钟内添加的数据
|
||||
->orderBy('id')
|
||||
->chunk(100, function($list) use ($userid) {
|
||||
foreach ($list as $item) {
|
||||
$this->endPush[] = [
|
||||
'tmpMsgId' => $item->id,
|
||||
'userid' => $userid,
|
||||
'msg' => Base::json2array($item->msg),
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录离线消息,等上线后重新发送
|
||||
* @param array $userFail
|
||||
@@ -71,30 +102,6 @@ class PushTask extends AbstractTask
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据会员ID推送离线时收到的消息
|
||||
* @param $userid
|
||||
*/
|
||||
private static function sendTmpMsgForUserid($userid)
|
||||
{
|
||||
if (empty($userid)) {
|
||||
return;
|
||||
}
|
||||
WebSocketTmpMsg::whereCreateId($userid)
|
||||
->whereSend(0)
|
||||
->where('created_at', '>', Carbon::now()->subMinute()) // 1分钟内添加的数据
|
||||
->orderBy('id')
|
||||
->chunk(100, function($list) use ($userid) {
|
||||
foreach ($list as $item) {
|
||||
self::push([
|
||||
'tmpMsgId' => $item->id,
|
||||
'userid' => $userid,
|
||||
'msg' => Base::json2array($item->msg),
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送消息
|
||||
* @param array $lists 消息列表
|
||||
@@ -172,7 +179,7 @@ class PushTask extends AbstractTask
|
||||
try {
|
||||
$swoole->push($fid, Base::array2json($msg));
|
||||
$tmpMsgId > 0 && WebSocketTmpMsg::whereId($tmpMsgId)->update(['send' => 1]);
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
44
app/Tasks/PushUmengMsg.php
Normal file
44
app/Tasks/PushUmengMsg.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace App\Tasks;
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Models\UmengAlias;
|
||||
use App\Module\Base;
|
||||
|
||||
/**
|
||||
* 推送友盟消息
|
||||
*/
|
||||
class PushUmengMsg extends AbstractTask
|
||||
{
|
||||
protected $userid = 0;
|
||||
protected $array = [];
|
||||
|
||||
/**
|
||||
* @param array|int $userid
|
||||
* @param array $array
|
||||
*/
|
||||
public function __construct($userid, $array = [])
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->userid = $userid;
|
||||
$this->array = is_array($array) ? $array : [];
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
if (empty($this->userid) || empty($this->array)) {
|
||||
return;
|
||||
}
|
||||
$setting = Base::setting('appPushSetting');
|
||||
if ($setting['push'] !== 'open') {
|
||||
return;
|
||||
}
|
||||
UmengAlias::pushMsgToUserid($this->userid, $this->array);
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,14 @@ use App\Models\User;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Request;
|
||||
|
||||
|
||||
/**
|
||||
* 推送回话消息
|
||||
* 推送会话消息
|
||||
* Class WebSocketDialogMsgTask
|
||||
* @package App\Tasks
|
||||
*/
|
||||
@@ -20,76 +23,207 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
{
|
||||
protected $id;
|
||||
protected $ignoreFd;
|
||||
protected $msgNotExistRetry = false; // 推送失败后重试
|
||||
protected $silence = false; // 静默推送(前端不通知、App不推送,如果会话设置了免打扰则强制静默)
|
||||
protected $endPush = [];
|
||||
protected $endArray = [];
|
||||
|
||||
/**
|
||||
* WebSocketDialogMsgTask constructor.
|
||||
* @param int $id 消息ID
|
||||
* @param mixed $ignoreFd
|
||||
*/
|
||||
public function __construct($id)
|
||||
public function __construct($id, $ignoreFd = null)
|
||||
{
|
||||
parent::__construct(...func_get_args());
|
||||
$this->id = $id;
|
||||
$this->ignoreFd = Request::header('fd');
|
||||
$this->ignoreFd = $ignoreFd === null ? Request::header('fd') : $ignoreFd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ignoreFd
|
||||
*/
|
||||
public function setIgnoreFd($ignoreFd)
|
||||
{
|
||||
$this->ignoreFd = $ignoreFd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $msgNotExistRetry
|
||||
*/
|
||||
public function setMsgNotExistRetry(bool $msgNotExistRetry): void
|
||||
{
|
||||
$this->msgNotExistRetry = $msgNotExistRetry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $silence
|
||||
*/
|
||||
public function setSilence(bool $silence): void
|
||||
{
|
||||
$this->silence = $silence;
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
global $_A;
|
||||
$_A = [
|
||||
'__fill_url_remote_url' => true,
|
||||
];
|
||||
|
||||
//
|
||||
$msg = WebSocketDialogMsg::find($this->id);
|
||||
if (empty($msg)) {
|
||||
if ($this->msgNotExistRetry) {
|
||||
$task = new WebSocketDialogMsgTask($this->id, $this->ignoreFd || '');
|
||||
$task->delay(1);
|
||||
$this->endArray[] = $task;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$dialog = WebSocketDialog::find($msg->dialog_id);
|
||||
if (empty($dialog)) {
|
||||
return;
|
||||
}
|
||||
$updateds = [];
|
||||
$silences = [];
|
||||
foreach ($dialog->dialogUser as $dialogUser) {
|
||||
$updateds[$dialogUser->userid] = $dialogUser->updated_at;
|
||||
$silences[$dialogUser->userid] = $dialogUser->silence;
|
||||
}
|
||||
$userids = array_keys($silences);
|
||||
|
||||
// 推送目标①:群成员
|
||||
$userids = $dialog->dialogUser->pluck('userid')->toArray();
|
||||
foreach ($userids AS $userid) {
|
||||
if ($userid == $msg->userid) {
|
||||
continue;
|
||||
// 提及会员
|
||||
$mentions = [];
|
||||
if ($msg->type === 'text') {
|
||||
preg_match_all("/<span class=\"mention user\" data-id=\"(\d+)\">/", $msg->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, $msg->userid])) {
|
||||
$dialog->joinGroup($diffids, $msg->userid);
|
||||
$dialog->pushMsg("groupJoin", null, $diffids);
|
||||
$userids = array_values(array_unique(array_merge($mentions, $userids)));
|
||||
}
|
||||
}
|
||||
|
||||
// 推送目标①:会话成员/群成员
|
||||
$array = [];
|
||||
foreach ($userids AS $userid) {
|
||||
$silence = $this->silence || $silences[$userid];
|
||||
$updated = $updateds[$userid] ?? $msg->created_at;
|
||||
if ($userid == $msg->userid) {
|
||||
$array[$userid] = [
|
||||
'userid' => $userid,
|
||||
'mention' => false,
|
||||
'silence' => $silence,
|
||||
'updated' => $updated,
|
||||
];
|
||||
} else {
|
||||
$mention = array_intersect([0, $userid], $mentions) ? 1 : 0;
|
||||
$silence = $mention ? false : $silence;
|
||||
WebSocketDialogMsgRead::createInstance([
|
||||
'dialog_id' => $msg->dialog_id,
|
||||
'msg_id' => $msg->id,
|
||||
'userid' => $userid,
|
||||
'mention' => $mention,
|
||||
'silence' => $silence,
|
||||
])->saveOrIgnore();
|
||||
$array[$userid] = [
|
||||
'userid' => $userid,
|
||||
'mention' => $mention,
|
||||
'silence' => $silence,
|
||||
'updated' => $updated,
|
||||
];
|
||||
// 机器人收到消处理
|
||||
$botUser = User::whereUserid($userid)->whereBot(1)->first();
|
||||
if ($botUser) {
|
||||
$this->endArray[] = new BotReceiveMsgTask($botUser->userid, $msg->id);
|
||||
}
|
||||
}
|
||||
WebSocketDialogMsgRead::createInstance([
|
||||
'dialog_id' => $msg->dialog_id,
|
||||
'msg_id' => $msg->id,
|
||||
'userid' => $userid,
|
||||
])->saveOrIgnore();
|
||||
}
|
||||
// 更新已发送数量
|
||||
$msg->send = WebSocketDialogMsgRead::whereMsgId($msg->id)->count();
|
||||
$msg->save();
|
||||
// 开始推送消息
|
||||
PushTask::push([
|
||||
'userid' => $userids,
|
||||
'ignoreFd' => $this->ignoreFd,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'add',
|
||||
'data' => $msg->toArray(),
|
||||
]
|
||||
]);
|
||||
$umengUserid = [];
|
||||
foreach ($array as $item) {
|
||||
$this->endPush[] = [
|
||||
'userid' => $item['userid'],
|
||||
'ignoreFd' => $this->ignoreFd,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'add',
|
||||
'silence' => $item['silence'] ? 1 : 0,
|
||||
'data' => array_merge($msg->toArray(), [
|
||||
'mention' => $item['mention'],
|
||||
'user_at' => Carbon::parse($item['updated'])->toDateTimeString('millisecond'),
|
||||
'user_ms' => Carbon::parse($item['updated'])->valueOf(),
|
||||
]),
|
||||
]
|
||||
];
|
||||
if ($item['userid'] != $msg->userid && !$item['silence'] && !$this->silence) {
|
||||
$umengUserid[] = $item['userid'];
|
||||
}
|
||||
}
|
||||
// umeng推送app
|
||||
if ($umengUserid) {
|
||||
$setting = Base::setting('appPushSetting');
|
||||
if ($setting['push'] === 'open') {
|
||||
$umengTitle = User::userid2nickname($msg->userid);
|
||||
if ($dialog->type == 'group') {
|
||||
$umengTitle = "{$dialog->getGroupName()} ($umengTitle)";
|
||||
}
|
||||
$this->endArray[] = new PushUmengMsg($umengUserid, [
|
||||
'title' => $umengTitle,
|
||||
'body' => $msg->previewMsg(),
|
||||
'description' => "MID:{$msg->id}",
|
||||
'seconds' => 3600,
|
||||
'badge' => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 推送目标②:正在打开这个任务会话的会员
|
||||
if ($dialog->type == 'group' && $dialog->group_type == 'task') {
|
||||
$list = User::whereTaskDialogId($dialog->id)->pluck('userid')->toArray();
|
||||
if ($list) {
|
||||
$array = [];
|
||||
foreach ($list as $uid) {
|
||||
if (!in_array($uid, $userids)) {
|
||||
$array[] = $uid;
|
||||
foreach ($list as $item) {
|
||||
if (!in_array($item, $userids)) {
|
||||
$array[] = $item;
|
||||
}
|
||||
}
|
||||
if ($array) {
|
||||
PushTask::push([
|
||||
$this->endPush[] = [
|
||||
'userid' => $array,
|
||||
'ignoreFd' => $this->ignoreFd,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'chat',
|
||||
'data' => $msg->toArray(),
|
||||
'silence' => $this->silence ? 1 : 0,
|
||||
'data' => array_merge($msg->toArray(), [
|
||||
'user_at' => Carbon::parse($msg->created_at)->toDateTimeString('millisecond'),
|
||||
'user_ms' => Carbon::parse($msg->created_at)->valueOf(),
|
||||
]),
|
||||
]
|
||||
]);
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
foreach ($this->endArray as $task) {
|
||||
Task::deliver($task);
|
||||
}
|
||||
PushTask::push($this->endPush);
|
||||
}
|
||||
}
|
||||
|
||||
15
bin/auto
Executable file
15
bin/auto
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ ! -f ".env" ]; then
|
||||
echo -e "配置文件不存在!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
debug=`cat .env | grep "^APP_DEBUG=" | awk -F '=' '{print $2}'`
|
||||
if [ "$debug" = "true" ]; then
|
||||
echo "[MODE] development"
|
||||
./bin/inotify ./app
|
||||
else
|
||||
echo "[MODE] production"
|
||||
php bin/laravels start -i
|
||||
fi
|
||||
89
bin/run
89
bin/run
@@ -1,89 +0,0 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class runLoader
|
||||
*/
|
||||
class runLoader
|
||||
{
|
||||
public function randString($length = 16)
|
||||
{
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
$passwordstr = '';
|
||||
$max = strlen($chars) - 1;
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$passwordstr .= $chars[mt_rand(0, $max)];
|
||||
}
|
||||
return $passwordstr;
|
||||
}
|
||||
|
||||
function getEnv(string $key)
|
||||
{
|
||||
if (empty($key) || !is_string($key)) {
|
||||
return '';
|
||||
}
|
||||
$envPath = realpath(__DIR__ . '/../') . DIRECTORY_SEPARATOR . '.env';
|
||||
if (!file_exists($envPath)) {
|
||||
return false;
|
||||
}
|
||||
$envContent = file_get_contents($envPath);
|
||||
preg_match_all("/^" . $key . "\s*=\s*(.*?)$/m", $envContent, $matchs);
|
||||
return $matchs[1] ?: '';
|
||||
}
|
||||
|
||||
function modifyEnv(array $data)
|
||||
{
|
||||
if (empty($data) || !is_array($data)) {
|
||||
return false;
|
||||
}
|
||||
$envPath = realpath(__DIR__ . '/../') . DIRECTORY_SEPARATOR . '.env';
|
||||
if (!file_exists($envPath)) {
|
||||
return false;
|
||||
}
|
||||
$envContent = file_get_contents($envPath);
|
||||
foreach ($data as $key => $val) {
|
||||
$envContent = preg_replace("/^" . $key . "\s*=\s*(.*?)$/m", $key . "=" . $val, $envContent);
|
||||
}
|
||||
file_put_contents($envPath, $envContent);
|
||||
return true;
|
||||
}
|
||||
|
||||
function modifyMode($type)
|
||||
{
|
||||
$filePath = realpath(__DIR__ . '/../') . DIRECTORY_SEPARATOR . '/docker/php/php.conf';
|
||||
if (!file_exists($filePath)) {
|
||||
return false;
|
||||
}
|
||||
$envContent = file_get_contents($filePath);
|
||||
$envContent = str_replace("#command=php bin/laravels start -i", "command=php bin/laravels start -i", $envContent);
|
||||
$envContent = str_replace("#command=./bin/inotify ./app", "command=./bin/inotify ./app", $envContent);
|
||||
if ($type == "dev") {
|
||||
$envContent = str_replace("command=php bin/laravels start -i", "#command=php bin/laravels start -i", $envContent);
|
||||
$this->modifyEnv([
|
||||
'APP_DEBUG' => 'true'
|
||||
]);
|
||||
} else {
|
||||
$envContent = str_replace("command=./bin/inotify ./app", "#command=./bin/inotify ./app", $envContent);
|
||||
$this->modifyEnv([
|
||||
'APP_DEBUG' => 'false'
|
||||
]);
|
||||
}
|
||||
file_put_contents($filePath, $envContent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$array = getopt('', ['port:', 'mode:']);
|
||||
$loader = new runLoader();
|
||||
|
||||
if (isset($array['mode'])) {
|
||||
$loader->modifyMode($array['mode']);
|
||||
}
|
||||
|
||||
$data = [];
|
||||
if (isset($array['port'])) {
|
||||
$data['APP_PORT'] = $array['port'];
|
||||
}
|
||||
if ($data) {
|
||||
$loader->modifyEnv($data);
|
||||
}
|
||||
60
cliff.toml
Normal file
60
cliff.toml
Normal file
@@ -0,0 +1,60 @@
|
||||
# configuration file for git-cliff (0.1.0)
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Changelog\n
|
||||
All notable changes to this project will be documented in this file.\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://tera.netlify.app/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}]
|
||||
{% else %}\
|
||||
## [Unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
# changelog footer
|
||||
footer = """
|
||||
"""
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = true
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^pref", group = "Performance"},
|
||||
{ message = "^refactor", group = "Refactor"},
|
||||
{ message = "^style", group = "Styling"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
]
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = true
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
skip_tags = "v0.1.0-beta.1"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags chronologically
|
||||
date_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
182
cmd
182
cmd
@@ -13,6 +13,7 @@ Error="${Red}[错误]${Font}"
|
||||
|
||||
cur_path="$(pwd)"
|
||||
cur_arg=$@
|
||||
COMPOSE="docker-compose"
|
||||
|
||||
judge() {
|
||||
if [[ 0 -eq $? ]]; then
|
||||
@@ -31,30 +32,64 @@ rand() {
|
||||
echo $(($num%$max+$min))
|
||||
}
|
||||
|
||||
supervisorctl_restart() {
|
||||
local RES=`run_exec php "supervisorctl update $1"`
|
||||
rand_string() {
|
||||
local lan=$1
|
||||
if [[ `uname` == 'Linux' ]]; then
|
||||
echo "$(date +%s%N | md5sum | cut -c 1-${lan})"
|
||||
else
|
||||
echo "$(docker run -it --rm alpine sh -c "date +%s%N | md5sum | cut -c 1-${lan}")"
|
||||
fi
|
||||
}
|
||||
|
||||
restart_php() {
|
||||
local RES=`run_exec php "supervisorctl update php"`
|
||||
if [ -z "$RES" ]; then
|
||||
run_exec php "supervisorctl restart $1"
|
||||
RES=`run_exec php "supervisorctl restart php"`
|
||||
fi
|
||||
local IN=`echo $RES | grep "ERROR"`
|
||||
if [[ "$IN" != "" ]]; then
|
||||
$COMPOSE stop php
|
||||
$COMPOSE start php
|
||||
else
|
||||
echo -e "$RES"
|
||||
fi
|
||||
}
|
||||
|
||||
switch_debug() {
|
||||
local debug="false"
|
||||
if [[ "$1" == "true" ]] || [[ "$1" == "dev" ]] || [[ "$1" == "open" ]]; then
|
||||
debug="true"
|
||||
fi
|
||||
if [[ "$(env_get APP_DEBUG)" != "$debug" ]]; then
|
||||
env_set APP_DEBUG "$debug"
|
||||
restart_php
|
||||
fi
|
||||
}
|
||||
|
||||
check_docker() {
|
||||
docker --version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
docker-compose --version &> /dev/null
|
||||
docker-compose version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker-compose!${Font}"
|
||||
docker compose version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装 Docker-compose!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
COMPOSE="docker compose"
|
||||
fi
|
||||
if [[ -n `$COMPOSE version | grep -E "\sv*1"` ]]; then
|
||||
$COMPOSE version
|
||||
echo -e "${Error} ${RedBG} Docker-compose 版本过低,请升级至v2+!${Font}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_node() {
|
||||
npm --version > /dev/null
|
||||
npm --version &> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 未安装nodejs!${Font}"
|
||||
exit 1
|
||||
@@ -62,7 +97,7 @@ check_node() {
|
||||
}
|
||||
|
||||
docker_name() {
|
||||
echo `docker-compose ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
}
|
||||
|
||||
run_compile() {
|
||||
@@ -71,14 +106,17 @@ run_compile() {
|
||||
if [ ! -d "./node_modules" ]; then
|
||||
npm install
|
||||
fi
|
||||
run_exec php "php bin/run --mode=$type"
|
||||
supervisorctl_restart php
|
||||
if [ "$type" = "dev" ]; then
|
||||
echo "<script>window.location.href=window.location.href.replace(/:\d+/, ':' + $(env_get APP_PORT))</script>" > ./index.html
|
||||
env_set APP_DEV_PORT $(rand 20001 30000)
|
||||
fi
|
||||
switch_debug "$type"
|
||||
#
|
||||
if [ "$type" = "prod" ]; then
|
||||
rm -rf "./public/js/build"
|
||||
npx mix --production
|
||||
npx vite build -- fromcmd
|
||||
else
|
||||
npx mix watch --hot
|
||||
npx vite -- fromcmd
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -97,14 +135,16 @@ run_electron() {
|
||||
if [ -d "./electron/dist" ]; then
|
||||
rm -rf "./electron/dist"
|
||||
fi
|
||||
if [ -d "./electron/public" ] && [ "$argv" != "--nobuild" ]; then
|
||||
if [ -d "./electron/public" ]; then
|
||||
rm -rf "./electron/public"
|
||||
fi
|
||||
mkdir -p ./electron/public
|
||||
cp ./electron/index.html ./electron/public/index.html
|
||||
#
|
||||
if [ "$argv" != "dev" ] && [ "$argv" != "--nobuild" ]; then
|
||||
npx mix --production -- --env --electron
|
||||
if [ "$argv" == "dev" ]; then
|
||||
switch_debug "$argv"
|
||||
else
|
||||
mkdir -p ./electron/public
|
||||
cp ./electron/index.html ./electron/public/index.html
|
||||
npx vite build -- fromcmd electronBuild
|
||||
fi
|
||||
node ./electron/build.js $argv
|
||||
}
|
||||
@@ -117,11 +157,7 @@ run_exec() {
|
||||
echo -e "${Error} ${RedBG} 没有找到 $container 容器! ${Font}"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$container" = "mariadb" ] || [ "$container" = "nginx" ] || [ "$container" = "redis" ]; then
|
||||
docker exec -it "$name" /bin/sh -c "$cmd"
|
||||
else
|
||||
docker exec -it "$name" /bin/bash -c "$cmd"
|
||||
fi
|
||||
docker exec -it "$name" /bin/sh -c "$cmd"
|
||||
}
|
||||
|
||||
run_mysql() {
|
||||
@@ -178,8 +214,11 @@ env_set() {
|
||||
if [ -z "$exist" ]; then
|
||||
echo "$key=$val" >> $cur_path/.env
|
||||
else
|
||||
command="sed -i '/^$key=/c\\$key=$val' /www/.env"
|
||||
docker run -it --rm -v ${cur_path}:/www alpine sh -c "$command"
|
||||
if [[ `uname` == 'Linux' ]]; then
|
||||
sed -i "/^${key}=/c\\${key}=${val}" ${cur_path}/.env
|
||||
else
|
||||
docker run -it --rm -v ${cur_path}:/www alpine sh -c "sed -i "/^${key}=/c\\${key}=${val}" /www/.env"
|
||||
fi
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 设置env参数失败!${Font}"
|
||||
exit 1
|
||||
@@ -192,10 +231,10 @@ env_init() {
|
||||
cp .env.docker .env
|
||||
fi
|
||||
if [ -z "$(env_get DB_ROOT_PASSWORD)" ]; then
|
||||
env_set DB_ROOT_PASSWORD "$(docker run -it --rm alpine sh -c "date +%s%N | md5sum | cut -c 1-16")"
|
||||
env_set DB_ROOT_PASSWORD "$(rand_string 16)"
|
||||
fi
|
||||
if [ -z "$(env_get APP_ID)" ]; then
|
||||
env_set APP_ID "$(docker run -it --rm alpine sh -c "date +%s%N | md5sum | cut -c 1-6")"
|
||||
env_set APP_ID "$(rand_string 6)"
|
||||
fi
|
||||
if [ -z "$(env_get APP_IPPR)" ]; then
|
||||
env_set APP_IPPR "10.$(rand 50 100).$(rand 100 200)"
|
||||
@@ -207,11 +246,14 @@ arg_get() {
|
||||
local value=""
|
||||
for var in $cur_arg; do
|
||||
if [[ "$find" == "y" ]]; then
|
||||
value=$var
|
||||
if [[ ! $var =~ "--" ]]; then
|
||||
value=$var
|
||||
fi
|
||||
break
|
||||
fi
|
||||
if [[ "--$1" == "$var" ]] || [[ "-$1" == "$var" ]]; then
|
||||
find="y"
|
||||
value="yes"
|
||||
fi
|
||||
done
|
||||
echo $value
|
||||
@@ -230,16 +272,19 @@ if [ $# -gt 0 ]; then
|
||||
if [[ "$1" == "init" ]] || [[ "$1" == "install" ]]; then
|
||||
shift 1
|
||||
# 初始化文件
|
||||
rm -rf composer.lock
|
||||
rm -rf package-lock.json
|
||||
if [[ -n "$(arg_get relock)" ]]; then
|
||||
rm -rf node_modules
|
||||
rm -rf package-lock.json
|
||||
rm -rf vendor
|
||||
rm -rf composer.lock
|
||||
fi
|
||||
mkdir -p "${cur_path}/docker/log/supervisor"
|
||||
mkdir -p "${cur_path}/docker/mysql/data"
|
||||
chmod -R 775 "${cur_path}/docker/log/supervisor"
|
||||
chmod -R 775 "${cur_path}/docker/mysql/data"
|
||||
# 启动容器
|
||||
[[ "$(arg_get port)" -gt 0 ]] && env_set APP_PORT "$(arg_get port)"
|
||||
docker-compose up -d
|
||||
docker-compose restart php
|
||||
$COMPOSE up php -d
|
||||
# 安装composer依赖
|
||||
run_exec php "composer install"
|
||||
if [ ! -f "${cur_path}/vendor/autoload.php" ]; then
|
||||
@@ -252,23 +297,27 @@ if [ $# -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
[[ -z "$(env_get APP_KEY)" ]] && run_exec php "php artisan key:generate"
|
||||
run_exec php "php bin/run --mode=prod"
|
||||
switch_debug "false"
|
||||
# 检查数据库
|
||||
remaining=10
|
||||
remaining=20
|
||||
while [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/db.opt" ]; do
|
||||
((remaining=$remaining-1))
|
||||
if [ $remaining -lt 0 ]; then
|
||||
echo -e "${Error} ${RedBG} 数据库安装失败! ${Font}"
|
||||
echo -e "${Error} ${RedBG} 数据库初始化失败! ${Font}"
|
||||
exit 1
|
||||
fi
|
||||
chmod -R 775 "${cur_path}/docker/mysql/data"
|
||||
sleep 3
|
||||
done
|
||||
run_exec php "php artisan migrate --seed"
|
||||
if [ ! -f "${cur_path}/docker/mysql/data/$(env_get DB_DATABASE)/$(env_get DB_PREFIX)migrations.ibd" ]; then
|
||||
echo -e "${Error} ${RedBG} 数据库安装失败! ${Font}"
|
||||
exit 1
|
||||
fi
|
||||
# 设置初始化密码
|
||||
res=`run_exec mariadb "sh /etc/mysql/repassword.sh"`
|
||||
docker-compose stop
|
||||
docker-compose start
|
||||
$COMPOSE up -d
|
||||
restart_php
|
||||
echo -e "${OK} ${GreenBG} 安装完成 ${Font}"
|
||||
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
|
||||
echo -e "$res"
|
||||
@@ -280,12 +329,12 @@ if [ $# -gt 0 ]; then
|
||||
git pull
|
||||
run_exec php "composer update"
|
||||
run_exec php "php artisan migrate"
|
||||
supervisorctl_restart php
|
||||
docker-compose up -d
|
||||
restart_php
|
||||
$COMPOSE up -d
|
||||
elif [[ "$1" == "uninstall" ]]; then
|
||||
shift 1
|
||||
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(y/n): " uninstall
|
||||
[[ -z ${uninstall} ]] && uninstall="N"
|
||||
read -rp "确定要卸载(含:删除容器、数据库、日志)吗?(Y/n): " uninstall
|
||||
[[ -z ${uninstall} ]] && uninstall="Y"
|
||||
case $uninstall in
|
||||
[yY][eE][sS] | [yY])
|
||||
echo -e "${RedBG} 开始卸载... ${Font}"
|
||||
@@ -295,31 +344,46 @@ if [ $# -gt 0 ]; then
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
docker-compose down
|
||||
$COMPOSE down
|
||||
rm -rf "./docker/mysql/data"
|
||||
rm -rf "./docker/log/supervisor"
|
||||
find "./storage/logs" -name "*.log" | xargs rm -rf
|
||||
echo -e "${OK} ${GreenBG} 卸载完成 ${Font}"
|
||||
elif [[ "$1" == "reinstall" ]]; then
|
||||
shift 1
|
||||
./cmd uninstall
|
||||
./cmd uninstall $@
|
||||
sleep 3
|
||||
./cmd install
|
||||
./cmd install $@
|
||||
elif [[ "$1" == "port" ]]; then
|
||||
shift 1
|
||||
env_set APP_PORT "$1"
|
||||
docker-compose up -d
|
||||
$COMPOSE up -d
|
||||
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
|
||||
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
|
||||
elif [[ "$1" == "url" ]]; then
|
||||
shift 1
|
||||
env_set APP_URL "$1"
|
||||
restart_php
|
||||
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
|
||||
elif [[ "$1" == "env" ]]; then
|
||||
shift 1
|
||||
if [ -n "$1" ]; then
|
||||
env_set $1 "$2"
|
||||
fi
|
||||
restart_php
|
||||
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
|
||||
elif [[ "$1" == "repassword" ]]; then
|
||||
shift 1
|
||||
run_exec mariadb "sh /etc/mysql/repassword.sh \"$@\""
|
||||
elif [[ "$1" == "dev" ]] || [[ "$1" == "development" ]]; then
|
||||
elif [[ "$1" == "serve" ]] || [[ "$1" == "dev" ]] || [[ "$1" == "development" ]]; then
|
||||
shift 1
|
||||
run_compile dev
|
||||
elif [[ "$1" == "prod" ]] || [[ "$1" == "production" ]]; then
|
||||
elif [[ "$1" == "build" ]] || [[ "$1" == "prod" ]] || [[ "$1" == "production" ]]; then
|
||||
shift 1
|
||||
run_compile prod
|
||||
elif [[ "$1" == "appbuild" ]] || [[ "$1" == "buildapp" ]]; then
|
||||
shift 1
|
||||
run_electron app $@
|
||||
elif [[ "$1" == "electron" ]]; then
|
||||
shift 1
|
||||
run_electron $@
|
||||
@@ -329,12 +393,8 @@ if [ $# -gt 0 ]; then
|
||||
docker run -it --rm -v ${cur_path}:/home/node/apidoc kuaifan/apidoc -i app/Http/Controllers/Api -o public/docs
|
||||
elif [[ "$1" == "debug" ]]; then
|
||||
shift 1
|
||||
if [[ "$@" == "close" ]]; then
|
||||
env_set APP_DEBUG "false"
|
||||
else
|
||||
env_set APP_DEBUG "true"
|
||||
fi
|
||||
supervisorctl_restart php
|
||||
switch_debug "$@"
|
||||
echo "success"
|
||||
elif [[ "$1" == "https" ]]; then
|
||||
shift 1
|
||||
if [[ "$@" == "auto" ]]; then
|
||||
@@ -342,7 +402,7 @@ if [ $# -gt 0 ]; then
|
||||
else
|
||||
env_set APP_SCHEME "true"
|
||||
fi
|
||||
supervisorctl_restart php
|
||||
restart_php
|
||||
elif [[ "$1" == "artisan" ]]; then
|
||||
shift 1
|
||||
e="php artisan $@" && run_exec php "$e"
|
||||
@@ -367,25 +427,29 @@ if [ $# -gt 0 ]; then
|
||||
elif [[ "$1" == "composer" ]]; then
|
||||
shift 1
|
||||
e="composer $@" && run_exec php "$e"
|
||||
elif [[ "$1" == "super" ]]; then
|
||||
elif [[ "$1" == "service" ]]; then
|
||||
shift 1
|
||||
supervisorctl_restart "$@"
|
||||
elif [[ "$1" == "supervisorctl" ]]; then
|
||||
e="service $@" && run_exec php "$e"
|
||||
elif [[ "$1" == "super" ]] || [[ "$1" == "supervisorctl" ]]; then
|
||||
shift 1
|
||||
e="supervisorctl $@" && run_exec php "$e"
|
||||
elif [[ "$1" == "models" ]]; then
|
||||
shift 1
|
||||
run_exec php "php app/Models/clearHelper.php"
|
||||
run_exec php "php artisan ide-helper:models -W"
|
||||
elif [[ "$1" == "translate" ]]; then
|
||||
shift 1
|
||||
run_exec php "cd /var/www/language && php translate.php"
|
||||
elif [[ "$1" == "test" ]]; then
|
||||
shift 1
|
||||
e="./vendor/bin/phpunit $@" && run_exec php "$e"
|
||||
elif [[ "$1" == "restart" ]]; then
|
||||
shift 1
|
||||
docker-compose stop "$@"
|
||||
docker-compose start "$@"
|
||||
$COMPOSE stop "$@"
|
||||
$COMPOSE start "$@"
|
||||
else
|
||||
docker-compose "$@"
|
||||
$COMPOSE "$@"
|
||||
fi
|
||||
else
|
||||
docker-compose ps
|
||||
$COMPOSE ps
|
||||
fi
|
||||
|
||||
@@ -14,16 +14,22 @@
|
||||
"ext-json": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-simplexml": "*",
|
||||
"directorytree/ldaprecord-laravel": "^2.7",
|
||||
"fideloper/proxy": "^4.4.1",
|
||||
"fruitcake/laravel-cors": "^2.0.4",
|
||||
"guanguans/notify": "^1.21.1",
|
||||
"guzzlehttp/guzzle": "^7.3.0",
|
||||
"laravel/framework": "^v8.48.1",
|
||||
"hedeqiang/umeng": "^2.1",
|
||||
"laravel/framework": "^v8.83.27",
|
||||
"laravel/tinker": "^v2.6.1",
|
||||
"lasserafn/php-initial-avatar-generator": "^4.2",
|
||||
"maatwebsite/excel": "^3.1.31",
|
||||
"madnest/madzipper": "^v1.1.0",
|
||||
"mews/captcha": "^3.2.6",
|
||||
"orangehill/iseed": "^3.0.1",
|
||||
"predis/predis": "^1.1.7"
|
||||
"overtrue/pinyin": "^4.0",
|
||||
"predis/predis": "^1.1.7",
|
||||
"symfony/mailer": "^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-ide-helper": "^v2.10.0",
|
||||
|
||||
10337
composer.lock
generated
Normal file
10337
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user