343 Commits
1.0.0 ... 1.1.2

Author SHA1 Message Date
c576299a60 Merge pull request #43 from v2board/dev
1.1.2
2020-02-17 22:06:10 +08:00
4926264160 update ui 2020-02-17 22:03:00 +08:00
0c1a53faab update ui 2020-02-17 21:34:06 +08:00
0f0b092f86 update ui 2020-02-17 21:29:53 +08:00
3beb938cbc update ui 2020-02-17 14:01:30 +08:00
c45578417f Merge pull request #42 from ColetteContreras/master
Update poseidon
2020-02-17 13:46:33 +08:00
088dc2cfa6 update ui 2020-02-17 13:41:19 +08:00
0b4c884824 update ui 2020-02-17 12:42:41 +08:00
7b9390a6cb update ui 2020-02-17 12:35:50 +08:00
2b69e83132 update order change process 2020-02-17 12:25:19 +08:00
56820346d0 Update poseidon 2020-02-17 11:30:58 +08:00
1ea307854f update theme 2020-02-17 03:55:38 +08:00
290ef77c03 update theme 2020-02-17 03:53:33 +08:00
172a639f59 update theme 2020-02-17 03:51:06 +08:00
bff92180b0 update theme 2020-02-17 03:37:04 +08:00
e094a2f4d1 update theme 2020-02-17 03:35:03 +08:00
b852180b4d update order change process 2020-02-17 01:43:18 +08:00
a0a5327f42 update order change process 2020-02-17 01:31:39 +08:00
874648a4c0 update order change process 2020-02-17 01:27:07 +08:00
15cd13c26c update order change process 2020-02-17 01:00:12 +08:00
76c50e01bf update order change process 2020-02-17 00:42:56 +08:00
9ae3994705 update order change process 2020-02-17 00:39:22 +08:00
cab7cc9d19 update order change process 2020-02-17 00:35:21 +08:00
dbac9d9dac Merge pull request #38 from v2board/dev
1.1.1
2020-02-16 18:39:47 +08:00
2e18b1b108 1.1.1 2020-02-16 18:39:10 +08:00
c1583a1014 Merge pull request #37 from v2board/dev
1.1.1
2020-02-16 17:54:29 +08:00
01c5901c71 1.1.1 2020-02-16 17:52:14 +08:00
0446e7d99f fix server rules maybe not defined 2020-02-16 16:51:25 +08:00
557f6bc0c2 fix admin login 2020-02-16 14:05:38 +08:00
e49dc87fda fix admin login 2020-02-16 13:55:48 +08:00
edf88cbccf fix admin login 2020-02-16 13:52:15 +08:00
19940f599d fix admin login 2020-02-16 13:48:31 +08:00
ace06630d8 fix commission frontent 2020-02-16 00:28:39 +08:00
50582137e4 update frontend 2020-02-15 23:27:59 +08:00
ab8da6bb02 update user fetch 2020-02-15 23:16:44 +08:00
0a58930619 compatibility old client 2020-02-15 23:04:50 +08:00
9d9866689d update order filter 2020-02-15 22:58:31 +08:00
2d21fa3fb7 update user update 2020-02-15 22:40:41 +08:00
5cd020ea7a update mailsend,frontend 2020-02-15 20:32:38 +08:00
43e7bb6c9f update mailsend,frontend 2020-02-15 17:24:00 +08:00
2b8456fdf5 update discount 2020-02-15 00:55:04 +08:00
b34fa70165 update userinfo fetch 2020-02-14 22:11:13 +08:00
d845439f14 add user discount 2020-02-14 21:54:16 +08:00
d356722482 update frontend 2020-02-14 18:24:59 +08:00
72ac20d387 update frontend 2020-02-13 17:09:11 +08:00
878a7da62c update frontend 2020-02-13 17:02:37 +08:00
4fd0c9ce51 update frontend 2020-02-13 16:58:48 +08:00
18ed6947b3 update frontend 2020-02-13 16:54:15 +08:00
b8b1e1043b update frontend 2020-02-13 16:45:54 +08:00
705ceb80b2 update frontend 2020-02-13 16:24:12 +08:00
47be10f274 update frontend 2020-02-13 16:22:30 +08:00
540b331f16 fix forget 2020-02-13 13:44:58 +08:00
e9ba149c0f update sql 2020-02-13 13:43:55 +08:00
895e0f177b fix description not show 2020-02-12 23:36:40 +08:00
9dc64114f5 opt paytaro error report 2020-02-12 14:01:40 +08:00
168b0ad72e opt paytaro error report 2020-02-12 14:01:00 +08:00
c82acafdf8 update api 2020-02-11 21:06:15 +08:00
e14489cf66 update api 2020-02-11 21:04:47 +08:00
96a36b1afb update ui 2020-02-11 17:12:57 +08:00
df9d99c271 update ui 2020-02-11 17:09:27 +08:00
79bd46c0da update ui 2020-02-11 16:35:12 +08:00
e638660bf7 update sql 2020-02-11 16:23:38 +08:00
880b8e8ad9 update ui 2020-02-11 16:21:36 +08:00
ba40e3a1bf fix order renew 2020-02-11 15:45:30 +08:00
8fafdc9bb5 fix order renew 2020-02-11 14:03:23 +08:00
e07b97b14e fix order renew 2020-02-11 13:31:58 +08:00
c817710556 fix order renew 2020-02-11 13:11:15 +08:00
19fd19fbcf add server rules 2020-02-10 21:24:49 +08:00
26e63cbe1c add server rules 2020-02-10 18:54:17 +08:00
5bd524fbf1 add server rules 2020-02-10 18:48:06 +08:00
3a7a0c6e62 remove web tls config 2020-02-10 16:11:38 +08:00
23bfb20307 opt tls 2020-02-10 15:52:07 +08:00
7f9ad68e1b opt tls 2020-02-10 15:51:01 +08:00
cb8cdb2e0e fix user sorter 2020-02-09 18:53:45 +08:00
5db26d862b fix get email whitelist suffix 2020-02-09 18:24:24 +08:00
13bc683faf opt register auto login 2020-02-09 18:21:58 +08:00
e9229781bc add email whitelist config 2020-02-09 18:20:33 +08:00
6b9e424b74 add email whitelist config 2020-02-09 18:09:48 +08:00
078e1e7bd6 add email whitelist config 2020-02-09 18:04:40 +08:00
d417e70475 add email whitelist config 2020-02-09 18:02:13 +08:00
214fb2a2bf add email whitelist config 2020-02-09 18:01:06 +08:00
f61e37aeaf add email whitelist config 2020-02-09 17:17:21 +08:00
98e23be297 add email blacklist config 2020-02-09 01:29:26 +08:00
5bc4bff8f9 add sorter 2020-02-09 00:32:39 +08:00
ab97db552d optimization try out 2020-02-08 23:03:48 +08:00
2e2a0bf273 fix try out 2020-02-08 22:26:38 +08:00
1f471545d4 add safe mode 2020-02-08 16:38:03 +08:00
ffe05a5467 optimization install 2020-02-08 16:19:22 +08:00
ec73224671 optimization install 2020-02-08 16:16:12 +08:00
3f4e3d46fb optimization install 2020-02-08 16:09:33 +08:00
6920352c0e optimization install 2020-02-08 16:07:20 +08:00
e57a70970f optimization install 2020-02-08 15:49:59 +08:00
406f1ee80d optimization install 2020-02-08 15:48:25 +08:00
f7558fecc7 optimization install 2020-02-08 15:45:23 +08:00
43d98a485d optimization install 2020-02-08 15:44:30 +08:00
be2caad604 optimization install 2020-02-08 15:43:13 +08:00
984abba50d optimization install 2020-02-08 15:26:18 +08:00
62f7d5b3e8 fix forget 2020-02-07 19:34:24 +08:00
37844c1bb4 fix 2020-02-05 21:09:33 +08:00
87e0d34961 fix tls 2020-02-05 18:46:10 +08:00
ce6bd80702 fix 2020-02-04 00:33:02 +08:00
79bb987c37 fix 2020-02-03 23:02:08 +08:00
ec8357aa3e fix api 2020-02-03 02:47:29 +08:00
24f5324c2a fix client route 2020-02-02 22:06:51 +08:00
0299cd63ad fix client route 2020-02-02 22:02:55 +08:00
bb566f01d9 fix client route 2020-02-02 22:01:50 +08:00
c03bd0098d fix client route 2020-02-02 21:59:12 +08:00
073f7611e1 update 2020-02-02 21:51:49 +08:00
517f7d0122 update clash rule 2020-02-02 21:50:58 +08:00
32d6a983a3 update changepassword 2020-02-02 20:44:52 +08:00
a8db24492b update frontend 2020-02-01 21:54:08 +08:00
1b23998248 split router 2020-02-01 20:58:36 +08:00
d744fba683 split router 2020-02-01 20:57:15 +08:00
3f6799aa4b split router 2020-02-01 20:54:47 +08:00
1d704a4424 split router 2020-02-01 20:51:45 +08:00
ab5aeb545e support multi password hash verify 2020-02-01 00:54:37 +08:00
5e7e42e52c support multi password hash verify 2020-01-31 21:57:43 +08:00
241cbd3016 support multi password hash verify 2020-01-31 21:54:17 +08:00
9ff815f853 update 2020-01-31 21:39:32 +08:00
b61e641dad update 2020-01-31 21:30:44 +08:00
a9eff7bea9 update 2020-01-31 20:04:47 +08:00
fc3c4b3b73 update 2020-01-31 17:54:41 +08:00
b75104475c update 2020-01-31 17:33:55 +08:00
6c606527b1 update 2020-01-31 17:18:00 +08:00
34892d9c34 Merge branch 'master' of https://github.com/v2board/v2board 2020-01-31 17:13:45 +08:00
51bb2779b9 update 2020-01-31 17:13:30 +08:00
45a257778c Merge pull request #25 from ColetteContreras/master
Add Poseidon
2020-01-31 15:18:13 +08:00
4286bbb988 Add Poseidon 2020-01-31 12:50:14 +08:00
2f8f3afebf update 2020-01-31 00:16:00 +08:00
15fc60d3c7 update 2020-01-31 00:03:18 +08:00
b8218f2c51 update 2020-01-30 20:16:14 +08:00
9080de4d0a update 2020-01-30 20:02:12 +08:00
50ffdd9db9 update 2020-01-30 19:59:06 +08:00
6cf3b73da2 update 2020-01-30 19:57:18 +08:00
e963a61910 update 2020-01-30 17:18:25 +08:00
038effdd42 update 2020-01-30 16:59:52 +08:00
5a4b208aab update 2020-01-29 17:10:10 +08:00
af400b3a0b update 2020-01-29 17:08:09 +08:00
9a4b2027f6 update 2020-01-29 17:01:12 +08:00
9ae2fc3f1d update 2020-01-29 16:58:45 +08:00
ba9d00f088 update 2020-01-29 16:49:32 +08:00
7fc34fa2b8 update 2020-01-29 16:42:47 +08:00
7cb5eb03b9 update 2020-01-29 16:37:57 +08:00
d7063191f4 update 2020-01-29 16:22:39 +08:00
deb7e4ed04 update 2020-01-29 16:10:56 +08:00
602e12d745 update 2020-01-29 16:10:21 +08:00
f2ffb184d4 update 2020-01-29 16:09:39 +08:00
6dd562df8c update 2020-01-29 16:08:50 +08:00
5fae1f2bd8 update 2020-01-27 15:51:05 +08:00
a60d68a13f update 2020-01-27 15:49:44 +08:00
dad9e43114 update 2020-01-25 23:04:52 +08:00
aac4ce8098 update 2020-01-20 23:34:53 +08:00
52b9ed47b0 update 2020-01-20 23:04:42 +08:00
6d76e30299 update 2020-01-20 20:12:13 +08:00
0675268a16 update 2020-01-20 17:13:14 +08:00
aaf94e61a2 update 2020-01-20 17:02:19 +08:00
47d5031fe6 update 2020-01-20 17:01:02 +08:00
8d4575ede9 update 2020-01-20 16:47:53 +08:00
e9a69cd0f6 update 2020-01-20 16:43:34 +08:00
625bcc59e8 update 2020-01-20 16:38:34 +08:00
34533ae10c update 2020-01-20 16:28:28 +08:00
b4fc90c133 update 2020-01-20 16:18:12 +08:00
12278e143e update 2020-01-20 16:16:59 +08:00
10510164e4 update 2020-01-20 15:24:48 +08:00
75e91b281c update 2020-01-20 14:47:36 +08:00
c027c1127b update 2020-01-20 14:04:03 +08:00
0dbdb55ed2 update 2020-01-20 14:01:53 +08:00
66042904e8 update 2020-01-20 13:58:50 +08:00
508961c4fb update 2020-01-20 13:57:30 +08:00
f03dbd1dcb update 2020-01-18 18:41:02 +08:00
e576d82955 update 2020-01-17 00:20:54 +08:00
88d7e2d35e update 2020-01-14 19:48:18 +08:00
9b6919e9c9 update 2020-01-14 19:45:19 +08:00
3e86cdcbbf update 2020-01-14 01:16:16 +08:00
9fc4a6129c update 2020-01-14 00:29:59 +08:00
db9743a67f update 2020-01-12 22:38:29 +08:00
0f5942bc03 update 2020-01-12 19:48:13 +08:00
28cc21e09b update 2020-01-12 02:35:40 +08:00
fc7f73b558 update 2020-01-12 02:32:15 +08:00
fb0ebea836 update 2020-01-12 02:16:05 +08:00
7d7cef5f37 update 2020-01-12 02:15:48 +08:00
abd488d247 update 2020-01-12 02:14:43 +08:00
e3916aaa06 update 2020-01-12 02:13:27 +08:00
32795cf541 update 2020-01-12 02:08:06 +08:00
415850184e update 2020-01-12 02:02:45 +08:00
afa3c0aa52 update 2020-01-12 01:53:05 +08:00
82875ae6f0 update 2020-01-12 01:48:01 +08:00
bcd85ef71a update 2020-01-12 01:47:54 +08:00
8f35924dee update 2020-01-12 01:41:49 +08:00
4a01b8f11e update 2020-01-12 01:27:36 +08:00
fc18168aa2 update 2020-01-12 00:49:13 +08:00
abdb6f80af update 2020-01-11 19:18:45 +08:00
fbf3a83104 update 2020-01-11 17:17:25 +08:00
515e23762f update 2020-01-11 15:39:07 +08:00
f7fdfadfb0 format 2020-01-11 13:36:52 +08:00
35f954cd84 update 2020-01-09 12:58:01 +08:00
ed0fe84687 update 2020-01-09 12:57:39 +08:00
a96ca5a363 update 2020-01-08 22:28:16 +08:00
6a051f469e update 2020-01-08 17:40:06 +08:00
5e9bc09396 Merge pull request #12 from ColetteContreras/refactor-cache
Use Cache for varieties of cache server
2020-01-08 17:38:44 +08:00
1ef8eab552 Use Cache for varieties of cache server 2020-01-08 01:34:48 +08:00
4d66941ef6 update 2020-01-06 13:48:58 +08:00
2e505a9759 update 2020-01-06 00:06:34 +08:00
85e0eb2760 update 2020-01-05 23:47:23 +08:00
c68440ce32 update 2020-01-05 23:30:07 +08:00
1cd0dbdd31 update 2020-01-05 23:14:42 +08:00
d268f6ddec Merge pull request #8 from chen-ran/master
Support mailgun
2020-01-05 22:07:02 +08:00
b75c33b1d9 support mailgun 2020-01-05 21:21:54 +08:00
0ee67ef270 update 2020-01-05 16:48:18 +08:00
44e57b7073 update 2020-01-05 15:48:51 +08:00
0de4a137bf update 2020-01-05 15:39:28 +08:00
392c849241 update 2020-01-05 15:35:12 +08:00
2f3f457ad9 update 2020-01-05 02:27:29 +08:00
776e866b3c update 2020-01-05 02:23:26 +08:00
9541bc8cd0 update 2020-01-05 02:16:41 +08:00
dc52c3191c update 2020-01-05 02:07:41 +08:00
85a29d3a8b update 2020-01-05 00:22:41 +08:00
7a0c9ce4c4 update 2020-01-04 18:11:13 +08:00
b4bb93e23e update 2020-01-04 18:08:05 +08:00
11d9654010 update 2020-01-04 17:46:53 +08:00
52163329da update 2020-01-04 17:36:31 +08:00
45b03b4fba update 2020-01-04 17:27:21 +08:00
0749372f34 update 2020-01-04 17:25:44 +08:00
588577d513 update 2020-01-04 17:21:46 +08:00
e3431c6ae7 update 2020-01-03 23:05:14 +08:00
2346b1a2dc update 2020-01-03 23:00:31 +08:00
c61f64d623 update 2020-01-03 22:42:27 +08:00
25fbdf0013 update 2020-01-03 21:29:25 +08:00
3a9b3ab32d update 2020-01-03 21:06:04 +08:00
a524fb6f76 update 2020-01-03 21:04:44 +08:00
2172f088a2 update 2020-01-03 20:55:54 +08:00
93fa074358 update 2020-01-03 20:09:21 +08:00
27e417a5f2 update 2020-01-03 20:04:20 +08:00
97e1d9b3bd update 2020-01-03 20:01:46 +08:00
1a0d8e9c55 update 2020-01-03 19:39:32 +08:00
b49b941e50 update 2020-01-03 19:13:04 +08:00
b9cee36641 update 2020-01-03 19:12:05 +08:00
2ecdc27921 update 2020-01-03 19:09:24 +08:00
2d58744de4 update 2020-01-03 12:38:25 +08:00
869a6a920b update 2020-01-03 12:36:44 +08:00
c82a189d76 update 2020-01-03 12:17:57 +08:00
7543819ef2 update 2020-01-03 01:47:15 +08:00
4ceca1957f update 2020-01-03 01:45:58 +08:00
44bd189259 update 2020-01-03 01:12:23 +08:00
b6752e3952 update 2020-01-03 00:45:33 +08:00
45ba9b0c15 update 2020-01-03 00:29:24 +08:00
52e7925cac update 2020-01-03 00:29:15 +08:00
54273a8f16 update 2020-01-03 00:24:45 +08:00
c6461f0bdf update 2020-01-02 23:32:31 +08:00
145f55ae29 update 2020-01-02 23:22:49 +08:00
a201b19940 update 2020-01-02 22:05:27 +08:00
f4166bed45 update 2020-01-02 22:03:24 +08:00
8f8be2ea33 update 2020-01-02 21:55:50 +08:00
df02973756 update 2020-01-02 21:53:02 +08:00
9e55cb2f5d update 2020-01-02 21:52:20 +08:00
5699fe09e9 update 2020-01-02 21:49:25 +08:00
34fa75b4cc update 2020-01-02 21:49:04 +08:00
96d3a27a5b update 2020-01-02 21:39:52 +08:00
b8c8335542 update 2020-01-02 21:38:32 +08:00
aceff450ec update 2020-01-02 13:09:59 +08:00
5d7b5eb8f6 update 2020-01-02 00:43:25 +08:00
afc9d64aab update 2020-01-02 00:21:39 +08:00
09e31dc70b update 2020-01-02 00:20:31 +08:00
d653541ef3 update 2020-01-02 00:19:04 +08:00
dc37499df9 update 2020-01-02 00:11:42 +08:00
2911680eaf update 2020-01-02 00:07:30 +08:00
6ff24f77b8 update 2020-01-02 00:06:23 +08:00
9f4c19bcab update 2020-01-02 00:05:54 +08:00
453a078cd5 update 2020-01-01 23:54:20 +08:00
20be5c3182 update 2020-01-01 23:50:50 +08:00
7639f07b83 update 2020-01-01 23:48:23 +08:00
2a693e4911 update 2020-01-01 23:42:25 +08:00
89d67279c6 update 2020-01-01 23:40:14 +08:00
8bc5004654 update 2020-01-01 18:05:44 +08:00
ec4c0ba339 update 2020-01-01 18:00:45 +08:00
b4e5eb26e3 update 2020-01-01 17:57:06 +08:00
1bf03b28c7 update 2020-01-01 17:55:16 +08:00
aa8f5bfa79 update 2020-01-01 16:35:24 +08:00
495aa04273 update 2020-01-01 16:35:14 +08:00
202a21c17a update 2020-01-01 16:20:44 +08:00
9eefb32a4c update 2020-01-01 15:59:53 +08:00
2eb594a4fa update 2020-01-01 02:25:49 +08:00
34e71ff049 update 2020-01-01 01:50:05 +08:00
63c1faba5e update 2020-01-01 01:36:22 +08:00
5b2e18f702 update 2019-12-31 18:16:43 +08:00
784b53c9f3 update 2019-12-31 18:13:42 +08:00
01d0f6b29d update 2019-12-31 17:49:52 +08:00
2d7d5a564e update 2019-12-31 17:49:24 +08:00
70c1d5c874 update 2019-12-31 15:58:53 +08:00
10029c8362 update 2019-12-31 15:57:12 +08:00
9cd377f8ed update 2019-12-31 15:54:24 +08:00
f5db443668 update 2019-12-31 15:53:27 +08:00
e1fce3ae37 update 2019-12-31 13:45:50 +08:00
fae5ef21e6 update 2019-12-31 01:06:48 +08:00
4c976b1cbe update 2019-12-31 01:05:41 +08:00
6de46f5c36 update 2019-12-31 01:04:55 +08:00
4992ea635c update 2019-12-30 22:52:02 +08:00
928e59a977 update 2019-12-30 22:45:42 +08:00
2be5b8e357 update 2019-12-30 22:45:10 +08:00
76ce89c17f update 2019-12-30 22:40:39 +08:00
8c5b32de90 update 2019-12-30 21:05:48 +08:00
0b89446b63 update 2019-12-30 19:03:15 +08:00
0d4a86c9e9 update 2019-12-30 17:54:50 +08:00
aa1d54137c update 2019-12-30 17:51:18 +08:00
eee6351e35 update 2019-12-30 17:48:26 +08:00
4b43bd2b9e update 2019-12-30 17:43:56 +08:00
ff6aeb92ec update 2019-12-30 16:57:21 +08:00
a9abdcd9d3 update 2019-12-30 16:48:59 +08:00
7965a020b6 update 2019-12-30 16:37:47 +08:00
17e626493a update 2019-12-30 16:26:25 +08:00
0de055a36b update 2019-12-30 16:25:32 +08:00
5070c851ed update 2019-12-30 16:21:50 +08:00
157a97b35d update 2019-12-30 15:13:11 +08:00
8e6ee35efb update 2019-12-29 13:33:42 +08:00
ec284241ee update 2019-12-29 13:29:52 +08:00
a3f59aac0a update 2019-12-29 13:21:36 +08:00
35c71be4a9 update 2019-12-29 12:12:33 +08:00
abf6dbf73c update 2019-12-29 01:07:38 +08:00
037ecc0a2a update 2019-12-28 17:46:47 +08:00
5a87d59d30 update 2019-12-28 17:34:08 +08:00
1351ec583e update 2019-12-28 17:31:56 +08:00
5b29257227 update 2019-12-28 12:31:47 +08:00
551073cb83 update 2019-12-28 11:41:47 +08:00
a1b466e2c2 update 2019-12-28 11:40:00 +08:00
0b3cf4a2a4 update 2019-12-27 18:01:19 +08:00
0dccfd9f09 update 2019-12-27 17:57:07 +08:00
988f088a58 update 2019-12-27 17:36:02 +08:00
493d019fc3 update 2019-12-27 16:22:07 +08:00
180ef19af7 update 2019-12-27 15:40:13 +08:00
4864ac7ea8 update 2019-12-27 15:28:03 +08:00
d3bc2c7d12 update 2019-12-27 15:14:49 +08:00
1c3e661f21 update 2019-12-27 15:11:26 +08:00
1297a0dc44 update 2019-12-27 14:25:25 +08:00
86357c91bd update 2019-12-27 01:59:15 +08:00
a3e73893db update 2019-12-27 01:58:21 +08:00
d197566922 support bitpayx, notify is not work 2019-12-27 01:50:26 +08:00
9385add291 add bitpayx class 2019-12-27 01:29:08 +08:00
6892006056 add composer autoload lib 2019-12-27 01:28:11 +08:00
f0b06cda7c update 2019-12-26 23:47:01 +08:00
ef580c94c4 update 2019-12-26 23:46:09 +08:00
79f90b724c update 2019-12-26 01:06:45 +08:00
9875afcf97 update 2019-12-25 23:15:40 +08:00
0bf7f7b92b update 2019-12-25 23:06:36 +08:00
218 changed files with 18931 additions and 6727 deletions

