Compare commits
1600 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
c6ebe994cc | ||
|
|
be3677cfa8 | ||
|
|
0c2e56271b | ||
|
|
b019d40009 | ||
|
|
361484be95 | ||
|
|
f0ce6cc28f | ||
|
|
ede298a142 | ||
|
|
aca1a4d34c | ||
|
|
e63c5c074c | ||
|
|
0d0b5dd552 | ||
|
|
08704e7f60 | ||
|
|
d48ed18102 | ||
|
|
3602acd187 | ||
|
|
ad477eb608 | ||
|
|
0a393845e9 | ||
|
|
59b98209ac | ||
|
|
ac26713f86 | ||
|
|
9250ef6f65 | ||
|
|
cabbeb07d0 | ||
|
|
43171645c0 | ||
|
|
79c1484288 | ||
|
|
e730875db3 | ||
|
|
18e9fb99b5 | ||
|
|
fd82b9b555 | ||
|
|
5c3bf0067e | ||
|
|
ac6b90c986 | ||
|
|
320f183b49 | ||
|
|
c8f11578d6 | ||
|
|
f33be18be3 | ||
|
|
513c7dbd85 | ||
|
|
02a9623310 | ||
|
|
58c1d52f52 | ||
|
|
b71c881b43 | ||
|
|
ffc2c7dea3 | ||
|
|
d7652a7a32 |
@@ -1,4 +1,4 @@
|
||||
APP_NAME=Dootask
|
||||
APP_NAME=DooTask
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
|
||||
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 }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,6 +8,7 @@
|
||||
/vendor
|
||||
/build
|
||||
/tmp
|
||||
/CHANGELOG.md
|
||||
._*
|
||||
.env
|
||||
.idea
|
||||
@@ -19,7 +20,6 @@ Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
test.*
|
||||
composer.lock
|
||||
package-lock.json
|
||||
laravels-timer-process.pid
|
||||
.DS_Store
|
||||
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "resources/drawio"]
|
||||
path = resources/drawio
|
||||
url = https://github.com/jgraph/drawio.git
|
||||
[submodule "resources/mobile"]
|
||||
path = resources/mobile
|
||||
url = git@github.com:kuaifan/dootask-app.git
|
||||
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应用
|
||||
1422
_ide_helper.php
1422
_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,11 @@ class FileController extends AbstractController
|
||||
$pid = intval($data['pid']);
|
||||
//
|
||||
$permission = 1000;
|
||||
$builder = File::wherePid($pid);
|
||||
if ($pid > 0) {
|
||||
File::permissionFind($pid, 0, $permission);
|
||||
$builder = File::wherePid($pid);
|
||||
} else {
|
||||
$builder = File::whereUserid($user->userid);
|
||||
$builder->whereUserid($user->userid);
|
||||
}
|
||||
//
|
||||
$array = $builder->take(500)->get()->toArray();
|
||||
@@ -67,6 +69,22 @@ class FileController extends AbstractController
|
||||
$temp['permission'] = $file->getPermission($user->userid);
|
||||
$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=''");
|
||||
@@ -75,8 +93,7 @@ class FileController extends AbstractController
|
||||
->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);
|
||||
$query->whereIn('file_users.userid', [0, $user->userid]);
|
||||
})
|
||||
->groupBy('files.id')
|
||||
->take(100)
|
||||
@@ -89,6 +106,10 @@ class FileController extends AbstractController
|
||||
}
|
||||
}
|
||||
}
|
||||
// 图片直接返回预览地址
|
||||
foreach ($array as &$item) {
|
||||
$item = File::handleImageUrl($item);
|
||||
}
|
||||
return Base::retSuccess('success', $array);
|
||||
}
|
||||
|
||||
@@ -150,14 +171,39 @@ class FileController extends AbstractController
|
||||
$user = User::auth();
|
||||
//
|
||||
$key = trim(Request::input('key'));
|
||||
if (empty($key)) {
|
||||
return Base::retError('请输入关键词');
|
||||
// 搜索自己的
|
||||
$builder = File::whereUserid($user->userid);
|
||||
if ($key) {
|
||||
$builder->where("name", "like", "%{$key}%");
|
||||
}
|
||||
$array = $builder->take(50)->get()->toArray();
|
||||
// 搜索共享的
|
||||
$take = 50 - count($array);
|
||||
if ($take > 0 && $key) {
|
||||
$list = File::where("name", "like", "%{$key}%")
|
||||
->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]);
|
||||
});
|
||||
})
|
||||
->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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,22 +251,30 @@ class FileController extends AbstractController
|
||||
'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) {
|
||||
@@ -243,7 +297,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);
|
||||
@@ -293,9 +348,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 +374,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 +385,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, 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, 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 +449,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 返回信息(错误描述)
|
||||
@@ -363,12 +459,25 @@ class FileController extends AbstractController
|
||||
{
|
||||
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 ($ids, &$files) {
|
||||
foreach ($ids as $id) {
|
||||
$file = File::permissionFind($id, 1000);
|
||||
$file->deleteFile();
|
||||
$files[] = $file;
|
||||
}
|
||||
});
|
||||
//
|
||||
return Base::retSuccess('删除成功', $files);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,8 +489,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,6 +507,9 @@ 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();
|
||||
@@ -404,8 +524,22 @@ class FileController extends AbstractController
|
||||
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 +550,7 @@ class FileController extends AbstractController
|
||||
* @apiGroup file
|
||||
* @apiName content__save
|
||||
*
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Number} id 文件ID
|
||||
* @apiParam {Object} [D] Request Payload 提交
|
||||
* - content: 内容
|
||||
*
|
||||
@@ -437,14 +571,14 @@ class FileController extends AbstractController
|
||||
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 +588,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();
|
||||
@@ -498,7 +663,7 @@ class FileController extends AbstractController
|
||||
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 +681,7 @@ class FileController extends AbstractController
|
||||
$content->save();
|
||||
//
|
||||
$file->size = $content->size;
|
||||
$file->updated_at = Carbon::now();
|
||||
$file->save();
|
||||
$file->pushMsg('update', $file);
|
||||
}
|
||||
@@ -531,7 +697,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,6 +709,7 @@ class FileController extends AbstractController
|
||||
$user = User::auth();
|
||||
//
|
||||
$pid = intval(Request::input('pid'));
|
||||
$webkitRelativePath = Request::input('webkitRelativePath');
|
||||
//
|
||||
$userid = $user->userid;
|
||||
if ($pid > 0) {
|
||||
@@ -557,7 +724,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 +768,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 +806,115 @@ 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()
|
||||
{
|
||||
$id = Request::input('id');
|
||||
//
|
||||
$file = File::permissionFind(intval($id));
|
||||
//
|
||||
$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);
|
||||
//
|
||||
$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 +950,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 +963,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 +978,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 +992,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 +1007,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 +1034,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
|
||||
@@ -765,23 +1064,17 @@ class FileController extends AbstractController
|
||||
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 +1092,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);
|
||||
$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
114
app/Http/Controllers/Api/PublicController.php
Executable file
114
app/Http/Controllers/Api/PublicController.php
Executable file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Models\UserCheckinMac;
|
||||
use App\Models\UserCheckinRecord;
|
||||
use App\Module\Base;
|
||||
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';
|
||||
}
|
||||
//
|
||||
$nowDate = date("Y-m-d");
|
||||
$nowTime = date("H:i:s");
|
||||
$macs = explode(",", $mac);
|
||||
foreach ($macs as $mac) {
|
||||
$mac = strtoupper($mac);
|
||||
if (Base::isMac($mac) && $UserCheckinMac = UserCheckinMac::whereMac($mac)->first()) {
|
||||
$array = [
|
||||
'userid' => $UserCheckinMac->userid,
|
||||
'mac' => $UserCheckinMac->mac,
|
||||
'date' => $nowDate,
|
||||
];
|
||||
$record = UserCheckinRecord::where($array)->first();
|
||||
if (empty($record)) {
|
||||
$record = UserCheckinRecord::createInstance($array);
|
||||
$record->save();
|
||||
}
|
||||
$record->times = Base::array2json(array_merge($record->times, [$nowTime]));
|
||||
$record->report_time = $time;
|
||||
$record->save();
|
||||
}
|
||||
}
|
||||
return 'success';
|
||||
}
|
||||
}
|
||||
516
app/Http/Controllers/Api/ReportController.php
Executable file
516
app/Http/Controllers/Api/ReportController.php
Executable file
@@ -0,0 +1,516 @@
|
||||
<?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
|
||||
* @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
|
||||
{
|
||||
$input = [
|
||||
"id" => Base::getPostValue("id", 0),
|
||||
"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',
|
||||
'receive' => 'required',
|
||||
'offset' => ['numeric', 'max:0'],
|
||||
], [
|
||||
'id.numeric' => 'ID只能是数字',
|
||||
'title.required' => '请填写标题',
|
||||
'type.required' => '请选择汇报类型',
|
||||
'type.in' => '汇报类型错误',
|
||||
'content.required' => '请填写汇报内容',
|
||||
'receive.required' => '请选择接收人',
|
||||
'offset.numeric' => '工作汇报周期格式错误,只能是数字',
|
||||
'offset.max' => '只能提交当天/本周或者之前的的工作汇报',
|
||||
]);
|
||||
if ($validator->fails())
|
||||
return Base::retError($validator->errors()->first());
|
||||
|
||||
$user = User::auth();
|
||||
// 接收人
|
||||
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 = Report::generateSign($input["type"], $input["offset"]);
|
||||
// 检查唯一标识是否存在
|
||||
if (empty($input["id"])) {
|
||||
if (Report::query()->whereSign($sign)->whereType($input["type"])->count() > 0)
|
||||
throw new ApiException("请勿重复提交工作汇报");
|
||||
}
|
||||
$report = Report::createInstance([
|
||||
"title" => $input["title"],
|
||||
"type" => $input["type"],
|
||||
"content" => htmlspecialchars($input["content"]),
|
||||
"userid" => $user->userid,
|
||||
"sign" => $sign,
|
||||
]);
|
||||
}
|
||||
|
||||
$report->save();
|
||||
if (!empty($input["receive_content"])) {
|
||||
// 删除关联
|
||||
$report->Receives()->delete();
|
||||
// 保存接收人
|
||||
$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', [
|
||||
"content" => $one->content,
|
||||
"title" => $one->title,
|
||||
"id" => $one->id,
|
||||
]);
|
||||
}
|
||||
|
||||
// 已完成的任务
|
||||
$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->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->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") . "]";
|
||||
}
|
||||
$data = [
|
||||
"time" => $start_time->toDateTimeString(),
|
||||
"complete_task" => $complete_task,
|
||||
"unfinished_task" => $unfinished_task,
|
||||
"content" => '<h2>' . Base::Lang('已完成工作') . '</h2><ol>' .
|
||||
$completeContent . '</ol><h2>' .
|
||||
Base::Lang('未完成的工作') . '</h2><ol>' .
|
||||
$unfinishedContent . '</ol>',
|
||||
"title" => $title,
|
||||
];
|
||||
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,20 @@
|
||||
|
||||
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 Arr;
|
||||
use Carbon\Carbon;
|
||||
use Guanguans\Notify\Factory;
|
||||
use Guanguans\Notify\Messages\EmailMessage;
|
||||
use Madzipper;
|
||||
use Request;
|
||||
use Response;
|
||||
use Session;
|
||||
|
||||
/**
|
||||
* @apiDefine system
|
||||
@@ -25,7 +35,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_invite', 'login_code', 'password_policy', 'project_invite', 'chat_information', 'auto_archived', 'archived_day', 'all_group_mute', 'all_group_autoin', 'start_home', 'home_footer'])
|
||||
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
@@ -41,7 +51,20 @@ 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_invite',
|
||||
'login_code',
|
||||
'password_policy',
|
||||
'project_invite',
|
||||
'chat_information',
|
||||
'auto_archived',
|
||||
'archived_day',
|
||||
'all_group_mute',
|
||||
'all_group_autoin',
|
||||
'start_home',
|
||||
'home_footer'
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
@@ -69,15 +92,239 @@ class SystemController extends AbstractController
|
||||
$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['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', 'task_start_minute', 'task_remind_hours', 'task_remind_hours2', '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',
|
||||
'task_start_minute',
|
||||
'task_remind_hours',
|
||||
'task_remind_hours2',
|
||||
'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'] = $setting['notice'] ?: 'close';
|
||||
$setting['task_start_minute'] = intval($setting['task_start_minute'] ?? -1);
|
||||
$setting['task_remind_hours'] = floatval($setting['task_remind_hours'] ?? -1);
|
||||
$setting['task_remind_hours2'] = floatval($setting['task_remind_hours2'] ?? -1);
|
||||
$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 03. 获取签到设置、保存签到设置(限管理员)
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
* @apiName setting__checkin
|
||||
*
|
||||
* @apiParam {String} type
|
||||
* - get: 获取(默认)
|
||||
* - save: 保存设置(参数:['open', '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',
|
||||
'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['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 04. 获取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', 'push_msg', 'push_task', 'task_start_minute', 'task_remind_hours', 'task_remind_hours2'])
|
||||
* @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',
|
||||
'push_msg',
|
||||
'push_task',
|
||||
'task_start_minute',
|
||||
'task_remind_hours',
|
||||
'task_remind_hours2'
|
||||
])) {
|
||||
unset($all[$key]);
|
||||
}
|
||||
}
|
||||
$setting = Base::setting('appPushSetting', Base::newTrim($all));
|
||||
} else {
|
||||
$setting = Base::setting('appPushSetting');
|
||||
}
|
||||
//
|
||||
$setting['push'] = $setting['push'] ?: 'close';
|
||||
$setting['push_msg'] = $setting['push_msg'] ?: 'open';
|
||||
$setting['push_task'] = $setting['push_task'] ?: 'open';
|
||||
$setting['task_start_minute'] = intval($setting['task_start_minute']);
|
||||
$setting['task_remind_hours'] = floatval($setting['task_remind_hours']);
|
||||
$setting['task_remind_hours2'] = floatval($setting['task_remind_hours2']);
|
||||
//
|
||||
return Base::retSuccess('success', $setting ?: json_decode('{}'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/demo 05. 获取演示帐号
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -101,12 +348,16 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/priority 03. 获取优先级、保存优先级
|
||||
* @api {post} api/system/priority 06. 任务优先级
|
||||
*
|
||||
* @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 +397,54 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/info 04. 获取终端详细信息
|
||||
* @api {post} api/system/column/template 07. 创建项目模板
|
||||
*
|
||||
* @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 08. 获取终端详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -175,7 +473,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ip 05. 获取IP地址
|
||||
* @api {get} api/system/get/ip 09. 获取IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -190,7 +488,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/cnip 06. 是否中国IP地址
|
||||
* @api {get} api/system/get/cnip 10. 是否中国IP地址
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -207,7 +505,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipgcj02 07. 获取IP地址经纬度
|
||||
* @api {get} api/system/get/ipgcj02 11. 获取IP地址经纬度
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -224,7 +522,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/ipinfo 08. 获取IP地址详细信息
|
||||
* @api {get} api/system/get/ipinfo 12. 获取IP地址详细信息
|
||||
*
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup system
|
||||
@@ -241,15 +539,22 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/imgupload 09. 上传图片
|
||||
* @api {post} api/system/imgupload 13. 上传图片
|
||||
*
|
||||
* @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 +565,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 +599,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/imgview 10. 浏览图片空间
|
||||
* @api {get} api/system/get/imgview 14. 浏览图片空间
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -309,7 +617,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 +695,7 @@ class SystemController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/system/fileupload 11. 上传文件
|
||||
* @api {post} api/system/fileupload 15. 上传文件
|
||||
*
|
||||
* @apiDescription 需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
@@ -407,7 +715,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 +735,291 @@ class SystemController extends AbstractController
|
||||
//
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/system/get/starthome 16. 启动首页设置信息
|
||||
*
|
||||
* @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 17. 邮件发送测试(限管理员)
|
||||
*
|
||||
* @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 17. 导出签到数据(限管理员)
|
||||
*
|
||||
* @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) > 20) {
|
||||
return Base::retError('导出成员限制最多20个');
|
||||
}
|
||||
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(20)->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;
|
||||
}
|
||||
$sheets[] = BillExport::create()->setTitle($nickname ?: $user->userid)->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 17. 下载导出的签到数据
|
||||
*
|
||||
* @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 18. 获取版本号
|
||||
*
|
||||
* @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,22 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\File;
|
||||
use App\Module\Base;
|
||||
use App\Module\Ihttp;
|
||||
use App\Module\RandomColor;
|
||||
use App\Tasks\AppPushTask;
|
||||
use App\Tasks\AutoArchivedTask;
|
||||
use App\Tasks\DeleteTmpTask;
|
||||
use App\Tasks\EmailNoticeTask;
|
||||
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 +33,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 +45,118 @@ 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()]);
|
||||
$hash = 'no';
|
||||
$path = public_path('js/hash');
|
||||
$murl = url('manifest.txt');
|
||||
if (file_exists($path)) {
|
||||
$hash = trim(file_get_contents(public_path('js/hash')));
|
||||
if (strlen($hash) > 16) {
|
||||
$hash = 'long';
|
||||
}
|
||||
}
|
||||
return response()->view('main', [
|
||||
'version' => Base::getVersion(),
|
||||
'hash' => $hash
|
||||
])->header('Link', "<{$murl}>; 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.2.1-34/web-apps/vendor/requirejs/require.js",
|
||||
"office/7.2.1-34/web-apps/apps/api/documents/api.js",
|
||||
"office/7.2.1-34/sdkjs/common/AllFonts.js",
|
||||
"office/7.2.1-34/web-apps/vendor/xregexp/xregexp-all-min.js",
|
||||
"office/7.2.1-34/web-apps/vendor/sockjs/sockjs.min.js",
|
||||
"office/7.2.1-34/web-apps/vendor/jszip/jszip.min.js",
|
||||
"office/7.2.1-34/web-apps/vendor/jszip-utils/jszip-utils.min.js",
|
||||
"office/7.2.1-34/sdkjs/common/libfont/wasm/fonts.js",
|
||||
"office/7.2.1-34/sdkjs/common/Charts/ChartStyles.js",
|
||||
"office/7.2.1-34/sdkjs/slide/themes//themes.js",
|
||||
|
||||
"office/7.2.1-34/web-apps/apps/presentationeditor/main/app.js",
|
||||
"office/7.2.1-34/sdkjs/slide/sdk-all-min.js",
|
||||
"office/7.2.1-34/sdkjs/slide/sdk-all.js",
|
||||
|
||||
"office/7.2.1-34/web-apps/apps/documenteditor/main/app.js",
|
||||
"office/7.2.1-34/sdkjs/word/sdk-all-min.js",
|
||||
"office/7.2.1-34/sdkjs/word/sdk-all.js",
|
||||
|
||||
"office/7.2.1-34/web-apps/apps/spreadsheeteditor/main/app.js",
|
||||
"office/7.2.1-34/sdkjs/cell/sdk-all-min.js",
|
||||
"office/7.2.1-34/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 +178,179 @@ 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 LoopTask());
|
||||
|
||||
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 = Request::input('q');
|
||||
$page = Request::input('p');
|
||||
$size = Request::input('c');
|
||||
$url = "https://app.diagrams.net/iconSearch?q={$query}&p={$page}&c={$size}";
|
||||
$result = Cache::remember("drawioIconsearch::" . md5($url), now()->addDays(15), function () use ($url) {
|
||||
return Ihttp::ihttp_get($url);
|
||||
});
|
||||
if (Base::isSuccess($result)) {
|
||||
return $result['data'];
|
||||
}
|
||||
return [
|
||||
'icons' => [],
|
||||
'total_count' => 0
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览文件
|
||||
* @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
|
||||
|
||||
@@ -21,6 +21,9 @@ class VerifyCsrfToken extends Middleware
|
||||
// 保存任务优先级
|
||||
'api/system/priority/',
|
||||
|
||||
// 保存创建项目列表模板
|
||||
'api/system/column/template/',
|
||||
|
||||
// 添加任务
|
||||
'api/project/task/add/',
|
||||
|
||||
@@ -30,8 +33,11 @@ class VerifyCsrfToken extends Middleware
|
||||
// 修改任务
|
||||
'api/project/task/update/',
|
||||
|
||||
// 上传任务问题
|
||||
'api/project/task/upload/',
|
||||
// 聊天发文本
|
||||
'api/dialog/msg/sendtext/',
|
||||
|
||||
// 聊天发语音
|
||||
'api/dialog/msg/sendrecord/',
|
||||
|
||||
// 聊天发文件
|
||||
'api/dialog/msg/sendfile/',
|
||||
@@ -44,5 +50,17 @@ class VerifyCsrfToken extends Middleware
|
||||
|
||||
// 保存文件内容(上传)
|
||||
'api/file/content/upload/',
|
||||
|
||||
// 保存汇报
|
||||
'api/report/store/',
|
||||
|
||||
// 签到设置
|
||||
'api/users/checkin/save/',
|
||||
|
||||
// 签到上报
|
||||
'api/public/checkin/report/',
|
||||
|
||||
// 发布桌面端
|
||||
'desktop/publish/',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -10,13 +10,15 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* App\Model\AbstractModel
|
||||
* App\Models\AbstractModel
|
||||
*
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel saveOrIgnore()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|AbstractModel getKeyValue()
|
||||
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelAppend()
|
||||
* @method static \Illuminate\Database\Eloquent\Model|object|static|null cancelHidden()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|static with($relations)
|
||||
* @method static \Illuminate\Database\Query\Builder|static select($columns = [])
|
||||
* @method static \Illuminate\Database\Query\Builder|static whereNotIn($column, $values, $boolean = 'and')
|
||||
@@ -44,7 +46,7 @@ class AbstractModel extends Model
|
||||
{
|
||||
try {
|
||||
return $this->save();
|
||||
} catch (\Exception $e) {
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -62,6 +64,24 @@ class AbstractModel extends Model
|
||||
return $this->$key;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消附加值
|
||||
* @return static
|
||||
*/
|
||||
protected function scopeCancelAppend()
|
||||
{
|
||||
return $this->setAppends([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消隐藏值
|
||||
* @return static
|
||||
*/
|
||||
protected function scopeCancelHidden()
|
||||
{
|
||||
return $this->setHidden([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为数组 / JSON 序列化准备日期。
|
||||
* @param DateTimeInterface $date
|
||||
@@ -133,9 +153,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)) {
|
||||
@@ -145,8 +166,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;
|
||||
|
||||
@@ -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)
|
||||
@@ -50,15 +54,55 @@ class File extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* 文件文件
|
||||
*/
|
||||
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 $userid
|
||||
* @return int -1:没有权限,0:访问权限,1:读写权限,1000:所有者
|
||||
* @return int -1:没有权限,0:访问权限,1:读写权限,1000:所有者或创建者
|
||||
*/
|
||||
public function getPermission($userid)
|
||||
{
|
||||
if ($userid == $this->userid) {
|
||||
// ① 自己的文件夹
|
||||
if ($userid == $this->userid || $userid == $this->created_id) {
|
||||
// ① 自己的文件夹 或 自己创建的文件夹
|
||||
return 1000;
|
||||
}
|
||||
$row = $this->getShareInfo();
|
||||
@@ -100,7 +144,7 @@ class File extends AbstractModel
|
||||
|
||||
/**
|
||||
* 是否处于共享文件夹内(不含自身)
|
||||
* @return bool
|
||||
* @return File|false
|
||||
*/
|
||||
public function isNnShare()
|
||||
{
|
||||
@@ -111,19 +155,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 +185,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 +200,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 +278,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 +290,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 +327,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;
|
||||
}
|
||||
@@ -214,10 +350,69 @@ class File extends AbstractModel
|
||||
Task::deliver($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推送会员
|
||||
* @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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理返回图片地址
|
||||
* @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 $id
|
||||
* @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者
|
||||
* @param int $limit 要求权限: 0-访问权限、1-读写权限、1000-所有者或创建者
|
||||
* @param $permission
|
||||
* @return File
|
||||
*/
|
||||
@@ -231,12 +426,140 @@ class File extends AbstractModel
|
||||
$permission = $file->getPermission(User::userid());
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,43 +41,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 Response::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 Response::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' => Base::generatePassword(64),
|
||||
]);
|
||||
$fileLink->save();
|
||||
} else {
|
||||
if ($refresh == 'yes') {
|
||||
$fileLink->code = Base::generatePassword(64);
|
||||
$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)->delete();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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)->update([
|
||||
'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 排序
|
||||
|
||||
@@ -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)
|
||||
@@ -112,7 +117,7 @@ class ProjectTask extends AbstractModel
|
||||
public function getFileNumAttribute()
|
||||
{
|
||||
if (!isset($this->appendattrs['file_num'])) {
|
||||
$this->appendattrs['file_num'] = ProjectTaskFile::whereTaskId($this->id)->count();
|
||||
$this->appendattrs['file_num'] = $this->parent_id > 0 ? 0 : ProjectTaskFile::whereTaskId($this->id)->count();
|
||||
}
|
||||
return $this->appendattrs['file_num'];
|
||||
}
|
||||
@@ -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,11 +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('任务状态未发生改变');
|
||||
}
|
||||
@@ -512,12 +556,11 @@ class ProjectTask extends AbstractModel
|
||||
// 判断符合流转
|
||||
$currentFlowItem = ProjectFlowItem::find($this->flow_item_id);
|
||||
if ($currentFlowItem) {
|
||||
if (!in_array($currentFlowItem->id, $newFlowItem->turns)) {
|
||||
if (!in_array($newFlowItem->id, $currentFlowItem->turns)) {
|
||||
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}]仅限状态负责人或项目负责人修改");
|
||||
}
|
||||
}
|
||||
@@ -539,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);
|
||||
@@ -561,9 +608,21 @@ 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')) {
|
||||
// 子任务:主任务已完成时无法修改
|
||||
if ($mainTask?->complete_at) {
|
||||
throw new ApiException('主任务已完成,无法修改子任务状态');
|
||||
}
|
||||
if (Base::isDate($data['complete_at'])) {
|
||||
// 标记已完成
|
||||
if ($this->complete_at) {
|
||||
@@ -578,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']) {
|
||||
@@ -591,6 +649,9 @@ 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')) {
|
||||
@@ -626,7 +687,7 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
$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();
|
||||
}
|
||||
@@ -637,16 +698,16 @@ 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);
|
||||
if ($this->parent_id > 0) {
|
||||
// 判断同步主任务时间(子任务时间 超出 主任务)
|
||||
$mainTask = self::find($this->parent_id);
|
||||
if ($mainTask) {
|
||||
$isUp = false;
|
||||
if ($start_at->lt(Carbon::parse($mainTask->start_at))) {
|
||||
@@ -669,7 +730,6 @@ class ProjectTask extends AbstractModel
|
||||
} else {
|
||||
if ($this->parent_id > 0) {
|
||||
// 清空子任务时间(子任务时间等于主任务时间)
|
||||
$mainTask = self::find($this->parent_id);
|
||||
$this->start_at = $mainTask->start_at;
|
||||
$this->end_at = $mainTask->end_at;
|
||||
}
|
||||
@@ -682,14 +742,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("同步修改{任务}时间");
|
||||
@@ -698,10 +762,40 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
});
|
||||
}
|
||||
$this->addLog("修改{任务}时间");
|
||||
$newStringAt = $this->start_at ? ($this->start_at->toDateTimeString() . '~' . $this->end_at->toDateTimeString()) : '';
|
||||
$this->addLog("修改{任务}时间" . ($desc ? "(备注:{$desc})" : ""), [
|
||||
'change' => [$oldStringAt, $newStringAt]
|
||||
]);
|
||||
|
||||
// 修改计划时间需要重置任务邮件提醒日志
|
||||
ProjectTaskMailLog::whereTaskId($this->id)->delete();
|
||||
}
|
||||
// 以下紧顶级任务可修改
|
||||
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 = [];
|
||||
@@ -728,7 +822,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();
|
||||
}
|
||||
@@ -756,18 +850,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;
|
||||
@@ -781,7 +877,9 @@ class ProjectTask extends AbstractModel
|
||||
$p = true;
|
||||
}
|
||||
if ($p) {
|
||||
$this->addLog("修改{任务}优先级");
|
||||
$this->addLog("修改{任务}优先级", [
|
||||
'change' => [$oldPName, $this->p_name]
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->save();
|
||||
@@ -791,6 +889,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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步项目成员至聊天室
|
||||
*/
|
||||
@@ -798,9 +1031,7 @@ class ProjectTask extends AbstractModel
|
||||
{
|
||||
if ($this->parent_id > 0) {
|
||||
$task = self::find($this->parent_id);
|
||||
if ($task) {
|
||||
$task->syncDialogUser();
|
||||
}
|
||||
$task?->syncDialogUser();
|
||||
return;
|
||||
}
|
||||
if (empty($this->dialog_id)) {
|
||||
@@ -812,9 +1043,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)->delete();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -824,7 +1057,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());
|
||||
@@ -860,6 +1093,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
|
||||
@@ -872,24 +1177,6 @@ class ProjectTask extends AbstractModel
|
||||
return $this->appendattrs['has_owner'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否负责人
|
||||
* @param bool $isParent 是父级任务的负责人也算
|
||||
* @return bool
|
||||
*/
|
||||
public function isOwner($isParent = true) {
|
||||
if ($this->owner) {
|
||||
return true;
|
||||
}
|
||||
if ($isParent && $this->parent_id > 0) {
|
||||
$parentTask = self::find($this->parent_id);
|
||||
if ($parentTask?->owner) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记已完成、未完成
|
||||
* @param Carbon|null $complete_at 完成时间
|
||||
@@ -898,10 +1185,16 @@ class ProjectTask extends AbstractModel
|
||||
public function completeTask($complete_at)
|
||||
{
|
||||
AbstractModel::transaction(function () use ($complete_at) {
|
||||
$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) {
|
||||
@@ -914,6 +1207,11 @@ class ProjectTask extends AbstractModel
|
||||
}
|
||||
$this->complete_at = $complete_at;
|
||||
$this->addLog("标记{任务}已完成");
|
||||
if ($addMsg) {
|
||||
WebSocketDialogMsg::sendMsg(null, $this->dialog_id, 'notice', [
|
||||
'notice' => '标记任务已完成'
|
||||
], 0, true, true);
|
||||
}
|
||||
}
|
||||
$this->save();
|
||||
});
|
||||
@@ -927,6 +1225,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) {
|
||||
// 取消归档
|
||||
@@ -934,7 +1242,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) {
|
||||
@@ -948,8 +1255,12 @@ class ProjectTask extends AbstractModel
|
||||
$this->archived_userid = $userid;
|
||||
$this->archived_follow = 0;
|
||||
$this->addLog($logText, [], $userid);
|
||||
$this->pushMsg('archived');
|
||||
}
|
||||
$this->pushMsg('update', [
|
||||
'id' => $this->id,
|
||||
'archived_at' => $this->archived_at,
|
||||
'archived_userid' => $this->archived_userid,
|
||||
]);
|
||||
self::whereParentId($this->id)->update([
|
||||
'archived_at' => $this->archived_at,
|
||||
'archived_userid' => $this->archived_userid,
|
||||
@@ -973,6 +1284,8 @@ class ProjectTask extends AbstractModel
|
||||
$dialog?->deleteDialog();
|
||||
}
|
||||
self::whereParentId($this->id)->delete();
|
||||
$this->deleted_userid = User::userid();
|
||||
$this->save();
|
||||
$this->addLog("删除{任务}");
|
||||
$this->delete();
|
||||
});
|
||||
@@ -982,6 +1295,28 @@ class ProjectTask extends AbstractModel
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原任务
|
||||
* @param bool $pushMsg 是否推送
|
||||
* @return bool
|
||||
*/
|
||||
public function recoveryTask($pushMsg = true)
|
||||
{
|
||||
AbstractModel::transaction(function () {
|
||||
if ($this->dialog_id) {
|
||||
$dialog = WebSocketDialog::withTrashed()->find($this->dialog_id);
|
||||
$dialog?->recoveryDialog();
|
||||
}
|
||||
self::whereParentId($this->id)->withTrashed()->restore();
|
||||
$this->addLog("还原{任务}");
|
||||
$this->restore();
|
||||
});
|
||||
if ($pushMsg) {
|
||||
$this->pushMsg('restore');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务日志
|
||||
* @param string $detail
|
||||
@@ -1033,28 +1368,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);
|
||||
@@ -1069,48 +1438,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
|
||||
{
|
||||
|
||||
}
|
||||
45
app/Models/ProjectTaskMailLog.php
Normal file
45
app/Models/ProjectTaskMailLog.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* App\Models\ProjectTaskMailLog
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 用户id
|
||||
* @property int|null $task_id 任务id
|
||||
* @property string|null $email 电子邮箱
|
||||
* @property int|null $type 提醒类型:0 任务开始提醒,1 距离到期提醒,2到期超时提醒
|
||||
* @property int|null $is_send 邮件发送是否成功:0否,1是
|
||||
* @property string|null $send_error 邮件发送错误详情
|
||||
* @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|ProjectTaskMailLog newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog newQuery()
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereIsSend($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereSendError($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereTaskId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|ProjectTaskMailLog whereUserid($value)
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog withTrashed()
|
||||
* @method static \Illuminate\Database\Query\Builder|ProjectTaskMailLog withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class ProjectTaskMailLog extends AbstractModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,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 +22,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 +38,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 +82,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)->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,10 +72,15 @@ class User extends AbstractModel
|
||||
protected $primaryKey = 'userid';
|
||||
|
||||
protected $hidden = [
|
||||
'disable_at',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
// 默认头像类型:auto自动生成,system系统默认
|
||||
public static $defaultAvatarMode = 'auto';
|
||||
|
||||
// 基本信息的字段
|
||||
public static $basicField = ['userid', 'email', 'nickname', 'profession', 'department', 'userimg', 'bot', 'az', 'pinyin', 'line_at', 'disable_at'];
|
||||
|
||||
/**
|
||||
* 更新数据校验
|
||||
* @param array $param
|
||||
@@ -96,11 +111,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 +127,43 @@ 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 "";
|
||||
}
|
||||
$list = UserDepartment::select(['id', 'owner_userid', 'name'])->whereIn('id', $this->department)->take(10)->get();
|
||||
$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 || intval(Cache::get("User::online:" . $this->userid, 0)) > 0;
|
||||
if ($online) {
|
||||
return true;
|
||||
}
|
||||
@@ -167,6 +208,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();
|
||||
});
|
||||
}
|
||||
|
||||
/** ***************************************************************************************** */
|
||||
/** ***************************************************************************************** */
|
||||
/** ***************************************************************************************** */
|
||||
@@ -181,10 +252,16 @@ 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('邮箱地址已存在');
|
||||
}
|
||||
//密码
|
||||
@@ -201,8 +278,15 @@ 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()) {
|
||||
// 加入全员群组
|
||||
if (Base::settingFind('system', 'all_group_autoin', 'yes') === 'yes') {
|
||||
$dialog = WebSocketDialog::whereGroupType('all')->orderByDesc('id')->first();
|
||||
$dialog?->joinGroup($user->userid, 0);
|
||||
}
|
||||
}
|
||||
return $user->find($user->userid);
|
||||
}
|
||||
|
||||
@@ -340,9 +424,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 +456,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 +490,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 +510,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 +534,37 @@ 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);
|
||||
}
|
||||
// 机器人头像
|
||||
if ($email == 'system-msg@bot.system') {
|
||||
return url("images/avatar/default_system.png");
|
||||
} elseif ($email == 'task-alert@bot.system') {
|
||||
return url("images/avatar/default_task.png");
|
||||
} elseif ($email == '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 +595,51 @@ 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();
|
||||
}
|
||||
//
|
||||
if ($key === 'system-msg') {
|
||||
$update['nickname'] = '系统消息';
|
||||
} elseif ($key === 'task-alert') {
|
||||
$update['nickname'] = '任务提醒';
|
||||
} elseif ($key === 'bot-manager') {
|
||||
$update['nickname'] = '机器人管理';
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
26
app/Models/UserBot.php
Normal file
26
app/Models/UserBot.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
/**
|
||||
* App\Models\UserBot
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $userid 所属人ID
|
||||
* @property int|null $bot_id 机器人ID
|
||||
* @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 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)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class UserBot extends AbstractModel
|
||||
{
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
153
app/Models/UserDepartment.php
Normal file
153
app/Models/UserDepartment.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?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();
|
||||
$dialog->pushMsg("groupDelete");
|
||||
}
|
||||
//
|
||||
$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,10 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Module\Base;
|
||||
use App\Tasks\PushTask;
|
||||
use Carbon\Carbon;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
@@ -14,6 +17,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @property string|null $group_type 聊天室类型
|
||||
* @property string|null $name 对话名称
|
||||
* @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
|
||||
@@ -29,6 +33,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @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()
|
||||
@@ -47,6 +52,188 @@ 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)
|
||||
{
|
||||
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->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
|
||||
// 未读信息
|
||||
$unreadBuilder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
|
||||
$this->unread = $unreadBuilder->count();
|
||||
$this->mention = 0;
|
||||
$this->last_umid = 0;
|
||||
if ($this->unread > 0) {
|
||||
$this->mention = $unreadBuilder->clone()->whereMention(1)->count();
|
||||
$this->last_umid = intval($unreadBuilder->clone()->orderByDesc('msg_id')->value('msg_id'));
|
||||
}
|
||||
$this->mark_unread = $this->mark_unread ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('mark_unread');
|
||||
// 对话人数
|
||||
$builder = WebSocketDialogUser::whereDialogId($this->id);
|
||||
$this->people = $builder->count();
|
||||
// 有待办
|
||||
$this->todo_num = WebSocketDialogMsgTodo::whereDialogId($this->id)->whereUserid($userid)->whereDoneAt(null)->count();
|
||||
}
|
||||
// 对方信息
|
||||
$this->dialog_user = null;
|
||||
$this->group_info = null;
|
||||
$this->top_at = $this->top_at ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('top_at');
|
||||
$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;
|
||||
} 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();
|
||||
}
|
||||
$this->pinyin = Base::cn2pinyin($this->name);
|
||||
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 +241,126 @@ 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();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原会话
|
||||
* @return bool
|
||||
*/
|
||||
public function recoveryDialog()
|
||||
{
|
||||
$this->restore();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查群组类型
|
||||
* @param string|array|null $groupType
|
||||
* @return void
|
||||
*/
|
||||
public function checkGroup($groupType = null)
|
||||
{
|
||||
if ($this->type !== 'group') {
|
||||
throw new ApiException('仅限群组操作');
|
||||
}
|
||||
if ($groupType) {
|
||||
$groupTypes = is_array($groupType) ? $groupType : [$groupType];
|
||||
if (!in_array($this->group_type, $groupTypes)) {
|
||||
throw new ApiException('操作的群组类型错误');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查禁言
|
||||
* @param $userid
|
||||
* @return void
|
||||
*/
|
||||
public function checkMute($userid)
|
||||
{
|
||||
if ($this->group_type === 'all') {
|
||||
$allGroupMute = Base::settingFind('system', 'all_group_mute');
|
||||
switch ($allGroupMute) {
|
||||
case 'all':
|
||||
throw new ApiException('当前会话全员禁言');
|
||||
case 'user':
|
||||
if (!User::find($userid)?->isAdmin()) {
|
||||
throw new ApiException('当前会话禁言');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群组名称
|
||||
* @return mixed|string|null
|
||||
*/
|
||||
public function getGroupName()
|
||||
{
|
||||
if (!isset($this->appendattrs['groupName'])) {
|
||||
$name = $this->name;
|
||||
if ($this->type == "group") {
|
||||
switch ($this->group_type) {
|
||||
case 'project':
|
||||
$name = \DB::table('projects')->where('dialog_id', $this->id)->value('name');
|
||||
break;
|
||||
case 'task':
|
||||
$name = \DB::table('project_tasks')->where('dialog_id', $this->id)->value('name');
|
||||
break;
|
||||
case 'all':
|
||||
$name = 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话(同时检验对话身份)
|
||||
* @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 +368,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 +385,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 +393,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 +414,7 @@ class WebSocketDialog extends AbstractModel
|
||||
WebSocketDialogUser::createInstance([
|
||||
'dialog_id' => $dialog->id,
|
||||
'userid' => $value,
|
||||
'important' => !in_array($group_type, ['user', 'all'])
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
@@ -159,47 +422,6 @@ 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
|
||||
@@ -208,6 +430,9 @@ class WebSocketDialog extends AbstractModel
|
||||
*/
|
||||
public static function checkUserDialog($userid, $userid2)
|
||||
{
|
||||
if ($userid == $userid2) {
|
||||
$userid2 = 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')
|
||||
@@ -234,5 +459,4 @@ class WebSocketDialog extends AbstractModel
|
||||
return $dialog;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,44 +8,84 @@ use App\Tasks\PushTask;
|
||||
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 +93,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 +128,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 +192,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 +211,624 @@ 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('sucess', $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 int $sender 发送的会员ID
|
||||
* @return mixed
|
||||
*/
|
||||
public function forwardMsg($dialogids, $userids, $sender)
|
||||
{
|
||||
return AbstractModel::transaction(function() use ($dialogids, $sender, $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, $sender);
|
||||
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($sender, $userid);
|
||||
if ($dialog && !in_array($dialog->id, $already)) {
|
||||
$res = self::sendMsg(null, $dialog->id, $this->type, $originalMsg, $sender);
|
||||
if (Base::isSuccess($res)) {
|
||||
$msgs[] = $res['data'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Base::retSuccess('转发成功', [
|
||||
'msgs' => $msgs
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
* @return void
|
||||
*/
|
||||
public function deleteMsg()
|
||||
{
|
||||
$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');
|
||||
}
|
||||
//
|
||||
$last_msg = null;
|
||||
if ($this->webSocketDialog) {
|
||||
$last_msg = WebSocketDialogMsg::whereDialogId($this->dialog_id)->orderByDesc('id')->first();
|
||||
$this->webSocketDialog->last_at = $last_msg->created_at;
|
||||
$this->webSocketDialog->save();
|
||||
}
|
||||
//
|
||||
$dialog = WebSocketDialog::find($this->dialog_id);
|
||||
if ($dialog) {
|
||||
$userids = $dialog->dialogUser->pluck('userid')->toArray();
|
||||
PushTask::push([
|
||||
'userid' => $userids,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'delete',
|
||||
'data' => [
|
||||
'id' => $this->id,
|
||||
'dialog_id' => $this->dialog_id,
|
||||
'last_msg' => $last_msg,
|
||||
'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);
|
||||
$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);
|
||||
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]) >= 32) {
|
||||
$keyId = $match[1];
|
||||
} else {
|
||||
throw new ApiException('文件分享错误');
|
||||
}
|
||||
}
|
||||
}
|
||||
$text = str_replace($matchs[0][$key], "[:{$matchChar[1]}:{$keyId}:{$matchValye[1]}:]", $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]) >= 32) {
|
||||
$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]) >= 32) {
|
||||
$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_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';
|
||||
}
|
||||
} 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();
|
||||
});
|
||||
//
|
||||
$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,21 +2,28 @@
|
||||
|
||||
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 $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 whereUserid($value)
|
||||
@@ -29,4 +36,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'];
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,10 @@ namespace App\Models;
|
||||
* @property int $id
|
||||
* @property int|null $dialog_id 对话ID
|
||||
* @property int|null $userid 会员ID
|
||||
* @property string|null $top_at 置顶时间
|
||||
* @property int|null $mark_unread 是否标记为未读:0否,1是
|
||||
* @property int|null $inviter 邀请人
|
||||
* @property int|null $important 是否不可移出(项目、任务、部门人员)
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser newModelQuery()
|
||||
@@ -16,6 +20,10 @@ namespace App\Models;
|
||||
* @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 whereTopAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogUser whereUserid($value)
|
||||
* @mixin \Eloquent
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,17 @@
|
||||
|
||||
namespace App\Module;
|
||||
|
||||
use App\Exceptions\ApiException;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Tmp;
|
||||
use Cache;
|
||||
use Exception;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Overtrue\Pinyin\Pinyin;
|
||||
use Redirect;
|
||||
use Request;
|
||||
use Storage;
|
||||
use Validator;
|
||||
|
||||
class Base
|
||||
{
|
||||
@@ -58,20 +60,30 @@ class Base
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取package配置文件
|
||||
* @return array
|
||||
*/
|
||||
public static function getPackage()
|
||||
{
|
||||
return Cache::remember("Base::package", now()->addSeconds(10), function () {
|
||||
$file = base_path('package.json');
|
||||
if (file_exists($file)) {
|
||||
$package = json_decode(file_get_contents($file), true);
|
||||
return is_array($package) ? $package : [];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本号
|
||||
* @return string
|
||||
*/
|
||||
public static function getVersion()
|
||||
{
|
||||
return Cache::remember("Base::version", now()->addSeconds(10), function () {
|
||||
$file = base_path('package.json');
|
||||
if (file_exists($file)) {
|
||||
$packageArray = json_decode(file_get_contents($file), true);
|
||||
return $packageArray['version'] ?? '1.0.0';
|
||||
}
|
||||
return '1.0.0';
|
||||
});
|
||||
$package = self::getPackage();
|
||||
return $package['version'] ?? '1.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +99,28 @@ class Base
|
||||
return $_A["__static_client_version"];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查客户端版本
|
||||
* @param string $min 最小版本
|
||||
* @return void
|
||||
*/
|
||||
public static function checkClientVersion($min)
|
||||
{
|
||||
if (!self::judgeClientVersion($min)) {
|
||||
throw new ApiException('当前版本 (v' . Base::getClientVersion() . ') 过低,最低版本要求 (v' . $min . ')。');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断客户端版本
|
||||
* @param $min
|
||||
* @return bool
|
||||
*/
|
||||
public static function judgeClientVersion($min)
|
||||
{
|
||||
return !version_compare(Base::getClientVersion(), $min, '<');
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否域名格式
|
||||
* @param $domain
|
||||
@@ -261,7 +295,7 @@ class Base
|
||||
{
|
||||
try {
|
||||
Storage::makeDirectory($path);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
if (!file_exists($path)) {
|
||||
self::makeDir(dirname($path));
|
||||
@@ -329,19 +363,15 @@ class Base
|
||||
{
|
||||
if (strtolower($charset) == 'utf-8') {
|
||||
if (Base::getStrlen($string) <= $length) return $string;
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
|
||||
$strcut = Base::utf8Substr($strcut, $length, $start);
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $strcut);
|
||||
$strcut = Base::utf8Substr($string, $length, $start);
|
||||
return $strcut . $dot;
|
||||
} else {
|
||||
$length = $length * 2;
|
||||
if (strlen($string) <= $length) return $string;
|
||||
$string = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $string);
|
||||
$strcut = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$strcut .= ord($string[$i]) > 127 ? $string[$i] . $string[++$i] : $string[$i];
|
||||
}
|
||||
$strcut = str_replace(array('&', '"', '<', '>'), array('&', '"', '<', '>'), $strcut);
|
||||
}
|
||||
return $strcut . $dot;
|
||||
}
|
||||
@@ -466,7 +496,7 @@ class Base
|
||||
try {
|
||||
$array = json_decode($string, true);
|
||||
return is_array($array) ? $array : [];
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -484,7 +514,7 @@ class Base
|
||||
}
|
||||
try {
|
||||
return json_encode($array, $options);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -697,24 +727,20 @@ class Base
|
||||
/**
|
||||
* 判断两个地址域名是否相同
|
||||
* @param string $var1
|
||||
* @param string $var2
|
||||
* @param string|array $var2
|
||||
* @return bool
|
||||
*/
|
||||
public static function hostContrast($var1, $var2)
|
||||
{
|
||||
$arr1 = parse_url($var1);
|
||||
$arr2 = parse_url($var2);
|
||||
$host1 = $arr1['host'] ?? $var1;
|
||||
//
|
||||
$host1 = $var1;
|
||||
if (isset($arr1['host'])) {
|
||||
$host1 = $arr1['host'];
|
||||
$host2 = [];
|
||||
foreach (is_array($var2) ? $var2 : [$var2] as $url) {
|
||||
$arr2 = parse_url($url);
|
||||
$host2[] = $arr2['host'] ?? $url;
|
||||
}
|
||||
//
|
||||
$host2 = $var2;
|
||||
if (isset($arr2['host'])) {
|
||||
$host2 = $arr2['host'];
|
||||
}
|
||||
return $host1 == $host2;
|
||||
return in_array($host1, $host2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -738,6 +764,7 @@ class Base
|
||||
*/
|
||||
public static function fillUrl($str = '')
|
||||
{
|
||||
global $_A;
|
||||
if (is_array($str)) {
|
||||
foreach ($str as $key => $item) {
|
||||
$str[$key] = Base::fillUrl($item);
|
||||
@@ -756,7 +783,14 @@ class Base
|
||||
) {
|
||||
return $str;
|
||||
} else {
|
||||
return Base::leftDelete(url($str), "http://localhost");
|
||||
if ($_A['__fill_url_remote_url'] === true) {
|
||||
return "{{RemoteURL}}" . $str;
|
||||
}
|
||||
try {
|
||||
return url($str);
|
||||
} catch (\Throwable) {
|
||||
return self::getSchemeAndHost() . "/" . $str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,10 +807,49 @@ class Base
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
$find = url('');
|
||||
try {
|
||||
$find = url('');
|
||||
} catch (\Throwable) {
|
||||
$find = self::getSchemeAndHost();
|
||||
}
|
||||
return Base::leftDelete($str, $find . '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主地址
|
||||
* @return string 如:http://127.0.0.1:8080
|
||||
*/
|
||||
public static function getSchemeAndHost()
|
||||
{
|
||||
$scheme = isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443' ? 'https://' : 'http://';
|
||||
return $scheme.($_SERVER['HTTP_HOST'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 地址后拼接参数
|
||||
* @param $url
|
||||
* @param $parames
|
||||
* @return mixed|string
|
||||
*/
|
||||
public static function urlAddparameter($url, $parames)
|
||||
{
|
||||
if ($parames && is_array($parames)) {
|
||||
$array = [];
|
||||
foreach ($parames as $key => $val) {
|
||||
$array[] = $key . "=" . $val;
|
||||
}
|
||||
if ($array) {
|
||||
$query = implode("&", $array);
|
||||
if (str_contains($url, "?")) {
|
||||
$url .= "&" . $query;
|
||||
} else {
|
||||
$url .= "?" . $query;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化内容图片地址
|
||||
* @param $content
|
||||
@@ -810,30 +883,34 @@ class Base
|
||||
* 打散字符串,只留为数字的项
|
||||
* @param $delimiter
|
||||
* @param $string
|
||||
* @param bool $reInt 是否格式化值
|
||||
* @return array
|
||||
*/
|
||||
public static function explodeInt($delimiter, $string = null)
|
||||
public static function explodeInt($delimiter, $string = null, $reInt = true)
|
||||
{
|
||||
if ($string == null) {
|
||||
$string = $delimiter;
|
||||
$delimiter = ',';
|
||||
}
|
||||
$array = is_array($string) ? $string : explode($delimiter, $string);
|
||||
return self::arrayRetainInt($array);
|
||||
return self::arrayRetainInt($array, $reInt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组只保留数字的
|
||||
* @param $array
|
||||
* @param bool $int 是否格式化值
|
||||
* @param bool $reInt 是否格式化值
|
||||
* @return array
|
||||
*/
|
||||
public static function arrayRetainInt($array, $int = false)
|
||||
public static function arrayRetainInt($array, $reInt = false)
|
||||
{
|
||||
if (!is_array($array)) {
|
||||
return $array;
|
||||
}
|
||||
foreach ($array as $k => $v) {
|
||||
if (!is_numeric($v)) {
|
||||
unset($array[$k]);
|
||||
} elseif ($int === true) {
|
||||
} elseif ($reInt === true) {
|
||||
$array[$k] = intval($v);
|
||||
}
|
||||
}
|
||||
@@ -929,13 +1006,16 @@ class Base
|
||||
|
||||
/**
|
||||
* 检测邮箱格式
|
||||
* @param string $str 需要检测的字符串
|
||||
* @return int
|
||||
* @param $str
|
||||
* @return bool
|
||||
*/
|
||||
public static function isMail($str)
|
||||
public static function isEmail($str)
|
||||
{
|
||||
$RegExp = '/^[a-z0-9][a-z\.0-9-_]+@[a-z0-9_-]+(?:\.[a-z]{0,3}\.[a-z]{0,2}|\.[a-z]{0,3}|\.[a-z]{0,2})$/i';
|
||||
return preg_match($RegExp, $str);
|
||||
if (filter_var($str, FILTER_VALIDATE_EMAIL)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -945,7 +1025,21 @@ class Base
|
||||
*/
|
||||
public static function isNumber($str)
|
||||
{
|
||||
if (preg_match("/^\d*$/", $str)) {
|
||||
if (preg_match("/^\d+$/", $str)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 正则判断是否MAC地址
|
||||
* @param $str
|
||||
* @return bool
|
||||
*/
|
||||
public static function isMac($str)
|
||||
{
|
||||
if (preg_match("/^[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}:[A-Fa-f\d]{2}$/", $str)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -1151,11 +1245,12 @@ class Base
|
||||
|
||||
/**
|
||||
* 获取或设置
|
||||
* @param $setname //配置名称
|
||||
* @param bool $array //保存内容
|
||||
* @param $setname // 配置名称
|
||||
* @param bool $array // 保存内容
|
||||
* @param false $isUpdate // 保存内容为更新模式,默认否
|
||||
* @return array
|
||||
*/
|
||||
public static function setting($setname, $array = false)
|
||||
public static function setting($setname, $array = false, $isUpdate = false)
|
||||
{
|
||||
global $_A;
|
||||
if (empty($setname)) {
|
||||
@@ -1166,15 +1261,19 @@ class Base
|
||||
}
|
||||
$setting = [];
|
||||
$row = Setting::whereName($setname)->first();
|
||||
if (!empty($row)) {
|
||||
if ($row) {
|
||||
$setting = Base::string2array($row->setting);
|
||||
} else {
|
||||
$row = Setting::createInstance(['name' => $setname]);
|
||||
$row->save();
|
||||
}
|
||||
if ($array !== false) {
|
||||
$setting = $array;
|
||||
$row->updateInstance(['setting' => $array]);
|
||||
if ($isUpdate && is_array($array)) {
|
||||
$setting = array_merge($setting, $array);
|
||||
} else {
|
||||
$setting = $array;
|
||||
}
|
||||
$row->updateInstance(['setting' => $setting]);
|
||||
$row->save();
|
||||
}
|
||||
$_A["__static_setting_" . $setname] = $setting;
|
||||
@@ -1557,7 +1656,7 @@ class Base
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户名、邮箱、手机账号、银行卡号中间字符串以*隐藏
|
||||
* 用户名、邮箱、手机帐号、银行卡号中间字符串以*隐藏
|
||||
* @param $str
|
||||
* @return string
|
||||
*/
|
||||
@@ -1656,24 +1755,46 @@ class Base
|
||||
*/
|
||||
public static function timeDiff($s, $e)
|
||||
{
|
||||
$d = $e - $s;
|
||||
if ($d > 86400) {
|
||||
$day = floor($d / 86400);
|
||||
$hour = ceil(($d - ($day * 86400)) / 3600);
|
||||
if ($hour > 0) {
|
||||
return $day . '天' . $hour . '小时';
|
||||
} else {
|
||||
return $day . '天';
|
||||
}
|
||||
} elseif ($d > 3600) {
|
||||
return ceil($d / 3600) . '小时';
|
||||
} elseif ($d > 60) {
|
||||
return ceil($d / 60) . '分钟';
|
||||
} elseif ($d > 1) {
|
||||
return '1分钟内';
|
||||
} else {
|
||||
return '0秒';
|
||||
$time = $e - $s;
|
||||
$days = 0;
|
||||
if ($time >= 86400) { // 如果大于1天
|
||||
$days = (int)($time / 86400);
|
||||
$time = $time % 86400; // 计算天后剩余的毫秒数
|
||||
}
|
||||
$hours = 0;
|
||||
if ($time >= 3600) { // 如果大于1小时
|
||||
$hours = (int)($time / 3600);
|
||||
$time = $time % 3600; // 计算小时后剩余的毫秒数
|
||||
}
|
||||
$minutes = ceil($time / 60); // 剩下的毫秒数都算作分
|
||||
$daysStr = $days > 0 ? $days . '天' : '';
|
||||
$hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : '';
|
||||
$minuteStr = ($minutes > 0) ? $minutes . '分' : '';
|
||||
return $daysStr . $hoursStr . $minuteStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间秒数格式化
|
||||
* @param int $time 时间秒数
|
||||
* @return string
|
||||
*/
|
||||
public static function timeFormat($time)
|
||||
{
|
||||
$days = 0;
|
||||
if ($time >= 86400) { // 如果大于1天
|
||||
$days = (int)($time / 86400);
|
||||
$time = $time % 86400; // 计算天后剩余的毫秒数
|
||||
}
|
||||
$hours = 0;
|
||||
if ($time >= 3600) { // 如果大于1小时
|
||||
$hours = (int)($time / 3600);
|
||||
$time = $time % 3600; // 计算小时后剩余的毫秒数
|
||||
}
|
||||
$minutes = ceil($time / 60); // 剩下的毫秒数都算作分
|
||||
$daysStr = $days > 0 ? $days . '天' : '';
|
||||
$hoursStr = ($hours > 0 || ($days > 0 && $minutes > 0)) ? $hours . '时' : '';
|
||||
$minuteStr = ($minutes > 0) ? $minutes . '分' : '';
|
||||
return $daysStr . $hoursStr . $minuteStr;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1717,7 +1838,7 @@ class Base
|
||||
$onlineip = '0,0,0,0';
|
||||
}
|
||||
preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/", $onlineip, $match);
|
||||
$_A["__static_ip"] = $match[0] ?: 'unknown';
|
||||
$_A["__static_ip"] = $match ? ($match[0] ?: 'unknown') : '';
|
||||
}
|
||||
return $_A["__static_ip"];
|
||||
}
|
||||
@@ -2081,9 +2202,42 @@ class Base
|
||||
return Min(Max(Base::nullShow(Request::input($inputName), $default), 1), $max);
|
||||
}
|
||||
|
||||
/**
|
||||
* base64语音保存
|
||||
* @param array $param [ base64=带前缀的base64, path=>文件路径 ]
|
||||
* @return array [name=>文件名, size=>文件大小(单位KB),file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名]
|
||||
*/
|
||||
public static function record64save($param)
|
||||
{
|
||||
$base64 = $param['base64'];
|
||||
if (preg_match('/^(data:\s*audio\/(\w+);base64,)/', $base64, $res)) {
|
||||
$extension = $res[2];
|
||||
if (!in_array($extension, ['mp3', 'wav'])) {
|
||||
return Base::retError('语音格式错误');
|
||||
}
|
||||
$fileName = 'record_' . md5($base64) . '.' . $extension;
|
||||
$fileDir = $param['path'];
|
||||
$filePath = public_path($fileDir);
|
||||
Base::makeDir($filePath);
|
||||
if (file_put_contents($filePath . $fileName, base64_decode(str_replace($res[1], '', $base64)))) {
|
||||
$fileSize = filesize($filePath . $fileName);
|
||||
$array = [
|
||||
"name" => $fileName, //原文件名
|
||||
"size" => Base::twoFloat($fileSize / 1024, true), //大小KB
|
||||
"file" => $filePath . $fileName, //文件的完整路径 "D:\www....KzZ.jpg"
|
||||
"path" => $fileDir . $fileName, //相对路径 "uploads/pic....KzZ.jpg"
|
||||
"url" => Base::fillUrl($fileDir . $fileName), //完整的URL "https://.....hhsKzZ.jpg"
|
||||
"ext" => $extension, //文件后缀名
|
||||
];
|
||||
return Base::retSuccess('success', $array);
|
||||
}
|
||||
}
|
||||
return Base::retError('语音保存失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* image64图片保存
|
||||
* @param array $param [ image64=带前缀的base64, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式] ]
|
||||
* @param array $param [ image64=带前缀的base64, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式], autoThumb=>false不要自动生成缩略图 ]
|
||||
* @return array [name=>文件名, size=>文件大小(单位KB),file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名]
|
||||
*/
|
||||
public static function image64save($param)
|
||||
@@ -2103,7 +2257,7 @@ class Base
|
||||
if ($width > 0 || $height > 0) {
|
||||
$scaleName = "_{WIDTH}x{HEIGHT}";
|
||||
if (isset($param['scale'][2])) {
|
||||
$scaleName .= $param['scale'][2];
|
||||
$scaleName .= "_c{$param['scale'][2]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2164,8 +2318,13 @@ class Base
|
||||
}
|
||||
//生成缩略图
|
||||
$array['thumb'] = $array['path'];
|
||||
if (Base::imgThumb($array['file'], $array['file'] . "_thumb.jpg", 180, 0)) {
|
||||
$array['thumb'] .= "_thumb.jpg";
|
||||
if ($extension === 'gif' && !isset($param['autoThumb'])) {
|
||||
$param['autoThumb'] = false;
|
||||
}
|
||||
if ($param['autoThumb'] !== false) {
|
||||
if (Base::imgThumb($array['file'], $array['file'] . "_thumb.jpg", 320, 0)) {
|
||||
$array['thumb'] .= "_thumb.jpg";
|
||||
}
|
||||
}
|
||||
$array['thumb'] = Base::fillUrl($array['thumb']);
|
||||
return Base::retSuccess('success', $array);
|
||||
@@ -2176,12 +2335,13 @@ class Base
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param array $param [ type=[文件类型], file=>Request::file, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式], size=>限制大小KB, autoThumb=>false不要自动生成缩略图 ]
|
||||
* @param array $param [ type=[文件类型], file=>Request::file, path=>文件路径, fileName=>文件名称, scale=>[压缩原图宽,高, 压缩方式], size=>限制大小KB, autoThumb=>false不要自动生成缩略图, chmod=>权限(默认0644) ]
|
||||
* @return array [name=>原文件名, size=>文件大小(单位KB),file=>绝对地址, path=>相对地址, url=>全路径地址, ext=>文件后缀名]
|
||||
*/
|
||||
public static function upload($param)
|
||||
{
|
||||
$file = $param['file'];
|
||||
$chmod = $param['chmod'] ?: 0644;
|
||||
if (empty($file)) {
|
||||
return Base::retError("您没有选择要上传的文件");
|
||||
}
|
||||
@@ -2214,7 +2374,7 @@ class Base
|
||||
$type = ['zip'];
|
||||
break;
|
||||
case 'file':
|
||||
$type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz'];
|
||||
$type = ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz', 'ai', 'avi', 'bmp', 'cdr', 'eps', 'mov', 'mp3', 'mp4', 'pr', 'psd', 'svg', 'tif'];
|
||||
break;
|
||||
case 'firmware':
|
||||
$type = ['img', 'tar', 'bin'];
|
||||
@@ -2222,21 +2382,11 @@ class Base
|
||||
case 'md':
|
||||
$type = ['md'];
|
||||
break;
|
||||
case 'desktop':
|
||||
$type = ['yml', 'yaml', 'dmg', 'pkg', 'blockmap', 'zip', 'exe', 'msi'];
|
||||
break;
|
||||
case 'more':
|
||||
$type = [
|
||||
'docx', 'wps', 'doc', 'xls', 'xlsx', 'ppt', 'pptx',
|
||||
'jpg', 'jpeg', 'png', 'gif', 'bmp', 'ico', 'raw',
|
||||
'rar', 'zip', 'jar', '7-zip', 'tar', 'gzip', '7z',
|
||||
'tif', 'tiff',
|
||||
'dwg', 'dxf',
|
||||
'ofd',
|
||||
'pdf',
|
||||
'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',
|
||||
'mp3', 'wav', 'mp4', 'flv',
|
||||
'avi', 'mov', 'wmv', 'mkv', '3gp', 'rm',
|
||||
];
|
||||
$type = []; // 不限制上传文件类型
|
||||
break;
|
||||
default:
|
||||
return Base::retError('错误的类型参数');
|
||||
@@ -2250,11 +2400,13 @@ class Base
|
||||
if ($param['size'] > 0 && $fileSize > $param['size'] * 1024) {
|
||||
return Base::retError('文件大小超限,最大限制:' . $param['size'] . 'KB');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable) {
|
||||
$fileSize = 0;
|
||||
}
|
||||
$scaleName = "";
|
||||
if ($param['fileName']) {
|
||||
if ($param['fileName'] === true) {
|
||||
$fileName = $file->getClientOriginalName();
|
||||
} elseif ($param['fileName']) {
|
||||
$fileName = $param['fileName'];
|
||||
} else {
|
||||
if ($param['scale'] && is_array($param['scale'])) {
|
||||
@@ -2262,30 +2414,36 @@ class Base
|
||||
if ($width > 0 || $height > 0) {
|
||||
$scaleName = "_{WIDTH}x{HEIGHT}";
|
||||
if (isset($param['scale'][2])) {
|
||||
$scaleName .= $param['scale'][2];
|
||||
$scaleName .= "_c{$param['scale'][2]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
$fileName = md5_file($file) . '.' . $extension;
|
||||
$scaleName = md5_file($file) . $scaleName . '.' . $extension;
|
||||
$fileName = md5_file($file);
|
||||
$scaleName = md5_file($file) . $scaleName;
|
||||
if ($extension) {
|
||||
$fileName = $fileName . '.' . $extension;
|
||||
$scaleName = $scaleName . '.' . $extension;
|
||||
}
|
||||
}
|
||||
//
|
||||
$file->move(public_path($param['path']), $fileName);
|
||||
//
|
||||
$path = $param['path'] . $fileName;
|
||||
$array = [
|
||||
"name" => $file->getClientOriginalName(), //原文件名
|
||||
"size" => Base::twoFloat($fileSize / 1024, true), //大小KB
|
||||
"file" => public_path($param['path'] . $fileName), //文件的完整路径 "D:\www....KzZ.jpg"
|
||||
"path" => $param['path'] . $fileName, //相对路径 "uploads/pic....KzZ.jpg"
|
||||
"url" => Base::fillUrl($param['path'] . $fileName), //完整的URL "https://.....hhsKzZ.jpg"
|
||||
"thumb" => '', //缩略图(预览图) "https://.....hhsKzZ.jpg_thumb.jpg"
|
||||
"width" => -1, //图片宽度
|
||||
"height" => -1, //图片高度
|
||||
"ext" => $extension, //文件后缀名
|
||||
"name" => $file->getClientOriginalName(), //原文件名
|
||||
"size" => Base::twoFloat($fileSize / 1024, true), //大小KB
|
||||
"file" => public_path($path), //文件的完整路径 "D:\www....KzZ.jpg"
|
||||
"path" => $path, //相对路径 "uploads/pic....KzZ.jpg"
|
||||
"url" => Base::fillUrl($path), //完整的URL "https://.....hhsKzZ.jpg"
|
||||
"thumb" => '', //缩略图(预览图) "https://.....hhsKzZ.jpg_thumb.jpg"
|
||||
"width" => -1, //图片宽度
|
||||
"height" => -1, //图片高度
|
||||
"ext" => $extension, //文件后缀名
|
||||
];
|
||||
if (!is_file($array['file'])) {
|
||||
return Base::retError('上传失败');
|
||||
}
|
||||
@chmod($array['file'], $chmod);
|
||||
//iOS照片颠倒处理
|
||||
if (in_array($extension, ['jpg', 'jpeg']) && function_exists('exif_read_data')) {
|
||||
$data = imagecreatefromstring(file_get_contents($array['file']));
|
||||
@@ -2342,7 +2500,9 @@ class Base
|
||||
}
|
||||
//生成缩略图
|
||||
$array['thumb'] = $array['path'];
|
||||
if ($param['autoThumb'] === "false") $param['autoThumb'] = false;
|
||||
if ($extension === 'gif' && !isset($param['autoThumb'])) {
|
||||
$param['autoThumb'] = false;
|
||||
}
|
||||
if ($param['autoThumb'] !== false) {
|
||||
if (Base::imgThumb($array['file'], $array['file'] . "_thumb.jpg", 320, 0)) {
|
||||
$array['thumb'] .= "_thumb.jpg";
|
||||
@@ -2357,6 +2517,37 @@ class Base
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件移动
|
||||
* @param array $uploadResult
|
||||
* @param string $newPath "/" 结尾
|
||||
* @return array
|
||||
*/
|
||||
public static function uploadMove($uploadResult, $newPath)
|
||||
{
|
||||
if (str_ends_with($newPath, "/") && file_exists($uploadResult['file'])) {
|
||||
Base::makeDir(public_path($newPath));
|
||||
$oldPath = dirname($uploadResult['path']) . "/";
|
||||
$newFile = str_replace($oldPath, $newPath, $uploadResult['file']);
|
||||
if (rename($uploadResult['file'], $newFile)) {
|
||||
$oldUrl = $uploadResult['url'];
|
||||
$uploadResult['file'] = $newFile;
|
||||
$uploadResult['path'] = str_replace($oldPath, $newPath, $uploadResult['path']);
|
||||
$uploadResult['url'] = str_replace($oldPath, $newPath, $uploadResult['url']);
|
||||
if ($uploadResult['thumb'] == $oldUrl) {
|
||||
$uploadResult['thumb'] = $uploadResult['url'];
|
||||
} elseif ($uploadResult['thumb']) {
|
||||
$oldThumb = substr($uploadResult['thumb'], strpos($uploadResult['thumb'], $newPath));
|
||||
$newThumb = str_replace($oldPath, $newPath, $oldThumb);
|
||||
if (file_exists(public_path($oldThumb)) && rename(public_path($oldThumb), public_path($newThumb))) {
|
||||
$uploadResult['thumb'] = str_replace($oldPath, $newPath, $uploadResult['thumb']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $uploadResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缩略图
|
||||
* @param string $src_img 源图绝对完整地址{带文件名及后缀名}
|
||||
@@ -2474,7 +2665,7 @@ class Base
|
||||
try {
|
||||
$white = imagecolorallocate($dst, 255, 255, 255);
|
||||
imagefill($dst, 0, 0, $white);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable) {
|
||||
|
||||
}
|
||||
if (function_exists('imagecopyresampled')) {
|
||||
@@ -2628,16 +2819,19 @@ class Base
|
||||
/**
|
||||
* 遍历获取文件
|
||||
* @param $dir
|
||||
* @param bool $subdirectory 是否遍历子目录
|
||||
* @return array
|
||||
*/
|
||||
public static function readDir($dir)
|
||||
public static function readDir($dir, $subdirectory = true)
|
||||
{
|
||||
$files = array();
|
||||
$dir_list = scandir($dir);
|
||||
foreach ($dir_list as $file) {
|
||||
if ($file != '..' && $file != '.') {
|
||||
if (is_dir($dir . '/' . $file)) {
|
||||
$files = array_merge($files, self::readDir($dir . '/' . $file));
|
||||
if ($subdirectory) {
|
||||
$files = array_merge($files, self::readDir($dir . '/' . $file, $subdirectory));
|
||||
}
|
||||
} else {
|
||||
$files[] = $dir . "/" . $file;
|
||||
}
|
||||
@@ -2656,36 +2850,34 @@ class Base
|
||||
if (empty($str)) {
|
||||
return '';
|
||||
}
|
||||
$fchar = ord($str[0]);
|
||||
if ($fchar >= ord('A') && $fchar <= ord('z')) return strtoupper($str[0]);
|
||||
$s1 = iconv('UTF-8', 'gb2312', $str);
|
||||
$s2 = iconv('gb2312', 'UTF-8', $s1);
|
||||
$s = $s2 == $str ? $s1 : $str;
|
||||
$asc = ord($s[0]) * 256 + ord($s[1]) - 65536;
|
||||
if ($asc >= -20319 && $asc <= -20284) return 'A';
|
||||
if ($asc >= -20283 && $asc <= -19776) return 'B';
|
||||
if ($asc >= -19775 && $asc <= -19219) return 'C';
|
||||
if ($asc >= -19218 && $asc <= -18711) return 'D';
|
||||
if ($asc >= -18710 && $asc <= -18527) return 'E';
|
||||
if ($asc >= -18526 && $asc <= -18240) return 'F';
|
||||
if ($asc >= -18239 && $asc <= -17923) return 'G';
|
||||
if ($asc >= -17922 && $asc <= -17418) return 'H';
|
||||
if ($asc >= -17417 && $asc <= -16475) return 'J';
|
||||
if ($asc >= -16474 && $asc <= -16213) return 'K';
|
||||
if ($asc >= -16212 && $asc <= -15641) return 'L';
|
||||
if ($asc >= -15640 && $asc <= -15166) return 'M';
|
||||
if ($asc >= -15165 && $asc <= -14923) return 'N';
|
||||
if ($asc >= -14922 && $asc <= -14915) return 'O';
|
||||
if ($asc >= -14914 && $asc <= -14631) return 'P';
|
||||
if ($asc >= -14630 && $asc <= -14150) return 'Q';
|
||||
if ($asc >= -14149 && $asc <= -14091) return 'R';
|
||||
if ($asc >= -14090 && $asc <= -13319) return 'S';
|
||||
if ($asc >= -13318 && $asc <= -12839) return 'T';
|
||||
if ($asc >= -12838 && $asc <= -12557) return 'W';
|
||||
if ($asc >= -12556 && $asc <= -11848) return 'X';
|
||||
if ($asc >= -11847 && $asc <= -11056) return 'Y';
|
||||
if ($asc >= -11055 && $asc <= -10247) return 'Z';
|
||||
return '#';
|
||||
$first = mb_substr($str, 0, 1);
|
||||
if (preg_match("/^\d$/", $first)) {
|
||||
return '#';
|
||||
}
|
||||
if (!preg_match("/^[a-zA-Z]$/", $first)) {
|
||||
$pinyin = new Pinyin();
|
||||
$first = $pinyin->abbr($first, '', PINYIN_NAME);
|
||||
}
|
||||
return $first ? strtoupper($first) : '#';
|
||||
}
|
||||
|
||||
/**
|
||||
* 中文转拼音
|
||||
* @param $str
|
||||
* @return string
|
||||
*/
|
||||
public static function cn2pinyin($str)
|
||||
{
|
||||
if (empty($str)) {
|
||||
return '';
|
||||
}
|
||||
if (!preg_match("/^[a-zA-Z0-9_.]+$/", $str)) {
|
||||
$str = Cache::rememberForever("cn2pinyin:" . md5($str), function() use ($str) {
|
||||
$pinyin = new Pinyin();
|
||||
return $pinyin->permalink($str, '');
|
||||
});
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2866,4 +3058,54 @@ class Base
|
||||
$matrix = array_unique($matrix, SORT_REGULAR);
|
||||
return array_merge($matrix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节转格式
|
||||
* @param $bytes
|
||||
* @return string
|
||||
*/
|
||||
public static function readableBytes($bytes)
|
||||
{
|
||||
$i = floor(log($bytes) / log(1024));
|
||||
$sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
return sprintf('%.02F', $bytes / pow(1024, $i)) * 1 . ' ' . $sizes[$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除emoji表情
|
||||
* @param $str
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
public static function filterEmoji($str)
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/./u',
|
||||
function (array $match) {
|
||||
return strlen($match[0]) >= 4 ? '' : $match[0];
|
||||
},
|
||||
$str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一验证器
|
||||
* @param $data
|
||||
* @param $messages
|
||||
*/
|
||||
public static function validator($data, $messages) {
|
||||
$rules = [];
|
||||
foreach ($messages as $key => $item) {
|
||||
$keys = explode(".", $key);
|
||||
if (isset($keys[1])) {
|
||||
if (isset($rules[$keys[0]])) {
|
||||
$rules[$keys[0]] = $rules[$keys[0]] . '|' . $keys[1];
|
||||
} else {
|
||||
$rules[$keys[0]] = $keys[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
$validator = Validator::make($data, $rules, $messages);
|
||||
if ($validator->fails()) {
|
||||
throw new ApiException($validator->errors()->first());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -204,7 +206,25 @@ class WebSocketService implements WebSocketHandlerInterface
|
||||
*/
|
||||
private function deleteUser($fd)
|
||||
{
|
||||
WebSocket::whereFd($fd)->delete();
|
||||
$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()
|
||||
]);
|
||||
}
|
||||
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(),
|
||||
])]);
|
||||
}
|
||||
}
|
||||
|
||||
126
app/Tasks/AppPushTask.php
Normal file
126
app/Tasks/AppPushTask.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskPushLog;
|
||||
use App\Models\User;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
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');
|
||||
$pushTask = $setting['push'] === 'open' && $setting['push_task'] !== 'close';
|
||||
if (!$pushTask) {
|
||||
return;
|
||||
}
|
||||
$start = intval($setting['task_start_minute']);
|
||||
$hours = floatval($setting['task_remind_hours']);
|
||||
$hours2 = floatval($setting['task_remind_hours2']);
|
||||
if ($start > -1) {
|
||||
ProjectTask::whereNull("complete_at")
|
||||
->whereNull("archived_at")
|
||||
->whereBetween("start_at", [
|
||||
Carbon::now()->subMinutes($start + 10),
|
||||
Carbon::now()->subMinutes($start)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$this->taskPush($task, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($hours > -1) {
|
||||
ProjectTask::whereNull("complete_at")
|
||||
->whereNull("archived_at")
|
||||
->whereBetween("end_at", [
|
||||
Carbon::now()->addMinutes($hours * 60),
|
||||
Carbon::now()->addMinutes($hours * 60 + 10)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$this->taskPush($task, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($hours2 > -1) {
|
||||
ProjectTask::whereNull("complete_at")
|
||||
->whereNull("archived_at")
|
||||
->whereBetween("end_at", [
|
||||
Carbon::now()->subMinutes($hours2 * 60 + 10),
|
||||
Carbon::now()->subMinutes($hours2 * 60)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$this->taskPush($task, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务过期前、超期后提醒
|
||||
* @param ProjectTask $task
|
||||
* @param int $type
|
||||
* @return void
|
||||
*/
|
||||
private function taskPush(ProjectTask $task, int $type)
|
||||
{
|
||||
$userids = $task->taskUser->where('owner', 1)->pluck('userid')->toArray();
|
||||
if (empty($userids)) {
|
||||
return;
|
||||
}
|
||||
$users = User::whereIn('userid', $userids)->whereNull('disable_at')->get();
|
||||
if (empty($users)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$botUser = User::botGetOrCreate('task-alert');
|
||||
if (empty($botUser)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$setting = Base::setting('appPushSetting');
|
||||
$text = view('push.task', [
|
||||
'type' => str_replace([0, 1, 2], ['start', 'before', 'after'], $type),
|
||||
'task' => $task,
|
||||
'setting' => $setting,
|
||||
])->render();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$data = [
|
||||
'type' => $type,
|
||||
'userid' => $user->userid,
|
||||
'task_id' => $task->id,
|
||||
];
|
||||
$pushLog = ProjectTaskPushLog::where($data)->exists();
|
||||
if ($pushLog) {
|
||||
continue;
|
||||
}
|
||||
//
|
||||
$dialog = WebSocketDialog::checkUserDialog($botUser->userid, $data['userid']);
|
||||
if ($dialog) {
|
||||
ProjectTaskPushLog::createInstance($data)->save();
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $botUser->userid); // todo 未能在任务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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
250
app/Tasks/BotReceiveMsgTask.php
Normal file
250
app/Tasks/BotReceiveMsgTask.php
Normal file
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Module\Base;
|
||||
|
||||
@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;
|
||||
}
|
||||
if ($botUser->email === 'bot-manager@bot.system') {
|
||||
$this->botManagerReceive($msg);
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 机器人管理处理消息
|
||||
* @param WebSocketDialogMsg $msg
|
||||
* @return void
|
||||
*/
|
||||
private function botManagerReceive(WebSocketDialogMsg $msg)
|
||||
{
|
||||
if ($msg->type === 'text') {
|
||||
$text = trim(strip_tags($msg->msg['text']));
|
||||
if (empty($text)) {
|
||||
return;
|
||||
}
|
||||
$array = Base::newTrim(explode(" ", "{$text} "));
|
||||
$type = $array[0];
|
||||
$data = [];
|
||||
$notice = "";
|
||||
switch ($type) {
|
||||
/**
|
||||
* 列表
|
||||
*/
|
||||
case '/list':
|
||||
$data = User::select(['users.*'])
|
||||
->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 '/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->userid, $msg->userid);
|
||||
if ($dialog) {
|
||||
$text = "你好,我是你的机器人:{$data->nickname}, 我的机器人ID是:{$data->userid}";
|
||||
WebSocketDialogMsg::sendMsg(null, $dialog->id, 'text', ['text' => $text], $data->userid); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 修改名字
|
||||
*/
|
||||
case '/setname':
|
||||
if (strlen($array[2]) < 2 || strlen($array[2]) > 20) {
|
||||
$type = "notice";
|
||||
$notice = "机器人名称由2-20个字符组成。";
|
||||
break;
|
||||
}
|
||||
$data = $this->botManagerOne($array[1], $msg->userid);
|
||||
if ($data) {
|
||||
$data->nickname = $array[2];
|
||||
$data->az = Base::getFirstCharter($array[2]);
|
||||
$data->pinyin = Base::cn2pinyin($array[2]);
|
||||
$data->save();
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
case '/deletebot':
|
||||
$data = $this->botManagerOne($array[1], $msg->userid);
|
||||
if ($data) {
|
||||
$data->deleteUser('delete bot');
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 获取Token
|
||||
*/
|
||||
case '/token':
|
||||
$data = $this->botManagerOne($array[1], $msg->userid);
|
||||
if ($data) {
|
||||
User::token($data);
|
||||
} else {
|
||||
$type = "notice";
|
||||
$notice = "机器人不存在。";
|
||||
}
|
||||
break;
|
||||
|
||||
/**
|
||||
* 更新Token
|
||||
*/
|
||||
case '/revoke':
|
||||
$data = $this->botManagerOne($array[1], $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 '/dialog':
|
||||
$data = $this->botManagerOne($array[1], $msg->userid);
|
||||
if ($data) {
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->where('web_socket_dialogs.name', 'LIKE', "%{$array[2]}%")
|
||||
->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,
|
||||
'version' => Base::getVersion()
|
||||
])->render();
|
||||
$text = preg_replace("/^\x20+/", "", $text);
|
||||
$text = preg_replace("/\n\x20+/", "\n", $text);
|
||||
WebSocketDialogMsg::sendMsg(null, $msg->dialog_id, 'text', ['text' => $text], $this->userid, false, false, true); // todo 未能在任务end事件来发送任务
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $botId
|
||||
* @param $userid
|
||||
* @return User
|
||||
*/
|
||||
private function botManagerOne($botId, $userid)
|
||||
{
|
||||
$botId = intval($botId);
|
||||
$userid = intval($userid);
|
||||
if ($botId > 0) {
|
||||
return User::select(['users.*'])
|
||||
->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;
|
||||
}
|
||||
}
|
||||
@@ -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 'tw':
|
||||
{
|
||||
TaskWorker::onlyTrashed()
|
||||
->where('deleted_at', '<', Carbon::now()->subHours($this->hours)->toDateTimeString())
|
||||
->orderBy('id')
|
||||
->forceDelete();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
279
app/Tasks/EmailNoticeTask.php
Normal file
279
app/Tasks/EmailNoticeTask.php
Normal file
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tasks;
|
||||
|
||||
use App\Models\ProjectTask;
|
||||
use App\Models\ProjectTaskMailLog;
|
||||
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'] === 'open') {
|
||||
$start = intval($setting['task_start_minute']);
|
||||
$hours = floatval($setting['task_remind_hours']);
|
||||
$hours2 = floatval($setting['task_remind_hours2']);
|
||||
if ($start > -1) {
|
||||
ProjectTask::whereNull("complete_at")
|
||||
->whereNull("archived_at")
|
||||
->whereBetween("start_at", [
|
||||
Carbon::now()->subMinutes($start + 10),
|
||||
Carbon::now()->subMinutes($start)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$this->taskEmail($task, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($hours > -1) {
|
||||
ProjectTask::whereNull("complete_at")
|
||||
->whereNull("archived_at")
|
||||
->whereBetween("end_at", [
|
||||
Carbon::now()->addMinutes($hours * 60),
|
||||
Carbon::now()->addMinutes($hours * 60 + 10)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$this->taskEmail($task, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($hours2 > -1) {
|
||||
ProjectTask::whereNull("complete_at")
|
||||
->whereNull("archived_at")
|
||||
->whereBetween("end_at", [
|
||||
Carbon::now()->subMinutes($hours2 * 60 + 10),
|
||||
Carbon::now()->subMinutes($hours2 * 60)
|
||||
])->chunkById(100, function ($tasks) {
|
||||
/** @var ProjectTask $task */
|
||||
foreach ($tasks as $task) {
|
||||
$this->taskEmail($task, 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// 消息通知
|
||||
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.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 ProjectTask $task
|
||||
* @param int $type
|
||||
* @return void
|
||||
*/
|
||||
private function taskEmail(ProjectTask $task, int $type)
|
||||
{
|
||||
$userids = $task->taskUser->where('owner', 1)->pluck('userid')->toArray();
|
||||
if (empty($userids)) {
|
||||
return;
|
||||
}
|
||||
$users = User::whereIn('userid', $userids)->whereNull('disable_at')->get();
|
||||
if (empty($users)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$setting = Base::setting('emailSetting');
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$data = [
|
||||
'type' => $type,
|
||||
'userid' => $user->userid,
|
||||
'task_id' => $task->id,
|
||||
];
|
||||
$emailLog = ProjectTaskMailLog::where($data)->exists();
|
||||
if ($emailLog) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (!Base::isEmail($user->email)) {
|
||||
throw new \Exception("User email '{$user->email}' address error");
|
||||
}
|
||||
$subject = match ($type) {
|
||||
1 => "任务提醒",
|
||||
2 => "任务过期提醒",
|
||||
default => "任务开始提醒",
|
||||
};
|
||||
$content = view('email.task', [
|
||||
'type' => str_replace([0, 1, 2], ['start', 'before', 'after'], $type),
|
||||
'user' => $user,
|
||||
'task' => $task,
|
||||
'setting' => $setting,
|
||||
])->render();
|
||||
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();
|
||||
});
|
||||
$data['is_send'] = 1;
|
||||
} catch (\Throwable $e) {
|
||||
$data['send_error'] = $e->getMessage();
|
||||
}
|
||||
$data['email'] = $user->email;
|
||||
ProjectTaskMailLog::createInstance($data)->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 未读消息通知
|
||||
* @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.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::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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,17 @@ namespace App\Tasks;
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\UserBot;
|
||||
use App\Models\WebSocketDialog;
|
||||
use App\Models\WebSocketDialogMsg;
|
||||
use App\Models\WebSocketDialogMsgRead;
|
||||
use App\Module\Base;
|
||||
use Hhxsv5\LaravelS\Swoole\Task\Task;
|
||||
use Request;
|
||||
|
||||
|
||||
/**
|
||||
* 推送回话消息
|
||||
* 推送会话消息
|
||||
* Class WebSocketDialogMsgTask
|
||||
* @package App\Tasks
|
||||
*/
|
||||
@@ -20,21 +23,62 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
{
|
||||
protected $id;
|
||||
protected $ignoreFd;
|
||||
protected $msgNotExistRetry = false; // 推送失败后重试
|
||||
protected $silence = false; // 静默推送(1:前端不通知、2: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);
|
||||
@@ -42,31 +86,87 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
return;
|
||||
}
|
||||
|
||||
// 推送目标①:群成员
|
||||
// 提及会员
|
||||
$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])));
|
||||
}
|
||||
}
|
||||
|
||||
// 将会话以外的成员加入会话内
|
||||
$userids = $dialog->dialogUser->pluck('userid')->toArray();
|
||||
$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) {
|
||||
if ($userid == $msg->userid) {
|
||||
continue;
|
||||
$array[$userid] = false;
|
||||
} else {
|
||||
$mention = array_intersect([0, $userid], $mentions) ? 1 : 0;
|
||||
WebSocketDialogMsgRead::createInstance([
|
||||
'dialog_id' => $msg->dialog_id,
|
||||
'msg_id' => $msg->id,
|
||||
'userid' => $userid,
|
||||
'mention' => $mention,
|
||||
])->saveOrIgnore();
|
||||
$array[$userid] = $mention;
|
||||
// 机器人收到消处理
|
||||
$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(),
|
||||
]
|
||||
]);
|
||||
foreach ($array as $userid => $mention) {
|
||||
$this->endPush[] = [
|
||||
'userid' => $userid,
|
||||
'ignoreFd' => $this->ignoreFd,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'add',
|
||||
'silence' => $this->silence ? 1 : 0,
|
||||
'data' => array_merge($msg->toArray(), [
|
||||
'mention' => $mention,
|
||||
]),
|
||||
]
|
||||
];
|
||||
}
|
||||
// umeng推送app
|
||||
$setting = Base::setting('appPushSetting');
|
||||
$pushMsg = $setting['push'] === 'open' && $setting['push_msg'] !== 'close';
|
||||
if (!$this->silence && $pushMsg) {
|
||||
$umengUserid = $array;
|
||||
if (isset($umengUserid[$msg->userid])) {
|
||||
unset($umengUserid[$msg->userid]);
|
||||
}
|
||||
$umengUserid = array_keys($umengUserid);
|
||||
$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') {
|
||||
@@ -79,17 +179,26 @@ class WebSocketDialogMsgTask extends AbstractTask
|
||||
}
|
||||
}
|
||||
if ($array) {
|
||||
PushTask::push([
|
||||
$this->endPush[] = [
|
||||
'userid' => $array,
|
||||
'ignoreFd' => $this->ignoreFd,
|
||||
'msg' => [
|
||||
'type' => 'dialog',
|
||||
'mode' => 'chat',
|
||||
'silence' => $this->silence ? 1 : 0,
|
||||
'data' => $msg->toArray(),
|
||||
]
|
||||
]);
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
foreach ($this->endArray as $task) {
|
||||
Task::deliver($task);
|
||||
}
|
||||
PushTask::push($this->endPush);
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
135
cmd
135
cmd
@@ -13,6 +13,7 @@ Error="${Red}[错误]${Font}"
|
||||
|
||||
cur_path="$(pwd)"
|
||||
cur_arg=$@
|
||||
COMPOSE="docker-compose"
|
||||
|
||||
judge() {
|
||||
if [[ 0 -eq $? ]]; then
|
||||
@@ -31,6 +32,15 @@ rand() {
|
||||
echo $(($num%$max+$min))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
supervisorctl_restart() {
|
||||
local RES=`run_exec php "supervisorctl update $1"`
|
||||
if [ -z "$RES" ]; then
|
||||
@@ -46,15 +56,24 @@ check_docker() {
|
||||
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 +81,7 @@ check_node() {
|
||||
}
|
||||
|
||||
docker_name() {
|
||||
echo `docker-compose ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
echo `$COMPOSE ps | awk '{print $1}' | grep "\-$1\-"`
|
||||
}
|
||||
|
||||
run_compile() {
|
||||
@@ -77,6 +96,7 @@ run_compile() {
|
||||
if [ "$type" = "prod" ]; then
|
||||
rm -rf "./public/js/build"
|
||||
npx mix --production
|
||||
echo "$(rand_string 16)" > ./public/js/hash
|
||||
else
|
||||
npx mix watch --hot
|
||||
fi
|
||||
@@ -106,6 +126,10 @@ run_electron() {
|
||||
if [ "$argv" != "dev" ] && [ "$argv" != "--nobuild" ]; then
|
||||
npx mix --production -- --env --electron
|
||||
fi
|
||||
if [ "$argv" == "dev" ]; then
|
||||
run_exec php "php bin/run --mode=$argv"
|
||||
supervisorctl_restart php
|
||||
fi
|
||||
node ./electron/build.js $argv
|
||||
}
|
||||
|
||||
@@ -117,11 +141,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 +198,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 +215,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,16 +230,28 @@ 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
|
||||
}
|
||||
|
||||
is_arm() {
|
||||
local get_arch=`arch`
|
||||
if [[ $get_arch =~ "aarch" ]] || [[ $get_arch =~ "arm" ]]; then
|
||||
echo "yes"
|
||||
else
|
||||
echo "no"
|
||||
fi
|
||||
}
|
||||
|
||||
####################################################################################
|
||||
####################################################################################
|
||||
####################################################################################
|
||||
@@ -229,17 +264,25 @@ fi
|
||||
if [ $# -gt 0 ]; then
|
||||
if [[ "$1" == "init" ]] || [[ "$1" == "install" ]]; then
|
||||
shift 1
|
||||
# 判断架构
|
||||
if [[ "$(is_arm)" == "yes" ]] && [[ -z "$(arg_get force)" ]]; then
|
||||
echo -e "${Error} ${RedBG}暂不支持arm架构,强制安装请使用:./cmd install --force${Font}"
|
||||
exit 1
|
||||
fi
|
||||
# 初始化文件
|
||||
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
|
||||
@@ -254,21 +297,25 @@ if [ $# -gt 0 ]; then
|
||||
[[ -z "$(env_get APP_KEY)" ]] && run_exec php "php artisan key:generate"
|
||||
run_exec php "php bin/run --mode=prod"
|
||||
# 检查数据库
|
||||
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
|
||||
supervisorctl_restart php
|
||||
echo -e "${OK} ${GreenBG} 安装完成 ${Font}"
|
||||
echo -e "地址: http://${GreenBG}127.0.0.1:$(env_get APP_PORT)${Font}"
|
||||
echo -e "$res"
|
||||
@@ -281,11 +328,11 @@ if [ $# -gt 0 ]; then
|
||||
run_exec php "composer update"
|
||||
run_exec php "php artisan migrate"
|
||||
supervisorctl_restart php
|
||||
docker-compose up -d
|
||||
$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,22 +342,34 @@ 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"
|
||||
supervisorctl_restart php
|
||||
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
|
||||
elif [[ "$1" == "env" ]]; then
|
||||
shift 1
|
||||
if [ -n "$1" ]; then
|
||||
env_set $1 "$2"
|
||||
fi
|
||||
supervisorctl_restart php
|
||||
echo -e "${OK} ${GreenBG} 修改成功 ${Font}"
|
||||
elif [[ "$1" == "repassword" ]]; then
|
||||
shift 1
|
||||
run_exec mariadb "sh /etc/mysql/repassword.sh \"$@\""
|
||||
@@ -320,6 +379,9 @@ if [ $# -gt 0 ]; then
|
||||
elif [[ "$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 $@
|
||||
@@ -367,25 +429,26 @@ 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" == "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
|
||||
|
||||
@@ -16,14 +16,19 @@
|
||||
"ext-simplexml": "*",
|
||||
"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",
|
||||
|
||||
10144
composer.lock
generated
Normal file
10144
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -300,9 +300,9 @@ return [
|
||||
'log_level' => 4,
|
||||
'log_file' => storage_path(sprintf('logs/swoole-%s.log', date('Y-m'))),
|
||||
'document_root' => base_path('public'),
|
||||
'buffer_output_size' => 200 * 1024 * 1024,
|
||||
'socket_buffer_size' => 256 * 1024 * 1024,
|
||||
'package_max_length' => 200 * 1024 * 1024,
|
||||
'buffer_output_size' => 2 * 1024 * 1024,
|
||||
'socket_buffer_size' => 8 * 1024 * 1024,
|
||||
'package_max_length' => 1024 * 1024 * 1024, // 1GB
|
||||
'reload_async' => true,
|
||||
'max_wait_time' => 60,
|
||||
'enable_reuse_port' => true,
|
||||
|
||||
@@ -17,7 +17,7 @@ class CreateProjectLogsTable extends Migration
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('project_id')->nullable()->default(0)->comment('项目ID');
|
||||
$table->bigInteger('column_id')->nullable()->default(0)->comment('列表ID');
|
||||
$table->bigInteger('task_id')->nullable()->default(0)->comment('项目ID');
|
||||
$table->bigInteger('task_id')->nullable()->default(0)->comment('任务ID');
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
|
||||
$table->string('detail', 500)->nullable()->default('')->comment('详细信息');
|
||||
$table->timestamps();
|
||||
|
||||
@@ -20,7 +20,7 @@ class CreateWebSocketDialogMsgReadsTable extends Migration
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('发送会员ID');
|
||||
$table->tinyInteger('after')->nullable()->default(0)->comment('在阅读之后才添加的记录');
|
||||
$table->timestamp('read_at')->nullable()->comment('阅读时间');
|
||||
$table->unique(['msg_id', 'userid'], 'IDEX_msg_id_userid');
|
||||
$table->unique(['msg_id', 'userid']);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class CreateFileLinksTable extends Migration
|
||||
{
|
||||
Schema::create('file_links', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('file_id')->nullable()->default(0)->comment('项目ID');
|
||||
$table->bigInteger('file_id')->nullable()->default(0)->comment('文件ID');
|
||||
$table->integer('num')->nullable()->default(0)->comment('累计访问');
|
||||
$table->string('code')->nullable()->default('')->comment('链接码');
|
||||
$table->timestamps();
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateReportsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if ( Schema::hasTable('reports') )
|
||||
return;
|
||||
|
||||
Schema::create('reports', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->timestamps();
|
||||
$table->string("title")->default("")->comment("标题");
|
||||
$table->enum("type", ["weekly", "daily"])->default("daily")->comment("汇报类型");
|
||||
$table->unsignedBigInteger("userid")->default(0);
|
||||
$table->longText("content")->nullable();
|
||||
$table->index(["userid", "created_at"]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('reports');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateReportReceivesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if ( Schema::hasTable('report_receives') )
|
||||
return;
|
||||
|
||||
Schema::create('report_receives', function (Blueprint $table) {
|
||||
$table->bigIncrements("id");
|
||||
$table->unsignedInteger("rid")->default(0);
|
||||
$table->timestamp("receive_time")->nullable()->comment("接收时间");
|
||||
$table->unsignedBigInteger("userid")->default(0)->comment("接收人");
|
||||
$table->unsignedTinyInteger("read")->default(0)->comment("是否已读");
|
||||
$table->index(["userid", "receive_time"]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('report_receives');
|
||||
}
|
||||
}
|
||||
32
database/migrations/2022_01_04_111739_add_report_sign.php
Normal file
32
database/migrations/2022_01_04_111739_add_report_sign.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddReportSign extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('reports', function (Blueprint $table) {
|
||||
$table->string("sign")->default("")->comment("汇报唯一标识");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('reports', function (Blueprint $table) {
|
||||
$table->dropColumn("sign");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class WebSocketDialogMsgsAddDeletes extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('web_socket_dialog_msgs', 'deleted_at')) {
|
||||
$table->softDeletes();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class InsertSettingColumnTemplate extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$array = \App\Module\Base::setting('columnTemplate');
|
||||
if (empty($array)) {
|
||||
\App\Module\Base::setting('columnTemplate', [
|
||||
[
|
||||
'name' => '软件开发',
|
||||
'columns' => ['产品规划', '前端开发', '后端开发', '测试', '发布', '其他'],
|
||||
],
|
||||
[
|
||||
'name' => '产品开发',
|
||||
'columns' => ['产品计划', '正在设计', '正在研发', '测试', '准备发布', '发布成功'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class WebSocketDialogUsersAddTopAt extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('web_socket_dialog_users', 'top_at')) {
|
||||
$table->timestamp('top_at')->nullable()->after('userid')->comment('置顶时间');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('web_socket_dialog_users', function (Blueprint $table) {
|
||||
$table->dropColumn("top_at");
|
||||
});
|
||||
}
|
||||
}
|
||||
30
database/migrations/2022_02_20_171030_files_update_type.php
Normal file
30
database/migrations/2022_02_20_171030_files_update_type.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use App\Models\File;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class FilesUpdateType extends Migration
|
||||
{
|
||||
/**
|
||||
* 更改流程图文件类型
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
File::whereType('flow')->update([
|
||||
'type' => 'drawio'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
File::whereType('drawio')->update([
|
||||
'type' => 'flow'
|
||||
]);
|
||||
}
|
||||
}
|
||||
75
database/migrations/2022_02_21_203230_files_update_ext.php
Normal file
75
database/migrations/2022_02_21_203230_files_update_ext.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use App\Models\File;
|
||||
use App\Models\FileContent;
|
||||
use App\Module\Base;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class FilesUpdateExt extends Migration
|
||||
{
|
||||
/**
|
||||
* 更新后缀
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
File::whereIn('type', ['mind', 'drawio', 'document'])->where('ext', '')->orderBy('id')->chunk(100, function($files) {
|
||||
/** @var File $file */
|
||||
foreach ($files as $file) {
|
||||
$fileContent = FileContent::whereFid($file->id)->orderByDesc('id')->first();
|
||||
$contentArray = Base::json2array($fileContent?->content);
|
||||
$contentString = '';
|
||||
//
|
||||
switch ($file->type) {
|
||||
case 'document':
|
||||
$file->ext = $contentArray['type'] ?: 'md';
|
||||
$contentString = $contentArray['content'];
|
||||
break;
|
||||
case 'drawio':
|
||||
$file->ext = 'drawio';
|
||||
$contentString = $contentArray['xml'];
|
||||
break;
|
||||
case 'mind':
|
||||
$file->ext = 'mind';
|
||||
$contentString = $fileContent?->content;
|
||||
break;
|
||||
}
|
||||
$file->save();
|
||||
//
|
||||
$path = 'uploads/file/' . $file->type . '/' . date("Ym", Carbon::parse($file->created_at)->timestamp) . '/' . $file->id . '/' . md5($contentString);
|
||||
$save = public_path($path);
|
||||
Base::makeDir(dirname($save));
|
||||
file_put_contents($save, $contentString);
|
||||
$content = [
|
||||
'type' => $file->ext,
|
||||
'url' => $path
|
||||
];
|
||||
//
|
||||
$content = FileContent::createInstance([
|
||||
'fid' => $file->id,
|
||||
'content' => $content,
|
||||
'text' => $fileContent?->text,
|
||||
'size' => $file->size,
|
||||
'userid' => $file->userid,
|
||||
]);
|
||||
$content->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
File::whereIn('ext', ['mind', 'drawio', 'md'])->update([
|
||||
'ext' => ''
|
||||
]);
|
||||
// ... 退回去意义不大,文件内容不做回滚操作
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ProjectUsersAddTopAt extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('project_users', 'top_at')) {
|
||||
$table->timestamp('top_at')->nullable()->after('owner')->comment('置顶时间');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_users', function (Blueprint $table) {
|
||||
$table->dropColumn("top_at");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateProjectTaskFlowChangesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('project_task_flow_changes', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('task_id')->nullable()->default(0)->comment('任务ID');
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('会员ID');
|
||||
$table->bigInteger('before_item_id')->nullable()->default(0)->comment('(变化前)工作流状态ID');
|
||||
$table->string('before_item_name', 50)->nullable()->default('')->comment('(变化前)工作流状态名称');
|
||||
$table->bigInteger('after_item_id')->nullable()->default(0)->comment('(变化后)工作流状态ID');
|
||||
$table->string('after_item_name', 50)->nullable()->default('')->comment('(变化后)工作流状态名称');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('project_task_flow_changes');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RenamePreProjectTaskFlowChangesItem extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('project_task_flow_changes', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('project_task_flow_changes', 'before_item_id')) {
|
||||
$table->renameColumn('before_item_id', 'before_flow_item_id');
|
||||
$table->renameColumn('before_item_name', 'before_flow_item_name');
|
||||
$table->renameColumn('after_item_id', 'after_flow_item_id');
|
||||
$table->renameColumn('after_item_name', 'after_flow_item_name');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('project_task_flow_changes', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddIsEmailVerityToUsers extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('users', 'is_email_verity')) {
|
||||
$table->boolean('is_email_verity')->default(0)->nullable()->after('disable_at')->comment('邮箱是否已验证');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn("is_email_verity");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateUserEmailVerificationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('user_email_verifications', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->bigInteger('userid')->nullable()->default(0)->comment('用户id');
|
||||
$table->string('code')->nullable()->default('')->comment('验证参数');
|
||||
$table->string('email')->nullable()->default('')->comment('电子邮箱');
|
||||
$table->integer('status')->nullable()->default(0)->comment('0-未验证,1-已验证');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_email_verifications');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user