View File

@ -7,16 +7,16 @@ APP_URL=http://localhost
LOG_CHANNEL=stack
DB_CONNECTION=mysql
DB_HOST=db
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=123456
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120
REDIS_HOST=127.0.0.1
@ -31,6 +31,8 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME=null
MAILGUN_DOMAIN=
MAILGUN_SECRET=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
/node_modules
/config/v2panel.php
/config/v2board.php
/public/hot
/public/storage
/public/env.example.js

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017-2019 Bruskyii Panda
Copyright (c) 2019 Tokumeikoi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -39,14 +39,14 @@ class CheckExpire extends Command
*/
public function handle()
{
$user = User::all();
foreach ($user as $item) {
if ($item->expired_at < time() || $item->u + $item->d >= $item->transfer_enable) {
$item->enable = 0;
$users = User::all();
foreach ($users as $user) {
if ($user->expired_at < time() || $user->u + $user->d >= $user->transfer_enable) {
$user->enable = 0;
} else {
$item->enable = 1;
$user->enable = 1;
}
$item->save();
$user->save();
}
}

View File

@ -7,6 +7,7 @@ use App\Models\Order;
use App\Models\User;
use App\Models\Plan;
use App\Utils\Helper;
use App\Models\Coupon;
class CheckOrder extends Command
{
@ -59,15 +60,26 @@ class CheckOrder extends Command
}
}
private function orderHandle ($order) {
private function orderHandle($order)
{
$user = User::find($order->user_id);
return $this->buy($order, $user);
}
private function buy ($order, $user) {
private function buy($order, $user)
{
$plan = Plan::find($order->plan_id);
// change plan process
if ($order->type == 3) {
$user->expired_at = time();
}
if ($order->refund_amount) {
$user->balance = $user->balance + $order->refund_amount;
}
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->enable = 1;
$user->u = 0;
$user->d = 0;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = $this->getTime($order->cycle, $user->expired_at);
@ -77,15 +89,20 @@ class CheckOrder extends Command
}
}
private function getTime ($str, $timestamp) {
private function getTime($str, $timestamp)
{
if ($timestamp < time()) {
$timestamp = time();
}
switch ($str) {
case 'month_price': return strtotime('+1 month', $timestamp);
case 'quarter_price': return strtotime('+3 month', $timestamp);
case 'half_year_price': return strtotime('+6 month', $timestamp);
case 'year_price': return strtotime('+12 month', $timestamp);
case 'month_price':
return strtotime('+1 month', $timestamp);
case 'quarter_price':
return strtotime('+3 month', $timestamp);
case 'half_year_price':
return strtotime('+6 month', $timestamp);
case 'year_price':
return strtotime('+12 month', $timestamp);
}
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\MailLog;
use App\Jobs\SendEmail;
class SendRemindMail extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'send:remindMail';
/**
* The console command description.
*
* @var string
*/
protected $description = '发送提醒邮件';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$users = User::all();
foreach ($users as $user) {
if ($user->remind_expire) $this->remindExpire($user);
if ($user->remind_traffic) $this->remindTraffic($user);
}
}
private function remindExpire($user)
{
if (($user->expired_at - 86400) < time() && $user->expired_at > time()) {
SendEmail::dispatch([
'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的服务即将到期',
'template_name' => 'mail.sendRemindExpire',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url')
]
]);
}
}
private function remindTraffic($user)
{
if ($this->remindTrafficIsWarnValue(($user->u + $user->d), $user->transfer_enable)) {
$sendCount = MailLog::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('template_name', 'mail.sendRemindTraffic')
->count();
if ($sendCount > 0) return;
SendEmail::dispatch([
'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的流量使用已达到80%',
'template_name' => 'mail.sendRemindTraffic',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url')
]
]);
}
}
private function remindTrafficIsWarnValue($ud, $transfer_enable)
{
if ($ud <= 0) return false;
if (($ud / $transfer_enable * 100) < 80) return false;
return true;
}
}

View File

@ -1,68 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\Order;
use App\Models\Server;
use App\Models\ServerLog;
use App\Utils\Helper;
use Illuminate\Support\Facades\Redis;
class SystemCache extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'system:cache';
/**
* The console command description.
*
* @var string
*/
protected $description = '系统缓存任务';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->setMonthIncome();
$this->setMonthRegisterTotal();
}
private function setMonthIncome() {
Redis::set(
'month_income',
Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->where('status', '3')
->sum('total_amount')
);
}
private function setMonthRegisterTotal() {
Redis::set(
'month_register_total',
User::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->count()
);
}
}

View File

@ -4,23 +4,27 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\Order;
use App\Models\Server;
use App\Models\ServerLog;
use App\Utils\Helper;
use Illuminate\Support\Facades\Cache;
class ImportReset extends Command
class V2boardCache extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'import:reset';
protected $signature = 'v2board:cache';
/**
* The console command description.
*
* @var string
*/
protected $description = '为导入用户重置所有uuid及token';
protected $description = '缓存任务';
/**
* Create a new command instance.
@ -39,11 +43,5 @@ class ImportReset extends Command
*/
public function handle()
{
$user = User::all();
foreach ($user as $item) {
$item->v2ray_uuid = Helper::guid(true);
$item->token = Helper::guid();
$item->save();
}
}
}

View File

@ -3,6 +3,7 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Encryption\Encrypter;
use App\Models\User;
use App\Utils\Helper;
use Illuminate\Support\Facades\DB;
@ -40,13 +41,35 @@ class V2boardInstall extends Command
*/
public function handle()
{
try {
$this->info("__ ______ ____ _ ");
$this->info("\ \ / /___ \| __ ) ___ __ _ _ __ __| | ");
$this->info(" \ \ / / __) | _ \ / _ \ / _` | '__/ _` | ");
$this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | ");
$this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| ");
if (\File::exists(base_path() . '/.lock')) {
abort(500, 'V2board 已安装,如需重新安装请删除目录下.lock文件');
}
\Artisan::call('key:generate');
if (!\File::exists(base_path() . '/.env')) {
if (!copy(base_path() . '/.env.example', base_path() . '/.env')) {
abort(500, '复制环境文件失败,请检查目录权限');
}
}
$this->saveToEnv([
'APP_KEY' => 'base64:' . base64_encode(Encrypter::generateKey('AES-256-CBC')),
'DB_HOST' => $this->ask('请输入数据库地址(默认:localhost', 'localhost'),
'DB_DATABASE' => $this->ask('请输入数据库名'),
'DB_USERNAME' => $this->ask('请输入数据库用户名'),
'DB_PASSWORD' => $this->ask('请输入数据库密码')
]);
\Artisan::call('config:clear');
\Artisan::call('config:cache');
try {
DB::connection()->getPdo();
$file = \File::get(base_path() . '/install.sql');
} catch (\Exception $e) {
abort(500, '数据库连接失败');
}
$file = \File::get(base_path() . '/database/install.sql');
if (!$file) {
abort(500, '数据库文件不存在');
}
@ -56,11 +79,13 @@ class V2boardInstall extends Command
abort(500, '数据库文件格式有误');
}
$this->info('正在导入数据库请稍等...');
foreach($sql as $item) {
foreach ($sql as $item) {
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {}
} catch (\Exception $e) {
}
}
$this->info('数据库导入完成');
$email = '';
while (!$email) {
$email = $this->ask('请输入管理员邮箱?');
@ -74,16 +99,56 @@ class V2boardInstall extends Command
}
$this->info('一切就绪');
$this->info('访问 http(s)://你的站点/admin 进入管理面板');
\File::put(base_path() . '/.lock', time());
} catch (\Exception $e) {
$this->error($e->getMessage());
}
}
private function registerAdmin ($email, $password) {
private function registerAdmin($email, $password)
{
$user = new User();
$user->email = $email;
if (strlen($password) < 8) {
abort(500, '管理员密码长度最小为8位字符');
}
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();
$user->is_admin = 1;
return $user->save();
}
private function saveToEnv($data = [])
{
function set_env_var($key, $value)
{
if (! is_bool(strpos($value, ' '))) {
$value = '"' . $value . '"';
}
$key = strtoupper($key);
$envPath = app()->environmentFilePath();
$contents = file_get_contents($envPath);
preg_match("/^{$key}=[^\r\n]*/m", $contents, $matches);
$oldValue = count($matches) ? $matches[0] : '';
if ($oldValue) {
$contents = str_replace("{$oldValue}", "{$key}={$value}", $contents);
} else {
$contents = $contents . "\n{$key}={$value}\n";
}
$file = fopen($envPath, 'w');
fwrite($file, $contents);
return fclose($file);
}
foreach($data as $key => $value) {
set_env_var($key, $value);
}
return true;
}
}

View File

@ -40,7 +40,7 @@ class V2boardUpdate extends Command
{
\Artisan::call('config:cache');
DB::connection()->getPdo();
$file = \File::get(base_path() . '/update.sql');
$file = \File::get(base_path() . '/database/update.sql');
if (!$file) {
abort(500, '数据库文件不存在');
}
@ -50,10 +50,11 @@ class V2boardUpdate extends Command
abort(500, '数据库文件格式有误');
}
$this->info('正在导入数据库请稍等...');
foreach($sql as $item) {
foreach ($sql as $item) {
try {
DB::select(DB::raw($item));
} catch (\Exception $e) {}
} catch (\Exception $e) {
}
}
$this->info('更新完毕');
}

View File

@ -24,17 +24,17 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
// check order
// v2board
$schedule->command('v2board:cache')->hourly();
// check
$schedule->command('check:order')->everyMinute();
// check expire
$schedule->command('check:expire')->everyMinute();
// check commission
$schedule->command('check:commission')->everyMinute();
// system cache
$schedule->command('system:cache')->hourly();
// reset
$schedule->command('reset:traffic')->monthlyOn(1, '00:00');
$schedule->command('reset:serverLog')->monthlyOn(1, '00:00');
$schedule->command('reset:traffic')->monthly();
$schedule->command('reset:serverLog')->monthly();
// send
$schedule->command('send:remindMail')->dailyAt('11:30');
}
/**
@ -44,7 +44,7 @@ class Kernel extends ConsoleKernel
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}

View File

@ -4,18 +4,14 @@ namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\ConfigSave;
use Illuminate\Http\Request;
use App\Utils\Dict;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\Order;
use App\Models\User;
class ConfigController extends Controller
{
public function init () {
}
public function fetch () {
public function fetch()
{
// TODO: default should be in Dict
return response([
'data' => [
'invite' => [
@ -25,13 +21,18 @@ class ConfigController extends Controller
'invite_never_expire' => config('v2board.invite_never_expire', 0)
],
'site' => [
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
'stop_register' => (int)config('v2board.stop_register', 0),
'email_verify' => (int)config('v2board.email_verify', 0),
'app_name' => config('v2board.app_name', 'V2Board'),
'app_description' => config('v2board.app_description', 'V2Board is best!'),
'app_url' => config('v2board.app_url'),
'subscribe_url' => config('v2board.subscribe_url'),
'plan_update_fee' => config('v2board.plan_update_fee', 0.5),
'plan_is_update' => (int)config('v2board.plan_is_update', 1)
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0),
'try_out_hour' => (int)config('v2board.try_out_hour', 1),
'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT)
],
'pay' => [
// alipay
@ -44,10 +45,24 @@ class ConfigController extends Controller
'stripe_pk_live' => config('v2board.stripe_pk_live'),
'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable'),
'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable'),
'stripe_webhook_key' => config('v2board.stripe_webhook_key')
'stripe_webhook_key' => config('v2board.stripe_webhook_key'),
// bitpayx
'bitpayx_enable' => config('v2board.bitpayx_enable'),
'bitpayx_appsecret' => config('v2board.bitpayx_appsecret'),
// paytaro
'paytaro_enable' => config('v2board.paytaro_enable'),
'paytaro_app_id' => config('v2board.paytaro_app_id'),
'paytaro_app_secret' => config('v2board.paytaro_app_secret')
],
'frontend' => [
'frontend_theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'),
'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'),
'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'),
'frontend_background_url' => config('v2board.frontend_background_url')
],
'server' => [
'server_token' => config('v2board.server_token')
'server_token' => config('v2board.server_token'),
'server_license' => config('v2board.server_license')
],
'tutorial' => [
'apple_id' => config('v2board.apple_id')
@ -56,17 +71,18 @@ class ConfigController extends Controller
]);
}
public function save (ConfigSave $request) {
public function save(ConfigSave $request)
{
$data = $request->input();
$array = \Config::get('v2board');
foreach ($data as $k => $v) {
if (!in_array($k, ConfigSave::filter())) {
abort(500, '禁止修改');
abort(500, '参数' . $k . '不在规则内,禁止修改');
}
$array[$k] = $v;
}
$data = var_export($array, 1);
if(!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
if (!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
abort(500, '修改失败');
}
\Artisan::call('config:cache');

View File

@ -0,0 +1,64 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\CouponSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Coupon;
use App\Utils\Helper;
class CouponController extends Controller
{
public function fetch(Request $request)
{
return response([
'data' => Coupon::all()
]);
}
public function save(CouponSave $request)
{
$params = $request->only([
'name',
'type',
'value',
'started_at',
'ended_at',
'limit_use'
]);
if (!$request->input('id')) {
$params['code'] = Helper::randomChar(8);
if (!Coupon::create($params)) {
abort(500, '创建失败');
}
} else {
if (!Coupon::find($request->input('id'))->update($params)) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$coupon = Coupon::find($request->input('id'));
if (!$coupon) {
abort(500, '优惠券不存在');
}
if (!$coupon->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\MailSend;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Jobs\SendEmail;
class MailController extends Controller
{
public function send(MailSend $request)
{
switch ($request->input('type')) {
case 1: $users = $this->getAllUser();
break;
case 2: $users = $this->getReceiver($request->input('receiver'));
break;
case 3: $users = $this->getSubscribeUser();
break;
case 4: $users = $this->getExpireUser();
break;
}
foreach ($users as $user) {
SendEmail::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'mail.sendEmailCustom',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url'),
'content' => $request->input('content')
]
])->onQueue('other_mail');
}
return response([
'data' => true
]);
}
private function getAllUser()
{
return User::all();
}
private function getReceiver($receiver)
{
if (empty($receiver)) {
abort(500, '收件人不能为空');
}
return User::whereIn('id', $receiver)->get();
}
private function getSubscribeUser()
{
return User::where('expired_at', '=>', time())->get();
}
private function getExpireUser()
{
return User::where('expired_at', '<', time())->get();
}
}

View File

@ -6,45 +6,40 @@ use App\Http\Requests\Admin\NoticeSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Notice;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class NoticeController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
return response([
'data' => Notice::orderBy('id', 'DESC')->get()
]);
}
public function save (NoticeSave $request) {
public function save(NoticeSave $request)
{
$data = $request->only([
'title',
'content',
'img_url'
]);
if (!$request->input('id')) {
if (!Notice::create($data)) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function update (NoticeSave $request) {
$data = $request->only([
'title',
'content',
'img_url'
]);
if (!Notice::where('id', $request->input('id'))->update($data)) {
} else {
if (!Notice::find($request->input('id'))->update($data)) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function drop (Request $request) {
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}

View File

@ -11,7 +11,8 @@ use App\Models\Plan;
class OrderController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$orderModel = Order::orderBy('created_at', 'DESC');
@ -25,6 +26,9 @@ class OrderController extends Controller
if ($request->input('id')) {
$orderModel->where('id', $request->input('id'));
}
if ($request->input('user_id')) {
$orderModel->where('user_id', $request->input('user_id'));
}
$total = $orderModel->count();
$res = $orderModel->forPage($current, $pageSize)
->get();
@ -42,7 +46,8 @@ class OrderController extends Controller
]);
}
public function update (OrderUpdate $request) {
public function update(OrderUpdate $request)
{
$updateData = $request->only([
'status',
'commission_status'
@ -63,7 +68,8 @@ class OrderController extends Controller
]);
}
public function repair (Request $request) {
public function repair(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, '参数错误');
}

View File

@ -12,13 +12,15 @@ use App\Models\User;
class PlanController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
return response([
'data' => Plan::get()
]);
}
public function save (PlanSave $request) {
public function save(PlanSave $request)
{
if ($request->input('id')) {
$plan = Plan::find($request->input('id'));
if (!$plan) {
@ -29,9 +31,6 @@ class PlanController extends Controller
}
$plan->name = $request->input('name');
$plan->content = $request->input('content');
if ($plan->content) {
$plan->content = str_replace(PHP_EOL, '', $plan->content);
}
$plan->transfer_enable = $request->input('transfer_enable');
$plan->group_id = $request->input('group_id');
$plan->month_price = $request->input('month_price');
@ -44,7 +43,8 @@ class PlanController extends Controller
]);
}
public function drop (Request $request) {
public function drop(Request $request)
{
if (Order::where('plan_id', $request->input('id'))->first()) {
abort(500, '该订阅下存在订单无法删除');
}
@ -62,7 +62,8 @@ class PlanController extends Controller
]);
}
public function update (PlanUpdate $request) {
public function update(PlanUpdate $request)
{
$updateData = $request->only([
'show',
'renew'

View File

@ -10,54 +10,72 @@ use App\Models\ServerGroup;
use App\Models\Server;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class ServerController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$server = Server::get();
for ($i = 0; $i < count($server); $i++) {
if (!empty($server[$i]['tags'])) {
$server[$i]['tags'] = json_decode($server[$i]['tags']);
}
$server[$i]['group_id'] = json_decode($server[$i]['group_id']);
$server[$i]['last_check_at'] = Redis::get('server_last_check_at_' . $server[$i]['id']);
if ($server[$i]['parent_id']) {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['parent_id']);
} else {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['id']);
}
}
return response([
'data' => $server
]);
}
public function save (ServerSave $request) {
public function save(ServerSave $request)
{
$params = $request->only(array_keys(ServerSave::RULES));
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if (isset($params['rules'])) {
if (!is_object(json_decode($params['rules']))) {
abort(500, '审计规则配置格式不正确');
}
}
if (isset($params['settings'])) {
if (!is_object(json_decode($params['settings']))) {
abort(500, '传输协议配置格式不正确');
}
}
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
} else {
$server = new Server();
}
$server->group_id = json_encode($request->input('group_id'));
$server->name = $request->input('name');
$server->host = $request->input('host');
$server->port = $request->input('port');
$server->server_port = $request->input('server_port');
$server->tls = $request->input('tls');
$server->tags = $request->input('tags') ? json_encode($request->input('tags')) : NULL;
$server->rate = $request->input('rate');
$server->network = $request->input('network');
if ($request->input('settings')) {
if (!is_object(json_decode($request->input('settings')))) {
abort(500, '传输协议配置格式不正确');
}
$server->settings = $request->input('settings');
if (!$server->update($params)) {
abort(500, '保存失败');
}
return response([
'data' => $server->save()
'data' => true
]);
}
public function groupFetch (Request $request) {
if (!Server::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function groupFetch(Request $request)
{
if ($request->input('group_id')) {
return response([
'data' => [ServerGroup::find($request->input('group_id'))]
@ -68,7 +86,8 @@ class ServerController extends Controller
]);
}
public function groupSave (Request $request) {
public function groupSave(Request $request)
{
if (empty($request->input('name'))) {
abort(500, '组名不能为空');
}
@ -85,7 +104,8 @@ class ServerController extends Controller
]);
}
public function groupDrop (Request $request) {
public function groupDrop(Request $request)
{
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
if (!$serverGroup) {
@ -112,7 +132,8 @@ class ServerController extends Controller
]);
}
public function drop (Request $request) {
public function drop(Request $request)
{
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
@ -124,16 +145,18 @@ class ServerController extends Controller
]);
}
public function update (ServerUpdate $request) {
$updateData = $request->only([
public function update(ServerUpdate $request)
{
$params = $request->only([
'show',
]);
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
if (!$server->update($updateData)) {
if (!$server->update($params)) {
abort(500, '保存失败');
}

View File

@ -10,22 +10,27 @@ use App\Models\Plan;
use App\Models\User;
use App\Models\Ticket;
use App\Models\Order;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class StatController extends Controller
{
public function getOverride (Request $request) {
public function getOverride(Request $request)
{
return response([
'data' => [
'month_income' => Redis::get('month_income'),
'month_register_total' => Redis::get('month_register_total'),
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->where('status', '3')
->sum('total_amount'),
'month_register_total' => User::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->count(),
'ticket_pendding_total' => Ticket::where('status', 0)
->count(),
'commission_pendding_total' => Order::where('commission_status', 0)
->where('invite_user_id', '!=', NULL)
->where('status', 3)
->count(),
]
]);
}

View File

@ -10,7 +10,8 @@ use Illuminate\Support\Facades\DB;
class TicketController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->first();
@ -43,7 +44,8 @@ class TicketController extends Controller
]);
}
public function reply (Request $request) {
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
@ -75,7 +77,8 @@ class TicketController extends Controller
]);
}
public function close (Request $request) {
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\TutorialSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Tutorial;
class TutorialController extends Controller
{
public function fetch(Request $request)
{
return response([
'data' => Tutorial::all()
]);
}
public function save(TutorialSave $request)
{
$params = $request->only([
'title',
'description',
'steps',
'icon'
]);
if (!$request->input('id')) {
if (!Tutorial::create($params)) {
abort(500, '创建失败');
}
} else {
if (!Tutorial::find($request->input('id'))->update($params)) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function show(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$tutorial = Tutorial::find($request->input('id'));
if (!$tutorial) {
abort(500, '教程不存在');
}
$tutorial->show = $tutorial->show ? 0 : 1;
if (!$tutorial->save()) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$tutorial = Tutorial::find($request->input('id'));
if (!$tutorial) {
abort(500, '教程不存在');
}
if (!$tutorial->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@ -11,13 +11,19 @@ use App\Models\Plan;
class UserController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$userModel = User::orderBy('created_at', 'DESC');
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$userModel = User::orderBy($sort, $sortType);
if ($request->input('email')) {
$userModel->where('email', $request->input('email'));
}
if ($request->input('invite_user_id')) {
$userModel->where('invite_user_id', $request->input('invite_user_id'));
}
$total = $userModel->count();
$res = $userModel->forPage($current, $pageSize)
->get();
@ -35,8 +41,9 @@ class UserController extends Controller
]);
}
public function id2UserInfo ($id) {
if (empty($id)) {
public function getUserInfoById(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
return response([
@ -46,11 +53,12 @@ class UserController extends Controller
'd',
'transfer_enable',
'expired_at'
])->find($id)
])->find($request->input('id'))
]);
}
public function update (UserUpdate $request) {
public function update(UserUpdate $request)
{
$updateData = $request->only([
'email',
'password',
@ -59,7 +67,12 @@ class UserController extends Controller
'banned',
'plan_id',
'commission_rate',
'is_admin'
'discount',
'is_admin',
'u',
'd',
'balance',
'commission_balance'
]);
$user = User::find($request->input('id'));
if (!$user) {
@ -73,7 +86,6 @@ class UserController extends Controller
} else {
unset($updateData['password']);
}
$updateData['transfer_enable'] = $updateData['transfer_enable'] * 1073741824;
if (isset($updateData['plan_id'])) {
$plan = Plan::find($updateData['plan_id']);
if (!$plan) {

View File

@ -1,9 +1,9 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\Client;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Plan;
use App\Models\Server;
@ -12,11 +12,13 @@ use App\Utils\Helper;
class AppController extends Controller
{
CONST CLIENT_CONFIG = '{"policy":{"levels":{"0":{"uplinkOnly":0}}},"dns":{"servers":["114.114.114.114","8.8.8.8"]},"outboundDetour":[{"protocol":"freedom","tag":"direct","settings":{}}],"inbound":{"listen":"0.0.0.0","port":31211,"protocol":"socks","settings":{"auth":"noauth","udp":true,"ip":"127.0.0.1"}},"inboundDetour":[{"listen":"0.0.0.0","allocate":{"strategy":"always","refresh":5,"concurrency":3},"port":31210,"protocol":"http","tag":"httpDetour","domainOverride":["http","tls"],"streamSettings":{},"settings":{"timeout":0}}],"routing":{"strategy":"rules","settings":{"domainStrategy":"IPIfNonMatch","rules":[{"port":"1-52","type":"field","outboundTag":"direct"},{"port":"54-79","type":"field","outboundTag":"direct"},{"port":"81-442","type":"field","outboundTag":"direct"},{"port":"444-65535","type":"field","outboundTag":"direct"},{"type":"field","ip":["0.0.0.0/8","10.0.0.0/8","100.64.0.0/10","127.0.0.0/8","169.254.0.0/16","172.16.0.0/12","192.0.0.0/24","192.0.2.0/24","192.168.0.0/16","198.18.0.0/15","198.51.100.0/24","203.0.113.0/24","::1/128","fc00::/7","fe80::/10"],"outboundTag":"direct"},{"type":"field","ip":["geoip:cn"],"outboundTag":"direct"}]}},"outbound":{"tag":"proxy","sendThrough":"0.0.0.0","mux":{"enabled":false,"concurrency":8},"protocol":"vmess","settings":{"vnext":[{"address":"server","port":443,"users":[{"id":"uuid","alterId":2,"security":"auto","level":0}],"remark":"remark"}]},"streamSettings":{"network":"tcp","tcpSettings":{"header":{"type":"none"}},"security":"none","tlsSettings":{"allowInsecure":true,"allowInsecureCiphers":true},"kcpSettings":{"header":{"type":"none"},"mtu":1350,"congestion":false,"tti":20,"uplinkCapacity":5,"writeBufferSize":1,"readBufferSize":1,"downlinkCapacity":20},"wsSettings":{"path":"","headers":{"Host":"server.cc"}}}}}';
CONST CLIENT_CONFIG = '{"policy":{"levels":{"0":{"uplinkOnly":0}}},"dns":{"servers":["114.114.114.114","8.8.8.8"]},"outboundDetour":[{"protocol":"freedom","tag":"direct","settings":{}}],"inbound":{"listen":"0.0.0.0","port":31211,"protocol":"socks","settings":{"auth":"noauth","udp":true,"ip":"127.0.0.1"}},"inboundDetour":[{"listen":"0.0.0.0","allocate":{"strategy":"always","refresh":5,"concurrency":3},"port":31210,"protocol":"http","tag":"httpDetour","domainOverride":["http","tls"],"streamSettings":{},"settings":{"timeout":0}}],"routing":{"strategy":"rules","settings":{"domainStrategy":"IPIfNonMatch","rules":[{"type":"field","ip":["geoip:cn"],"outboundTag":"direct"},{"type":"field","ip":["0.0.0.0/8","10.0.0.0/8","100.64.0.0/10","127.0.0.0/8","169.254.0.0/16","172.16.0.0/12","192.0.0.0/24","192.0.2.0/24","192.168.0.0/16","198.18.0.0/15","198.51.100.0/24","203.0.113.0/24","::1/128","fc00::/7","fe80::/10"],"outboundTag":"direct"}]}},"outbound":{"tag":"proxy","sendThrough":"0.0.0.0","mux":{"enabled":false,"concurrency":8},"protocol":"vmess","settings":{"vnext":[{"address":"server","port":443,"users":[{"id":"uuid","alterId":2,"security":"auto","level":0}],"remark":"remark"}]},"streamSettings":{"network":"tcp","tcpSettings":{"header":{"type":"none"}},"security":"none","tlsSettings":{"allowInsecure":true,"allowInsecureCiphers":true},"kcpSettings":{"header":{"type":"none"},"mtu":1350,"congestion":false,"tti":20,"uplinkCapacity":5,"writeBufferSize":1,"readBufferSize":1,"downlinkCapacity":20},"wsSettings":{"path":"","headers":{"Host":"server.cc"}}}}}';
CONST SOCKS_PORT = 10010;
CONST HTTP_PORT = 10011;
public function data (Request $request) {
// TODO: 1.1.1 abolish
public function data(Request $request)
{
$user = $request->user;
$nodes = [];
if ($user->plan_id) {
@ -49,7 +51,8 @@ class AppController extends Controller
]);
}
public function config (Request $request) {
public function config(Request $request)
{
if (empty($request->input('server_id'))) {
abort(500, '参数错误');
}
@ -77,22 +80,28 @@ class AppController extends Controller
$json->outbound->streamSettings->network = $server->network;
if ($server->settings) {
switch ($server->network) {
case 'tcp': $json->outbound->streamSettings->tcpSettings = json_decode($server->settings);
case 'tcp':
$json->outbound->streamSettings->tcpSettings = json_decode($server->settings);
break;
case 'kcp': $json->outbound->streamSettings->kcpSettings = json_decode($server->settings);
case 'kcp':
$json->outbound->streamSettings->kcpSettings = json_decode($server->settings);
break;
case 'ws': $json->outbound->streamSettings->wsSettings = json_decode($server->settings);
case 'ws':
$json->outbound->streamSettings->wsSettings = json_decode($server->settings);
break;
case 'http': $json->outbound->streamSettings->httpSettings = json_decode($server->settings);
case 'http':
$json->outbound->streamSettings->httpSettings = json_decode($server->settings);
break;
case 'domainsocket': $json->outbound->streamSettings->dsSettings = json_decode($server->settings);
case 'domainsocket':
$json->outbound->streamSettings->dsSettings = json_decode($server->settings);
break;
case 'quic': $json->outbound->streamSettings->quicSettings = json_decode($server->settings);
case 'quic':
$json->outbound->streamSettings->quicSettings = json_decode($server->settings);
break;
}
}
if ($request->input('is_global')) {
$json->routing->settings->rules[5]->outboundTag = 'proxy';
$json->routing->settings->rules[0]->outboundTag = 'proxy';
}
if ($server->tls) {
$json->outbound->streamSettings->security = "tls";

View File

@ -0,0 +1,161 @@
<?php
namespace App\Http\Controllers\Client;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Server;
use App\Utils\Helper;
use Symfony\Component\Yaml\Yaml;
class ClientController extends Controller
{
public function subscribe(Request $request)
{
$user = $request->user;
$server = [];
// account not expired and is not banned.
if ($user->expired_at > time() && !$user->banned) {
$servers = Server::where('show', 1)
->orderBy('name')
->get();
foreach ($servers as $item) {
$groupId = json_decode($item['group_id']);
if (in_array($user->group_id, $groupId)) {
array_push($server, $item);
}
}
}
if (isset($_SERVER['HTTP_USER_AGENT'])) {
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult%20X') !== false) {
die($this->quantumultX($user, $server));
}
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult') !== false) {
die($this->quantumult($user, $server));
}
if (strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'clash') !== false) {
die($this->clash($user, $server));
}
}
die($this->origin($user, $server));
}
private function quantumultX($user, $server)
{
$uri = '';
foreach ($server as $item) {
$uri .= "vmess=" . $item->host . ":" . $item->port . ", method=none, password=" . $user->v2ray_uuid . ", fast-open=false, udp-relay=false, tag=" . $item->name;
if ($item->network == 'ws') {
$uri .= ', obfs=ws';
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if (isset($wsSettings->path)) $uri .= ', obfs-uri=' . $wsSettings->path;
if (isset($wsSettings->headers->Host)) $uri .= ', obfs-host=' . $wsSettings->headers->Host;
}
}
$uri .= "\r\n";
}
return base64_encode($uri);
}
private function quantumult($user, $server)
{
$uri = '';
header('subscription-userinfo: upload=' . $user->u . '; download=' . $user->d . ';total=' . $user->transfer_enable);
foreach ($server as $item) {
$str = '';
$str .= $item->name . '= vmess, ' . $item->host . ', ' . $item->port . ', chacha20-ietf-poly1305, "' . $user->v2ray_uuid . '", over-tls=' . ($item->tls ? "true" : "false") . ', certificate=0, group=' . config('v2board.app_name', 'V2Board');
if ($item->network === 'ws') {
$str .= ', obfs=ws';
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if (isset($wsSettings->path)) $str .= ', obfs-path="' . $wsSettings->path . '"';
if (isset($wsSettings->headers->Host)) $str .= ', obfs-header="Host:' . $wsSettings->headers->Host . '"';
}
}
$uri .= "vmess://" . base64_encode($str) . "\r\n";
}
return base64_encode($uri);
}
private function origin($user, $server)
{
$uri = '';
foreach ($server as $item) {
$uri .= Helper::buildVmessLink($item, $user);
}
return base64_encode($uri);
}
private function clash($user, $server)
{
$proxy = [];
$proxyGroup = [];
$proxies = [];
$rules = [];
foreach ($server as $item) {
$array = [];
$array['name'] = $item->name;
$array['type'] = 'vmess';
$array['server'] = $item->host;
$array['port'] = $item->port;
$array['uuid'] = $user->v2ray_uuid;
$array['alterId'] = $user->v2ray_alter_id;
$array['cipher'] = 'auto';
if ($item->tls) {
$array['tls'] = true;
}
if ($item->network == 'ws') {
$array['network'] = $item->network;
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if (isset($wsSettings->path)) $array['ws-path'] = $wsSettings->path;
if (isset($wsSettings->headers->Host)) $array['ws-headers'] = [
'Host' => $wsSettings->headers->Host
];
}
}
array_push($proxy, $array);
array_push($proxies, $item->name);
}
array_push($proxyGroup, [
'name' => 'auto',
'type' => 'url-test',
'proxies' => $proxies,
'url' => 'https://www.bing.com',
'interval' => 300
]);
array_push($proxyGroup, [
'name' => 'fallback-auto',
'type' => 'fallback',
'proxies' => $proxies,
'url' => 'https://www.bing.com',
'interval' => 300
]);
array_push($proxyGroup, [
'name' => 'select',
'type' => 'select',
'proxies' => $proxies
]);
try {
$rules = Yaml::parseFile(base_path() . '/resources/rules/clash.rule.yaml')['Rule'];
} catch (\Exception $e) {}
$config = [
'port' => 7890,
'socks-port' => 7891,
'allow-lan' => false,
'mode' => 'Rule',
'log-level' => 'info',
'external-controller' => '0.0.0.0:9090',
'secret' => '',
'Proxy' => $proxy,
'Proxy Group' => $proxyGroup,
'Rule' => $rules
];
return Yaml::dump($config);
}
}

View File

@ -1,143 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Plan;
use App\Models\Server;
use App\Utils\Helper;
use Symfony\Component\Yaml\Yaml;
class ClientController extends Controller
{
public function subscribe (Request $request) {
$user = $request->user;
$server = [];
if ($user->expired_at > time()) {
$servers = Server::where('show', 1)
->orderBy('name')
->get();
foreach ($servers as $item) {
$groupId = json_decode($item['group_id']);
if (in_array($user->group_id, $groupId)) {
array_push($server, $item);
}
}
}
if(isset($_SERVER['HTTP_USER_AGENT'])) {
if(strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult%20X') !== false) {
die($this->quantumultX($user, $server));
}
if(strpos($_SERVER['HTTP_USER_AGENT'], 'Quantumult') !== false) {
die($this->quantumult($user, $server));
}
if(strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'clash') !== false) {
die($this->clash($user, $server));
}
}
die($this->origin($user, $server));
}
private function quantumultX ($user, $server) {
$uri = '';
foreach($server as $item) {
$uri .= "vmess=".$item->host.":".$item->port.", method=none, password=".$user->v2ray_uuid.", fast-open=false, udp-relay=false, tag=".$item->name;
if ($item->network == 'ws') {
$uri .= ', obfs=ws';
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if ($wsSettings->path) $uri .= ', obfs-uri='.$wsSettings->path;
}
}
$uri .= "\r\n";
}
return base64_encode($uri);
}
private function quantumult ($user, $server) {
$uri = '';
header('subscription-userinfo: upload='.$user->u.'; download='.$user->d.';total='.$user->transfer_enable);
foreach($server as $item) {
$str = '';
$str .= $item->name.'= vmess, '.$item->host.', '.$item->port.', chacha20-ietf-poly1305, "'.$user->v2ray_uuid.'", over-tls='.($item->tls?"true":"false").', certificate=0, group='.config('v2board.app_name', 'V2Board');
if ($item->network === 'ws') {
$str .= ', obfs=ws';
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if ($wsSettings->path) $str .= ', obfs-path="'.$wsSettings->path.'"';
if ($wsSettings->headers->Host) $str .= ', obfs-header="Host:'.$wsSettings->headers->Host.'"';
}
}
$uri .= "vmess://".base64_encode($str)."\r\n";
}
return base64_encode($uri);
}
private function origin ($user, $server) {
$uri = '';
foreach($server as $item) {
$uri .= Helper::buildVmessLink($item, $user);
}
return base64_encode($uri);
}
private function clash ($user, $server) {
$proxy = [];
$proxyGroup = [];
$proxies = [];
foreach ($server as $item) {
$array = [];
$array['name'] = $item->name;
$array['type'] = 'vmess';
$array['server'] = $item->host;
$array['port'] = $item->port;
$array['uuid'] = $user->v2ray_uuid;
$array['alterId'] = $user->v2ray_alter_id;
$array['cipher'] = 'auto';
if ($item->tls) {
$array['tls'] = true;
}
if ($item->network == 'ws') {
$array['network'] = $item->network;
if ($item->settings) {
$wsSettings = json_decode($item->settings);
if ($wsSettings->path) $array['ws-path'] = $wsSettings->path;
if ($wsSettings->headers->Host) $array['ws-headers'] = [
'Host' => $wsSettings->headers->Host
];
}
}
array_push($proxy, $array);
array_push($proxies, $item->name);
}
array_push($proxyGroup, [
'name' => config('v2board.app_name', 'V2Board'),
'type' => 'select',
'proxies' => $proxies
]);
$config = [
'port' => 7890,
'socks-port' => 0,
'allow-lan' => false,
'mode' => 'Rule',
'log-level' => 'info',
'external-controller' => '0.0.0.0:9090',
'secret' => '',
'Proxy' => $proxy,
'Proxy Group' => $proxyGroup,
'Rule' => [
'DOMAIN-SUFFIX,google.com,'.config('v2board.app_name', 'V2Board'),
'DOMAIN-KEYWORD,google,'.config('v2board.app_name', 'V2Board'),
'DOMAIN,google.com,'.config('v2board.app_name', 'V2Board'),
'DOMAIN-SUFFIX,ad.com,REJECT',
'IP-CIDR,127.0.0.0/8,DIRECT',
'GEOIP,CN,DIRECT',
'MATCH,'.config('v2board.app_name', 'V2Board')
]
];
return Yaml::dump($config);
}
}

View File

@ -7,11 +7,14 @@ use App\Http\Controllers\Controller;
use App\Models\Order;
use Omnipay\Omnipay;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
use Library\BitpayX;
use Library\PayTaro;
class OrderController extends Controller
{
public function alipayNotify (Request $request) {
public function alipayNotify(Request $request)
{
Log::info('alipayNotifyData: ' . json_encode($_POST));
$gateway = Omnipay::create('Alipay_AopF2F');
$gateway->setSignType('RSA2'); //RSA/RSA2
@ -24,24 +27,16 @@ class OrderController extends Controller
/** @var \Omnipay\Alipay\Responses\AopCompletePurchaseResponse $response */
$response = $request->send();
if($response->isPaid()){
$order = Order::where('trade_no', $_POST['out_trade_no'])->first();
if (!$order) {
abort(500, 'fail');
}
if ($order->status == 1) {
die('success');
}
$order->status = 1;
$order->callback_no = $_POST['trade_no'];
if (!$order->save()) {
abort(500, 'fail');
}
if ($response->isPaid()) {
/**
* Payment is successful
*/
if (!$this->handle($_POST['out_trade_no'], $_POST['trade_no'])) {
abort(500, 'fail');
}
die('success'); //The response should be 'success' only
}else{
} else {
/**
* Payment is not successful
*/
@ -55,7 +50,8 @@ class OrderController extends Controller
}
}
public function stripeNotify (Request $request) {
public function stripeNotify(Request $request)
{
Log::info('stripeNotifyData: ' . json_encode($request->input()));
\Stripe\Stripe::setApiKey(config('v2board.stripe_sk_live'));
@ -77,23 +73,14 @@ class OrderController extends Controller
'source' => $source['id'],
]);
if ($charge['status'] == 'succeeded') {
$trade_no = Redis::get($source['id']);
$trade_no = Cache::get($source['id']);
if (!$trade_no) {
abort(500, 'redis is not found trade no by stripe source id.');
abort(500, 'redis is not found trade no by stripe source id');
}
$order = Order::where('trade_no', $trade_no)->first();
if (!$order) {
abort(500, 'order is not found');
}
if ($order->status !== 0) {
die('order is paid');
}
$order->status = 1;
$order->callback_no = $source['id'];
if (!$order->save()) {
if (!$this->handle($trade_no, $source['id'])) {
abort(500, 'fail');
}
Redis::del($source['id']);
Cache::forget($source['id']);
die('success');
}
break;
@ -101,4 +88,63 @@ class OrderController extends Controller
abort(500, 'event is not support');
}
}
public function bitpayXNotify(Request $request)
{
$inputString = file_get_contents('php://input', 'r');
Log::info('bitpayXNotifyData: ' . $inputString);
$inputStripped = str_replace(array("\r", "\n", "\t", "\v"), '', $inputString);
$inputJSON = json_decode($inputStripped, true); //convert JSON into array
$bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));
$params = [
'status' => $inputJSON['status'],
'order_id' => $inputJSON['order_id'],
'merchant_order_id' => $inputJSON['merchant_order_id'],
'price_amount' => $inputJSON['price_amount'],
'price_currency' => $inputJSON['price_currency'],
'pay_amount' => $inputJSON['pay_amount'],
'pay_currency' => $inputJSON['pay_currency'],
'created_at_t' => $inputJSON['created_at_t']
];
$strToSign = $bitpayX->prepareSignId($inputJSON['merchant_order_id']);
if (!$bitpayX->verify($strToSign, $inputJSON['token'])) {
abort(500, 'sign error');
}
if ($params['status'] !== 'PAID') {
abort(500, 'order is not paid');
}
if (!$this->handle($params['merchant_order_id'], $params['order_id'])) {
abort(500, 'order process fail');
}
die('success');
}
public function payTaroNotify(Request $request)
{
Log::info('payTaroNotify: ' . json_encode($request->input()));
$payTaro = new PayTaro(config('v2board.paytaro_app_id'), config('v2board.paytaro_app_secret'));
if (!$payTaro->verify($request->input())) {
abort(500, 'fail');
}
if (!$this->handle($request->input('out_trade_no'), $request->input('trade_no'))) {
abort(500, 'fail');
}
die('success');
}
private function handle($tradeNo, $callbackNo)
{
$order = Order::where('trade_no', $tradeNo)->first();
if (!$order) {
abort(500, 'order is not found');
}
if ($order->status !== 0) {
return true;
}
$order->status = 1;
$order->callback_no = $callbackNo;
return $order->save();
}
}

View File

@ -8,7 +8,8 @@ use App\Models\Plan;
class PlanController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
$plan = Plan::where('show', 1)->get();
return response([
'data' => $plan

View File

@ -1,17 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Notice;
use App\Utils\Helper;
class NoticeController extends Controller
{
public function fetch (Request $request) {
return response([
'data' => Notice::orderBy('created_at', 'DESC')->first()
]);
}
}

View File

@ -1,265 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\OrderSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Redis;
use App\Models\Order;
use App\Models\Plan;
use App\Models\User;
use App\Utils\Helper;
use Omnipay\Omnipay;
use Stripe\Stripe;
use Stripe\Source;
class OrderController extends Controller
{
public function fetch (Request $request) {
$order = Order::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC')
->get();
$plan = Plan::get();
for($i = 0; $i < count($order); $i++) {
for($x = 0; $x < count($plan); $x++) {
if ($order[$i]['plan_id'] === $plan[$x]['id']) {
$order[$i]['plan'] = $plan[$x];
}
}
}
return response([
'data' => $order
]);
}
public function details (Request $request) {
$order = Order::where('user_id', $request->session()->get('id'))
->where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
$order['plan'] = Plan::find($order->plan_id);
$order['update_fee'] = config('v2board.plan_update_fee', 0.5);
if (!$order['plan']) {
abort(500, '订阅不存在');
}
return response([
'data' => $order
]);
}
public function save (OrderSave $request) {
$plan = Plan::find($request->input('plan_id'));
$user = User::find($request->session()->get('id'));
if (!$plan) {
abort(500, '该订阅不存在');
}
if (!($plan->show || $user->plan_id == $plan->id)) {
abort(500, '该订阅已售罄');
}
if (!$plan->show && !$plan->renew) {
abort(500, '该订阅无法续费,请更换其他订阅');
}
if (!(int)$plan[$request->input('cycle')]) {
abort(500, '该订阅周期无法进行购买,请选择其他周期');
}
$order = new Order();
$order->user_id = $request->session()->get('id');
$order->plan_id = $plan->id;
$order->cycle = $request->input('cycle');
$order->trade_no = Helper::guid();
$order->total_amount = $plan[$request->input('cycle')];
if ($user->expired_at > time() && $order->plan_id !== $user->plan_id) {
$order->type = 3;
if (!(int)config('v2board.plan_is_update', 1)) abort(500, '目前不允许更改订阅,请联系管理员');
$order->total_amount = $order->total_amount + (ceil(($user->expired_at - time()) / 86400) * config('v2board.plan_update_fee', 0.5) * 100);
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
$order->type = 2;
} else {
$order->type = 1;
}
if ($user->invite_user_id) {
$order->invite_user_id = $user->invite_user_id;
$inviter = User::find($user->invite_user_id);
if ($inviter && $inviter->commission_rate) {
$order->commission_balance = $order->total_amount * ($inviter->commission_rate / 100);
} else {
$order->commission_balance = $order->total_amount * (config('v2board.invite_commission', 10) / 100);
}
}
if (!$order->save()) {
abort(500, '订单创建失败');
}
return response([
'data' => $order->trade_no
]);
}
public function checkout (Request $request) {
$tradeNo = $request->input('trade_no');
$method = $request->input('method');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
->where('status', 0)
->first();
if (!$order) {
abort(500, '订单不存在或以支付');
}
switch ($method) {
// return type => 0: QRCode / 1: URL
case 0:
// alipayF2F
if (!(int)config('v2board.alipay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 0,
'data' => $this->alipayF2F($tradeNo, $order->total_amount)
]);
case 2:
// stripeAlipay
if (!(int)config('v2board.stripe_alipay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->stripeAlipay($order)
]);
case 3:
// stripeWepay
if (!(int)config('v2board.stripe_wepay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 0,
'data' => $this->stripeWepay($order)
]);
default:
abort(500, '支付方式不存在');
}
}
public function check (Request $request) {
$tradeNo = $request->input('trade_no');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
return response([
'data' => $order->status
]);
}
public function getPaymentMethod () {
$data = [];
if ((int)config('v2board.alipay_enable')) {
$alipayF2F = new \StdClass();
$alipayF2F->name = '支付宝';
$alipayF2F->method = 0;
$alipayF2F->icon = 'alipay';
array_push($data, $alipayF2F);
}
if ((int)config('v2board.stripe_alipay_enable')) {
$stripeAlipay = new \StdClass();
$stripeAlipay->name = '支付宝';
$stripeAlipay->method = 2;
$stripeAlipay->icon = 'alipay';
array_push($data, $stripeAlipay);
}
if ((int)config('v2board.stripe_wepay_enable')) {
$stripeWepay = new \StdClass();
$stripeWepay->name = '微信';
$stripeWepay->method = 3;
$stripeWepay->icon = 'wechat';
array_push($data, $stripeWepay);
}
return response([
'data' => $data
]);
}
private function alipayF2F ($tradeNo, $totalAmount) {
$gateway = Omnipay::create('Alipay_AopF2F');
$gateway->setSignType('RSA2'); //RSA/RSA2
$gateway->setAppId(config('v2board.alipay_appid'));
$gateway->setPrivateKey(config('v2board.alipay_privkey')); // 可以是路径,也可以是密钥内容
$gateway->setAlipayPublicKey(config('v2board.alipay_pubkey')); // 可以是路径,也可以是密钥内容
$gateway->setNotifyUrl(url('/api/v1/guest/order/alipayNotify'));
$request = $gateway->purchase();
$request->setBizContent([
'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅',
'out_trade_no' => $tradeNo,
'total_amount' => $totalAmount / 100
]);
/** @var \Omnipay\Alipay\Responses\AopTradePreCreateResponse $response */
$response = $request->send();
$result = $response->getAlipayResponse();
if ($result['code'] !== '10000') {
abort(500, $result['sub_msg']);
}
// 获取收款二维码内容
return $response->getQrCode();
}
private function stripeAlipay ($order) {
$exchange = Helper::exchange('CNY', 'HKD');
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd',
'type' => 'alipay',
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
]);
if (!$source['redirect']['url']) {
abort(500, '支付网关请求失败');
}
if (!Redis::set($source['id'], $order->trade_no)) {
abort(500, '订单创建失败');
}
Redis::expire($source['id'], 3600);
return $source['redirect']['url'];
}
private function stripeWepay ($order) {
$exchange = Helper::exchange('CNY', 'HKD');
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd',
'type' => 'wechat',
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
]);
if (!$source['wechat']['qr_code_url']) {
abort(500, '支付网关请求失败');
}
if (!Redis::set($source['id'], $order->trade_no)) {
abort(500, '订单创建失败');
}
Redis::expire($source['id'], 3600);
return $source['wechat']['qr_code_url'];
}
}

View File

@ -0,0 +1,211 @@
<?php
namespace App\Http\Controllers\Passport;
use App\Http\Controllers\Controller;
use App\Http\Requests\Passport\AuthRegister;
use App\Http\Requests\Passport\AuthForget;
use App\Http\Requests\Passport\AuthLogin;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Plan;
use App\Models\User;
use App\Models\InviteCode;
use App\Utils\Helper;
use App\Utils\Dict;
class AuthController extends Controller
{
public function register(AuthRegister $request)
{
if ((int)config('v2board.email_whitelist_enable', 0)) {
if (!Helper::emailSuffixVerify(
$request->input('email'),
config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT))
) {
abort(500, '邮箱后缀不处于白名单中');
}
}
if ((int)config('v2board.stop_register', 0)) {
abort(500, '本站已关闭注册');
}
if ((int)config('v2board.invite_force', 0)) {
if (empty($request->input('invite_code'))) {
abort(500, '必须使用邀请码才可以注册');
}
}
if ((int)config('v2board.email_verify', 0)) {
$redisKey = 'sendEmailVerify:' . $request->input('email');
if (empty($request->input('email_code'))) {
abort(500, '邮箱验证码不能为空');
}
if (Cache::get($redisKey) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
}
$email = $request->input('email');
$password = $request->input('password');
$exist = User::where('email', $email)->first();
if ($exist) {
abort(500, '邮箱已存在系统中');
}
$user = new User();
$user->email = $email;
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();
if ($request->input('invite_code')) {
$inviteCode = InviteCode::where('code', $request->input('invite_code'))
->where('status', 0)
->first();
if (!$inviteCode) {
if ((int)config('v2board.invite_force', 0)) {
abort(500, '邀请码无效');
}
} else {
$user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
if (!(int)config('v2board.invite_never_expire', env('V2BOARD_INVITE_NEVER_EXPIRE'))) {
$inviteCode->status = 1;
$inviteCode->save();
}
}
}
// try out
if ((int)config('v2board.try_out_plan_id', 0)) {
$plan = Plan::find(config('v2board.try_out_plan_id'));
if ($plan) {
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = time() + (config('v2board.try_out_hour', 1) * 3600);
}
}
if (!$user->save()) {
abort(500, '注册失败');
}
if ((int)config('v2board.email_verify', 0)) {
Cache::forget($redisKey);
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
return response()->json([
'data' => true
]);
}
public function login(AuthLogin $request)
{
$email = $request->input('email');
$password = $request->input('password');
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, '用户名或密码错误');
}
if (!Helper::multiPasswordVerify(
$user->password_algo,
$password,
$user->password)
) {
abort(500, '用户名或密码错误');
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
}
$data = [
'token' => $user->token
];
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
$data['is_admin'] = true;
}
return response([
'data' => $data
]);
}
public function token2Login(Request $request)
{
if ($request->input('token')) {
$user = User::where('token', $request->input('token'))->first();
if (!$user) {
return header('Location:' . config('v2board.app_url'));
}
$code = Helper::guid();
$key = 'token2Login_' . $code;
Cache::put($key, $user->id, 600);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (config('v2board.app_url')) {
$location = config('v2board.app_url') . $redirect;
} else {
$location = url($redirect);
}
return header('Location:' . $location);
}
if ($request->input('verify')) {
$key = 'token2Login_' . $request->input('verify');
$userId = Cache::get($key);
if (!$userId) {
abort(500, '令牌有误');
}
$user = User::find($userId);
if (!$user) {
abort(500, '用户不存在');
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
}
Cache::forget($key);
return response([
'data' => true
]);
}
}
public function check(Request $request)
{
$data = [
'is_login' => $request->session()->get('id') ? true : false
];
if ($request->session()->get('is_admin')) {
$data['is_admin'] = true;
}
return response([
'data' => $data
]);
}
public function forget(AuthForget $request)
{
$redisKey = 'sendEmailVerify:' . $request->input('email');
if (Cache::get($redisKey) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
$user = User::where('email', $request->input('email'))->first();
if (!$user) {
abort(500, '该邮箱不存在系统中');
}
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
if (!$user->save()) {
abort(500, '重置失败');
}
Cache::forget($redisKey);
return response([
'data' => true
]);
}
}

View File

@ -7,52 +7,80 @@ use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis;
use App\Utils\Helper;
use Illuminate\Support\Facades\Cache;
use App\Jobs\SendEmail;
use App\Models\InviteCode;
use App\Utils\Dict;
class CommController extends Controller
{
public function config () {
public function config()
{
return response([
'data' => [
'isEmailVerify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0
'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0,
'emailWhitelistSuffix' => (int)config('v2board.email_whitelist_enable', 0)
? $this->getEmailSuffix()
: 0
]
]);
}
private function isEmailVerify () {
private function isEmailVerify()
{
return response([
'data' => (int)config('v2board.email_verify', 0) ? 1 : 0
]);
}
public function sendEmailVerify (CommSendEmailVerify $request) {
public function sendEmailVerify(CommSendEmailVerify $request)
{
$email = $request->input('email');
$redisKey = 'sendEmailVerify:' . $email;
if (Redis::get($redisKey)) {
$cacheKey = 'sendEmailVerify:' . $email;
if (Cache::get($cacheKey)) {
abort(500, '验证码已发送,请过一会在请求');
}
$code = rand(100000, 999999);
$code = Helper::randomChar(6);
$subject = config('v2board.app_name', 'V2Board') . '邮箱验证码';
Mail::send(
'mail.sendEmailVerify',
[
'code' => $code,
'name' => config('v2board.app_name', 'V2Board')
],
function ($message) use($email, $subject) {
$message->to($email)->subject($subject);
}
);
if (count(Mail::failures()) >= 1) {
// 发送失败
abort(500, '发送失败');
}
Redis::set($redisKey, $code);
Redis::expire($redisKey, 600);
SendEmail::dispatch([
'email' => $email,
'subject' => $subject,
'template_name' => 'mail.sendEmailVerify',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'code' => $code,
'url' => config('v2board.app_url')
]
])->onQueue('verify_mail');
Cache::put($cacheKey, $code, 60);
return response([
'data' => true
]);
}
public function pv(Request $request)
{
$inviteCode = InviteCode::where('code', $request->input('invite_code'))->first();
if ($inviteCode) {
$inviteCode->pv = $inviteCode->pv + 1;
$inviteCode->save();
}
return response([
'data' => true
]);
}
private function getEmailSuffix()
{
$suffix = config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT);
if (!is_array($suffix)) {
return preg_split('/,/', $suffix);
}
return $suffix;
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace App\Http\Controllers\Passport;
use App\Http\Requests\Passport\ForgetIndex;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis;
class ForgetController extends Controller
{
public function index (ForgetIndex $request) {
$redisKey = 'sendEmailVerify:' . $request->input('email');
if (Redis::get($redisKey) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
$user = User::where('email', $request->input('email'))->first();
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
if (!$user->save()) {
abort(500, '重置失败');
}
Redis::del($redisKey);
return response([
'data' => true
]);
}
}

View File

@ -1,57 +0,0 @@
<?php
namespace App\Http\Controllers\Passport;
use Illuminate\Http\Request;
use App\Http\Requests\Passport\LoginIndex;
use App\Http\Controllers\Controller;
use App\Models\User;
class LoginController extends Controller
{
public function index (LoginIndex $request) {
$email = $request->input('email');
$password = $request->input('password');
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, '用户名或密码错误');
}
if (!password_verify($password, $user->password)) {
abort(500, '用户名或密码错误');
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
}
return response([
'data' => [
'is_admin' => $user->is_admin ? 2 : 1,
'token' => $user->token
]
]);
}
public function token2Login (Request $request) {
if (empty($request->input('token'))) {
abort(500, '参数错误');
}
$redirect = $request->input('redirect') ? $request->input('redirect') : 'dashboard';
$user = User::where('token', $request->input('token'))->first();
if ($user) {
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
if ($user->is_admin) {
$request->session()->put('is_admin', true);
}
}
if (config('v2board.app_url')) {
$location = config('v2board.app_url') . '/#/' . $redirect;
} else {
$location = url('/#/' . $redirect);
}
header('Location:' . $location);
}
}

View File

@ -1,73 +0,0 @@
<?php
namespace App\Http\Controllers\Passport;
use App\Http\Requests\Passport\RegisterIndex;
use App\Http\Requests\Passport\RegisterSendEmailVerify;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Redis;
use App\Utils\Helper;
use App\Models\InviteCode;
class RegisterController extends Controller
{
public function index (RegisterIndex $request) {
if ((int)config('v2board.stop_register', 0)) {
abort(500, '本站已关闭注册');
}
if ((int)config('v2board.invite_force', 0)) {
if (empty($request->input('invite_code'))) {
abort(500, '必须使用邀请码才可以注册');
}
}
if ((int)config('v2board.email_verify', 0)) {
$redisKey = 'sendEmailVerify:' . $request->input('email');
if (empty($request->input('email_code'))) {
abort(500, '邮箱验证码不能为空');
}
if (Redis::get($redisKey) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
}
}
$email = $request->input('email');
$password = $request->input('password');
$exist = User::where('email', $email)->first();
if ($exist) {
abort(500, '邮箱已存在系统中');
}
$user = new User();
$user->email = $email;
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();
if ($request->input('invite_code')) {
$inviteCode = InviteCode::where('code', $request->input('invite_code'))
->where('status', 0)
->first();
if (!$inviteCode) {
if ((int)config('v2board.invite_force', 0)) {
abort(500, '邀请码无效');
}
} else {
$user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
if (!(int)config('v2board.invite_never_expire', env('V2BOARD_INVITE_NEVER_EXPIRE'))) {
$inviteCode->status = 1;
$inviteCode->save();
}
}
}
if (!$user->save()) {
abort(500, '注册失败');
}
if ((int)config('v2board.email_verify', 0)) {
Redis::del($redisKey);
}
return response()->json([
'data' => true
]);
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Plan;
class PlanController extends Controller
{
public function fetch (Request $request) {
if (empty($request->input('plan_id'))) {
abort(500, '参数错误');
}
$plan = Plan::find($request->input('plan_id'));
if (!$plan) {
abort(500, '该订阅不存在');
}
return response([
'data' => $plan
]);
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace App\Http\Controllers\Server;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as BaseController;
class Controller extends BaseController
{
public function __construct(Request $request) {
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');
}
if ($token !== config('v2board.server_token')) {
abort(500, 'token is error');
}
}
}

View File

@ -3,25 +3,37 @@
namespace App\Http\Controllers\Server;
use Illuminate\Http\Request;
use App\Http\Controllers\Server\Controller;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Plan;
use App\Models\Server;
use App\Models\ServerLog;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Cache;
class DeepbworkController extends Controller
{
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"routing":{"settings":{"rules":[{"inboundTag":["api"],"outboundTag":"api","type":"field"}]},"strategy":"rules"},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"settings":{"rules":[{"inboundTag":["api"],"outboundTag":"api","type":"field"}]},"strategy":"rules"},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
public function __construct(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');
}
if ($token !== config('v2board.server_token')) {
abort(500, 'token is error');
}
}
// 后端获取用户
public function user (Request $request) {
public function user(Request $request)
{
$nodeId = $request->input('node_id');
$server = Server::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Redis::set('server_last_check_at_' . $server->id, time());
Cache::put('server_last_check_at_' . $server->id, time());
$users = User::whereIn('group_id', json_decode($server->group_id))
->select([
'id',
@ -40,7 +52,7 @@ class DeepbworkController extends Controller
foreach ($users as $user) {
$user->v2ray_user = [
"uuid" => $user->v2ray_uuid,
"email" => sprintf("%s@v2panel.user", $user->v2ray_uuid),
"email" => sprintf("%s@v2board.user", $user->v2ray_uuid),
"alter_id" => $user->v2ray_alter_id,
"level" => $user->v2ray_level,
];
@ -56,7 +68,8 @@ class DeepbworkController extends Controller
}
// 后端提交数据
public function submit (Request $request) {
public function submit(Request $request)
{
Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = Server::find($request->input('node_id'));
if (!$server) {
@ -92,15 +105,16 @@ class DeepbworkController extends Controller
}
// 后端获取配置
public function config (Request $request) {
public function config(Request $request)
{
$nodeId = $request->input('node_id');
$localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) {
abort(1000, '参数错误');
abort(500, '参数错误');
}
$server = Server::find($nodeId);
if (!$server) {
abort(1001, '节点不存在');
abort(500, '节点不存在');
}
$json = json_decode(self::SERVER_CONFIG);
$json->inboundDetour[0]->port = (int)$localPort;
@ -108,23 +122,54 @@ class DeepbworkController extends Controller
$json->inbound->streamSettings->network = $server->network;
if ($server->settings) {
switch ($server->network) {
case 'tcp': $json->inbound->streamSettings->tcpSettings = json_decode($server->settings);
case 'tcp':
$json->inbound->streamSettings->tcpSettings = json_decode($server->settings);
break;
case 'kcp': $json->inbound->streamSettings->kcpSettings = json_decode($server->settings);
case 'kcp':
$json->inbound->streamSettings->kcpSettings = json_decode($server->settings);
break;
case 'ws': $json->inbound->streamSettings->wsSettings = json_decode($server->settings);
case 'ws':
$json->inbound->streamSettings->wsSettings = json_decode($server->settings);
break;
case 'http': $json->inbound->streamSettings->httpSettings = json_decode($server->settings);
case 'http':
$json->inbound->streamSettings->httpSettings = json_decode($server->settings);
break;
case 'domainsocket': $json->inbound->streamSettings->dsSettings = json_decode($server->settings);
case 'domainsocket':
$json->inbound->streamSettings->dsSettings = json_decode($server->settings);
break;
case 'quic': $json->inbound->streamSettings->quicSettings = json_decode($server->settings);
case 'quic':
$json->inbound->streamSettings->quicSettings = json_decode($server->settings);
break;
}
}
if ($server->rules) {
$rules = json_decode($server->rules);
// domain
if (isset($rules->domain)) {
$domainObj = new \StdClass();
$domainObj->type = 'field';
$domainObj->domain = $rules->domain;
$domainObj->outboundTag = 'block';
array_push($json->routing->settings->rules, $domainObj);
}
// protocol
if (isset($rules->protocol)) {
$protocolObj = new \StdClass();
$protocolObj->type = 'field';
$protocolObj->protocol = $rules->protocol;
$protocolObj->outboundTag = 'block';
array_push($json->routing->settings->rules, $protocolObj);
}
}
if ((int)$server->tls) {
$json->inbound->streamSettings->security = "tls";
$tls = (object) array("certificateFile" => "/home/v2ray.crt", "keyFile" => "/home/v2ray.key");
$json->inbound->streamSettings->security = 'tls';
$tls = (object)[
'certificateFile' => '/home/v2ray.crt',
'keyFile' => '/home/v2ray.key'
];
$json->inbound->streamSettings->tlsSettings = new \StdClass();
$json->inbound->streamSettings->tlsSettings->certificates[0] = $tls;
}

View File

@ -0,0 +1,197 @@
<?php
namespace App\Http\Controllers\Server;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Plan;
use App\Models\Server;
use App\Models\ServerLog;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
class PoseidonController extends Controller
{
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"streamSettings":{"network":"tcp"},"tag":"proxy"},"inboundDetour":[{"listen":"0.0.0.0","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"outbound":{"protocol":"freedom","settings":{}},"outboundDetour":[{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"settings":{"rules":[{"inboundTag":["api"],"outboundTag":"api","type":"field"}]},"strategy":"rules"},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
// 后端获取用户
public function user(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
$nodeId = $request->input('node_id');
$server = Server::find($nodeId);
if (!$server) {
return $this->error("server could not be found", 404);
}
Cache::put('server_last_check_at_' . $server->id, time());
$users = User::whereIn('group_id', json_decode($server->group_id))
->select([
'id',
'email',
't',
'u',
'd',
'transfer_enable',
'enable',
'v2ray_uuid',
'v2ray_alter_id',
'v2ray_level'
])
->whereRaw('u + d < transfer_enable')
->where('enable', 1)
->get();
$result = [];
foreach ($users as $user) {
$user->v2ray_user = [
"uuid" => $user->v2ray_uuid,
"email" => sprintf("%s@v2board.user", $user->v2ray_uuid),
"alter_id" => $user->v2ray_alter_id,
"level" => $user->v2ray_level,
];
unset($user['v2ray_uuid']);
unset($user['v2ray_alter_id']);
unset($user['v2ray_level']);
array_push($result, $user);
}
return $this->success($result);
}
// 后端提交数据
public function submit(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = Server::find($request->input('node_id'));
if (!$server) {
return $this->error("server could not be found", 404);
}
$data = file_get_contents('php://input');
$data = json_decode($data, true);
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
$user = User::find($item['user_id']);
$user->t = time();
$user->u = $user->u + $u;
$user->d = $user->d + $d;
$user->save();
$serverLog = new ServerLog();
$serverLog->user_id = $item['user_id'];
$serverLog->server_id = $request->input('node_id');
$serverLog->u = $item['u'];
$serverLog->d = $item['d'];
$serverLog->rate = $server->rate;
$serverLog->save();
}
return $this->success('');
}
// 后端获取配置
public function config(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
$nodeId = $request->input('node_id');
$localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) {
return $this->error('invalid parameters', 400);
}
$server = Server::find($nodeId);
if (!$server) {
return $this->error("server could not be found", 404);
}
$json = json_decode(self::SERVER_CONFIG);
$json->inboundDetour[0]->port = (int)$localPort;
$json->inbound->port = (int)$server->server_port;
$json->inbound->streamSettings->network = $server->network;
if ($server->settings) {
switch ($server->network) {
case 'tcp':
$json->inbound->streamSettings->tcpSettings = json_decode($server->settings);
break;
case 'kcp':
$json->inbound->streamSettings->kcpSettings = json_decode($server->settings);
break;
case 'ws':
$json->inbound->streamSettings->wsSettings = json_decode($server->settings);
break;
case 'http':
$json->inbound->streamSettings->httpSettings = json_decode($server->settings);
break;
case 'domainsocket':
$json->inbound->streamSettings->dsSettings = json_decode($server->settings);
break;
case 'quic':
$json->inbound->streamSettings->quicSettings = json_decode($server->settings);
break;
}
}
if ($server->rules) {
$rules = json_decode($server->rules);
// domain
if (isset($rules->domain)) {
$domainObj = new \StdClass();
$domainObj->type = 'field';
$domainObj->domain = $rules->domain;
$domainObj->outboundTag = 'block';
array_push($json->routing->settings->rules, $domainObj);
}
// protocol
if (isset($rules->protocol)) {
$protocolObj = new \StdClass();
$protocolObj->type = 'field';
$protocolObj->protocol = $rules->protocol;
$protocolObj->outboundTag = 'block';
array_push($json->routing->settings->rules, $protocolObj);
}
}
if ((int)$server->tls) {
$json->inbound->streamSettings->security = 'tls';
$tls = (object)[
'certificateFile' => '/home/v2ray.crt',
'keyFile' => '/home/v2ray.key'
];
$json->inbound->streamSettings->tlsSettings = new \StdClass();
$json->inbound->streamSettings->tlsSettings->certificates[0] = $tls;
}
$json->poseidon = [
'license_key' => (string)config('v2board.server_license'),
];
return $this->success($json);
}
protected function verifyToken(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
return $this->error("token must be set");
}
if ($token !== config('v2board.server_token')) {
return $this->error("invalid token");
}
}
protected function error($msg, int $status = 400) {
return response([
'msg' => $msg,
], $status);
}
protected function success($data) {
return response([
'msg' => 'ok',
'data' => $data,
]);
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
class TutorialController extends Controller
{
public function getSubscribeUrl (Request $request) {
$user = User::find($request->session()->get('id'));
return response([
'data' => [
'subscribe_url' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token']
]
]);
}
public function getAppleID (Request $request) {
$user = User::find($request->session()->get('id'));
if ($user->expired_at < time()) {
return response([
'data' => [
]
]);
}
return response([
'data' => [
'apple_id' => config('v2board.apple_id'),
'apple_id_password' => config('v2board.apple_id_password')
]
]);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Coupon;
class CouponController extends Controller
{
public function check(Request $request)
{
if (empty($request->input('code'))) {
abort(500, '优惠券码不能为空');
}
$coupon = Coupon::where('code', $request->input('code'))->first();
if (!$coupon) {
abort(500, '优惠券无效');
}
if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) {
abort(500, '优惠券已无可用次数');
}
if (time() < $coupon->started_at) {
abort(500, '优惠券还未到可用时间');
}
if (time() > $coupon->ended_at) {
abort(500, '优惠券已过期');
}
return response([
'data' => $coupon
]);
}
}

View File

@ -1,9 +1,9 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Order;
use App\Models\InviteCode;
@ -11,7 +11,8 @@ use App\Utils\Helper;
class InviteController extends Controller
{
public function save (Request $request) {
public function save(Request $request)
{
if (InviteCode::where('user_id', $request->session()->get('id'))->where('status', 0)->count() >= config('v2board.invite_gen_limit', 5)) {
abort(500, '已达到创建数量上限');
}
@ -23,7 +24,8 @@ class InviteController extends Controller
]);
}
public function details (Request $request) {
public function details(Request $request)
{
return response([
'data' => Order::where('invite_user_id', $request->session()->get('id'))
->where('status', 3)
@ -38,7 +40,8 @@ class InviteController extends Controller
]);
}
public function fetch (Request $request) {
public function fetch(Request $request)
{
$codes = InviteCode::where('user_id', $request->session()->get('id'))
->where('status', 0)
->get();
@ -52,7 +55,7 @@ class InviteController extends Controller
(int)User::where('invite_user_id', $request->session()->get('id'))->count(),
//有效的佣金
(int)Order::where('status', 3)
->where('commission_status', 1)
->where('commission_status', 2)
->where('invite_user_id', $request->session()->get('id'))
->sum('commission_balance'),
//确认中的佣金
@ -61,7 +64,9 @@ class InviteController extends Controller
->where('invite_user_id', $request->session()->get('id'))
->sum('commission_balance'),
//佣金比例
(int)$commission_rate
(int)$commission_rate,
//可用佣金
(int)$user->commission_balance
];
return response([
'data' => [

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Notice;
use App\Utils\Helper;
class NoticeController extends Controller
{
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = 5;
$model = Notice::orderBy('created_at', 'DESC');
$total = $model->count();
$res = $model->forPage($current, $pageSize)
->get();
return response([
'data' => $res,
'total' => $total
]);
}
}

View File

@ -0,0 +1,463 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\OrderSave;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use App\Models\Order;
use App\Models\Plan;
use App\Models\User;
use App\Models\Coupon;
use App\Utils\Helper;
use Omnipay\Omnipay;
use Stripe\Stripe;
use Stripe\Source;
use Library\BitpayX;
use Library\PayTaro;
class OrderController extends Controller
{
public function fetch(Request $request)
{
$model = Order::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC');
if ($request->input('status') !== null) {
$model->where('status', $request->input('status'));
}
$order = $model->get();
$plan = Plan::get();
for ($i = 0; $i < count($order); $i++) {
for ($x = 0; $x < count($plan); $x++) {
if ($order[$i]['plan_id'] === $plan[$x]['id']) {
$order[$i]['plan'] = $plan[$x];
}
}
}
return response([
'data' => $order
]);
}
public function details(Request $request)
{
$order = Order::where('user_id', $request->session()->get('id'))
->where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
$order['plan'] = Plan::find($order->plan_id);
$order['try_out_plan_id'] = (int)config('v2board.try_out_plan_id');
if (!$order['plan']) {
abort(500, '订阅不存在');
}
return response([
'data' => $order
]);
}
private function isNotCompleteOrderByUserId($userId)
{
$order = Order::whereIn('status', [0, 1])
->where('user_id', $userId)
->first();
if (!$order) {
return false;
}
return true;
}
// surplus value
private function getSurplusValue(User $user)
{
$plan = Plan::find($user->plan_id);
$dayPrice = 0;
if ($plan->month_price) {
$dayPrice = $plan->month_price / 30;
} else if ($plan->quarter_price) {
$dayPrice = $plan->quarter_price / 91;
} else if ($plan->half_year_price) {
$dayPrice = $plan->half_year_price / 183;
} else if ($plan->year_price) {
$dayPrice = $plan->year_price / 365;
}
$remainingDay = ($user->expired_at - time()) / 86400;
return $remainingDay * $dayPrice;
}
public function save(OrderSave $request)
{
if ($this->isNotCompleteOrderByUserId($request->session()->get('id'))) {
abort(500, '存在未付款订单,请取消后再试');
}
$plan = Plan::find($request->input('plan_id'));
$user = User::find($request->session()->get('id'));
if (!$plan) {
abort(500, '该订阅不存在');
}
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
abort(500, '该订阅已售罄');
}
if (!$plan->renew && $user->plan_id == $plan->id) {
abort(500, '该订阅无法续费,请更换其他订阅');
}
if ($plan[$request->input('cycle')] === NULL) {
abort(500, '该订阅周期无法进行购买,请选择其他周期');
}
if ($request->input('coupon_code')) {
$coupon = Coupon::where('code', $request->input('coupon_code'))->first();
if (!$coupon) {
abort(500, '优惠券无效');
}
if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) {
abort(500, '优惠券已无可用次数');
}
if (time() < $coupon->started_at) {
abort(500, '优惠券还未到可用时间');
}
if (time() > $coupon->ended_at) {
abort(500, '优惠券已过期');
}
}
DB::beginTransaction();
$order = new Order();
$order->user_id = $request->session()->get('id');
$order->plan_id = $plan->id;
$order->cycle = $request->input('cycle');
$order->trade_no = Helper::guid();
$order->total_amount = $plan[$request->input('cycle')];
// renew and change subscribe process
if ($user->expired_at > time() && $order->plan_id !== $user->plan_id) {
if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系管理员');
$order->type = 3;
$order->surplus_amount = $this->getSurplusValue($user);
if ($order->surplus_amount >= $order->total_amount) {
$order->refund_amount = $order->surplus_amount - $order->total_amount;
$order->total_amount = 0;
} else {
$order->total_amount = $order->total_amount - $order->surplus_amount;
}
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
$order->type = 2;
} else {
$order->type = 1;
}
// discount start
// coupon
if (isset($coupon)) {
switch ($coupon->type) {
case 1:
$order->discount_amount = $coupon->value;
break;
case 2:
$order->discount_amount = $order->total_amount * ($coupon->value / 100);
break;
}
if ($coupon->limit_use !== NULL) {
$coupon->limit_use = $coupon->limit_use - 1;
if (!$coupon->save()) {
DB::rollback();
abort(500, '优惠券使用失败');
}
}
}
// user
if ($user->discount) {
$order->discount_amount = $order->discount_amount + ($order->total_amount * ($user->discount / 100));
}
// discount complete
$order->total_amount = $order->total_amount - $order->discount_amount;
// discount end
// invite process
if ($user->invite_user_id && $order->total_amount > 0) {
$order->invite_user_id = $user->invite_user_id;
$inviter = User::find($user->invite_user_id);
if ($inviter && $inviter->commission_rate) {
$order->commission_balance = $order->total_amount * ($inviter->commission_rate / 100);
} else {
$order->commission_balance = $order->total_amount * (config('v2board.invite_commission', 10) / 100);
}
}
if (!$order->save()) {
DB::rollback();
abort(500, '订单创建失败');
}
DB::commit();
return response([
'data' => $order->trade_no
]);
}
public function checkout(Request $request)
{
$tradeNo = $request->input('trade_no');
$method = $request->input('method');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
->where('status', 0)
->first();
if (!$order) {
abort(500, '订单不存在或已支付');
}
// free process
if ($order->total_amount <= 0) {
$order->total_amount = 0;
$order->status = 1;
$order->save();
exit();
}
switch ($method) {
// return type => 0: QRCode / 1: URL
case 0:
// alipayF2F
if (!(int)config('v2board.alipay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 0,
'data' => $this->alipayF2F($tradeNo, $order->total_amount)
]);
case 2:
// stripeAlipay
if (!(int)config('v2board.stripe_alipay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->stripeAlipay($order)
]);
case 3:
// stripeWepay
if (!(int)config('v2board.stripe_wepay_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 0,
'data' => $this->stripeWepay($order)
]);
case 4:
// bitpayX
if (!(int)config('v2board.bitpayx_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->bitpayX($order)
]);
case 5:
if (!(int)config('v2board.paytaro_enable')) {
abort(500, '支付方式不可用');
}
return response([
'type' => 1,
'data' => $this->payTaro($order)
]);
default:
abort(500, '支付方式不存在');
}
}
public function check(Request $request)
{
$tradeNo = $request->input('trade_no');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
return response([
'data' => $order->status
]);
}
public function getPaymentMethod()
{
$data = [];
if ((int)config('v2board.alipay_enable')) {
$alipayF2F = new \StdClass();
$alipayF2F->name = '支付宝';
$alipayF2F->method = 0;
$alipayF2F->icon = 'alipay';
array_push($data, $alipayF2F);
}
if ((int)config('v2board.stripe_alipay_enable')) {
$stripeAlipay = new \StdClass();
$stripeAlipay->name = '支付宝';
$stripeAlipay->method = 2;
$stripeAlipay->icon = 'alipay';
array_push($data, $stripeAlipay);
}
if ((int)config('v2board.stripe_wepay_enable')) {
$stripeWepay = new \StdClass();
$stripeWepay->name = '微信';
$stripeWepay->method = 3;
$stripeWepay->icon = 'wechat';
array_push($data, $stripeWepay);
}
if ((int)config('v2board.bitpayx_enable')) {
$bitpayX = new \StdClass();
$bitpayX->name = '聚合支付';
$bitpayX->method = 4;
$bitpayX->icon = 'wallet';
array_push($data, $bitpayX);
}
if ((int)config('v2board.paytaro_enable')) {
$obj = new \StdClass();
$obj->name = '聚合支付';
$obj->method = 5;
$obj->icon = 'wallet';
array_push($data, $obj);
}
return response([
'data' => $data
]);
}
public function cancel(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, '参数有误');
}
$order = Order::where('trade_no', $request->input('trade_no'))
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
if ($order->status !== 0) {
abort(500, '只可以取消待支付订单');
}
$order->status = 2;
if (!$order->save()) {
abort(500, '取消失败');
}
return response([
'data' => true
]);
}
private function alipayF2F($tradeNo, $totalAmount)
{
$gateway = Omnipay::create('Alipay_AopF2F');
$gateway->setSignType('RSA2'); //RSA/RSA2
$gateway->setAppId(config('v2board.alipay_appid'));
$gateway->setPrivateKey(config('v2board.alipay_privkey')); // 可以是路径,也可以是密钥内容
$gateway->setAlipayPublicKey(config('v2board.alipay_pubkey')); // 可以是路径,也可以是密钥内容
$gateway->setNotifyUrl(url('/api/v1/guest/order/alipayNotify'));
$request = $gateway->purchase();
$request->setBizContent([
'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅',
'out_trade_no' => $tradeNo,
'total_amount' => $totalAmount / 100
]);
/** @var \Omnipay\Alipay\Responses\AopTradePreCreateResponse $response */
$response = $request->send();
$result = $response->getAlipayResponse();
if ($result['code'] !== '10000') {
abort(500, $result['sub_msg']);
}
// 获取收款二维码内容
return $response->getQrCode();
}
private function stripeAlipay($order)
{
$exchange = Helper::exchange('CNY', 'HKD');
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd',
'type' => 'alipay',
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
]);
if (!$source['redirect']['url']) {
abort(500, '支付网关请求失败');
}
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败');
}
return $source['redirect']['url'];
}
private function stripeWepay($order)
{
$exchange = Helper::exchange('CNY', 'HKD');
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => 'hkd',
'type' => 'wechat',
'redirect' => [
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]
]);
if (!$source['wechat']['qr_code_url']) {
abort(500, '支付网关请求失败');
}
if (!Cache::put($source['id'], $order->trade_no, 3600)) {
abort(500, '订单创建失败');
}
return $source['wechat']['qr_code_url'];
}
private function bitpayX($order)
{
$bitpayX = new BitpayX(config('v2board.bitpayx_appsecret'));
$params = [
'merchant_order_id' => $order->trade_no,
'price_amount' => $order->total_amount / 100,
'price_currency' => 'CNY',
'title' => '支付单号:' . $order->trade_no,
'description' => '充值:' . $order->total_amount / 100 . ' 元',
'callback_url' => url('/api/v1/guest/order/bitpayXNotify'),
'success_url' => config('v2board.app_url', env('APP_URL')) . '/#/order',
'cancel_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
];
$strToSign = $bitpayX->prepareSignId($params['merchant_order_id']);
$params['token'] = $bitpayX->sign($strToSign);
$result = $bitpayX->mprequest($params);
Log::info('bitpayXSubmit: ' . json_encode($result));
return isset($result['payment_url']) ? $result['payment_url'] : false;
}
private function payTaro($order)
{
$payTaro = new PayTaro(config('v2board.paytaro_app_id'), config('v2board.paytaro_app_secret'));
$result = $payTaro->pay([
'app_id' => config('v2board.paytaro_app_id'),
'out_trade_no' => $order->trade_no,
'total_amount' => $order->total_amount,
'notify_url' => url('/api/v1/guest/order/payTaroNotify'),
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
]);
return $result;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Plan;
class PlanController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$plan = Plan::where('id', $request->input('id'))
->first();
if (!$plan) {
abort(500, '该订阅不存在');
}
return response([
'data' => $plan
]);
}
$plan = Plan::where('show', 1)
->get();
return response([
'data' => $plan
]);
}
}

View File

@ -1,18 +1,20 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Server;
use App\Models\ServerLog;
use App\Models\User;
use App\Utils\Helper;
class ServerController extends Controller {
public function fetch (Request $request) {
class ServerController extends Controller
{
public function fetch(Request $request)
{
$user = User::find($request->session()->get('id'));
$server = [];
if ($user->expired_at > time()) {
@ -28,25 +30,33 @@ class ServerController extends Controller {
}
for ($i = 0; $i < count($server); $i++) {
$server[$i]['link'] = Helper::buildVmessLink($server[$i], $user);
$server[$i]['last_check_at'] = Redis::get('server_last_check_at_' . $server[$i]['id']);
if ($server[$i]['parent_id']) {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['parent_id']);
} else {
$server[$i]['last_check_at'] = Cache::get('server_last_check_at_' . $server[$i]['id']);
}
}
return response([
'data' => $server
]);
}
public function logFetch (Request $request) {
public function logFetch(Request $request)
{
$type = $request->input('type') ? $request->input('type') : 0;
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$serverLogModel = ServerLog::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC');
switch ($type) {
case 0: $serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')));
case 0:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')));
break;
case 1: $serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')) - 604800);
case 1:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')) - 604800);
break;
case 2: $serverLogModel->where('created_at', '>=', strtotime(date('Y-m-1')));
case 2:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-1')));
}
$sum = [
'u' => $serverLogModel->sum('u'),

View File

@ -1,10 +1,10 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\User;
use App\Http\Requests\TicketSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\TicketSave;
use Illuminate\Http\Request;
use App\Models\Ticket;
use App\Models\TicketMessage;
use App\Utils\Helper;
@ -12,7 +12,8 @@ use Illuminate\Support\Facades\DB;
class TicketController extends Controller
{
public function fetch (Request $request) {
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->session()->get('id'))
@ -47,7 +48,8 @@ class TicketController extends Controller
]);
}
public function save (TicketSave $request) {
public function save(TicketSave $request)
{
DB::beginTransaction();
$ticket = Ticket::create(array_merge($request->only([
'subject',
@ -75,7 +77,8 @@ class TicketController extends Controller
]);
}
public function reply (Request $request) {
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
@ -112,7 +115,8 @@ class TicketController extends Controller
}
public function close (Request $request) {
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
@ -131,7 +135,8 @@ class TicketController extends Controller
]);
}
private function getLastMessage ($ticketId) {
private function getLastMessage($ticketId)
{
return TicketMessage::where('ticket_id', $ticketId)
->orderBy('id', 'DESC')
->first();

View File

@ -0,0 +1,76 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Tutorial;
class TutorialController extends Controller
{
public function getSubscribeUrl(Request $request)
{
$user = User::find($request->session()->get('id'));
return response([
'data' => [
'subscribe_url' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token']
]
]);
}
public function getAppleID(Request $request)
{
$user = User::find($request->session()->get('id'));
if ($user->expired_at < time()) {
return response([
'data' => [
]
]);
}
return response([
'data' => [
'apple_id' => config('v2board.apple_id'),
'apple_id_password' => config('v2board.apple_id_password')
]
]);
}
public function fetch(Request $request)
{
if ($request->input('id')) {
$tutorial = Tutorial::where('show', 1)
->where('id', $request->input('id'))
->first();
if (!$tutorial) {
abort(500, '教程不存在');
}
return response([
'data' => $tutorial
]);
}
$tutorial = Tutorial::select(['id', 'title', 'description', 'icon'])
->where('show', 1)
->get();
$user = User::find($request->session()->get('id'));
$response = [
'data' => [
'tutorials' => $tutorial,
'safe_area_var' => [
'subscribe_url' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'],
'app_name' => config('v2board.app_name', 'V2board'),
'apple_id' => $user->expired_at > time() ? config('v2board.apple_id', '管理员暂无提供AppleID信息') : '账号过期或未订阅',
'apple_id_password' => $user->expired_at > time() ? config('v2board.apple_id_password', '管理员暂无提供AppleID信息') : '账号过期或未订阅'
]
]
];
// fuck support shadowrocket urlsafeb64 subscribe
$response['data']['safe_area_var']['b64_subscribe_url'] = str_replace(
array('+', '/', '='),
array('-', '_', ''),
base64_encode($response['data']['safe_area_var']['subscribe_url'])
);
// end
return response($response);
}
}

View File

@ -1,10 +1,10 @@
<?php
namespace App\Http\Controllers;
namespace App\Http\Controllers\User;
use App\Http\Requests\UserUpdate;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserUpdate;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Plan;
use App\Models\Server;
@ -15,13 +15,16 @@ use App\Models\ServerLog;
class UserController extends Controller
{
public function logout (Request $request) {
public function logout(Request $request)
{
$request->session()->flush();
return response([
'data' => $request->session()->flush()
'data' => true
]);
}
public function changePassword (Request $request) {
public function changePassword(Request $request)
{
if (empty($request->input('old_password'))) {
abort(500, '旧密码不能为空');
}
@ -29,10 +32,15 @@ class UserController extends Controller
abort(500, '新密码不能为空');
}
$user = User::find($request->session()->get('id'));
if (!password_verify($request->input('old_password'), $user->password)) {
if (!Helper::multiPasswordVerify(
$user->password_algo,
$request->input('old_password'),
$user->password)
) {
abort(500, '旧密码有误');
}
$user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
if (!$user->save()) {
abort(500, '保存失败');
}
@ -42,10 +50,12 @@ class UserController extends Controller
]);
}
public function info (Request $request) {
public function info(Request $request)
{
$user = User::where('id', $request->session()->get('id'))
->select([
'email',
'transfer_enable',
'last_login_at',
'created_at',
'enable',
@ -54,7 +64,10 @@ class UserController extends Controller
'remind_traffic',
'expired_at',
'balance',
'commission_balance'
'commission_balance',
'plan_id',
'discount',
'commission_rate'
])
->first();
$user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon';
@ -63,7 +76,8 @@ class UserController extends Controller
]);
}
public function getStat (Request $request) {
public function getStat(Request $request)
{
$stat = [
Order::where('status', 0)
->where('user_id', $request->session()->get('id'))
@ -79,7 +93,8 @@ class UserController extends Controller
]);
}
public function getSubscribe (Request $request) {
public function getSubscribe(Request $request)
{
$user = User::find($request->session()->get('id'));
if ($user->plan_id) {
$user['plan'] = Plan::find($user->plan_id);
@ -93,7 +108,8 @@ class UserController extends Controller
]);
}
public function resetSecurity (Request $request) {
public function resetSecurity(Request $request)
{
$user = User::find($request->session()->get('id'));
$user->v2ray_uuid = Helper::guid(true);
$user->token = Helper::guid();
@ -101,11 +117,12 @@ class UserController extends Controller
abort(500, '重置失败');
}
return response([
'data' => true
'data' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user->token
]);
}
public function update (UserUpdate $request) {
public function update(UserUpdate $request)
{
$updateData = $request->only([
'remind_expire',
'remind_traffic'

View File

@ -14,7 +14,7 @@ class Authenticate extends Middleware
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
if (!$request->expectsJson()) {
return route('login');
}
}

View File

@ -9,9 +9,9 @@ class CORS
public function handle($request, Closure $next)
{
$origin = $request->header('origin');
if(empty($origin)){
if (empty($origin)) {
$referer = $request->header('referer');
if(!empty($referer)&&preg_match("/^((https|http):\/\/)?([^\/]+)/i", $referer, $matches)){
if (!empty($referer) && preg_match("/^((https|http):\/\/)?([^\/]+)/i", $referer, $matches)) {
$origin = $matches[0];
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use App\Models\User;
class Server
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');
}
if ($token !== config('v2board.server_token')) {
abort(500, 'token is error');
}
return $next($request);
}
}

View File

@ -15,6 +15,13 @@ class User
*/
public function handle($request, Closure $next)
{
if ($request->input('access_token')) {
$user = \App\Models\User::where('token', $request->input('access_token'))->first();
if ($user) {
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
}
}
if (!$request->session()->get('id')) {
abort(403, '未登录或登陆已过期');
}

View File

@ -6,37 +6,60 @@ use Illuminate\Foundation\Http\FormRequest;
class ConfigSave extends FormRequest
{
public static function filter() {
return [
'invite_force',
'invite_commission',
'invite_gen_limit',
'invite_never_expire',
'stop_register',
'email_verify',
'app_name',
'app_url',
'subscribe_url',
'plan_update_fee',
'plan_is_update',
CONST RULES = [
'safe_mode_enable' => 'in:0,1',
'invite_force' => 'in:0,1',
'invite_commission' => 'integer',
'invite_gen_limit' => 'integer',
'invite_never_expire' => 'in:0,1',
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_name' => '',
'app_description' => '',
'app_url' => 'url',
'subscribe_url' => 'url',
'plan_change_enable' => 'in:0,1',
'try_out_enable' => 'in:0,1',
'try_out_plan_id' => 'integer',
'try_out_hour' => 'numeric',
'email_whitelist_enable' => 'in:0,1',
'email_whitelist_suffix' => '',
// server
'server_token',
'server_token' => 'nullable|min:16',
'server_license' => 'nullable',
// alipay
'alipay_enable',
'alipay_appid',
'alipay_pubkey',
'alipay_privkey',
'alipay_enable' => 'in:0,1',
'alipay_appid' => 'nullable|integer|min:16',
'alipay_pubkey' => 'max:2048',
'alipay_privkey' => 'max:2048',
// stripe
'stripe_sk_live',
'stripe_pk_live',
'stripe_alipay_enable',
'stripe_wepay_enable',
'stripe_webhook_key',
'stripe_alipay_enable' => 'in:0,1',
'stripe_wepay_enable' => 'in:0,1',
'stripe_sk_live' => '',
'stripe_pk_live' => '',
'stripe_webhook_key' => '',
// bitpayx
'bitpayx_enable' => 'in:0,1',
'bitpayx_appsecret' => '',
// paytaro
'paytaro_enable' => 'in:0,1',
'paytaro_app_id' => '',
'paytaro_app_secret' => '',
// frontend
'frontend_theme_sidebar' => 'in:dark,light',
'frontend_theme_header' => 'in:dark,light',
'frontend_theme_color' => 'in:default,darkblue,black',
'frontend_background_url' => 'nullable|url',
// tutorial
'apple_id',
'apple_id_password'
'apple_id' => 'email',
'apple_id_password' => ''
];
public static function filter()
{
return array_keys(self::RULES);
}
/**
* Get the validation rules that apply to the request.
*
@ -44,30 +67,7 @@ class ConfigSave extends FormRequest
*/
public function rules()
{
return [
'invite_force' => 'in:0,1',
'invite_commission' => 'integer',
'invite_gen_limit' => 'integer',
'invite_never_expire' => 'in:0,1',
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_url' => 'url',
'subscribe_url' => 'url',
'plan_update_fee' => 'numeric',
'plan_is_update' => 'in:0,1',
// server
'server_token' => 'min:16',
// alipay
'alipay_enable' => 'in:0,1',
'alipay_appid' => 'integer|min:16',
'alipay_pubkey' => 'max:2048',
'alipay_privkey' => 'max:2048',
// stripe
'stripe_alipay_enable' => 'in:0,1',
'stripe_wepay_enable' => 'in:0,1',
// tutorial
'apple_id' => 'email'
];
return self::RULES;
}
public function messages()

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class CouponSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'type' => 'required|in:1,2',
'value' => 'required|integer',
'started_at' => 'required|integer',
'ended_at' => 'required|integer',
'limit_use' => 'nullable|integer'
];
}
public function messages()
{
return [
'name.required' => '名称不能为空',
'type.required' => '类型不能为空',
'type.in' => '类型格式有误',
'value.required' => '金额或比例不能为空',
'value.integer' => '金额或比例格式有误',
'started_at.required' => '开始时间不能为空',
'started_at.integer' => '开始时间格式有误',
'ended_at.required' => '结束时间不能为空',
'ended_at.integer' => '结束时间格式有误',
'limit_use.integer' => '使用次数格式有误'
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class MailSend extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'type' => 'required|in:1,2,3,4',
'subject' => 'required',
'content' => 'required',
'receiver' => 'array'
];
}
public function messages()
{
return [
'type.required' => '发送类型不能为空',
'type.in' => '发送类型格式有误',
'subject.required' => '主题不能为空',
'content.required' => '内容不能为空',
'receiver.array' => '收件人格式有误'
];
}
}

View File

@ -17,10 +17,10 @@ class PlanSave extends FormRequest
'name' => 'required',
'group_id' => 'required',
'transfer_enable' => 'required',
'month_price' => 'required|numeric',
'quarter_price' => 'required|numeric',
'half_year_price' => 'required|numeric',
'year_price' => 'required|numeric'
'month_price' => 'nullable|integer',
'quarter_price' => 'nullable|integer',
'half_year_price' => 'nullable|integer',
'year_price' => 'nullable|integer'
];
}
@ -30,10 +30,10 @@ class PlanSave extends FormRequest
'name.required' => '套餐名称不能为空',
'group_id.required' => '权限组不能为空',
'transfer_enable.required' => '流量不能为空',
'month_price.required' => '月付金额不能为空',
'quarter_price.required' => '季付金额不能为空',
'half_year_price.required' => '半年付金额不能为空',
'year_price.required' => '年付金额不能为空'
'month_price.integer' => '月付金额格式有误',
'quarter_price.integer' => '季付金额格式有误',
'half_year_price.integer' => '半年付金额格式有误',
'year_price.integer' => '年付金额格式有误'
];
}
}

View File

@ -6,6 +6,21 @@ use Illuminate\Foundation\Http\FormRequest;
class ServerSave extends FormRequest
{
CONST RULES = [
'rules' => '',
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'tls' => 'required',
'tags' => 'array',
'rate' => 'required|numeric',
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic',
'settings' => ''
];
/**
* Get the validation rules that apply to the request.
*
@ -13,17 +28,7 @@ class ServerSave extends FormRequest
*/
public function rules()
{
return [
'name' => 'required',
'group_id' => 'required|array',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'tls' => 'required',
'tags' => 'array',
'rate' => 'required|numeric',
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic'
];
return self::RULES;
}
public function messages()
@ -32,6 +37,7 @@ class ServerSave extends FormRequest
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'parent_id.integer' => '父ID格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',
'server_port.required' => '后端服务端口不能为空',

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class TutorialSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'title' => 'required',
'description' => 'required',
'icon' => 'required'
];
}
public function messages()
{
return [
'title.required' => '标题不能为空',
'description.required' => '描述不能为空',
'icon.required' => '图标不能为空'
];
}
}

View File

@ -20,7 +20,8 @@ class UserUpdate extends FormRequest
'banned' => 'required|in:0,1',
'is_admin' => 'required|in:0,1',
'plan_id' => 'integer',
'commission_rate' => 'nullable|integer|min:0|max:100'
'commission_rate' => 'nullable|integer|min:0|max:100',
'discount' => 'nullable|integer|min:0|max:100'
];
}
@ -39,7 +40,11 @@ class UserUpdate extends FormRequest
'commission_rate.integer' => '推荐返利比例格式不正确',
'commission_rate.nullable' => '推荐返利比例格式不正确',
'commission_rate.min' => '推荐返利比例最小为0',
'commission_rate.max' => '推荐返利比例最大为100'
'commission_rate.max' => '推荐返利比例最大为100',
'discount.integer' => '专属折扣比例格式不正确',
'discount.nullable' => '专属折扣比例格式不正确',
'discount.min' => '专属折扣比例最小为0',
'discount.max' => '专属折扣比例最大为100'
];
}
}

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Passport;
use Illuminate\Foundation\Http\FormRequest;
class ForgetIndex extends FormRequest
class AuthForget extends FormRequest
{
/**
* Get the validation rules that apply to the request.

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Passport;
use Illuminate\Foundation\Http\FormRequest;
class LoginIndex extends FormRequest
class AuthLogin extends FormRequest
{
/**
* Get the validation rules that apply to the request.

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Passport;
use Illuminate\Foundation\Http\FormRequest;
class RegisterIndex extends FormRequest
class AuthRegister extends FormRequest
{
/**
* Get the validation rules that apply to the request.

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Requests;
namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Requests;
namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest;

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Requests;
namespace App\Http\Requests\User;
use Illuminate\Foundation\Http\FormRequest;

View File

@ -0,0 +1,62 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class AdminRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'admin',
'middleware' => 'admin'
], function ($router) {
// Config
$router->get ('/config/fetch', 'Admin\\ConfigController@fetch');
$router->post('/config/save', 'Admin\\ConfigController@save');
// Plan
$router->get ('/plan/fetch', 'Admin\\PlanController@fetch');
$router->post('/plan/save', 'Admin\\PlanController@save');
$router->post('/plan/drop', 'Admin\\PlanController@drop');
$router->post('/plan/update', 'Admin\\PlanController@update');
// Server
$router->get ('/server/fetch', 'Admin\\ServerController@fetch');
$router->post('/server/save', 'Admin\\ServerController@save');
$router->get ('/server/group/fetch', 'Admin\\ServerController@groupFetch');
$router->post('/server/group/save', 'Admin\\ServerController@groupSave');
$router->post('/server/group/drop', 'Admin\\ServerController@groupDrop');
$router->post('/server/drop', 'Admin\\ServerController@drop');
$router->post('/server/update', 'Admin\\ServerController@update');
// Order
$router->get ('/order/fetch', 'Admin\\OrderController@fetch');
$router->post('/order/repair', 'Admin\\OrderController@repair');
$router->post('/order/update', 'Admin\\OrderController@update');
// User
$router->get ('/user/fetch', 'Admin\\UserController@fetch');
$router->post('/user/update', 'Admin\\UserController@update');
$router->get ('/user/getUserInfoById', 'Admin\\UserController@getUserInfoById');
// Stat
$router->get ('/stat/getOverride', 'Admin\\StatController@getOverride');
// Notice
$router->get ('/notice/fetch', 'Admin\\NoticeController@fetch');
$router->post('/notice/save', 'Admin\\NoticeController@save');
$router->post('/notice/update', 'Admin\\NoticeController@update');
$router->post('/notice/drop', 'Admin\\NoticeController@drop');
// Ticket
$router->get ('/ticket/fetch', 'Admin\\TicketController@fetch');
$router->post('/ticket/reply', 'Admin\\TicketController@reply');
$router->post('/ticket/close', 'Admin\\TicketController@close');
// Mail
$router->post('/mail/send', 'Admin\\MailController@send');
// Coupon
$router->get ('/coupon/fetch', 'Admin\\CouponController@fetch');
$router->post('/coupon/save', 'Admin\\CouponController@save');
$router->post('/coupon/drop', 'Admin\\CouponController@drop');
// Tutorial
$router->get ('/tutorial/fetch', 'Admin\\TutorialController@fetch');
$router->post('/tutorial/save', 'Admin\\TutorialController@save');
$router->post('/tutorial/show', 'Admin\\TutorialController@show');
$router->post('/tutorial/drop', 'Admin\\TutorialController@drop');
});
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class ClientRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'client',
'middleware' => 'client'
], function ($router) {
// Client
$router->get('/subscribe', 'Client\\ClientController@subscribe');
// App
$router->get('/app/data', 'Client\\AppController@data');
$router->get('/app/config', 'Client\\AppController@config');
});
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class GuestRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'guest'
], function ($router) {
// Plan
$router->get ('/plan/fetch', 'Guest\\PlanController@fetch');
// Order
$router->post('/order/alipayNotify', 'Guest\\OrderController@alipayNotify');
$router->post('/order/stripeNotify', 'Guest\\OrderController@stripeNotify');
$router->post('/order/bitpayXNotify', 'Guest\\OrderController@bitpayXNotify');
$router->post('/order/payTaroNotify', 'Guest\\OrderController@payTaroNotify');
});
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class PassportRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'passport'
], function ($router) {
// TODO: 1.1.1 abolish
$router->post('/login', 'Passport\\AuthController@login');
// Auth
$router->post('/auth/register', 'Passport\\AuthController@register');
$router->post('/auth/login', 'Passport\\AuthController@login');
$router->get ('/auth/token2Login', 'Passport\\AuthController@token2Login');
$router->get ('/auth/check', 'Passport\\AuthController@check');
$router->post('/auth/forget', 'Passport\\AuthController@forget');
// Comm
$router->get ('/comm/config', 'Passport\\CommController@config');
$router->post('/comm/sendEmailVerify', 'Passport\\CommController@sendEmailVerify');
$router->post('/comm/pv', 'Passport\\CommController@pv');
});
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class ServerRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'server'
], function ($router) {
$router->any('/{class}/{action}', function($class, $action) {
$ctrl = \App::make("\\App\\Http\\Controllers\\Server\\" . ucfirst($class) . "Controller");
return \App::call([$ctrl, $action]);
});
});
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class UserRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'user',
'middleware' => 'user'
], function ($router) {
// User
$router->get ('/resetSecurity', 'User\\UserController@resetSecurity');
$router->get ('/logout', 'User\\UserController@logout');
$router->get ('/info', 'User\\UserController@info');
$router->post('/changePassword', 'User\\UserController@changePassword');
$router->post('/update', 'User\\UserController@update');
$router->get ('/getSubscribe', 'User\\UserController@getSubscribe');
$router->get ('/getStat', 'User\\UserController@getStat');
// Order
$router->post('/order/save', 'User\\OrderController@save');
$router->post('/order/checkout', 'User\\OrderController@checkout');
$router->get ('/order/check', 'User\\OrderController@check');
$router->get ('/order/details', 'User\\OrderController@details');
$router->get ('/order/fetch', 'User\\OrderController@fetch');
$router->get ('/order/getPaymentMethod', 'User\\OrderController@getPaymentMethod');
$router->post('/order/cancel', 'User\\OrderController@cancel');
// Plan
$router->get ('/plan/fetch', 'User\\PlanController@fetch');
// Invite
$router->get ('/invite/save', 'User\\InviteController@save');
$router->get ('/invite/fetch', 'User\\InviteController@fetch');
$router->get ('/invite/details', 'User\\InviteController@details');
// Tutorial
$router->get ('/tutorial/getSubscribeUrl', 'User\\TutorialController@getSubscribeUrl');
$router->get ('/tutorial/getAppleID', 'User\\TutorialController@getAppleID');
$router->get ('/tutorial/fetch', 'User\\TutorialController@fetch');
// Notice
$router->get ('/notice/fetch', 'User\\NoticeController@fetch');
// Ticket
$router->post('/ticket/reply', 'User\\TicketController@reply');
$router->post('/ticket/close', 'User\\TicketController@close');
$router->post('/ticket/save', 'User\\TicketController@save');
$router->get ('/ticket/fetch', 'User\\TicketController@fetch');
// Server
$router->get ('/server/fetch', 'User\\ServerController@fetch');
$router->get ('/server/log/fetch', 'User\\ServerController@logFetch');
// Coupon
$router->post('/coupon/check', 'User\\CouponController@check');
});
}
}

57
app/Jobs/SendEmail.php Normal file
View File

@ -0,0 +1,57 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
use App\Models\MailLog;
class SendEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $params;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($params)
{
$this->params = $params;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$params = $this->params;
$email = $params['email'];
$subject = $params['subject'];
try {
Mail::send(
$params['template_name'],
$params['template_value'],
function ($message) use ($email, $subject) {
$message->to($email)->subject($subject);
}
);
} catch (\Exception $e) {
$error = $e->getMessage();
}
MailLog::create([
'email' => $params['email'],
'subject' => $params['subject'],
'template_name' => $params['template_name'],
'error' => isset($error) ? $error : NULL
]);
}
}

12
app/Models/Coupon.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Coupon extends Model
{
protected $table = 'v2_coupon';
protected $dateFormat = 'U';
protected $guarded = ['id'];
}

12
app/Models/MailLog.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class MailLog extends Model
{
protected $table = 'v2_mail_log';
protected $dateFormat = 'U';
protected $guarded = ['id'];
}

12
app/Models/Tutorial.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Tutorial extends Model
{
protected $table = 'v2_tutorial';
protected $dateFormat = 'U';
protected $guarded = ['id'];
}

View File

@ -36,7 +36,6 @@ class RouteServiceProvider extends ServiceProvider
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
@ -65,9 +64,14 @@ class RouteServiceProvider extends ServiceProvider
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::group([
'prefix' => '/api/v1',
'middleware' => 'api',
'namespace' => $this->namespace
], function ($router) {
foreach (glob(app_path('Http//Routes') . '/*.php') as $file) {
$this->app->make('App\\Http\\Routes\\' . basename($file, '.php'))->map($router);
}
});
}
}

8
app/Utils/CacheKey.php Normal file
View File

@ -0,0 +1,8 @@
<?php
namespace App\Utils;
class CacheKey
{
}

18
app/Utils/Dict.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Utils;
class Dict
{
CONST EMAIL_WHITELIST_SUFFIX_DEFAULT = [
'gmail.com',
'qq.com',
'163.com',
'yahoo.com',
'sina.com',
'126.com',
'outlook.com',
'yeah.net',
'foxmail.com'
];
}

View File

@ -4,7 +4,8 @@ namespace App\Utils;
class Helper
{
public static function guid ($format = false) {
public static function guid($format = false)
{
if (function_exists('com_create_guid') === true) {
return md5(trim(com_create_guid(), '{}'));
}
@ -14,16 +15,18 @@ class Helper
if ($format) {
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
return md5(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)).'-'.time());
return md5(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)) . '-' . time());
}
public static function exchange ($from, $to) {
public static function exchange($from, $to)
{
$result = file_get_contents('https://api.exchangeratesapi.io/latest?symbols=' . $to . '&base=' . $from);
$result = json_decode($result, true);
return $result['rates'][$to];
}
public static function randomChar($len, $special=false){
public static function randomChar($len, $special = false)
{
$chars = array(
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
@ -33,7 +36,7 @@ class Helper
"3", "4", "5", "6", "7", "8", "9"
);
if($special){
if ($special) {
$chars = array_merge($chars, array(
"!", "@", "#", "$", "?", "|", "{", "/", ":", ";",
"%", "^", "&", "*", "(", ")", "-", "_", "[", "]",
@ -44,28 +47,52 @@ class Helper
$charsLen = count($chars) - 1;
shuffle($chars);
$str = '';
for($i=0; $i<$len; $i++){
for ($i = 0; $i < $len; $i++) {
$str .= $chars[mt_rand(0, $charsLen)];
}
return $str;
}
public static function buildVmessLink($item, $user) {
public static function buildVmessLink($item, $user)
{
$config = [
"v" => "2",
"ps" => $item->name,
"add" => $item->host,
"port" => $item->port,
"id" => $user->v2ray_uuid,
"aid" => "2",
"net" => $item->network,
"type" => "chacha20-poly1305",
"type" => "none",
"host" => "",
"tls" => $item->tls?"tls":"",
"path" => "",
"tls" => $item->tls ? "tls" : ""
];
if ($item->network == 'ws') {
$wsSettings = json_decode($item->settings);
if ($wsSettings->path) $config['path'] = $wsSettings->path;
if (isset($wsSettings->path)) $config['path'] = $wsSettings->path;
if (isset($wsSettings->headers->Host)) $config['host'] = $wsSettings->headers->Host;
}
return "vmess://".base64_encode(json_encode($config))."\r\n";
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function multiPasswordVerify($algo, $password, $hash)
{
switch($algo) {
case 'md5': return md5($password) === $hash;
case 'sha256': return hash('sha256', $password) === $hash;
default: return password_verify($password, $hash);
}
}
public static function emailSuffixVerify($email, $suffixs)
{
$suffix = preg_split('/@/', $email)[1];
if (!$suffix) return false;
if (!is_array($suffixs)) {
$suffixs = preg_split('/,/', $suffixs);
}
if (!in_array($suffix, $suffixs)) return false;
return true;
}
}

View File

@ -14,6 +14,7 @@
"laravel/framework": "^6.0",
"laravel/tinker": "^1.0",
"lokielse/omnipay-alipay": "^3.0",
"php-curl-class/php-curl-class": "^8.6",
"stripe/stripe-php": "^7.5",
"symfony/yaml": "^4.3"
},
@ -36,7 +37,8 @@
},
"autoload": {
"psr-4": {
"App\\": "app/"
"App\\": "app/",
"Library\\": "library/"
},
"classmap": [
"database/seeds",

View File

@ -228,4 +228,11 @@ return [
],
/*
|--------------------------------------------------------------------------
| V2board version
|--------------------------------------------------------------------------
*/
'version' => '1.1.2'
];

View File

@ -98,6 +98,6 @@ return [
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache'),
];

View File

@ -123,7 +123,7 @@ return [
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [

View File

@ -51,7 +51,7 @@ return [
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'url' => env('APP_URL') . '/storage',
'visibility' => 'public',
],

View File

@ -126,7 +126,7 @@ return [
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
Str::slug(env('APP_NAME', 'laravel'), '_') . '_session'
),
/*

0
database/.gitignore vendored Executable file → Normal file
View File

1
database/factories/UserFactory.php Executable file → Normal file
View File

@ -1,6 +1,7 @@
<?php
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

241
database/install.sql Normal file
View File

@ -0,0 +1,241 @@
-- Adminer 4.7.3 MySQL dump
SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
SET NAMES utf8mb4;
DROP TABLE IF EXISTS `failed_jobs`;
CREATE TABLE `failed_jobs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `v2_coupon`;
CREATE TABLE `v2_coupon` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` char(8) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`type` tinyint(1) NOT NULL,
`value` int(11) NOT NULL,
`limit_use` int(11) DEFAULT NULL,
`started_at` int(11) NOT NULL,
`ended_at` int(11) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_invite_code`;
CREATE TABLE `v2_invite_code` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`code` char(32) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0',
`pv` int(11) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_mail_log`;
CREATE TABLE `v2_mail_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(64) NOT NULL,
`subject` varchar(255) NOT NULL,
`template_name` varchar(255) NOT NULL,
`error` varchar(255) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_notice`;
CREATE TABLE `v2_notice` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
`img_url` varchar(255) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_order`;
CREATE TABLE `v2_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`invite_user_id` int(11) DEFAULT NULL,
`user_id` int(11) NOT NULL,
`plan_id` int(11) NOT NULL,
`type` int(11) NOT NULL COMMENT '1新购2续费3升级',
`cycle` varchar(255) NOT NULL,
`trade_no` varchar(36) NOT NULL,
`callback_no` varchar(255) DEFAULT NULL,
`total_amount` int(11) NOT NULL,
`discount_amount` int(11) DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0',
`commission_status` tinyint(1) NOT NULL DEFAULT '0',
`commission_balance` int(11) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_plan`;
CREATE TABLE `v2_plan` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`transfer_enable` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`show` tinyint(1) NOT NULL DEFAULT '0',
`renew` tinyint(1) NOT NULL DEFAULT '1',
`content` text,
`month_price` int(11) DEFAULT '0',
`quarter_price` int(11) DEFAULT '0',
`half_year_price` int(11) DEFAULT '0',
`year_price` int(11) DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server`;
CREATE TABLE `v2_server` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`host` varchar(255) NOT NULL,
`port` int(11) NOT NULL,
`server_port` int(11) NOT NULL,
`tls` tinyint(4) NOT NULL DEFAULT '0',
`tags` varchar(255) DEFAULT NULL,
`rate` varchar(11) NOT NULL,
`network` varchar(11) NOT NULL,
`settings` text,
`rules` text,
`show` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server_group`;
CREATE TABLE `v2_server_group` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server_log`;
CREATE TABLE `v2_server_log` (
`user_id` int(11) NOT NULL,
`server_id` int(11) NOT NULL,
`u` varchar(255) NOT NULL,
`d` varchar(255) NOT NULL,
`rate` decimal(10,2) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_ticket`;
CREATE TABLE `v2_ticket` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`last_reply_user_id` int(11) NOT NULL,
`subject` varchar(255) NOT NULL,
`level` tinyint(1) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0:已开启 1:已关闭',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_ticket_message`;
CREATE TABLE `v2_ticket_message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`ticket_id` int(11) NOT NULL,
`message` varchar(255) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_tutorial`;
CREATE TABLE `v2_tutorial` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`description` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`icon` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`steps` text,
`show` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `v2_tutorial` (`id`, `title`, `description`, `icon`, `steps`, `show`, `created_at`, `updated_at`) VALUES
(1, 'Windows', '兼容 Windows 7 以上的版本', 'fab fa-2x fa-windows', '[{\"default_area\":\"<div><div>下载 V2rayN 客户端。</div><div>下载完成后解压解压完成后运行V2rayN</div><div>运行时请右键,以管理员身份运行</div></div>\",\"download_url\":\"/downloads/V2rayN.zip\"},{\"default_area\":\"<div>点击订阅按钮,选择订阅设置点击添加,输入如下内容后点击确定保存</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UkcHNtERTnjLVS8.jpg\"},{\"default_area\":\"<div>点击订阅后,从服务器列表选择服务器</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/BgPGFQ3kCSuIRjJ.jpg\"},{\"default_area\":\"<div>点击参数设置找到Http代理选择PAC模式后按确定保存即启动代理。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/vnVykKEFT8Lzo3f.jpg\"}]', 1, 1577972408, 1577984396),
(2, 'Android', '兼容 Android 6 以上的版本', 'fab fa-2x fa-android', '[{\"default_area\":\"<div>下载 V2rayNG 客户端。</div>\",\"safe_area\":\"\",\"download_url\":\"/downloads/V2rayNG.apk\"},{\"default_area\":\"<div>打开 V2rayNG 点击左上角的菜单图标打开侧边栏,随后点击 订阅设置,点击右上角的➕按钮新增订阅。</div><div>按照下方内容进行填写,填写完毕后点击右上角的☑️按钮。</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"download_url\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ghuVkTe6LBqRxSO.jpg\"},{\"default_area\":\"<div>再次从侧边栏进入 设置 页面,点击 路由模式 将其更改为 \\b绕过局域网及大陆地址。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/Tf1AGoXZuhJrwOq.jpg\"},{\"default_area\":\"<div>随后从侧边栏回到 配置文件 页面,点击右上角的省略号图标选择更新订阅。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UtfPShQXupRmB4L.jpg\"},{\"img_url\":\"https://i.loli.net/2019/11/21/ZkbNsSrAg3m5Dny.jpg\",\"default_area\":\"<div>点击选择您需要的节点点击右下角的V字按钮即可连接。</div>\"}]', 1, 1577972534, 1577984397),
(3, 'macOS', '兼容 Yosemite 以上的版本', 'fab fa-2x fa-apple', '[{\"default_area\":\"<div>下载 ClashX 客户端,安装后运行。</div>\",\"download_url\":\"/downloads/ClashX.dmg\",\"img_url\":\"https://i.loli.net/2019/11/20/uNGrjl2noCL1f5B.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+M(订阅快捷键),在弹出的窗口点击添加输入下方信息</div>\",\"safe_area\":\"<div>Url<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>Config Name<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/20/8eB13mRbFuszwxg.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+S(设置为系统代理快捷键),即连接完成</div>\"}]', 1, 1577979855, 1577984397),
(4, 'iOS', '兼容 iOS 9 以上的版本', 'fab fa-2x fa-apple', '[{\"default_area\":\"<div>iOS上使用请在iOS浏览器中打开本页</div>\"},{\"default_area\":\"<div>在 App Store 登录本站提供的美区 Apple ID 下载客户端。</div><div>为了保护您的隐私,请勿在手机设置里直接登录,仅在 App Store 登录即可。</div><div>登陆完成后点击下方下载会自动唤起下载。</div>\",\"safe_area\":\"<div>Apple ID<code onclick=\\\"safeAreaCopy(\'{{$apple_id}}\')\\\">{{$apple_id}}</code></div><div>密码:<code onclick=\\\"safeAreaCopy(\'{{$apple_id_password}}\')\\\">点击复制密码</code></div>\",\"download_url\":\"https://apps.apple.com/us/app/shadowrocket/id932747118\",\"img_url\":\"https://i.loli.net/2019/11/21/5idkjJ61stWgREV.jpg\"},{\"default_area\":\"<div>待客户端安装完成后,点击下方一键订阅按钮会自动唤起并进行订阅</div>\",\"safe_area\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ZcqlNMb3eg5Uhxd.jpg\",\"download_url\":\"shadowrocket://add/sub://{{$b64_subscribe_url}}?remark={{$app_name}}\"},{\"default_area\":\"<div>选择节点进行链接,首次链接过程授权窗口请一路允许。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/9Zdxksr7Ey6hjlm.jpg\"}]', 1, 1577982016, 1577983283);
DROP TABLE IF EXISTS `v2_user`;
CREATE TABLE `v2_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`invite_user_id` int(11) DEFAULT NULL,
`email` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL,
`password_algo` char(10) DEFAULT NULL,
`balance` int(11) NOT NULL DEFAULT '0',
`discount` int(11) DEFAULT NULL,
`commission_rate` int(11) DEFAULT NULL,
`commission_balance` int(11) NOT NULL DEFAULT '0',
`t` int(11) NOT NULL DEFAULT '0',
`u` bigint(20) NOT NULL DEFAULT '0',
`d` bigint(20) NOT NULL DEFAULT '0',
`transfer_enable` bigint(20) NOT NULL DEFAULT '0',
`enable` tinyint(1) NOT NULL DEFAULT '1',
`banned` tinyint(1) NOT NULL DEFAULT '0',
`is_admin` tinyint(1) NOT NULL DEFAULT '0',
`last_login_at` int(11) DEFAULT NULL,
`last_login_ip` int(11) DEFAULT NULL,
`v2ray_uuid` varchar(36) NOT NULL,
`v2ray_alter_id` tinyint(4) NOT NULL DEFAULT '2',
`v2ray_level` tinyint(4) NOT NULL DEFAULT '0',
`group_id` int(11) DEFAULT NULL,
`plan_id` int(11) DEFAULT NULL,
`remind_expire` tinyint(4) DEFAULT '1',
`remind_traffic` tinyint(4) DEFAULT '1',
`token` char(32) NOT NULL,
`expired_at` bigint(20) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2020-02-14 10:22:53

View File

View File

View File

0
database/seeds/DatabaseSeeder.php Executable file → Normal file
View File

164
database/update.sql Normal file
View File

@ -0,0 +1,164 @@
ALTER TABLE `v2_server`
ADD `last_check_at` int(11) NULL AFTER `rate`;
ALTER TABLE `v2_server`
ADD `network` varchar(11) COLLATE 'utf8_general_ci' NOT NULL AFTER `rate`;
ALTER TABLE `v2_server`
ADD `settings` text COLLATE 'utf8_general_ci' NULL AFTER `network`;
ALTER TABLE `v2_server`
ADD `show` tinyint(1) NOT NULL DEFAULT '0' AFTER `settings`;
ALTER TABLE `v2_user`
CHANGE `enable` `enable` tinyint(1) NOT NULL DEFAULT '1' AFTER `transfer_enable`;
ALTER TABLE `v2_order`
ADD `type` int(11) NOT NULL COMMENT '1新购2续费3升级' AFTER `plan_id`;
ALTER TABLE `v2_user`
ADD `commission_rate` int(11) NULL AFTER `password`;
ALTER TABLE `v2_user`
ADD `balance` int(11) NOT NULL DEFAULT '0' AFTER `password`;
CREATE TABLE `v2_notice` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
);
ALTER TABLE `v2_notice`
ADD `img_url` varchar(255) COLLATE 'utf8_general_ci' NULL AFTER `content`;
CREATE TABLE `v2_ticket` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`subject` varchar(255) NOT NULL,
`level` tinyint(1) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `v2_ticket_message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`ticket_id` int(11) NOT NULL,
`message` varchar(255) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `v2_ticket`
ADD `last_reply_user_id` int(11) NOT NULL AFTER `user_id`;
ALTER TABLE `v2_user`
CHANGE `last_login_at` `last_login_at` int(11) NULL AFTER `is_admin`;
ALTER TABLE `v2_server_log`
CHANGE `node_id` `server_id` int(11) NOT NULL AFTER `user_id`,
CHANGE `u` `u` varchar(255) COLLATE 'utf8_general_ci' NOT NULL AFTER `server_id`,
CHANGE `d` `d` varchar(255) COLLATE 'utf8_general_ci' NOT NULL AFTER `u`,
CHANGE `rate` `rate` int(11) NOT NULL AFTER `d`;
ALTER TABLE `v2_server`
DROP `last_check_at`;
ALTER TABLE `v2_server`
CHANGE `name` `name` varchar(255) COLLATE 'utf8mb4_general_ci' NOT NULL AFTER `group_id`;
ALTER TABLE `v2_plan`
CHANGE `month_price` `month_price` int(11) NULL DEFAULT '0' AFTER `content`,
CHANGE `quarter_price` `quarter_price` int(11) NULL DEFAULT '0' AFTER `month_price`,
CHANGE `half_year_price` `half_year_price` int(11) NULL DEFAULT '0' AFTER `quarter_price`,
CHANGE `year_price` `year_price` int(11) NULL DEFAULT '0' AFTER `half_year_price`;
ALTER TABLE `v2_server`
ADD `parent_id` int(11) NULL AFTER `group_id`;
CREATE TABLE `v2_mail_log` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`email` varchar(64) NOT NULL,
`subject` varchar(255) NOT NULL,
`template_name` varchar(255) NOT NULL,
`error` varchar(255) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
);
CREATE TABLE `v2_coupon` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`code` char(32) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`type` tinyint(1) NOT NULL,
`value` int(11) NOT NULL,
`limit_use` int(11) DEFAULT NULL,
`started_at` int(11) NOT NULL,
`ended_at` int(11) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
);
ALTER TABLE `v2_order`
ADD `discount_amount` int(11) NULL AFTER `total_amount`;
CREATE TABLE `v2_tutorial` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`title` varchar(255) COLLATE 'utf8mb4_general_ci' NOT NULL,
`description` varchar(255) COLLATE 'utf8mb4_general_ci' NOT NULL,
`icon` varchar(255) COLLATE 'utf8mb4_general_ci' NOT NULL,
`steps` text NULL,
`show` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
);
SET NAMES utf8mb4;
INSERT INTO `v2_tutorial` (`id`, `title`, `description`, `icon`, `steps`, `show`, `created_at`, `updated_at`) VALUES
(1, 'Windows', '兼容 Windows 7 以上的版本', 'fab fa-2x fa-windows', '[{\"default_area\":\"<div><div>下载 V2rayN 客户端。</div><div>下载完成后解压解压完成后运行V2rayN</div><div>运行时请右键,以管理员身份运行</div></div>\",\"download_url\":\"/downloads/V2rayN.zip\"},{\"default_area\":\"<div>点击订阅按钮,选择订阅设置点击添加,输入如下内容后点击确定保存</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UkcHNtERTnjLVS8.jpg\"},{\"default_area\":\"<div>点击订阅后,从服务器列表选择服务器</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/BgPGFQ3kCSuIRjJ.jpg\"},{\"default_area\":\"<div>点击参数设置找到Http代理选择PAC模式后按确定保存即启动代理。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/vnVykKEFT8Lzo3f.jpg\"}]', 1, 1577972408, 1577980882),
(2, 'Android', '兼容 Android 6 以上的版本', 'fab fa-2x fa-android', '[{\"default_area\":\"<div>下载 V2rayNG 客户端。</div>\",\"safe_area\":\"\",\"download_url\":\"/downloads/V2rayNG.apk\"},{\"default_area\":\"<div>打开 V2rayNG 点击左上角的菜单图标打开侧边栏,随后点击 订阅设置,点击右上角的➕按钮新增订阅。</div><div>按照下方内容进行填写,填写完毕后点击右上角的☑️按钮。</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url)<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"download_url\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ghuVkTe6LBqRxSO.jpg\"},{\"default_area\":\"<div>再次从侧边栏进入 设置 页面,点击 路由模式 将其更改为 \\b绕过局域网及大陆地址。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/Tf1AGoXZuhJrwOq.jpg\"},{\"default_area\":\"<div>随后从侧边栏回到 配置文件 页面,点击右上角的省略号图标选择更新订阅。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UtfPShQXupRmB4L.jpg\"},{\"img_url\":\"https://i.loli.net/2019/11/21/ZkbNsSrAg3m5Dny.jpg\",\"default_area\":\"<div>点击选择您需要的节点点击右下角的V字按钮即可连接。</div>\"}]', 1, 1577972534, 1577981610),
(3, 'macOS', '兼容 Yosemite 以上的版本', 'fab fa-2x fa-apple', '[{\"default_area\":\"<div>下载 ClashX 客户端,安装后运行。</div>\",\"download_url\":\"/downloads/ClashX.dmg\",\"img_url\":\"https://i.loli.net/2019/11/20/uNGrjl2noCL1f5B.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+M(订阅快捷键),在弹出的窗口点击添加输入下方信息</div>\",\"safe_area\":\"<div>Url<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>Config Name<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/20/8eB13mRbFuszwxg.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+S(设置为系统代理快捷键),即连接完成</div>\"}]', 1, 1577979855, 1577981646),
(4, 'iOS', '兼容 iOS 9 以上的版本', 'fab fa-2x fa-apple', '[{\"default_area\":\"<div>iOS上使用请在iOS浏览器中打开本页</div>\"},{\"default_area\":\"<div>在 App Store 登录本站提供的美区 Apple ID 下载客户端。</div><div>为了保护您的隐私,请勿在手机设置里直接登录,仅在 App Store 登录即可。</div><div>登陆完成后点击下方下载会自动唤起下载。</div>\",\"safe_area\":\"<div>Apple ID<code onclick=\\\"safeAreaCopy(\'{{$apple_id}}\')\\\">{{$apple_id}}</code></div><div>密码:<code onclick=\\\"safeAreaCopy(\'{{$apple_id_password}}\')\\\">点击复制密码</code></div>\",\"download_url\":\"https://apps.apple.com/us/app/shadowrocket/id932747118\",\"img_url\":\"https://i.loli.net/2019/11/21/5idkjJ61stWgREV.jpg\"},{\"default_area\":\"<div>待客户端安装完成后,点击下方一键订阅按钮会自动唤起并进行订阅</div>\",\"safe_area\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ZcqlNMb3eg5Uhxd.jpg\",\"download_url\":\"shadowrocket://add/sub://{{$b64_subscribe_url}}?remark={{$app_name}}\"},{\"default_area\":\"<div>选择节点进行链接,首次链接过程授权窗口请一路允许。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/9Zdxksr7Ey6hjlm.jpg\"}]', 1, 1577982016, 1577983283);
ALTER TABLE `v2_server_log`
CHANGE `rate` `rate` decimal(10,2) NOT NULL AFTER `d`;
ALTER TABLE `v2_order`
DROP `method`;
ALTER TABLE `v2_invite_code`
ADD `pv` int(11) NOT NULL DEFAULT '0' AFTER `status`;
ALTER TABLE `v2_user`
ADD `password_algo` char(10) COLLATE 'utf8_general_ci' NULL AFTER `password`;
ALTER TABLE `v2_server`
CHANGE `tls` `tls` tinyint(4) NOT NULL DEFAULT '0' AFTER `server_port`;
ALTER TABLE `v2_server`
ADD `rules` text COLLATE 'utf8_general_ci' NULL AFTER `settings`;
CREATE TABLE `failed_jobs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`connection` text COLLATE utf8mb4_unicode_ci NOT NULL,
`queue` text COLLATE utf8mb4_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`exception` longtext COLLATE utf8mb4_unicode_ci NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `v2_user`
ADD `discount` int(11) NULL AFTER `balance`;
ALTER TABLE `v2_order`
ADD `surplus_amount` int(11) NULL COMMENT '剩余价值' AFTER `discount_amount`;
ALTER TABLE `v2_order`
ADD `refund_amount` int(11) NULL COMMENT '退款金额' AFTER `surplus_amount`;

View File

@ -1,173 +0,0 @@
-- Adminer 4.7.3 MySQL dump
SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
DROP TABLE IF EXISTS `v2_invite_code`;
CREATE TABLE `v2_invite_code` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`code` char(32) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_notice`;
CREATE TABLE `v2_notice` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
`img_url` varchar(255) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_order`;
CREATE TABLE `v2_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`invite_user_id` int(11) DEFAULT NULL,
`user_id` int(11) NOT NULL,
`plan_id` int(11) NOT NULL,
`type` int(11) NOT NULL COMMENT '1新购2续费3升级',
`cycle` varchar(255) NOT NULL,
`trade_no` varchar(36) NOT NULL,
`callback_no` varchar(255) DEFAULT NULL,
`total_amount` int(11) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0',
`commission_status` tinyint(1) NOT NULL DEFAULT '0',
`commission_balance` int(11) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_plan`;
CREATE TABLE `v2_plan` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`transfer_enable` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`show` tinyint(1) NOT NULL DEFAULT '0',
`renew` tinyint(1) NOT NULL DEFAULT '1',
`content` text,
`month_price` int(11) NOT NULL DEFAULT '0',
`quarter_price` int(11) NOT NULL DEFAULT '0',
`half_year_price` int(11) NOT NULL DEFAULT '0',
`year_price` int(11) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server`;
CREATE TABLE `v2_server` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`host` varchar(255) NOT NULL,
`port` int(11) NOT NULL,
`server_port` int(11) NOT NULL,
`tls` tinyint(4) NOT NULL,
`tags` varchar(255) DEFAULT NULL,
`rate` varchar(11) NOT NULL,
`network` varchar(11) NOT NULL,
`settings` text,
`show` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server_group`;
CREATE TABLE `v2_server_group` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server_log`;
CREATE TABLE `v2_server_log` (
`user_id` int(11) NOT NULL,
`server_id` int(11) NOT NULL,
`u` varchar(255) NOT NULL,
`d` varchar(255) NOT NULL,
`rate` int(11) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_ticket`;
CREATE TABLE `v2_ticket` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`last_reply_user_id` int(11) NOT NULL,
`subject` varchar(255) NOT NULL,
`level` tinyint(1) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0:已开启 1:已关闭',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_ticket_message`;
CREATE TABLE `v2_ticket_message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`ticket_id` int(11) NOT NULL,
`message` varchar(255) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_user`;
CREATE TABLE `v2_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`invite_user_id` int(11) DEFAULT NULL,
`email` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL,
`balance` int(11) NOT NULL DEFAULT '0',
`commission_rate` int(11) DEFAULT NULL,
`commission_balance` int(11) NOT NULL DEFAULT '0',
`t` int(11) NOT NULL DEFAULT '0',
`u` bigint(20) NOT NULL DEFAULT '0',
`d` bigint(20) NOT NULL DEFAULT '0',
`transfer_enable` bigint(20) NOT NULL DEFAULT '0',
`enable` tinyint(1) NOT NULL DEFAULT '1',
`banned` tinyint(1) NOT NULL DEFAULT '0',
`is_admin` tinyint(1) NOT NULL DEFAULT '0',
`last_login_at` int(11) DEFAULT NULL,
`last_login_ip` int(11) DEFAULT NULL,
`v2ray_uuid` varchar(36) NOT NULL,
`v2ray_alter_id` tinyint(4) NOT NULL DEFAULT '2',
`v2ray_level` tinyint(4) NOT NULL DEFAULT '0',
`group_id` int(11) DEFAULT NULL,
`plan_id` int(11) DEFAULT NULL,
`remind_expire` tinyint(4) DEFAULT '1',
`remind_traffic` tinyint(4) DEFAULT '1',
`token` char(32) NOT NULL,
`expired_at` bigint(20) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2019-12-23 08:30:39

93
library/BitpayX.php Normal file
View File

@ -0,0 +1,93 @@
<?php
namespace Library;
class BitpayX
{
private $bitpayxAppSecret;
private $bitpayxGatewayUri;
/**
* 签名初始化
* @param merKey 签名密钥
*/
public function __construct($bitpayxAppSecret)
{
$this->bitpayxAppSecret = $bitpayxAppSecret;
$this->bitpayxGatewayUri = 'https://api.mugglepay.com/v1/';
}
public function prepareSignId($tradeno)
{
$data_sign = array();
$data_sign['merchant_order_id'] = $tradeno;
$data_sign['secret'] = $this->bitpayxAppSecret;
$data_sign['type'] = 'FIAT';
ksort($data_sign);
return http_build_query($data_sign);
}
public function sign($data)
{
return strtolower(md5(md5($data) . $this->bitpayxAppSecret));
}
public function verify($data, $signature)
{
$mySign = $this->sign($data);
return $mySign === $signature;
}
public function mprequest($data)
{
$headers = array('content-type: application/json', 'token: ' . $this->bitpayxAppSecret);
$curl = curl_init();
$url = $this->bitpayxGatewayUri . 'orders';
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, 1);
$data_string = json_encode($data);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
$data = curl_exec($curl);
curl_close($curl);
return json_decode($data, true);
}
public function mpcheckout($orderId, $data)
{
$headers = array('content-type: application/json', 'token: ' . $this->bitpayxAppSecret);
$curl = curl_init();
$url = $this->bitpayxGatewayUri . 'orders/' . $orderId . '/checkout';
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, 1);
$data_string = json_encode($data);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
$data = curl_exec($curl);
curl_close($curl);
return json_decode($data, true);
}
public function refund($merchantTradeNo)
{
// TODO
return true;
}
public function buildHtml($params, $method = 'post', $target = '_self')
{
// var_dump($params);exit;
$html = "<form id='submit' name='submit' action='" . $this->gatewayUri . "' method='$method' target='$target'>";
foreach ($params as $key => $value) {
$html .= "<input type='hidden' name='$key' value='$value'/>";
}
$html .= "</form><script>document.forms['submit'].submit();</script>";
return $html;
}
}

49
library/PayTaro.php Normal file
View File

@ -0,0 +1,49 @@
<?php
namespace Library;
use \Curl\Curl;
class PayTaro
{
private $appId;
private $appSecret;
public function __construct($appId, $appSecret)
{
$this->appId = $appId;
$this->appSecret = $appSecret;
}
public function pay($params)
{
ksort($params);
$str = http_build_query($params) . $this->appSecret;
$params['sign'] = md5($str);
$curl = new Curl();
$curl->post('https://api.paytaro.com/v1/gateway/fetch', http_build_query($params));
$result = $curl->response;
if ($curl->error) {
$errors = (array)$result->errors;
abort(500, $errors[array_keys($errors)[0]][0]);
}
$curl->close();
if (!isset($result->data->trade_no)) {
abort(500, '接口请求失败');
}
return $result->data->pay_url;
}
public function verify($params)
{
$sign = $params['sign'];
unset($params['sign']);
ksort($params);
reset($params);
$str = http_build_query($params) . $this->appSecret;
if ($sign !== md5($str)) {
return false;
}
return true;
}
}

63
library/TomatoPay.php Normal file
View File

@ -0,0 +1,63 @@
<?php
namespace Library;
class TomatoPay
{
private $mchid;
private $account;
private $key;
public function __construct($mchid, $account, $key)
{
$this->mchid = $mchid;
$this->account = $account;
$this->key = $key;
}
public function alipay($cny, $trade)
{
$params = [
'mchid' => $this->mchid,
'account' => $this->account,
'cny' => $cny,
'type' => '1',
'trade' => $trade
];
$params['signs'] = $this->sign($params);
return $this->buildHtml('https://b.fanqieui.com/gateways/alipay.php', $params);
}
public function wxpay($cny, $trade)
{
$params = [
'mchid' => $this->mchid,
'account' => $this->account,
'cny' => $cny,
'type' => '1',
'trade' => $trade
];
$params['signs'] = $this->sign($params);
return $this->buildHtml('https://b.fanqieui.com/gateways/wxpay.php', $params);
}
public function sign($params)
{
$o = '';
foreach ($params as $k => $v) {
$o .= "$k=" . ($v) . "&";
}
return md5(substr($o, 0, -1) . $this->key);
}
public function buildHtml($url, $params, $method = 'post', $target = '_self')
{
// return var_dump($params);
$html = "<form id='submit' name='submit' action='" . $url . "' method='$method' target='$target'>";
foreach ($params as $key => $value) {
$html .= "<input type='hidden' name='$key' value='$value'/>";
}
$html .= "</form><script>document.forms['submit'].submit();</script>";
return $html;
}
}

5
pm2.yaml Normal file
View File

@ -0,0 +1,5 @@
apps:
- name : 'V2Board'
script : 'php artisan queue:work --queue=verify_mail,other_mail'
instances: 4
out_file : './storage/logs/queue/queue.log'

1
public/assets/admin/antd.async.js vendored Normal file

File diff suppressed because one or more lines are too long

26
public/assets/admin/antd.chunk.css vendored Normal file

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More