554 Commits
1.3 ... 1.5.2

Author SHA1 Message Date
1a9b8b09bb Merge pull request #467 from v2board/dev
1.5.2
2021-07-29 20:59:52 +09:00
cb621a93ae update: version 2021-07-29 20:56:28 +09:00
fb449b490e Merge pull request #466 from v2board/dev
1.5.2
2021-07-29 20:15:19 +09:00
e35ec76f81 update: add subscribe anxray 2021-07-29 20:13:01 +09:00
5a60380765 update: add theme config 2021-07-29 13:38:13 +09:00
f409d89c4a update: add config 2021-07-29 02:41:31 +09:00
de045c79f5 update: custom theme 2021-07-29 02:36:51 +09:00
6a336d4253 update: web route 2021-07-29 02:29:15 +09:00
0ce3948c12 update: theme directory 2021-07-29 02:21:09 +09:00
7338c4a294 update: user 2021-07-27 19:16:49 +09:00
9417623fcf fix: email suffix select 2021-07-27 18:32:29 +09:00
4f29264de9 update: fix email suffix 2021-07-26 13:50:04 +09:00
34d8f0d5f0 update: statistics check time 2021-07-24 01:31:23 +09:00
b585038916 update: user 2021-07-23 20:43:09 +09:00
5abb642277 update: user 2021-07-22 22:36:48 +09:00
01cf486137 update: comm config 2021-07-22 22:32:42 +09:00
cb811bda5a update: user 2021-07-22 21:12:09 +09:00
2306e38d0c update: remove aliyun repo 2021-07-22 01:22:14 +09:00
2798c1df06 update: user language 2021-07-20 21:16:46 +09:00
a727745b43 update: order ui 2021-07-20 18:22:26 +09:00
af901291a5 update: quick login url 2021-07-19 21:17:34 +09:00
42d755c7d9 update: get comm config api 2021-07-19 18:44:40 +09:00
56a4ce5fe4 fix: surplus 2021-07-18 22:27:23 +09:00
67ab0d1f22 update: user 2021-07-16 18:06:11 +09:00
230470c037 fix: knowledge sort 2021-07-16 17:46:36 +09:00
77aec7d553 update: commission type and opt knowledge sort timestamp 2021-07-14 20:08:30 +09:00
88948eb8ee fix: change plan refund surplus amount 2021-07-11 01:13:09 +09:00
4d38ce3968 Merge pull request #463 from betaxab/fix-vmss-tls 2021-07-09 20:24:25 +09:00
a88121626b fix: knowledge subscribe url 2021-07-09 00:01:13 +09:00
90409b1107 update: add default subscribe, 1.5.3 remove this 2021-07-07 19:54:31 +09:00
04615688f7 update: subscribe url random 2021-07-06 18:04:59 +09:00
f48de9f07d Protocols: fix vmess tls sni field 2021-07-05 21:01:28 +08:00
4722379e79 update: add new client protocol 2021-07-04 13:21:10 +09:00
9aed2554ee Merge pull request #462 from betaxab/routerprotocol
Protocols: add Passwall & SSRPlus subscription support
2021-07-04 13:08:07 +09:00
7eb3d9fed7 Protocols: add Passwall & SSRPlus subscription support
Usage:
add &flag=passwall at the end of subscription link for OpenWRT Passwall Luci Plugin
add &flag=ssrplus at the end of subscription link for OpenWRT ShadowsocksR Plus+ Luci Plugin
2021-07-03 23:02:47 +08:00
567acdd03b Merge pull request #461 from betaxab/v2rayprotocol
Protocols: add V2ray Client Protocol
2021-07-03 23:56:00 +09:00
a0d18d93d3 Protocols: add V2ray Client Protocol
Usage:
add &flag=v2rayng at the end of subscription link for V2rayNG Client
add &flag=v2rayn at the end of subscription link for V2rayN Client
2021-07-03 22:54:13 +08:00
1c419283c0 Merge pull request #460 from betaxab/p3
Protocols: fix QuantumultX subscription
2021-07-03 14:26:42 +09:00
d25f6bff65 Protocols: fix QuantumultX subscription 2021-07-03 12:53:22 +08:00
0b97dd0995 fix: app subscribe 2021-07-03 03:31:28 +09:00
6f90c6b878 update: code 2021-07-02 23:35:10 +09:00
5b8591fde9 update: code 2021-07-02 23:34:42 +09:00
dfeec044ea fix: statsistics 2021-07-02 22:43:22 +09:00
70b47ec4b0 update: remove origin subscribe method 2021-07-02 22:24:39 +09:00
cd85fba9c7 restore: origin subscribe method 2021-07-02 22:14:07 +09:00
e01e951f7f update: code 2021-07-02 22:07:54 +09:00
0284e47155 fix: shadowrocket grpc 2021-07-02 22:05:12 +09:00
a4e1ba4016 fix: shadowrocket grpc 2021-07-02 21:57:00 +09:00
f95deb3f16 fix: shadowrocket grpc 2021-07-02 21:52:13 +09:00
dfef6d2d94 update: remove origin subscribe method 2021-07-02 21:25:26 +09:00
efc8419eb5 update: grpc 2021-07-02 20:46:16 +09:00
aecfa85efd update 2021-07-01 23:25:59 +09:00
9db5de09f2 update: order process event 2021-07-01 22:04:22 +09:00
b174403a2a remove: soft delete 2021-07-01 20:06:09 +09:00
0f488540f4 update: middleware 2021-07-01 18:16:27 +09:00
d00ad94c46 update: sql 2021-07-01 18:14:49 +09:00
5fa7c534ff update: middleware 2021-07-01 18:11:23 +09:00
386c1339f5 fix: plan update 2021-07-01 18:02:13 +09:00
6509091e4f update: check order php env memory limit 2021-07-01 12:41:34 +09:00
078dfbf339 fix: safe delete sql 2021-07-01 03:01:26 +09:00
de6ff1dca9 update: add user softdelete api 2021-07-01 00:35:22 +09:00
044d1e9b7f update: message box 2021-06-30 00:48:28 +09:00
9c711b4ea6 update: middleware 2021-06-25 22:35:14 +09:00
760d248e4e update: ui 2021-06-23 19:36:40 +09:00
1a2e8e2966 fix: ui event 2021-06-23 19:24:30 +09:00
b00b58d73d update: ui 2021-06-23 19:15:19 +09:00
43027660be update: ui 2021-06-23 19:11:56 +09:00
fe69a5976b update: ui 2021-06-23 19:06:48 +09:00
ecebba1d51 fix: language change 2021-06-23 18:02:35 +09:00
efc43dc45e update: ui 2021-06-23 16:32:25 +09:00
c5d88bfbfc update: ui 2021-06-23 16:25:55 +09:00
6928fd3fef update readme 2021-06-23 02:31:50 +09:00
e4f178e167 add: wechatpay native 2021-06-14 22:29:31 +09:00
343c93ad8e add: wechatpay native 2021-06-14 22:27:29 +09:00
f80af8efdc update: dev version 2021-06-12 16:49:34 +09:00
e5b998ee8d fix: user update 2021-06-12 16:46:58 +09:00
d510064732 update: support grpc for clash & shadowrocket 2021-06-12 15:42:44 +09:00
c5e2ec1d12 update: gitignore 2021-06-12 01:57:49 +09:00
ee2ca23487 update: language more 2021-06-12 01:56:39 +09:00
a5532490ba update: telegram ticket replay notify 2021-06-10 22:24:54 +09:00
2f175323a3 update: css 2021-06-08 20:30:36 +09:00
f896370e64 update: sort leave alert 2021-06-08 20:07:10 +09:00
0313c35dbe update: user edit 2021-06-04 00:56:28 +09:00
232cb18a25 update: frontend 2021-06-02 21:10:16 +09:00
04a05a6ba4 Merge pull request #445 from betaxab/p1
ClientController: clash: add subscription-userinfo header
2021-05-26 23:26:35 +09:00
e2b4df592d update: frontend 2021-05-26 23:15:18 +09:00
668663f978 update: user frontend 2021-05-26 22:11:03 +09:00
2e7544c71c ClientController: clash: add subscription-userinfo header 2021-05-26 17:07:38 +08:00
1fb3f62cff update: user frontend 2021-05-26 17:35:57 +09:00
02a1728bff update: payment service 2021-05-26 01:38:57 +09:00
4ea71d85be update: user frontend 2021-05-24 22:49:57 +09:00
afe8bb3171 update: check order 2021-05-24 21:10:54 +09:00
ef17be2046 fix: ticket filter 2021-05-22 14:18:27 +09:00
53dea06a6c update: user 2021-05-21 02:40:31 +09:00
229a3022ac Merge pull request #439 from v2board/dev
1.5.1
2021-05-18 21:00:54 +09:00
554dc4c12b update: version 2021-05-18 21:00:01 +09:00
6175e59e6f remove: old route 2021-05-17 02:30:39 +09:00
9df6e52438 update: composer2 2021-05-17 02:30:00 +09:00
63acb4c581 fix: remark input 2021-05-16 23:16:33 +09:00
f54c82dcf9 remove: old code 2021-05-16 03:11:24 +09:00
b92b38a635 fix: stripe notify 2021-05-15 02:59:46 +09:00
ddab443d75 fix: payment input 2021-05-14 22:53:07 +09:00
8f806e6de7 update: chart smooth 2021-05-13 18:18:06 +09:00
f1c62e2732 knowledge: add access area 2021-05-13 18:08:09 +09:00
3275b96a0a fix: user tos target 2021-05-09 20:26:00 +09:00
240555104a Merge branch 'dev' of https://github.com/v2board/v2board into dev 2021-05-09 00:46:31 +09:00
30ff71bd11 payment: support epay 2021-05-09 00:46:21 +09:00
b17bbad745 Merge pull request #417 from ColetteContreras/patch-2 2021-05-07 14:16:51 +09:00
a301b8461e update: user 2021-05-07 12:07:20 +09:00
3e354bf5af update: admin 2021-05-07 01:27:54 +09:00
b9796f462c update: payment 2021-05-07 01:18:38 +09:00
dc6317db97 update 2021-05-07 01:17:55 +09:00
b3b0988730 update: tos config 2021-05-07 01:06:21 +09:00
4070761cd0 update: add tos 2021-05-07 00:56:54 +09:00
2c408a2f56 update 2021-05-07 00:15:30 +09:00
2cfaeb2fb9 add: tos url 2021-05-07 00:12:58 +09:00
ef1c0b6091 add: payment drop 2021-05-07 00:10:05 +09:00
8e1a313709 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2021-05-07 00:08:00 +09:00
d5cf1bc735 Merge pull request #407 from betaxab/p1 2021-05-03 13:32:19 +09:00
e1c63bd556 Merge pull request #412 from betaxab/p3 2021-05-03 13:31:07 +09:00
324e218767 update: frontent 2021-05-02 22:38:22 +09:00
04b5d16457 add: alipay f2f 2021-05-01 00:27:02 +09:00
2431ffaba7 update: payment 2021-04-29 01:39:45 +09:00
6b31c39e6e update: payment 2021-04-29 01:33:54 +09:00
2769a6adf4 update: stripe wepay 2021-04-29 01:27:24 +09:00
aa78761115 fix: payment 2021-04-29 01:25:11 +09:00
3e60cbf968 add: stripe wepay 2021-04-29 01:22:40 +09:00
84583bd384 update: payment 2021-04-28 18:48:18 +09:00
0333d62e6f fix: payment 2021-04-28 18:29:28 +09:00
3bfa2180e6 update 2021-04-28 18:00:22 +09:00
22ee741200 rewrite: payment 2021-04-28 17:56:08 +09:00
58b27cdd50 fix: hidden order fetch params 2021-04-15 22:41:29 +09:00
743311f2f7 Update PoseidonController.php
Fix server status
2021-04-06 07:03:45 +08:00
9c04f75b85 update: exchange api 2021-04-02 01:45:11 +09:00
c7d7dfda28 fix: more 2021-03-28 22:58:35 +09:00
35362689c4 update: config 2021-03-26 02:20:53 +09:00
b740998760 feature: customer service system 2021-03-25 18:26:22 +09:00
488eafdd67 feature: order filter 2021-03-25 17:59:28 +09:00
ff30d3dcb2 feature: server push status 2021-03-24 20:57:06 +09:00
cbe5882e01 feature: server push status 2021-03-24 16:04:09 +09:00
4e2e3cd2a0 rm: remove server name check 2021-03-22 02:33:33 +09:00
326fb5d918 fix: server name repeat 2021-03-20 02:24:55 +09:00
a49faf3e1f V2boardStatistics: comply with psr-4 autoloading standard 2021-03-19 18:28:58 +08:00
e4b76a705f fix: stat 2021-03-13 01:03:53 +09:00
a03125ee6a fix: stat 2021-03-12 01:24:50 +09:00
58a63ae819 add: server name check 2021-03-10 19:52:51 +09:00
ab8ebd593f URLSchemes: let's support standard v2ray vmess URL Schemes 2021-03-08 19:12:25 +08:00
11b6c8448a fix: user filter 2021-03-07 02:44:39 +09:00
9ce9af4917 fix: statistics 2021-03-06 01:22:37 +09:00
550f0fee37 update: admin user update 2021-03-02 21:12:32 +09:00
ab9c6c85bb fix: server rank 2021-02-26 23:54:38 +09:00
0e6f6358d8 opt v2board statistics 2021-02-23 14:49:07 +09:00
b0ddf7d45f opt 2021-02-19 01:15:37 +09:00
f8a851d464 Merge pull request #401 from v2board/dev
1.5.0
2021-02-17 17:20:51 +09:00
da8bff5609 update: frontend 2021-02-17 17:15:41 +09:00
d3150cadac update: version 2021-02-17 17:11:45 +09:00
cdddbae19a fix: something 2021-02-17 17:11:17 +09:00
7c40a146a9 update: app config 2021-02-16 01:53:37 +09:00
f81426b2c4 Merge pull request #399 from v2board/dev
1.4.3
2021-02-13 17:34:45 +09:00
e581d08f95 feature: more language switch 2021-02-06 00:03:58 +09:00
2ba924e578 update: withdraw close 2021-02-03 19:27:21 +09:00
77f6b3f289 feature: close withdraw 2021-02-03 19:17:32 +09:00
2979003b6f fix: get reset day 2021-02-03 19:03:28 +09:00
2be497b04d Merge pull request #388 from betaxab/p1 2021-02-03 13:08:17 +09:00
f55fc86547 fix: order chart 2021-02-01 19:04:03 +09:00
bc9b3b6e76 fix: server charts 2021-01-26 22:22:47 +09:00
fcafb65ce9 fix: config save 2021-01-26 22:20:27 +09:00
34be1b9579 fix: stat charts 2021-01-26 19:33:11 +09:00
627ff98882 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2021-01-21 23:29:34 +09:00
007b10d925 update: user remarks 2021-01-21 23:29:15 +09:00
f855ecc758 remove: test line 2021-01-21 15:52:12 +09:00
af8b2b61d4 language: update English translation
- minor Chinese entry adjustments
2021-01-21 00:50:56 +08:00
f7a7c21c16 update: backend language and order result page 2021-01-20 19:51:55 +09:00
edee396c9b fix: coupon generate 2021-01-15 00:35:36 +09:00
54d1226c8b fix: coupon generate 2021-01-15 00:33:30 +09:00
1ce019cfa5 update: commission 2021-01-13 12:24:22 +09:00
fe736df68d update: crisp and tawk info 2021-01-13 00:47:41 +09:00
72bf45e4d5 update: user 2021-01-12 00:49:59 +09:00
c5639a9bef update: other 2021-01-10 03:25:31 +09:00
3f1c505922 update: user 2021-01-08 14:21:13 +09:00
ec0fbabd07 update 2021-01-08 02:00:18 +09:00
eca105b7d1 update: frontend 2021-01-08 01:58:44 +09:00
a55647ab7a update: config 2021-01-08 01:33:11 +09:00
633b9ad912 Merge pull request #377 from wloot/patch-6
[security] Fix user info leak in getSubscribe()
2021-01-07 17:16:07 +09:00
be146bc98b Merge pull request #373 from betaxab/p1
subs: following shadowsocks SIP008 new changes
2021-01-07 17:11:11 +09:00
79cbe6ce39 Merge pull request #364 from wloot/patch-2
QuantumultX: keep expected behavior for host
2021-01-07 17:10:25 +09:00
f8185b51b7 update: more feature 2021-01-02 22:35:41 +09:00
b4d74a016a fix: traffic show 2021-01-02 12:07:28 +09:00
dd51daf9d8 [security] Fix user info leak in getSubscribe()
getSubscribe() leaks all user info even password hash, fix it.
2020-12-31 08:23:46 +08:00
4958c94bf2 update: user frontend 2020-12-28 17:23:56 +08:00
5766ec1951 update: statistics 2020-12-25 00:53:46 +08:00
faeaa02a84 compress 2020-12-24 23:49:52 +08:00
66c547d0ac update: more feature 2020-12-24 23:41:32 +08:00
d1cfc9815b subs: following shadowsocks SIP008 new changes
- do not use infix dereference operator to following the whole
2020-12-24 13:02:57 +08:00
b8b7033132 update: sql 2020-12-22 15:21:18 +08:00
9a465d9bb4 update: statistics 2020-12-21 17:11:48 +08:00
bcea2e7a54 update: statistics 2020-12-20 21:06:23 +08:00
7e5e696c70 update: statistics 2020-12-20 16:57:33 +08:00
1d70382aee update: db 2020-12-20 02:49:37 +08:00
8af862633f fix: frontend user edit 2020-12-19 21:33:11 +08:00
c0e60978ce fix: frontend user edit 2020-12-19 19:16:37 +08:00
f036b46bf0 QuantumultX: keep expected behavior for host
According to the documentation, quantumultx exposes one param for both sni and ws host. and sni priority is usually higher than ws host in other similar apps, we keep the behavior here.
ref: 8b6d7d84fc/sample.conf (L91)
2020-12-16 23:49:34 +08:00
a0d1b10614 fix: quantumultx tls-host key error 2020-12-16 23:15:20 +08:00
5fbefc7a98 Merge branch 'dev' 2020-12-14 11:03:57 +08:00
5a5491591b update: version 2020-12-14 11:03:32 +08:00
77e5eeea1b Merge branch 'dev' 2020-12-14 11:00:46 +08:00
da86b8cba9 update 2020-12-14 11:00:11 +08:00
91dfa5069c Merge pull request #361 from v2board/dev
1.4.2
2020-12-13 23:58:49 +08:00
2393ccbec7 update: server manage 2020-12-13 23:53:20 +08:00
962cfa9bea Merge pull request #359 from v2board/dev
1.4.2
2020-12-13 23:19:33 +08:00
8df4b6869a update: frontend 2020-12-13 23:15:47 +08:00
2159ab568a update: admin copy subscribe url 2020-12-13 23:09:32 +08:00
ef077cd095 update: frontend 2020-12-13 14:49:36 +08:00
17be643ce2 update: server manage 2020-12-13 14:16:07 +08:00
3b969c2c52 update: server manage 2020-12-13 12:17:48 +08:00
67d257c7b2 update: server manage 2020-12-13 12:16:05 +08:00
bc709a9c94 update: user filter 2020-12-11 22:32:10 +08:00
06f1ea0b81 update: user filter 2020-12-11 21:56:30 +08:00
0b425c1a01 update: admin 2020-12-11 20:45:54 +08:00
d072dc3e32 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-12-11 20:40:43 +08:00
db3e2abbbf update: user filter 2020-12-11 20:40:27 +08:00
c72e6cf4ea Merge pull request #356 from v2board/dev
1.4.1
2020-12-11 16:09:12 +08:00
b58df71db4 Merge pull request #355 from wloot/patch-1
Patch 1
2020-12-11 16:08:37 +08:00
91eb83388c Fix allowInsecure 2020-12-11 01:11:26 +08:00
16cf1f135a Fix allowInsecure 2020-12-11 01:11:04 +08:00
f3ef9e457e Fix allowInsecure 2020-12-11 01:09:49 +08:00
be1d22b423 Merge pull request #352 from betaxab/p1
rules: update default clash & surfboard & surge rules
2020-12-10 23:50:37 +08:00
ba2c92866c fix: mail knowledge link 2020-12-10 23:40:34 +08:00
91b016c231 update: log 2020-12-09 11:46:37 +08:00
d1b2e315ce update: log 2020-12-09 11:44:51 +08:00
8f0e3ce27d update: frontend 2020-12-08 18:59:07 +08:00
a47bac5ebc fix: ipad ua 2020-12-07 18:31:47 +08:00
d84af96535 update: app rules 2020-12-07 18:05:22 +08:00
29e7be855c rules: update default clash & surfboard & surge rules
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-12-05 21:25:11 +08:00
1ca9899437 Merge pull request #345 from wloot/buildvmess
clean up buildvmess()
2020-12-05 17:16:41 +08:00
254fdf6049 update: reset day 2020-12-04 21:17:25 +08:00
896d9eb030 update: frontend 2020-12-03 21:29:13 +08:00
501986bf53 update: frontend 2020-12-03 14:16:47 +08:00
c83c7478b7 update: frontend 2020-11-30 14:07:04 +08:00
e0e9187655 update: frontend 2020-11-30 12:11:06 +08:00
8a894a9a32 update: reset day 2020-11-30 12:04:00 +08:00
f9e55a3905 update: remove coupon log 2020-11-28 23:04:56 +08:00
f81ecbea5d Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-11-28 23:04:45 +08:00
487bece1bb update: ui 2020-11-28 23:04:11 +08:00
5f0e62f43c Merge pull request #344 from betaxab/p1
subscription: add shadowsocks SIP008 subscription support
2020-11-27 01:48:50 +08:00
a8b6ebdc60 Merge pull request #333 from betaxab/p2
rules: update default clash & surfboard & surge rules
2020-11-27 01:48:00 +08:00
9af98f72fd update: check commission 2020-11-26 15:07:47 +08:00
a54f64b698 update: check commission 2020-11-26 14:44:00 +08:00
3e97b26593 rules: update default clash & surfboard & surge rules
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-11-25 17:38:39 +08:00
cb2dd63e98 update: ui 2020-11-24 14:58:04 +08:00
512c06f15d update: ui 2020-11-24 00:57:27 +08:00
d645e9b98a update: app version 2020-11-22 16:25:56 +08:00
49c00c7a63 update: app version 2020-11-22 02:01:44 +08:00
7fe8a5b239 clean up buildvmess() 2020-11-20 00:53:53 +08:00
589eec9392 clean up buildvmess() 2020-11-20 00:30:49 +08:00
cada8ae9e8 update: app version 2020-11-19 21:30:27 +08:00
3ec90c4c52 update: app version 2020-11-19 21:27:53 +08:00
c1c6dccbfa update: app version 2020-11-19 21:24:14 +08:00
baf719fcff Merge remote-tracking branch 'origin/dev' into dev 2020-11-19 21:21:39 +08:00
7a9527d48a update: app version 2020-11-19 21:21:21 +08:00
7f12a79d07 subscription: add shadowsocks SIP008 subscription support
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-11-19 13:05:49 +08:00
a0dcf51c28 Merge pull request #343 from ColetteContreras/fixPoseidon
Fix Poseidon, add ETag
2020-11-19 12:24:19 +08:00
6e4693534f Merge pull request #332 from betaxab/p1
OrderAssign: fixes can not assign two year & three year plan
2020-11-19 12:19:08 +08:00
d2bce02d4e Add ETag for caching 2020-11-18 05:10:32 -05:00
2b5ef08fe0 Fix alterid 2020-11-18 04:56:27 -05:00
732edcad57 update: fix opcache return null 2020-11-18 11:12:38 +08:00
e030ed8d80 update: custom v2ray alter id 2020-11-17 23:02:12 +08:00
717043c96a update: custom v2ray alter id 2020-11-17 22:18:00 +08:00
04250fb7ac update: custom v2ray alter id 2020-11-17 22:12:51 +08:00
d1e831e961 update: custom v2ray alter id 2020-11-17 21:24:42 +08:00
fb9465b5a6 update: custom v2ray alter id 2020-11-17 21:23:16 +08:00
ad98651f93 update: admin ui 2020-11-17 17:30:28 +08:00
9035afaa78 update: commission process 2020-11-17 17:01:02 +08:00
92e3e4e01f update: fix status 2020-11-17 16:40:13 +08:00
be19a93efb update: fix commission first 2020-11-17 16:34:10 +08:00
3aabeff5de update: fix guest 2020-11-16 22:22:23 +08:00
5889d527ad update: server 2020-11-16 11:58:34 +08:00
2063e3c51d update: quantumult 2020-11-15 23:42:58 +08:00
592b751e2c update: server 2020-11-15 17:10:32 +08:00
b4212e7af4 update: server 2020-11-15 15:25:32 +08:00
c7aa63759e update: frontend 2020-11-15 15:20:51 +08:00
b3f075ae42 update: server 2020-11-15 15:14:47 +08:00
6fd7b48d6f update: server 2020-11-15 15:14:19 +08:00
f1fd300a35 update: version 2020-11-15 12:45:35 +08:00
204982e4c5 update: frontend 2020-11-15 12:44:23 +08:00
5df67aef16 update: app 2020-11-14 23:04:11 +08:00
ad8dee359f fix: v2ray advance config 2020-11-14 23:02:49 +08:00
6ccb3fa7be update: frontend 2020-11-14 21:33:53 +08:00
3bf11a136a update: server sort 2020-11-14 17:41:26 +08:00
2fc7c8c07c clear: files 2020-11-14 17:27:54 +08:00
5b79ea569c update: server sort 2020-11-14 17:26:17 +08:00
9fa53efc0c update: sql 2020-11-13 02:17:19 +08:00
fb257c999f update: comm 2020-11-11 17:15:07 +08:00
cefb2b3a27 update: app 2020-11-10 21:20:18 +08:00
7263b6d0ed update: user filter 2020-11-10 21:08:57 +08:00
e6d5aadbd2 update: server submit 2020-11-09 20:44:03 +08:00
342415d1db OrderAssign: fixes can not assign two year & three year plan
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-11-09 09:26:07 +08:00
1788250dc8 update: server submit 2020-11-09 00:15:20 +08:00
9917e21a61 update: server submit 2020-11-09 00:12:19 +08:00
59a8429456 update: user manage 2020-11-07 18:01:23 +08:00
9345bf7979 update: order 2020-11-07 17:23:47 +08:00
7042dce0a1 update: traffic submit 2020-11-07 16:38:57 +08:00
675353bbe1 fix: bug 2020-11-07 16:05:26 +08:00
93c90ddaf0 fix: bug 2020-11-07 16:04:42 +08:00
1d92d6b2f9 fix: more bug 2020-11-07 15:44:57 +08:00
34b8b666f4 Merge branch 'master' into dev 2020-11-04 02:12:03 +08:00
1f9a8c807f fix: install sql 2020-11-04 02:11:44 +08:00
6d9ab7dfe2 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-11-02 22:22:48 +08:00
ebce59a0d1 fix: stat 2020-11-02 22:22:18 +08:00
c6be6b2fbc Merge pull request #327 from v2board/dev
1.4
2020-11-01 16:50:50 +08:00
e6f1bae7e9 Merge pull request #325 from betaxab/p1
ShadowsocksTidalabController: update name
2020-10-29 14:53:58 +08:00
8c777807a9 ShadowsocksTidalabController: update name
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-10-29 14:48:37 +08:00
2ee8a234a9 Merge pull request #316 from betaxab/p2
Utils: adjust QuantumultX & Surge & Surfboard
2020-10-29 14:42:09 +08:00
5cba4f2517 Merge pull request #324 from v2board/revert-323-patch-1
Revert "Fix Shadowsocks of Surfboard"
2020-10-29 14:41:57 +08:00
0d750af0cb Revert "Fix Shadowsocks of Surfboard" 2020-10-29 14:41:38 +08:00
0342ed69b6 Merge pull request #315 from betaxab/p1
dumpCSV: add BOM to prevent Chinese garbled in Excel
2020-10-29 14:40:12 +08:00
655b1dd49a Merge pull request #323 from Mazeorz/patch-1
Fix Shadowsocks of Surfboard
2020-10-29 14:39:31 +08:00
8935989b06 update: commission withdraw limit 2020-10-29 01:01:49 +08:00
9dc19baeb6 update: commission withdraw limit 2020-10-29 01:00:22 +08:00
c02db9d957 update: commission withdraw limit 2020-10-29 00:37:37 +08:00
5d6fc44281 Utils: adjust QuantumultX & Surge & Surfboard & Clash
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-10-27 09:37:02 +08:00
2bc77526fd Fix Shadowsocks of Surfboard
Modified the generated parameter format
2020-10-26 16:49:16 +08:00
06d22e7585 fix: reset password 2020-10-26 16:29:48 +08:00
645638feb9 fix: reset passwordk 2020-10-26 16:22:38 +08:00
ebec80a75c fix: expired buy reset issue 2020-10-26 15:50:37 +08:00
d654e01f95 fix: expired buy reset issue 2020-10-26 15:39:06 +08:00
be18727de1 update: staff 2020-10-25 14:18:48 +08:00
e82c5bc5ff update: config 2020-10-25 03:56:43 +08:00
69caf0d61c update: knowledge var 2020-10-25 00:41:27 +08:00
eab3cc48bd update: coupon 2020-10-25 00:04:36 +08:00
bf4e63ed9f update: add subscribe flag 2020-10-24 23:43:45 +08:00
4901cbfea5 update: coupon generate 2020-10-24 23:26:59 +08:00
1a94a48cf4 update: coupon generate 2020-10-24 23:08:14 +08:00
4ca1b9e8ff update: telegram notify 2020-10-20 23:11:59 +08:00
f2e7ada947 update: frontend 2020-10-20 11:28:45 +08:00
07cc8275d8 feature: multiple ban 2020-10-20 02:09:32 +08:00
16ae59c992 feature: new send mail 2020-10-19 00:37:27 +08:00
bbdda28197 update: knowledge 2020-10-18 20:07:26 +08:00
550a787c1a update: user filter 2020-10-18 19:21:21 +08:00
392c3677bc update: knowledge 2020-10-18 18:57:21 +08:00
28262568b2 update: knowledge 2020-10-18 16:38:11 +08:00
a2877b9a78 feature: knowledge base & surplus switch 2020-10-18 03:19:16 +08:00
5c1558beb9 feature: knowledge base & surplus switch 2020-10-18 02:53:56 +08:00
9041ee2a37 feature: knowledge base & surplus switch 2020-10-18 02:51:32 +08:00
be3e808551 update: dump csv 2020-10-14 19:42:08 +08:00
1116c7ef28 fix: token2login not working 2020-10-14 16:21:20 +08:00
eecb01c24e update: shell 2020-10-14 15:27:11 +08:00
7b4aec55b8 update: ja-jp 2020-10-14 00:52:47 +08:00
393680c963 feature: i18n 2020-10-13 23:55:17 +08:00
c984fa2e0b update: order service 2020-10-11 01:22:43 +08:00
8213dd3a73 update: admin frontend 2020-10-11 00:54:29 +08:00
56714617ea fix: reset traffic 2020-10-11 00:28:09 +08:00
00c5016d5a update: shadowsocks more cipher 2020-10-09 23:52:25 +08:00
ec4cd8acef update: google recaptcha 2020-10-09 23:34:34 +08:00
11adc63a3e update: google recaptcha 2020-10-09 23:30:12 +08:00
54ab44c3fd feature: google recaptcha 2020-10-09 23:27:36 +08:00
575bbe5174 dumpCSV: add BOM to prevent Chinese garbled in Excel
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-10-09 08:29:42 +08:00
3d26fda064 feature: dump csv 2020-10-09 00:50:10 +08:00
2ede0f3f17 feature: dump csv 2020-10-08 22:42:26 +08:00
a00eca2fda feature: dump csv 2020-10-08 22:35:07 +08:00
7a2967f41c feature: user filter & user generate 2020-10-08 12:13:47 +08:00
90545a333f Merge pull request #310 from betaxab/p1
Add Quantumult X & Surge Shadowsocks support
2020-10-08 12:12:40 +08:00
c6bf19ea75 Merge pull request #311 from betaxab/p2
subs: add Surfboard Shadowsocks support
2020-10-08 12:12:21 +08:00
c326d0ab5c subs: add Surfboard Shadowsocks support
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-10-04 23:57:28 +08:00
1acf64db44 subs: add Quantumult X & Surge Shadowsocks support
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-10-04 22:55:40 +08:00
87a9f6727b fix: shadowsocks server port 2020-10-04 16:26:28 +08:00
6fd577d2c8 update: support shadowsocks app subscribe 2020-10-04 16:07:19 +08:00
63adb9c4a8 fix: trojan urlencode issue 2020-10-04 15:35:42 +08:00
3be39043cb update: server 2020-10-04 14:52:33 +08:00
1f4778eaff update: server 2020-10-04 14:34:48 +08:00
ba2e0a6b66 feature: shadowsocks and more 2020-10-04 14:21:09 +08:00
ac48f90678 fix: remind traffic 2020-09-23 23:15:43 +08:00
76f0b4d4d0 fix: remind traffic 2020-09-23 22:40:20 +08:00
29fc4206c0 update: opcache fail abort 2020-09-23 14:37:24 +08:00
c8e6c79dd0 feature: staff permission 2020-09-20 16:41:48 +08:00
f0f636c722 feature: staff permission 2020-09-19 22:52:05 +08:00
d500769bd7 feature: fuzzy search 2020-09-18 14:18:56 +08:00
644aedb999 fix: order surplus issue 2020-09-16 20:44:50 +08:00
c8f1a23358 fix: order surplus issue 2020-09-16 20:31:30 +08:00
bb1a59291f fix: reset traffic 2020-09-14 17:43:47 +08:00
0cfa6a0676 fix: reset traffic 2020-09-14 17:41:26 +08:00
8aa3fb2f09 optimization: code 2020-09-11 01:22:55 +08:00
fe0332a9f9 optimization: code 2020-09-11 01:02:31 +08:00
e1cac79318 fix: cycle sort 2020-09-10 22:15:07 +08:00
85c4e477d2 feature: more cycle 2020-09-10 21:48:44 +08:00
8a44ccb3fc fix: onetime reset package 2020-09-10 13:08:12 +08:00
136c5cf9e9 fix: onetime change cycle invalid 2020-09-10 13:01:55 +08:00
2402a59b57 fix: tutorial apple id not null 2020-09-08 17:14:23 +08:00
c97451276a fix: var name 2020-09-08 13:58:58 +08:00
2a53e93444 readme: thanks jetbrains 2020-09-04 13:49:58 +08:00
20911fb669 fix: telegram multiple notification issues 2020-09-01 15:41:01 +08:00
31a222f3d8 fix: clash proxies merge 2020-09-01 10:08:42 +08:00
c1f0521955 Merge pull request #300 from v2board/dev
1.3.2
2020-08-30 00:55:40 +08:00
d9d1947625 update: readme 2020-08-29 21:42:31 +08:00
366cf483ed frontend: email config gui 2020-08-24 13:50:34 +08:00
8c65a7d20e mail: smtp config gui 2020-08-22 14:28:47 +08:00
ce8652fe20 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-08-20 14:31:35 +08:00
03a9e16bb3 order: fix new buy order reset traffic issue 2020-08-20 14:31:16 +08:00
adb201ec86 Merge pull request #294 from betaxab/p1
Quantumult X data usage & expires notification
2020-08-19 22:55:25 +08:00
727c4c8f9f opt: mail config 2020-08-19 15:41:31 +08:00
7c54939970 opt: mail & reset traffic 2020-08-19 12:52:49 +08:00
f3e9d43c44 feature: smtp gui config 2020-08-18 23:27:31 +08:00
c77199ce68 update: frontend 2020-08-18 16:04:18 +08:00
b0afc290e3 quantumultx: add data plan & exipres notification support
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-08-16 01:15:59 +08:00
a8a6c6382e update 2020-08-13 21:36:59 +08:00
bd35c782f0 Merge pull request #295 from U-v-U/patch-1
add: added "sni" field for Trojan Link
2020-08-13 20:48:45 +08:00
QxQ
8f9e708e7b add: added "sni" field for Trojan Link 2020-08-13 19:30:07 +08:00
c94ecf1acd feature: update app 2020-08-13 14:27:12 +08:00
654e46a51e feature: telegram unbind & fix bind 2020-08-12 13:33:00 +08:00
8b32002cbd frontend: opt 2020-08-10 21:51:43 +08:00
fa51565928 frontend: opt 2020-08-10 21:49:36 +08:00
95d71ae77f feature: reset day 2020-08-10 21:17:01 +08:00
4f60cc5311 opt: reset server log schedule quarterly 2020-08-10 19:46:06 +08:00
58aacf562c feature: add telegram reply ticket 2020-08-03 16:28:30 +08:00
6a79ba5744 feature: add telegram reply ticket 2020-08-03 16:27:37 +08:00
4a9367158b fix: ticket service 2020-08-03 16:17:03 +08:00
f626acc0aa opt: ticket service 2020-08-03 16:10:56 +08:00
36bc93e1f8 fix: ticket telegram notify 2020-08-02 15:07:28 +08:00
04c06afcff fix: telegram service 2020-08-02 15:05:27 +08:00
c239a83ab3 frontend: plan tag add price 2020-07-31 15:33:56 +08:00
6e7fb4284a telegram: order notify 2020-07-31 15:22:27 +08:00
eb53067e67 vmess: sniffing opt 2020-07-31 15:11:35 +08:00
c9357b602a vmess: sniffing opt 2020-07-31 15:03:12 +08:00
855e3c1c26 Merge pull request #290 from betaxab/p1
Fixes Quantumult X tls-verification
2020-07-29 20:16:30 +08:00
f506d3fe96 quantumultx: fixes tls-verification
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-07-29 20:15:54 +08:00
17d873c8ba Merge pull request #288 from betaxab/patch-1
PHP 7.4 implode compatibility fix
2020-07-28 16:34:52 +08:00
f274cb0d4b PHP 7.4 implode compatibility fix
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-07-28 16:11:27 +08:00
0486f4e6ac frontend: more update 2020-07-26 17:27:42 +08:00
9a68ff6c61 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-07-26 17:23:32 +08:00
1cf0ccb865 feature: add vmess global rules 2020-07-26 17:17:35 +08:00
5b1aee7a79 Merge pull request #285 from betaxab/patch-1
修复 Shadowrocket 空格问题
2020-07-25 16:14:43 +08:00
1b4d03044d quantumultx: fix wss 2020-07-25 15:34:25 +08:00
fb732a8307 frontend: fix user cancel button 2020-07-25 14:54:33 +08:00
3cf39ff045 frontend: progress opt 2020-07-25 02:17:03 +08:00
f80b2cd439 order: fix mgate payment 2020-07-25 00:33:34 +08:00
a75928a91b opcache: fix opcache issue 2020-07-25 00:28:04 +08:00
43193386bb opcache: fix opcache issue 2020-07-25 00:19:13 +08:00
f4b6f0aefe vmess: fix rulesttings is null object 2020-07-24 18:08:36 +08:00
4852e6e79d mgate: fix error report 2020-07-24 02:17:30 +08:00
d8ee3c6c51 shadowrocket: remark space issue fixes, add shadowrocket STATUS feature
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-07-23 12:40:26 +08:00
e3aa467a74 vmess: fix possible config issue 2020-07-22 23:45:00 +08:00
6bbe0e99fa Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-07-22 17:01:09 +08:00
7e3da7b970 trojan: fix child node online count 2020-07-22 17:00:46 +08:00
e096fd064d Merge pull request #278 from DesperadoJ/patch-1
Enable UDP in Clash
2020-07-21 16:19:45 +08:00
9ea482582e Merge pull request #283 from phlinhng/shadowrocket-url
Shadowrocket specified url generator
2020-07-21 12:11:49 +08:00
84d852f396 Shadowrocket specified url generator
shadowrocket ClientController

enable tfo on vmess for shadowrocket

enable tfo on trojan for shadowrocket

fix typo
2020-07-21 00:17:25 +08:00
e1ab2c9f6e frontend: update payment 2020-07-20 19:58:27 +08:00
1f9ebe1ff7 Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-07-20 19:58:03 +08:00
cff5d205bc feature: added epay & mGate payment 2020-07-20 19:56:39 +08:00
f8ea476bcc Merge pull request #281 from betaxab/patch-1
clash: add tls servername support
2020-07-18 16:23:00 +08:00
d94b361f2f clash: add tls servername support
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-07-18 00:55:24 +08:00
e500fb77dc fix: payment validate 2020-07-17 23:25:46 +08:00
dc98303bb4 feature: add new payment gateway 2020-07-17 21:20:43 +08:00
59761baa4d feature: add custom admin path 2020-07-17 20:01:28 +08:00
b974d1d227 sql: fix historical issues 2020-07-17 19:44:16 +08:00
bb9b1c4a57 optimization: validated 2020-07-15 15:57:12 +08:00
e2a07cc4d1 order: expire user is new order 2020-07-13 17:09:13 +08:00
85fd3d000c Enable UDP in Clash 2020-07-12 11:19:38 +08:00
f204dd2d72 coupon: fix undefined index 2020-07-10 13:21:22 +08:00
137b018aad order: fix reset price buy 2020-07-07 14:11:15 +08:00
72d2b79a9f stripe: add jpy 2020-07-06 19:45:41 +08:00
fcc6332dcd frontend: fix datepicker 2020-07-06 19:40:58 +08:00
e0927de030 Merge pull request #273 from v2board/dev
1.3.1
2020-07-05 19:25:40 +08:00
b706b8b3a1 telegram: fix api 2020-07-05 19:24:25 +08:00
f3dee5c230 Merge pull request #271 from v2board/dev
1.3.1
2020-07-05 14:46:47 +08:00
b984b8dd98 frontend: fix tag 2020-07-05 14:45:44 +08:00
d2589d340d frontend: fix transfer convert 2020-07-05 14:34:03 +08:00
b5376c9c1e Merge pull request #270 from v2board/dev
1.3.1
2020-07-05 14:19:50 +08:00
7c69031db8 update clash default config 2020-07-05 14:17:59 +08:00
43f1dbaedb Merge branch 'dev' of https://github.com/v2board/v2board into dev 2020-07-05 14:16:42 +08:00
b083c4fe78 update version 2020-07-05 14:15:52 +08:00
b2c53804d7 Merge pull request #264 from DesperadoJ/dev
Update subscribe URL for Surge & Surfboard
2020-07-05 14:08:30 +08:00
92e6947525 Merge pull request #269 from betaxab/patch-1
surge: Disable network framework
2020-07-05 14:06:50 +08:00
91bf999162 surge: Disable network framework, because it will cause the Surge to stop running
Signed-off-by: Beta Soft <betaxab@gmail.com>
2020-07-05 14:03:25 +08:00
4f8ba2b59d support new clash config 2020-07-05 13:18:43 +08:00
001f0ced41 update client 2020-07-04 17:40:23 +08:00
58ad896e45 update client 2020-07-04 17:09:57 +08:00
6e789037d1 fix trojan user online 2020-07-03 15:24:10 +08:00
365775970c update 2020-07-02 21:45:32 +08:00
971637ffd6 update tls path 2020-07-02 21:41:14 +08:00
a905a5ad27 fix trojan server list sort 2020-07-02 01:31:44 +08:00
565072e333 support trojan parent status 2020-07-01 23:08:26 +08:00
dc42e82dc0 update trojan 2020-07-01 18:50:31 +08:00
4c457d183f update trojan 2020-07-01 18:49:51 +08:00
2d67446ce3 update trojan 2020-07-01 18:41:12 +08:00
4c73d55342 update trojan 2020-07-01 18:40:47 +08:00
ee762fe69a update trojan 2020-07-01 18:32:41 +08:00
2782bd1a2c update trojan 2020-07-01 18:31:44 +08:00
2a237abade update trojan 2020-07-01 15:38:23 +08:00
75588d46f1 update trojan 2020-07-01 15:23:39 +08:00
37449b6640 update 2020-07-01 14:08:51 +08:00
0248a562b3 fix 2020-06-27 20:08:16 +08:00
13cfb11a85 optimization 2020-06-26 16:23:11 +08:00
015798accd update plan fetch 2020-06-24 14:26:43 +08:00
f10fc4c13e update version clean cache 2020-06-24 13:56:33 +08:00
b5a9b3e68c more optimization && add plan user count 2020-06-24 13:47:25 +08:00
ffd13fbc64 support clash for android oneclick subscribe 2020-06-21 23:56:09 +08:00
8bff334758 optimize frontend 2020-06-21 13:58:43 +08:00
a63a154ee2 recovery quantumult, next month delete. 2020-06-20 22:59:05 +08:00
976dfd2d98 recovery quantumult, next month delete. 2020-06-20 22:50:43 +08:00
4f277097f6 add custom coupon code 2020-06-20 19:18:57 +08:00
cfd72ac515 update telegram commands 2020-06-20 18:08:26 +08:00
6324645f08 add custom coupon code 2020-06-20 00:50:21 +08:00
80e6fb7186 update server group 2020-06-19 01:25:42 +08:00
13f17d41f0 update trojan server 2020-06-18 20:25:53 +08:00
a45f8e121d update set server copy show default 2020-06-18 17:39:29 +08:00
b6f2a034ec bot add getLatestUrl 2020-06-18 16:26:50 +08:00
d3d18d2390 add server description 2020-06-18 02:25:05 +08:00
65a4abf51d update 2020-06-15 15:38:04 +08:00
0c7d27e331 Update subscribe URL for Surge & Surfboard 2020-06-14 21:48:07 +08:00
ac13d1a8e1 fix subscribe 2020-06-14 12:33:47 +08:00
0f43aee0e3 fix trojan 2020-06-14 02:29:17 +08:00
bbb42c0d46 coupon plan limit 2020-06-13 23:27:43 +08:00
3cdfc69b5d fix trojan server online 2020-06-13 19:02:58 +08:00
bf915214dd support trojan surge 2020-06-13 18:33:42 +08:00
2fc77a38f2 fix trojan shadowrocket remarks 2020-06-12 17:16:21 +08:00
e733a53e85 fix trojan shadowrocket remarks 2020-06-12 17:12:17 +08:00
ff3451d0e0 update trojan server 2020-06-12 17:05:36 +08:00
d418443404 update 2020-06-12 16:59:18 +08:00
172af72761 update trojan server 2020-06-12 14:37:17 +08:00
2414ad5d96 update trojan server 2020-06-12 14:22:00 +08:00
796de25e2d update trojan server 2020-06-12 02:06:56 +08:00
9874ef2f72 update trojan server 2020-06-12 01:31:00 +08:00
da98dcad9c update trojan server 2020-06-12 00:40:37 +08:00
0e4d5c9e99 update trojan server 2020-06-12 00:35:35 +08:00
066fd96fb5 update trojan server 2020-06-12 00:18:35 +08:00
f24b38a4ba update trojan server 2020-06-11 21:50:54 +08:00
a1ae4caee3 update trojan server 2020-06-11 21:50:18 +08:00
747a3c5c06 update frontend 2020-06-11 21:39:14 +08:00
943304eb02 support trojan and more optimization 2020-06-11 20:47:02 +08:00
9b82df98f5 update 2020-06-08 01:08:07 +08:00
3df4e04605 update online user stat 2020-06-05 13:38:17 +08:00
eace40eb08 update online user stat 2020-06-05 13:30:38 +08:00
49b60f7c23 update online user stat 2020-06-05 13:26:22 +08:00
47a6c4077a update online user stat 2020-06-05 13:25:32 +08:00
3200c427ce update cacheServerStat 2020-06-05 01:16:27 +08:00
a40a1ab674 update cacheServerStat 2020-06-05 01:13:45 +08:00
397436761f update server stat 2020-06-05 00:52:17 +08:00
0fc9b1d867 update server stat and app default config 2020-06-05 00:28:14 +08:00
d6b22011ba update server stat and app default config 2020-06-05 00:25:15 +08:00
78f8bd6906 support stripe credit card 2020-06-04 19:17:47 +08:00
2e64b3873c support stripe credit card 2020-06-04 18:59:02 +08:00
022df26134 stripe test 2020-06-04 18:39:54 +08:00
54e59f1178 stripe test 2020-06-04 18:33:44 +08:00
5982f3349c stripe test 2020-06-04 18:29:41 +08:00
beb2c90321 stripe test 2020-06-04 18:28:47 +08:00
036bb00c08 stripe test 2020-06-04 18:26:28 +08:00
b001f54b84 stripe test 2020-06-04 18:12:53 +08:00
15a3f16b9f stripe test 2020-06-04 18:06:18 +08:00
5d413fac55 stripe test 2020-06-04 18:02:19 +08:00
136cffcf13 stripe test 2020-06-04 18:00:33 +08:00
ca04634537 possible change order issues 2020-06-02 03:11:31 +08:00
288bd43d44 fix search 2020-06-02 03:02:07 +08:00
26df97a862 opt 2020-06-02 01:43:22 +08:00
213 changed files with 7438 additions and 2871 deletions

1
.gitignore vendored
View File

@ -18,3 +18,4 @@ composer.phar
composer.lock
yarn.lock
docker-compose.yml
.DS_Store

View File

@ -1,16 +0,0 @@
php:
preset: laravel
enabled:
- alpha_ordered_imports
disabled:
- length_ordered_imports
- unused_use
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

View File

@ -5,6 +5,7 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class CheckCommission extends Command
{
@ -47,7 +48,8 @@ class CheckCommission extends Command
{
if ((int)config('v2board.commission_auto_check_enable', 1)) {
Order::where('commission_status', 0)
->where('status', 3)
->where('invite_user_id', '!=', NULL)
->whereNotIn('status', [0, 2])
->where('updated_at', '<=', strtotime('-3 day', time()))
->update([
'commission_status' => 1
@ -58,17 +60,24 @@ class CheckCommission extends Command
public function autoPayCommission()
{
$order = Order::where('commission_status', 1)
->where('status', 3)
->where('invite_user_id', '!=', NULL)
->get();
foreach ($order as $item) {
if ($item->invite_user_id) {
$inviter = User::find($item->invite_user_id);
if (!$inviter) continue;
$inviter = User::find($item->invite_user_id);
if (!$inviter) continue;
if ((int)config('v2board.withdraw_close_enable', 0)) {
$inviter->balance = $inviter->balance + $item->commission_balance;
} else {
$inviter->commission_balance = $inviter->commission_balance + $item->commission_balance;
if ($inviter->save()) {
$item->commission_status = 2;
$item->save();
}
DB::beginTransaction();
if ($inviter->save()) {
$item->commission_status = 2;
if (!$item->save()) {
DB::rollBack();
continue;
}
DB::commit();
}
}
}

View File

@ -7,8 +7,6 @@ use Illuminate\Console\Command;
use App\Models\Order;
use App\Models\User;
use App\Models\Plan;
use App\Utils\Helper;
use App\Models\Coupon;
use Illuminate\Support\Facades\DB;
class CheckOrder extends Command
@ -44,112 +42,23 @@ class CheckOrder extends Command
*/
public function handle()
{
$orders = Order::get();
ini_set('memory_limit', -1);
$orders = Order::whereIn('status', [0, 1])
->get();
foreach ($orders as $item) {
$orderService = new OrderService($item);
switch ($item->status) {
// cancel
case 0:
if (strtotime($item->created_at) <= (time() - 1800)) {
$orderService = new OrderService($item);
$orderService->cancel();
}
break;
case 1:
$this->orderHandle($item);
$orderService->open();
break;
}
}
}
private function orderHandle(Order $order)
{
$user = User::find($order->user_id);
$plan = Plan::find($order->plan_id);
if ($order->refund_amount) {
$user->balance = $user->balance + $order->refund_amount;
}
DB::beginTransaction();
if ($order->surplus_order_ids) {
try {
Order::whereIn('id', json_decode($order->surplus_order_ids))->update([
'status' => 4
]);
} catch (\Exception $e) {
DB::rollback();
abort(500, '开通失败');
}
}
switch ((string)$order->cycle) {
case 'onetime_price':
$this->buyByOneTime($order, $user, $plan);
break;
case 'reset_price':
$this->buyReset($user);
break;
default:
$this->buyByCycle($order, $user, $plan);
}
if (!$user->save()) {
DB::rollBack();
abort(500, '开通失败');
}
$order->status = 3;
if (!$order->save()) {
DB::rollBack();
abort(500, '开通失败');
}
DB::commit();
}
private function buyReset(User $user)
{
$user->u = 0;
$user->d = 0;
}
private function buyByCycle(Order $order, User $user, Plan $plan)
{
// change plan process
if ((int)$order->type === 3) {
$user->expired_at = time();
}
$user->transfer_enable = $plan->transfer_enable * 1073741824;
if ((int)config('v2board.renew_reset_traffic_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);
}
private function buyByOneTime(Order $order, User $user, Plan $plan)
{
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->u = 0;
$user->d = 0;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = NULL;
}
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);
}
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Console\Commands;
use App\Services\ServerService;
use App\Services\TelegramService;
use App\Utils\CacheKey;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
class CheckServer extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'check:server';
/**
* 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->checkOffline();
}
private function checkOffline()
{
$serverService = new ServerService();
$servers = $serverService->getAllServers();
foreach ($servers as $server) {
if ($server['parent_id']) continue;
if ($server['last_check_at'] && (time() - $server['last_check_at']) > 1800) {
$telegramService = new TelegramService();
$message = sprintf(
"节点掉线通知\r\n----\r\n节点名称:%s\r\n节点地址:%s\r\n",
$server['name'],
$server['host']
);
$telegramService->sendMessageWithAdmin($message);
Cache::forget(CacheKey::get(sprintf("SERVER_%s_LAST_CHECK_AT", strtoupper($server['type'])), $server->id));
}
}
}
}

View File

@ -4,9 +4,11 @@ namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class ResetTraffic extends Command
{
protected $builder;
/**
* The name and signature of the console command.
*
@ -29,6 +31,8 @@ class ResetTraffic extends Command
public function __construct()
{
parent::__construct();
$this->builder = User::where('expired_at', '!=', NULL)
->where('expired_at', '>', time());
}
/**
@ -38,36 +42,38 @@ class ResetTraffic extends Command
*/
public function handle()
{
$user = User::where('expired_at', '!=', NULL)
->where('expired_at', '>', time());
DB::beginTransaction();
$resetTrafficMethod = config('v2board.reset_traffic_method', 0);
switch ((int)$resetTrafficMethod) {
// 1 a month
case 0:
$this->resetByMonthFirstDay($user);
$this->resetByMonthFirstDay();
break;
// expire day
case 1:
$this->resetByExpireDay($user);
$this->resetByExpireDay();
break;
}
DB::commit();
}
private function resetByMonthFirstDay($user):void
private function resetByMonthFirstDay():void
{
$builder = $this->builder;
if ((string)date('d') === '01') {
$user->update([
$builder->update([
'u' => 0,
'd' => 0
]);
}
}
private function resetByExpireDay($user):void
private function resetByExpireDay():void
{
$builder = $this->builder;
$lastDay = date('d', strtotime('last day of +0 months'));
$users = [];
foreach ($user->get() as $item) {
foreach ($builder->get() as $item) {
$expireDay = date('d', $item->expired_at);
$today = date('d');
if ($expireDay === $today) {

View File

@ -43,7 +43,6 @@ class SendRemindMail extends Command
$users = User::all();
foreach ($users as $user) {
if ($user->remind_expire) $this->remindExpire($user);
if ($user->remind_traffic) $this->remindTraffic($user);
}
}
@ -61,31 +60,4 @@ class SendRemindMail extends Command
]);
}
}
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', 'like', '%remindTraffic%')
->count();
if ($sendCount > 0) return;
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的流量使用已达到80%',
'template_name' => 'remindTraffic',
'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

@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Models\Order;
use Illuminate\Console\Command;
class Test extends Command

View File

@ -1,47 +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\Cache;
class V2boardCache extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'v2board: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()
{
}
}

View File

@ -114,7 +114,7 @@ class V2boardInstall extends Command
abort(500, '管理员密码长度最小为8位字符');
}
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true);
$user->uuid = Helper::guid(true);
$user->token = Helper::guid();
$user->is_admin = 1;
return $user->save();

View File

@ -0,0 +1,100 @@
<?php
namespace App\Console\Commands;
use App\Jobs\StatServerJob;
use Illuminate\Console\Command;
use App\Models\Order;
use App\Models\StatOrder;
use App\Models\ServerLog;
use Illuminate\Support\Facades\DB;
class V2boardStatistics extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'v2board:statistics';
/**
* 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->statOrder();
$this->statServer();
}
private function statOrder()
{
$endAt = strtotime(date('Y-m-d'));
$startAt = strtotime('-1 day', $endAt);
$builder = Order::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt)
->whereNotIn('status', [0, 2]);
$orderCount = $builder->count();
$orderAmount = $builder->sum('total_amount');
$builder = $builder->where('commission_balance', '!=', 0);
$commissionCount = $builder->count();
$commissionAmount = $builder->sum('commission_balance');
$data = [
'order_count' => $orderCount,
'order_amount' => $orderAmount,
'commission_count' => $commissionCount,
'commission_amount' => $commissionAmount,
'record_type' => 'd',
'record_at' => $startAt
];
$statistic = StatOrder::where('record_at', $startAt)
->where('record_type', 'd')
->first();
if ($statistic) {
$statistic->update($data);
return;
}
StatOrder::create($data);
}
private function statServer()
{
$endAt = strtotime(date('Y-m-d'));
$startAt = strtotime('-1 day', $endAt);
$statistics = ServerLog::select([
'server_id',
'method as server_type',
DB::raw("sum(u) as u"),
DB::raw("sum(d) as d"),
])
->where('log_at', '>=', $startAt)
->where('log_at', '<', $endAt)
->groupBy('server_id', 'method')
->get()
->toArray();
foreach ($statistics as $statistic) {
$statistic['record_type'] = 'd';
$statistic['record_at'] = $startAt;
StatServerJob::dispatch($statistic);
}
}
}

View File

@ -25,13 +25,13 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule)
{
// v2board
$schedule->command('v2board:cache')->hourly();
$schedule->command('v2board:statistics')->dailyAt('0:10');
// check
$schedule->command('check:order')->everyMinute();
$schedule->command('check:commission')->everyMinute();
// reset
$schedule->command('reset:traffic')->daily();
$schedule->command('reset:serverLog')->monthly();
$schedule->command('reset:serverLog')->quarterly();
// send
$schedule->command('send:remindMail')->dailyAt('11:30');
}

View File

@ -21,6 +21,17 @@ class ConfigController extends Controller
]);
}
public function getThemeTemplate()
{
$path = public_path('theme/');
$files = array_map(function ($item) use ($path) {
return str_replace($path, '', $item);
}, glob($path . '*'));
return response([
'data' => $files
]);
}
public function setTelegramWebhook(Request $request)
{
$telegramService = new TelegramService($request->input('telegram_bot_token'));
@ -46,7 +57,10 @@ class ConfigController extends Controller
'invite_gen_limit' => config('v2board.invite_gen_limit', 5),
'invite_never_expire' => config('v2board.invite_never_expire', 0),
'commission_first_time_enable' => config('v2board.commission_first_time_enable', 1),
'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1)
'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1),
'commission_withdraw_limit' => config('v2board.commission_withdraw_limit', 100),
'commission_withdraw_method' => config('v2board.commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
'withdraw_close_enable' => config('v2board.withdraw_close_enable', 0)
],
'site' => [
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
@ -60,12 +74,19 @@ class ConfigController extends Controller
'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),
'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0)
'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0),
'recaptcha_enable' => (int)config('v2board.recaptcha_enable', 0),
'recaptcha_key' => config('v2board.recaptcha_key'),
'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
'tos_url' => config('v2board.tos_url')
],
'subscribe' => [
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0),
'renew_reset_traffic_enable' => (int)config('v2board.renew_reset_traffic_enable', 1)
'surplus_enable' => (int)config('v2board.surplus_enable', 1),
'new_order_event_id' => (int)config('v2board.new_order_event_id', 0),
'renew_order_event_id' => (int)config('v2board.renew_order_event_id', 0),
'change_order_event_id' => (int)config('v2board.change_order_event_id', 0),
],
'pay' => [
// alipay
@ -76,40 +97,68 @@ class ConfigController extends Controller
// stripe
'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable', 0),
'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable', 0),
'stripe_card_enable' => (int)config('v2board.stripe_card_enable', 0),
'stripe_sk_live' => config('v2board.stripe_sk_live'),
'stripe_pk_live' => config('v2board.stripe_pk_live'),
'stripe_webhook_key' => config('v2board.stripe_webhook_key'),
'stripe_currency' => config('v2board.stripe_currency', 'hkd'),
// bitpayx
'bitpayx_name' => config('v2board.bitpayx_name', '聚合支付'),
'bitpayx_name' => config('v2board.bitpayx_name', '在线支付'),
'bitpayx_enable' => (int)config('v2board.bitpayx_enable', 0),
'bitpayx_appsecret' => config('v2board.bitpayx_appsecret'),
// paytaro
'paytaro_name' => config('v2board.paytaro_name', '聚合支付'),
'paytaro_enable' => (int)config('v2board.paytaro_enable', 0),
'paytaro_app_id' => config('v2board.paytaro_app_id'),
'paytaro_app_secret' => config('v2board.paytaro_app_secret')
// mGate
'mgate_name' => config('v2board.mgate_name', '在线支付'),
'mgate_enable' => (int)config('v2board.mgate_enable', 0),
'mgate_url' => config('v2board.mgate_url'),
'mgate_app_id' => config('v2board.mgate_app_id'),
'mgate_app_secret' => config('v2board.mgate_app_secret'),
// Epay
'epay_name' => config('v2board.epay_name', '在线支付'),
'epay_enable' => (int)config('v2board.epay_enable', 0),
'epay_url' => config('v2board.epay_url'),
'epay_pid' => config('v2board.epay_pid'),
'epay_key' => config('v2board.epay_key'),
],
'frontend' => [
'frontend_theme' => config('v2board.frontend_theme', 'v2board'),
'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')
'frontend_background_url' => config('v2board.frontend_background_url'),
'frontend_admin_path' => config('v2board.frontend_admin_path', 'admin'),
'frontend_customer_service_method' => config('v2board.frontend_customer_service_method', 0),
'frontend_customer_service_id' => config('v2board.frontend_customer_service_id'),
],
'server' => [
'server_token' => config('v2board.server_token'),
'server_license' => config('v2board.server_license'),
'server_log_level' => config('v2board.server_log_level', 'none')
'server_log_enable' => config('v2board.server_log_enable', 0),
'server_v2ray_domain' => config('v2board.server_v2ray_domain'),
'server_v2ray_protocol' => config('v2board.server_v2ray_protocol'),
],
'tutorial' => [
'apple_id' => config('v2board.apple_id')
],
'email' => [
'email_template' => config('v2board.email_template', 'default')
'email_template' => config('v2board.email_template', 'default'),
'email_host' => config('v2board.email_host'),
'email_port' => config('v2board.email_port'),
'email_username' => config('v2board.email_username'),
'email_password' => config('v2board.email_password'),
'email_encryption' => config('v2board.email_encryption'),
'email_from_address' => config('v2board.email_from_address')
],
'telegram' => [
'telegram_bot_enable' => config('v2board.telegram_bot_enable', 0),
'telegram_bot_token' => config('v2board.telegram_bot_token')
],
'app' => [
'windows_version' => config('v2board.windows_version'),
'windows_download_url' => config('v2board.windows_download_url'),
'macos_version' => config('v2board.macos_version'),
'macos_download_url' => config('v2board.macos_download_url'),
'android_version' => config('v2board.android_version'),
'android_download_url' => config('v2board.android_download_url')
]
]
]);
@ -120,7 +169,7 @@ class ConfigController extends Controller
$data = $request->input();
$array = \Config::get('v2board');
foreach ($data as $k => $v) {
if (!in_array($k, array_keys(ConfigSave::RULES))) {
if (!in_array($k, array_keys($request->validated()))) {
abort(500, '参数' . $k . '不在规则内,禁止修改');
}
$array[$k] = $v;
@ -129,10 +178,12 @@ class ConfigController extends Controller
if (!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
abort(500, '修改失败');
}
\Artisan::call('config:cache');
if (function_exists('opcache')) {
opcache_reset();
if (function_exists('opcache_reset')) {
if (opcache_reset() === false) {
abort(500, '缓存清除失败,请卸载或检查opcache配置状态');
}
}
\Artisan::call('config:cache');
return response([
'data' => true
]);

View File

@ -3,33 +3,47 @@
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\CouponSave;
use App\Http\Requests\Admin\CouponGenerate;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Coupon;
use App\Utils\Helper;
use Illuminate\Support\Facades\DB;
class CouponController extends Controller
{
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = Coupon::orderBy($sort, $sortType);
$total = $builder->count();
$coupons = $builder->forPage($current, $pageSize)
->get();
foreach ($coupons as $k => $v) {
if ($coupons[$k]['limit_plan_ids']) $coupons[$k]['limit_plan_ids'] = json_decode($coupons[$k]['limit_plan_ids']);
}
return response([
'data' => Coupon::all()
'data' => $coupons,
'total' => $total
]);
}
public function save(CouponSave $request)
{
$params = $request->only([
'name',
'type',
'value',
'started_at',
'ended_at',
'limit_use'
]);
$params = $request->validated();
if (isset($params['limit_plan_ids'])) {
$params['limit_plan_ids'] = json_encode($params['limit_plan_ids']);
}
if (!$request->input('id')) {
$params['code'] = Helper::randomChar(8);
if (!isset($params['code'])) {
$params['code'] = Helper::randomChar(8);
}
if (!Coupon::create($params)) {
abort(500, '创建失败');
}
@ -46,6 +60,70 @@ class CouponController extends Controller
]);
}
public function generate(CouponGenerate $request)
{
if ($request->input('generate_count')) {
$this->multiGenerate($request);
return;
}
$params = $request->validated();
if (isset($params['limit_plan_ids'])) {
$params['limit_plan_ids'] = json_encode($params['limit_plan_ids']);
}
if (!$request->input('id')) {
if (!isset($params['code'])) {
$params['code'] = Helper::randomChar(8);
}
if (!Coupon::create($params)) {
abort(500, '创建失败');
}
} else {
try {
Coupon::find($request->input('id'))->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
private function multiGenerate(CouponGenerate $request)
{
$coupons = [];
$coupon = $request->validated();
if (isset($coupon['limit_plan_ids'])) {
$coupon['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
}
$coupon['created_at'] = $coupon['updated_at'] = time();
unset($coupon['generate_count']);
for ($i = 0;$i < $request->input('generate_count');$i++) {
$coupon['code'] = Helper::randomChar(8);
array_push($coupons, $coupon);
}
DB::beginTransaction();
if (!Coupon::insert($coupons)) {
DB::rollBack();
abort(500, '生成失败');
}
DB::commit();
$data = "名称,类型,金额或比例,开始时间,结束时间,可用次数,可用于订阅,券码,生成时间\r\n";
foreach($coupons as $coupon) {
$type = ['', '金额', '比例'][$coupon['type']];
$value = ['', ($coupon['value'] / 100),$coupon['value']][$coupon['type']];
$startTime = date('Y-m-d H:i:s', $coupon['started_at']);
$endTime = date('Y-m-d H:i:s', $coupon['ended_at']);
$limitUse = $coupon['limit_use'] ?? '不限制';
$createTime = date('Y-m-d H:i:s', $coupon['created_at']);
$limitPlanIds = $coupon['limit_plan_ids'] ?? '不限制';
$data .= "{$coupon['name']},{$type},{$value},{$startTime},{$endTime},{$limitUse},{$limitPlanIds},{$coupon['code']},{$createTime}\r\n";
}
echo $data;
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {

View File

@ -0,0 +1,113 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\KnowledgeSave;
use App\Http\Requests\Admin\KnowledgeSort;
use App\Models\Knowledge;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
class KnowledgeController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$knowledge = Knowledge::find($request->input('id'))->toArray();
if (!$knowledge) abort(500, '知识不存在');
return response([
'data' => $knowledge
]);
}
return response([
'data' => Knowledge::select(['title', 'id', 'updated_at', 'category', 'show'])
->orderBy('sort', 'ASC')
->get()
]);
}
public function getCategory(Request $request)
{
return response([
'data' => array_keys(Knowledge::get()->groupBy('category')->toArray())
]);
}
public function save(KnowledgeSave $request)
{
$params = $request->validated();
if (!$request->input('id')) {
if (!Knowledge::create($params)) {
abort(500, '创建失败');
}
} else {
try {
Knowledge::find($request->input('id'))->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function show(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$knowledge = Knowledge::find($request->input('id'));
if (!$knowledge) {
abort(500, '知识不存在');
}
$knowledge->show = $knowledge->show ? 0 : 1;
if (!$knowledge->save()) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function sort(KnowledgeSort $request)
{
DB::beginTransaction();
try {
foreach ($request->input('knowledge_ids') as $k => $v) {
$knowledge = Knowledge::find($v);
$knowledge->timestamps = false;
$knowledge->update(['sort' => $k + 1]);
}
} catch (\Exception $e) {
DB::rollBack();
abort(500, '保存失败');
}
DB::commit();
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$knowledge = Knowledge::find($request->input('id'));
if (!$knowledge) {
abort(500, '知识不存在');
}
if (!$knowledge->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@ -1,47 +0,0 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\MailSend;
use App\Services\UserService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Jobs\SendEmailJob;
class MailController extends Controller
{
public function send(MailSend $request)
{
$userService = new UserService();
$users = [];
switch ($request->input('type')) {
case 1: $users = $userService->getAllUsers();
break;
case 2: $users = $userService->getUsersByIds($request->input('receiver'));
break;
// available users
case 3: $users = $userService->getAvailableUsers();
break;
// un available users
case 4: $users = $userService->getUnAvailbaleUsers();
break;
}
foreach ($users as $user) {
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'notify',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url'),
'content' => $request->input('content')
]
]);
}
return response([
'data' => true
]);
}
}

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\OrderAssign;
use App\Http\Requests\Admin\OrderUpdate;
use App\Http\Requests\Admin\OrderFetch;
use App\Services\OrderService;
use App\Utils\Helper;
use Illuminate\Http\Request;
@ -15,25 +16,36 @@ use Illuminate\Support\Facades\DB;
class OrderController extends Controller
{
public function fetch(Request $request)
private function filter(Request $request, &$builder)
{
if ($request->input('filter')) {
foreach ($request->input('filter') as $filter) {
if ($filter['key'] === 'email') {
$user = User::where('email', "%{$filter['value']}%")->first();
if (!$user) continue;
$builder->where('user_id', $user->id);
continue;
}
if ($filter['condition'] === '模糊') {
$filter['condition'] = 'like';
$filter['value'] = "%{$filter['value']}%";
}
$builder->where($filter['key'], $filter['condition'], $filter['value']);
}
}
}
public function fetch(OrderFetch $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$orderModel = Order::orderBy('created_at', 'DESC');
if ($request->input('trade_no')) {
$orderModel->where('trade_no', $request->input('trade_no'));
}
if ($request->input('is_commission')) {
$orderModel->where('invite_user_id', '!=', NULL);
$orderModel->where('status', 3);
$orderModel->whereNotIn('status', [0, 2]);
$orderModel->where('commission_balance', '>', 0);
}
if ($request->input('id')) {
$orderModel->where('id', $request->input('id'));
}
if ($request->input('user_id')) {
$orderModel->where('user_id', $request->input('user_id'));
}
$this->filter($request, $orderModel);
$total = $orderModel->count();
$res = $orderModel->forPage($current, $pageSize)
->get();

View File

@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Services\PaymentService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Payment;
class PaymentController extends Controller
{
public function getPaymentMethods()
{
$methods = [];
foreach (glob(base_path('app//Payments') . '/*.php') as $file) {
array_push($methods, pathinfo($file)['filename']);
}
return response([
'data' => $methods
]);
}
public function fetch()
{
$payments = Payment::all();
foreach ($payments as $k => $v) {
$payments[$k]['notify_url'] = url("/api/v1/guest/payment/notify/{$v->payment}/{$v->uuid}");
}
return response([
'data' => $payments
]);
}
public function getPaymentForm(Request $request)
{
$paymentService = new PaymentService($request->input('payment'), $request->input('id'));
return response([
'data' => $paymentService->form()
]);
}
public function save(Request $request)
{
if ($request->input('id')) {
$payment = Payment::find($request->input('id'));
if (!$payment) abort(500, '支付方式不存在');
try {
$payment->update($request->input());
} catch (\Exception $e) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
if (!Payment::create([
'name' => $request->input('name'),
'payment' => $request->input('payment'),
'config' => $request->input('config'),
'uuid' => Helper::guid()
])) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
$payment = Payment::find($request->input('id'));
if (!$payment) abort(500, '支付方式不存在');
return response([
'data' => $payment->delete()
]);
}
}

View File

@ -16,14 +16,33 @@ class PlanController extends Controller
{
public function fetch(Request $request)
{
$counts = User::select(
DB::raw("plan_id"),
DB::raw("count(*) as count")
)
->where('plan_id', '!=', NULL)
->where(function ($query) {
$query->where('expired_at', '>=', time())
->orWhere('expired_at', NULL);
})
->groupBy("plan_id")
->get();
$plans = Plan::orderBy('sort', 'ASC')->get();
foreach ($plans as $k => $v) {
$plans[$k]->count = 0;
foreach ($counts as $kk => $vv) {
if ($plans[$k]->id === $counts[$kk]->plan_id) $plans[$k]->count = $counts[$kk]->count;
}
}
return response([
'data' => Plan::orderBy('sort', 'ASC')->get()
'data' => $plans
]);
}
public function save(PlanSave $request)
{
$params = $request->only(array_keys(PlanSave::RULES));
$params = $request->validated();
if ($request->input('id')) {
$plan = Plan::find($request->input('id'));
if (!$plan) {
@ -33,8 +52,8 @@ class PlanController extends Controller
// update user group id and transfer
try {
User::where('plan_id', $plan->id)->update([
'group_id' => $plan->group_id,
'transfer_enable' => $plan->transfer_enable * 1073741824
'group_id' => $params['group_id'],
'transfer_enable' => $params['transfer_enable'] * 1073741824
]);
$plan->update($params);
} catch (\Exception $e) {

View File

@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Models\Plan;
use App\Models\Server;
use App\Models\ServerGroup;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class GroupController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('group_id')) {
return response([
'data' => [ServerGroup::find($request->input('group_id'))]
]);
}
return response([
'data' => ServerGroup::get()
]);
}
public function save(Request $request)
{
if (empty($request->input('name'))) {
abort(500, '组名不能为空');
}
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
} else {
$serverGroup = new ServerGroup();
}
$serverGroup->name = $request->input('name');
return response([
'data' => $serverGroup->save()
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
if (!$serverGroup) {
abort(500, '组不存在');
}
}
$servers = Server::all();
foreach ($servers as $server) {
$groupId = json_decode($server->group_id);
if (in_array($request->input('id'), $groupId)) {
abort(500, '该组已被节点所使用,无法删除');
}
}
if (Plan::where('group_id', $request->input('id'))->first()) {
abort(500, '该组已被订阅所使用,无法删除');
}
if (User::where('group_id', $request->input('id'))->first()) {
abort(500, '该组已被用户所使用,无法删除');
}
return response([
'data' => $serverGroup->delete()
]);
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Models\Server;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
class ManageController extends Controller
{
public function getNodes(Request $request)
{
$serverService = new ServerService();
return response([
'data' => $serverService->getAllServers()
]);
}
public function sort(Request $request)
{
DB::beginTransaction();
foreach ($request->input('sorts') as $k => $v) {
switch ($v['key']) {
case 'shadowsocks':
if (!ServerShadowsocks::find($v['value'])->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
break;
case 'v2ray':
if (!Server::find($v['value'])->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
break;
case 'trojan':
if (!ServerTrojan::find($v['value'])->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
break;
}
}
DB::commit();
return response([
'data' => true
]);
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Http\Requests\Admin\ServerShadowsocksSave;
use App\Http\Requests\Admin\ServerShadowsocksUpdate;
use App\Models\ServerShadowsocks;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class ShadowsocksController extends Controller
{
public function save(ServerShadowsocksSave $request)
{
$params = $request->validated();
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if ($request->input('id')) {
$server = ServerShadowsocks::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!ServerShadowsocks::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = ServerShadowsocks::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(ServerShadowsocksUpdate $request)
{
$params = $request->only([
'show',
]);
$server = ServerShadowsocks::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = ServerShadowsocks::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!ServerShadowsocks::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Http\Requests\Admin\ServerTrojanSave;
use App\Http\Requests\Admin\ServerTrojanUpdate;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerTrojan;
class TrojanController extends Controller
{
public function save(ServerTrojanSave $request)
{
$params = $request->validated();
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if ($request->input('id')) {
$server = ServerTrojan::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!ServerTrojan::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = ServerTrojan::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(ServerTrojanUpdate $request)
{
$params = $request->only([
'show',
]);
$server = ServerTrojan::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = ServerTrojan::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!ServerTrojan::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
public function viewConfig(Request $request)
{
$serverService = new ServerService();
$config = $serverService->getTrojanConfig($request->input('node_id'), 23333);
return response([
'data' => $config
]);
}
}

View File

@ -0,0 +1,129 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Http\Requests\Admin\ServerV2raySave;
use App\Http\Requests\Admin\ServerV2rayUpdate;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Server;
class V2rayController extends Controller
{
public function save(ServerV2raySave $request)
{
$params = $request->validated();
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if (isset($params['dnsSettings'])) {
if (!is_object(json_decode($params['dnsSettings']))) {
abort(500, 'DNS规则配置格式不正确');
}
}
if (isset($params['ruleSettings'])) {
if (!is_object(json_decode($params['ruleSettings']))) {
abort(500, '审计规则配置格式不正确');
}
}
if (isset($params['networkSettings'])) {
if (!is_object(json_decode($params['networkSettings']))) {
abort(500, '传输协议配置格式不正确');
}
}
if (isset($params['tlsSettings'])) {
if (!is_object(json_decode($params['tlsSettings']))) {
abort(500, 'TLS配置格式不正确');
}
}
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!Server::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(ServerV2rayUpdate $request)
{
$params = $request->only([
'show',
]);
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = Server::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!Server::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
public function viewConfig(Request $request)
{
$serverService = new ServerService();
$config = $serverService->getV2RayConfig($request->input('node_id'), 23333);
return response([
'data' => $config
]);
}
}

View File

@ -1,226 +0,0 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\ServerSave;
use App\Http\Requests\Admin\ServerSort;
use App\Http\Requests\Admin\ServerUpdate;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerGroup;
use App\Models\Server;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class ServerController extends Controller
{
public function fetch(Request $request)
{
$server = Server::orderBy('sort', 'ASC')->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']);
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)
{
$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['dnsSettings'])) {
if (!is_object(json_decode($params['dnsSettings']))) {
abort(500, 'DNS规则配置格式不正确');
}
}
if (isset($params['ruleSettings'])) {
if (!is_object(json_decode($params['ruleSettings']))) {
abort(500, '审计规则配置格式不正确');
}
}
if (isset($params['networkSettings'])) {
if (!is_object(json_decode($params['networkSettings']))) {
abort(500, '传输协议配置格式不正确');
}
}
if (isset($params['tlsSettings'])) {
if (!is_object(json_decode($params['tlsSettings']))) {
abort(500, 'TLS配置格式不正确');
}
}
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
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'))]
]);
}
return response([
'data' => ServerGroup::get()
]);
}
public function groupSave(Request $request)
{
if (empty($request->input('name'))) {
abort(500, '组名不能为空');
}
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
} else {
$serverGroup = new ServerGroup();
}
$serverGroup->name = $request->input('name');
return response([
'data' => $serverGroup->save()
]);
}
public function groupDrop(Request $request)
{
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
if (!$serverGroup) {
abort(500, '组不存在');
}
}
$servers = Server::all();
foreach ($servers as $server) {
$groupId = json_decode($server->group_id);
if (in_array($request->input('id'), $groupId)) {
abort(500, '该组已被节点所使用,无法删除');
}
}
if (Plan::where('group_id', $request->input('id'))->first()) {
abort(500, '该组已被订阅所使用,无法删除');
}
if (User::where('group_id', $request->input('id'))->first()) {
abort(500, '该组已被用户所使用,无法删除');
}
return response([
'data' => $serverGroup->delete()
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(ServerUpdate $request)
{
$params = $request->only([
'show',
]);
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = Server::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
if (!Server::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
public function viewConfig(Request $request)
{
$serverService = new ServerService();
$config = $serverService->getConfig($request->input('node_id'), 23333);
return response([
'data' => $config
]);
}
public function sort(ServerSort $request)
{
DB::beginTransaction();
foreach ($request->input('server_ids') as $k => $v) {
if (!Server::find($v)->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
}
DB::commit();
return response([
'data' => true
]);
}
}

View File

@ -2,6 +2,9 @@
namespace App\Http\Controllers\Admin;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerGroup;
@ -10,7 +13,10 @@ use App\Models\Plan;
use App\Models\User;
use App\Models\Ticket;
use App\Models\Order;
use App\Models\StatOrder;
use App\Models\StatServer;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class StatController extends Controller
{
@ -20,7 +26,7 @@ class StatController extends Controller
'data' => [
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->whereIn('status', [3, 4])
->whereNotIn('status', [0, 2])
->sum('total_amount'),
'month_register_total' => User::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
@ -29,18 +35,91 @@ class StatController extends Controller
->count(),
'commission_pendding_total' => Order::where('commission_status', 0)
->where('invite_user_id', '!=', NULL)
->where('status', 3)
->whereNotIn('status', [0, 2])
->where('commission_balance', '>', 0)
->count(),
'day_income' => Order::where('created_at', '>=', strtotime(date('Y-m-d')))
->where('created_at', '<', time())
->where('status', 3)
->whereNotIn('status', [0, 2])
->sum('total_amount'),
'last_month_income' => Order::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
->where('created_at', '<', strtotime(date('Y-m-1')))
->where('status', 3)
->whereNotIn('status', [0, 2])
->sum('total_amount')
]
]);
}
public function getOrder(Request $request)
{
$statistics = StatOrder::where('record_type', 'd')
->limit(31)
->orderBy('record_at', 'DESC')
->get()
->toArray();
$result = [];
foreach ($statistics as $statistic) {
$date = date('m-d', $statistic['record_at']);
array_push($result, [
'type' => '收款金额',
'date' => $date,
'value' => $statistic['order_amount'] / 100
]);
array_push($result, [
'type' => '收款笔数',
'date' => $date,
'value' => $statistic['order_count']
]);
array_push($result, [
'type' => '佣金金额',
'date' => $date,
'value' => $statistic['commission_amount'] / 100
]);
array_push($result, [
'type' => '佣金笔数',
'date' => $date,
'value' => $statistic['commission_count']
]);
}
$result = array_reverse($result);
return response([
'data' => $result
]);
}
public function getServerLastRank()
{
$servers = [
'shadowsocks' => ServerShadowsocks::where('parent_id', null)->get()->toArray(),
'vmess' => Server::where('parent_id', null)->get()->toArray(),
'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray()
];
$timestamp = strtotime('-1 day', strtotime(date('Y-m-d')));
$statistics = StatServer::select([
'server_id',
'server_type',
'u',
'd',
DB::raw('(u+d) as total')
])
->where('record_at', '>=', $timestamp)
->where('record_type', 'd')
->limit(10)
->orderBy('total', 'DESC')
->get()
->toArray();
foreach ($statistics as $k => $v) {
foreach ($servers[$v['server_type']] as $server) {
if ($server['id'] === $v['server_id']) {
$statistics[$k]['server_name'] = $server['name'];
}
}
$statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824;
}
array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics);
return response([
'data' => $statistics
]);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin;
use App\Jobs\SendEmailJob;
use App\Services\TicketService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
@ -63,27 +64,12 @@ class TicketController extends Controller
if (empty($request->input('message'))) {
abort(500, '消息不能为空');
}
$ticket = Ticket::where('id', $request->input('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
}
if ($ticket->status) {
abort(500, '工单已关闭,无法回复');
}
DB::beginTransaction();
$ticketMessage = TicketMessage::create([
'user_id' => $request->session()->get('id'),
'ticket_id' => $ticket->id,
'message' => $request->input('message')
]);
$ticket->last_reply_user_id = $request->session()->get('id');
if (!$ticketMessage || !$ticket->save()) {
DB::rollback();
abort(500, '工单回复失败');
}
DB::commit();
$this->sendEmailNotify($ticket, $ticketMessage);
$ticketService = new TicketService();
$ticketService->replyByAdmin(
$request->input('id'),
$request->input('message'),
$request->session()->get('id')
);
return response([
'data' => true
]);
@ -107,24 +93,4 @@ class TicketController extends Controller
'data' => true
]);
}
// 半小时内不再重复通知
private function sendEmailNotify(Ticket $ticket, TicketMessage $ticketMessage)
{
$user = User::find($ticket->user_id);
$cacheKey = 'ticket_sendEmailNotify_' . $ticket->user_id;
if (!Cache::get($cacheKey)) {
Cache::put($cacheKey, 1, 1800);
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => '您在' . config('v2board.app_name', 'V2Board') . '的工单得到了回复',
'template_name' => 'notify',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url'),
'content' => "主题:{$ticket->subject}\r\n回复内容:{$ticketMessage->message}"
]
]);
}
}
}

View File

@ -1,93 +0,0 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\TutorialSave;
use App\Http\Requests\Admin\TutorialSort;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Tutorial;
use Illuminate\Support\Facades\DB;
class TutorialController extends Controller
{
public function fetch(Request $request)
{
return response([
'data' => Tutorial::orderBy('sort', 'ASC')->get()
]);
}
public function save(TutorialSave $request)
{
$params = $request->only(array_keys(TutorialSave::RULES));
if (!$request->input('id')) {
if (!Tutorial::create($params)) {
abort(500, '创建失败');
}
} else {
try {
Tutorial::find($request->input('id'))->update($params);
} catch (\Exception $e) {
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 sort(TutorialSort $request)
{
DB::beginTransaction();
foreach ($request->input('tutorial_ids') as $k => $v) {
if (!Tutorial::find($v)->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
}
DB::commit();
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

@ -2,28 +2,62 @@
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\UserFetch;
use App\Http\Requests\Admin\UserGenerate;
use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Admin\UserUpdate;
use App\Jobs\SendEmailJob;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Order;
use App\Models\User;
use App\Models\Plan;
use Illuminate\Support\Facades\DB;
class UserController extends Controller
{
public function fetch(Request $request)
public function resetSecret(Request $request)
{
$user = User::find($request->input('id'));
if (!$user) abort(500, '用户不存在');
$user->token = Helper::guid();
$user->uuid = Helper::guid(true);
return response([
'data' => $user->save()
]);
}
private function filter(Request $request, $builder)
{
if ($request->input('filter')) {
foreach ($request->input('filter') as $filter) {
if ($filter['key'] === 'invite_by_email') {
$user = User::where('email', $filter['value'])->first();
if (!$user) continue;
$builder->where('invite_user_id', $user->id);
continue;
}
if ($filter['key'] === 'd' || $filter['key'] === 'transfer_enable') {
$filter['value'] = $filter['value'] * 1073741824;
}
if ($filter['condition'] === '模糊') {
$filter['condition'] = 'like';
$filter['value'] = "%{$filter['value']}%";
}
$builder->where($filter['key'], $filter['condition'], $filter['value']);
}
}
}
public function fetch(UserFetch $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$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'));
}
$this->filter($request, $userModel);
$total = $userModel->count();
$res = $userModel->forPage($current, $pageSize)
->get();
@ -34,6 +68,7 @@ class UserController extends Controller
$res[$i]['plan_name'] = $plan[$k]['name'];
}
}
$res[$i]['subscribe_url'] = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $res[$i]['token'];
}
return response([
'data' => $res,
@ -46,14 +81,18 @@ class UserController extends Controller
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$user = User::find($request->input('id'));
if ($user->invite_user_id) {
$user['invite_user'] = User::find($user->invite_user_id);
}
return response([
'data' => User::find($request->input('id'))
'data' => $user
]);
}
public function update(UserUpdate $request)
{
$params = $request->only(array_keys(UserUpdate::RULES));
$params = $request->validated();
$user = User::find($request->input('id'));
if (!$user) {
abort(500, '用户不存在');
@ -74,6 +113,14 @@ class UserController extends Controller
}
$params['group_id'] = $plan->group_id;
}
if ($request->input('invite_user_email')) {
$inviteUser = User::where('email', $request->input('invite_user_email'))->first();
if ($inviteUser) {
$params['invite_user_id'] = $inviteUser->id;
}
} else {
$params['invite_user_id'] = null;
}
try {
$user->update($params);
@ -84,4 +131,150 @@ class UserController extends Controller
'data' => true
]);
}
public function dumpCSV(Request $request)
{
$userModel = User::orderBy('id', 'asc');
$this->filter($request, $userModel);
$res = $userModel->get();
$plan = Plan::get();
for ($i = 0; $i < count($res); $i++) {
for ($k = 0; $k < count($plan); $k++) {
if ($plan[$k]['id'] == $res[$i]['plan_id']) {
$res[$i]['plan_name'] = $plan[$k]['name'];
}
}
}
$data = "邮箱,余额,推广佣金,总流量,剩余流量,套餐到期时间,订阅计划,订阅地址\r\n";
$baseUrl = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL')));
foreach($res as $user) {
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$balance = $user['balance'] / 100;
$commissionBalance = $user['commission_balance'] / 100;
$transferEnable = $user['transfer_enable'] ? $user['transfer_enable'] / 1073741824 : 0;
$notUseFlow = (($user['transfer_enable'] - ($user['u'] + $user['d'])) / 1073741824) ?? 0;
$planName = $user['plan_name'] ?? '无订阅';
$subscribeUrl = $baseUrl . '/api/v1/client/subscribe?token=' . $user['token'];
$data .= "{$user['email']},{$balance},{$commissionBalance},{$transferEnable},{$notUseFlow},{$expireDate},{$planName},{$subscribeUrl}\r\n";
}
echo "\xEF\xBB\xBF" . $data;
}
public function generate(UserGenerate $request)
{
if ($request->input('email_prefix')) {
if ($request->input('plan_id')) {
$plan = Plan::find($request->input('plan_id'));
if (!$plan) {
abort(500, '订阅计划不存在');
}
}
$user = [
'email' => $request->input('email_prefix') . '@' . $request->input('email_suffix'),
'plan_id' => isset($plan->id) ? $plan->id : NULL,
'group_id' => isset($plan->group_id) ? $plan->group_id : NULL,
'transfer_enable' => isset($plan->transfer_enable) ? $plan->transfer_enable * 1073741824 : 0,
'expired_at' => $request->input('expired_at') ?? NULL,
'uuid' => Helper::guid(true),
'token' => Helper::guid()
];
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
if (!User::create($user)) {
abort(500, '生成失败');
}
return response([
'data' => true
]);
}
if ($request->input('generate_count')) {
$this->multiGenerate($request);
}
}
private function multiGenerate(Request $request)
{
if ($request->input('plan_id')) {
$plan = Plan::find($request->input('plan_id'));
if (!$plan) {
abort(500, '订阅计划不存在');
}
}
$users = [];
for ($i = 0;$i < $request->input('generate_count');$i++) {
$user = [
'email' => Helper::randomChar(6) . '@' . $request->input('email_suffix'),
'plan_id' => isset($plan->id) ? $plan->id : NULL,
'group_id' => isset($plan->group_id) ? $plan->group_id : NULL,
'transfer_enable' => isset($plan->transfer_enable) ? $plan->transfer_enable * 1073741824 : 0,
'expired_at' => $request->input('expired_at') ?? NULL,
'uuid' => Helper::guid(true),
'token' => Helper::guid(),
'created_at' => time(),
'updated_at' => time()
];
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
array_push($users, $user);
}
DB::beginTransaction();
if (!User::insert($users)) {
DB::rollBack();
abort(500, '生成失败');
}
DB::commit();
$data = "账号,密码,过期时间,UUID,创建时间,订阅地址\r\n";
$baseUrl = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL')));
foreach($users as $user) {
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$createDate = date('Y-m-d H:i:s', $user['created_at']);
$password = $request->input('password') ?? $user['email'];
$subscribeUrl = $baseUrl . '/api/v1/client/subscribe?token=' . $user['token'];
$data .= "{$user['email']},{$password},{$expireDate},{$user['uuid']},{$createDate},{$subscribeUrl}\r\n";
}
echo $data;
}
public function sendMail(UserSendMail $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
$users = $builder->get();
foreach ($users as $user) {
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'notify',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url'),
'content' => $request->input('content')
]
]);
}
return response([
'data' => true
]);
}
public function ban(Request $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
try {
$builder->update([
'banned' => 1
]);
} catch (\Exception $e) {
abort(500, '处理失败');
}
return response([
'data' => true
]);
}
}

View File

@ -3,12 +3,13 @@
namespace App\Http\Controllers\Client;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\Clash;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Plan;
use App\Models\Server;
use App\Models\Notice;
use App\Utils\Helper;
use Illuminate\Support\Facades\File;
use Symfony\Component\Yaml\Yaml;
class AppController extends Controller
{
@ -16,96 +17,78 @@ class AppController extends Controller
CONST SOCKS_PORT = 10010;
CONST HTTP_PORT = 10011;
// TODO: 1.1.1 abolish
public function data(Request $request)
public function getConfig(Request $request)
{
$servers = [];
$user = $request->user;
$nodes = [];
if ($user->plan_id) {
$user['plan'] = Plan::find($user->plan_id);
if (!$user['plan']) {
abort(500, '订阅计划不存在');
$userService = new UserService();
if ($userService->isAvailable($user)) {
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
}
$defaultConfig = base_path() . '/resources/rules/app.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.app.clash.yaml';
if (File::exists($customConfig)) {
$config = Yaml::parseFile($customConfig);
} else {
$config = Yaml::parseFile($defaultConfig);
}
$proxy = [];
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
array_push($proxy, Protocols\Clash::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
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($nodes, $item);
}
}
if ($item['type'] === 'v2ray') {
array_push($proxy, Protocols\Clash::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'trojan') {
array_push($proxy, Protocols\Clash::buildTrojan($user['uuid'], $item));
array_push($proxies, $item['name']);
}
}
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
foreach ($config['proxy-groups'] as $k => $v) {
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
}
die(Yaml::dump($config));
}
public function getVersion(Request $request)
{
if (strpos($request->header('user-agent'), 'tidalab/4.0.0') !== false
|| strpos($request->header('user-agent'), 'tunnelab/4.0.0') !== false
) {
if (strpos($request->header('user-agent'), 'Win64') !== false) {
return response([
'data' => [
'version' => config('v2board.windows_version'),
'download_url' => config('v2board.windows_download_url')
]
]);
} else {
return response([
'data' => [
'version' => config('v2board.macos_version'),
'download_url' => config('v2board.macos_download_url')
]
]);
}
return;
}
return response([
'data' => [
'nodes' => $nodes,
'u' => $user->u,
'd' => $user->d,
'transfer_enable' => $user->transfer_enable,
'expired_at' => $user->expired_at,
'plan' => isset($user['plan']) ? $user['plan'] : false,
'notice' => Notice::orderBy('created_at', 'DESC')->first()
'windows_version' => config('v2board.windows_version'),
'windows_download_url' => config('v2board.windows_download_url'),
'macos_version' => config('v2board.macos_version'),
'macos_download_url' => config('v2board.macos_download_url'),
'android_version' => config('v2board.android_version'),
'android_download_url' => config('v2board.android_download_url')
]
]);
}
public function config(Request $request)
{
if (empty($request->input('server_id'))) {
abort(500, '参数错误');
}
$user = $request->user;
if ($user->expired_at < time() && $user->expired_at !== NULL) {
abort(500, '订阅计划已过期');
}
$server = Server::where('show', 1)
->where('id', $request->input('server_id'))
->first();
if (!$server) {
abort(500, '服务器不存在');
}
$json = json_decode(self::CLIENT_CONFIG);
//socks
$json->inbound->port = (int)self::SOCKS_PORT;
//http
$json->inboundDetour[0]->port = (int)self::HTTP_PORT;
//other
$json->outbound->settings->vnext[0]->address = (string)$server->host;
$json->outbound->settings->vnext[0]->port = (int)$server->port;
$json->outbound->settings->vnext[0]->users[0]->id = (string)$user->v2ray_uuid;
$json->outbound->settings->vnext[0]->users[0]->alterId = (int)$user->v2ray_alter_id;
$json->outbound->settings->vnext[0]->remark = (string)$server->name;
$json->outbound->streamSettings->network = $server->network;
if ($server->networkSettings) {
switch ($server->network) {
case 'tcp':
$json->outbound->streamSettings->tcpSettings = json_decode($server->networkSettings);
break;
case 'kcp':
$json->outbound->streamSettings->kcpSettings = json_decode($server->networkSettings);
break;
case 'ws':
$json->outbound->streamSettings->wsSettings = json_decode($server->networkSettings);
break;
case 'http':
$json->outbound->streamSettings->httpSettings = json_decode($server->networkSettings);
break;
case 'domainsocket':
$json->outbound->streamSettings->dsSettings = json_decode($server->networkSettings);
break;
case 'quic':
$json->outbound->streamSettings->quicSettings = json_decode($server->networkSettings);
break;
}
}
if ($request->input('is_global')) {
$json->routing->settings->rules[0]->outboundTag = 'proxy';
}
if ($server->tls) {
$json->outbound->streamSettings->security = "tls";
}
die(json_encode($json, JSON_UNESCAPED_UNICODE));
}
}

View File

@ -2,261 +2,40 @@
namespace App\Http\Controllers\Client;
use App\Http\Controllers\Client\Protocols\V2rayN;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Models\Server;
use App\Utils\Helper;
use Symfony\Component\Yaml\Yaml;
use App\Services\UserService;
class ClientController extends Controller
{
public function subscribe(Request $request)
{
$flag = $request->input('flag')
?? (isset($_SERVER['HTTP_USER_AGENT'])
? $_SERVER['HTTP_USER_AGENT']
: '');
$flag = strtolower($flag);
$user = $request->user;
$server = [];
// account not expired and is not banned.
$userService = new UserService();
if ($userService->isAvailable($user)) {
$servers = Server::where('show', 1)
->orderBy('sort', 'ASC')
->get();
foreach ($servers as $item) {
$groupId = json_decode($item['group_id']);
if (in_array($user->group_id, $groupId)) {
array_push($server, $item);
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
if ($flag) {
foreach (glob(app_path('Http//Controllers//Client//Protocols') . '/*.php') as $file) {
$file = 'App\\Http\\Controllers\\Client\\Protocols\\' . basename($file, '.php');
$class = new $file($user, $servers);
if (strpos($flag, $class->flag) !== false) {
die($class->handle());
}
}
}
// todo 1.5.3 remove
$class = new V2rayN($user, $servers);
die($class->handle());
die('该客户端暂不支持进行订阅');
}
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));
}
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Surfboard') !== false) {
die($this->surfboard($user, $server));
}
if (strpos($_SERVER['HTTP_USER_AGENT'], 'Surge') !== false) {
die($this->surge($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->tls) {
$tlsSettings = json_decode($item->tlsSettings);
if ($item->network === 'tcp') $uri .= ', obfs=over-tls';
if (isset($tlsSettings->allowInsecure)) {
// Default: tls-verification=true
$uri .= ', tls-verification=' . ($tlsSettings->allowInsecure ? "false" : "true");
}
if (isset($tlsSettings->serverName)) {
$uri .= ', obfs-host=' . $tlsSettings->serverName;
}
}
if ($item->network === 'ws') {
$uri .= ', obfs=' . ($item->tls ? 'wss' : 'ws');
if ($item->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
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->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
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 surge($user, $server)
{
$proxies = '';
$proxyGroup = '';
foreach ($server as $item) {
// [Proxy]
$proxies .= $item->name . ' = vmess, ' . $item->host . ', ' . $item->port . ', username=' . $user->v2ray_uuid . ', tfo=true';
if ($item->tls) {
$tlsSettings = json_decode($item->tlsSettings);
$proxies .= ', tls=' . ($item->tls ? "true" : "false");
if (isset($tlsSettings->allowInsecure)) {
$proxies .= ', skip-cert-verify=' . ($tlsSettings->allowInsecure ? "true" : "false");
}
}
if ($item->network == 'ws') {
$proxies .= ', ws=true';
if ($item->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
if (isset($wsSettings->path)) $proxies .= ', ws-path=' . $wsSettings->path;
if (isset($wsSettings->headers->Host)) $proxies .= ', ws-headers=host:' . $wsSettings->headers->Host;
}
}
$proxies .= "\r\n";
// [Proxy Group]
$proxyGroup .= $item->name . ', ';
}
$defaultConfig = base_path() . '/resources/rules/default.surge.conf';
$customConfig = base_path() . '/resources/rules/custom.surge.conf';
if (\File::exists($customConfig)) {
$config = file_get_contents("$customConfig");
} else {
$config = file_get_contents("$defaultConfig");
}
// Subscription link
$subsURL = 'http';
if (isset( $_SERVER['HTTPS'] ) && strtolower( $_SERVER['HTTPS'] ) == 'on') {
$subsURL .= 's';
}
$subsURL .= '://';
if ($_SERVER['SERVER_PORT'] != ('80' || '443')) {
$subsURL .= $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI'];
} else {
$subsURL .= $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
}
$config = str_replace('$subs_link',$subsURL,$config);
$config = str_replace('$proxies',$proxies,$config);
$config = str_replace('$proxy_group',rtrim($proxyGroup, ', '),$config);
return $config;
}
private function surfboard($user, $server)
{
$proxies = '';
$proxyGroup = '';
foreach ($server as $item) {
// [Proxy]
$proxies .= $item->name . ' = vmess, ' . $item->host . ', ' . $item->port . ', username=' . $user->v2ray_uuid;
if ($item->tls) {
$tlsSettings = json_decode($item->tlsSettings);
$proxies .= ', tls=' . ($item->tls ? "true" : "false");
if (isset($tlsSettings->allowInsecure)) {
$proxies .= ', skip-cert-verify=' . ($tlsSettings->allowInsecure ? "true" : "false");
}
}
if ($item->network == 'ws') {
$proxies .= ', ws=true';
if ($item->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
if (isset($wsSettings->path)) $proxies .= ', ws-path=' . $wsSettings->path;
if (isset($wsSettings->headers->Host)) $proxies .= ', ws-headers=host:' . $wsSettings->headers->Host;
}
}
$proxies .= "\r\n";
// [Proxy Group]
$proxyGroup .= $item->name . ', ';
}
$defaultConfig = base_path() . '/resources/rules/default.surfboard.conf';
$customConfig = base_path() . '/resources/rules/custom.surfboard.conf';
if (\File::exists($customConfig)) {
$config = file_get_contents("$customConfig");
} else {
$config = file_get_contents("$defaultConfig");
}
// Subscription link
$subsURL = 'http';
if (isset( $_SERVER['HTTPS'] ) && strtolower( $_SERVER['HTTPS'] ) == 'on') {
$subsURL .= 's';
}
$subsURL .= '://';
if ($_SERVER['SERVER_PORT'] != ('80' || '443')) {
$subsURL .= $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . $_SERVER['REQUEST_URI'];
} else {
$subsURL .= $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
}
$config = str_replace('$subs_link',$subsURL,$config);
$config = str_replace('$proxies',$proxies,$config);
$config = str_replace('$proxy_group',rtrim($proxyGroup, ', '),$config);
return $config;
}
private function clash($user, $server)
{
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.clash.yaml';
if (\File::exists($customConfig)) {
$config = Yaml::parseFile($customConfig);
} else {
$config = Yaml::parseFile($defaultConfig);
}
$proxy = [];
$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) {
$tlsSettings = json_decode($item->tlsSettings);
$array['tls'] = true;
if (isset($tlsSettings->allowInsecure)) $array['skip-cert-verify'] = ($tlsSettings->allowInsecure ? true : false );
}
if ($item->network == 'ws') {
$array['network'] = $item->network;
if ($item->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
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);
}
$config['Proxy'] = array_merge($config['Proxy'] ? $config['Proxy'] : [], $proxy);
foreach ($config['Proxy Group'] as $k => $v) {
$config['Proxy Group'][$k]['proxies'] = array_merge($config['Proxy Group'][$k]['proxies'], $proxies);
}
$yaml = Yaml::dump($config);
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml;
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class AnXray
{
public $flag = 'anxray';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($uuid, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$uuid}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildShadowsocksSIP008($uuid, $server)
{
$config = [
"id" => $server['id'],
"remarks" => $server['name'],
"server" => $server['host'],
"server_port" => $server['port'],
"password" => $uuid,
"method" => $server['cipher']
];
return $config;
}
public static function buildVmess($uuid, $server)
{
$config = [
"encryption" => "none",
"type" => urlencode($server['network']),
"security" => $server['tls'] ? "tls" : "",
"sni" => $server['tls'] ? urlencode(json_decode($server['tlsSettings'], true)['serverName']) : ""
];
if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path'])) $config['path'] = urlencode($wsSettings['path']);
if (isset($wsSettings['headers']['Host'])) $config['host'] = urlencode($wsSettings['headers']['Host']);
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true);
if (isset($grpcSettings['serviceName'])) $config['serviceName'] = urlencode($grpcSettings['serviceName']);
}
return "vmess://" . $uuid . "@" . $server['host'] . ":" . $server['port'] . "?" . http_build_query($config) . "#" . urlencode($server['name']) . "\r\n";
}
public static function buildTrojan($uuid, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$uuid}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,129 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
use Symfony\Component\Yaml\Yaml;
class Clash
{
public $flag = 'clash';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.clash.yaml';
if (\File::exists($customConfig)) {
$config = Yaml::parseFile($customConfig);
} else {
$config = Yaml::parseFile($defaultConfig);
}
$proxy = [];
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'v2ray') {
array_push($proxy, self::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'trojan') {
array_push($proxy, self::buildTrojan($user['uuid'], $item));
array_push($proxies, $item['name']);
}
}
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
foreach ($config['proxy-groups'] as $k => $v) {
if (!is_array($config['proxy-groups'][$k]['proxies'])) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
}
$yaml = Yaml::dump($config);
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml;
}
public static function buildShadowsocks($uuid, $server)
{
$array = [];
$array['name'] = $server['name'];
$array['type'] = 'ss';
$array['server'] = $server['host'];
$array['port'] = $server['port'];
$array['cipher'] = $server['cipher'];
$array['password'] = $uuid;
$array['udp'] = true;
return $array;
}
public static function buildVmess($uuid, $server)
{
$array = [];
$array['name'] = $server['name'];
$array['type'] = 'vmess';
$array['server'] = $server['host'];
$array['port'] = $server['port'];
$array['uuid'] = $uuid;
$array['alterId'] = $server['alter_id'];
$array['cipher'] = 'auto';
$array['udp'] = true;
if ($server['tls']) {
$array['tls'] = true;
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
$array['skip-cert-verify'] = ($tlsSettings['allowInsecure'] ? true : false);
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$array['servername'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'ws') {
$array['network'] = 'ws';
if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
$array['ws-path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
$array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']];
}
}
if ($server['network'] === 'grpc') {
$array['network'] = 'grpc';
if ($server['networkSettings']) {
$grpcObject = json_decode($server['networkSettings'], true);
$array['grpc-opts'] = [];
$array['grpc-opts']['grpc-service-name'] = $grpcObject['serviceName'];
}
}
return $array;
}
public static function buildTrojan($password, $server)
{
$array = [];
$array['name'] = $server['name'];
$array['type'] = 'trojan';
$array['server'] = $server['host'];
$array['port'] = $server['port'];
$array['password'] = $password;
$array['udp'] = true;
if (!empty($server['server_name'])) $array['sni'] = $server['server_name'];
if (!empty($server['allow_insecure'])) $array['skip-cert-verify'] = ($server['allow_insecure'] ? true : false);
return $array;
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class Passwall
{
public $flag = 'passwall';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$config = [
"v" => "2",
"ps" => $server['name'],
"add" => $server['host'],
"port" => (string)$server['port'],
"id" => $uuid,
"aid" => (string)$server['alter_id'],
"net" => $server['network'],
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true);
if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName'];
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class QuantumultX
{
public $flag = 'quantumult%20x';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$config = [
"shadowsocks={$server['host']}:{$server['port']}",
"method={$server['cipher']}",
"password={$password}",
'fast-open=true',
'udp-relay=true',
"tag={$server['name']}"
];
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildVmess($uuid, $server)
{
$config = [
"vmess={$server['host']}:{$server['port']}",
'method=chacha20-poly1305',
"password={$uuid}",
'fast-open=true',
'udp-relay=true',
"tag={$server['name']}"
];
if ($server['tls']) {
if ($server['network'] === 'tcp')
array_push($config, 'obfs=over-tls');
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'tls-verification=' . ($tlsSettings['allowInsecure'] ? 'false' : 'true'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$host = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'ws') {
if ($server['tls'])
array_push($config, 'obfs=wss');
else
array_push($config, 'obfs=ws');
if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "obfs-uri={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']) && !isset($host))
$host = $wsSettings['headers']['Host'];
}
}
if (isset($host)) {
array_push($config, "obfs-host={$host}");
}
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildTrojan($password, $server)
{
$config = [
"trojan={$server['host']}:{$server['port']}",
"password={$password}",
'over-tls=true',
$server['server_name'] ? "tls-host={$server['server_name']}" : "",
// Tips: allowInsecure=false = tls-verification=true
$server['allow_insecure'] ? 'tls-verification=false' : 'tls-verification=true',
'fast-open=true',
'udp-relay=true',
"tag={$server['name']}"
];
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class SSRPlus
{
public $flag = 'ssrplus';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$config = [
"v" => "2",
"ps" => $server['name'],
"add" => $server['host'],
"port" => (string)$server['port'],
"id" => $uuid,
"aid" => (string)$server['alter_id'],
"net" => $server['network'],
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true);
if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName'];
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class Shadowrocket
{
public $flag = 'shadowrocket';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
//display remaining traffic and expire date
$upload = round($user['u'] / (1024*1024*1024), 2);
$download = round($user['d'] / (1024*1024*1024), 2);
$totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
$expiredDate = date('Y-m-d', $user['expired_at']);
$uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n";
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$userinfo = base64_encode('auto:' . $uuid . '@' . $server['host'] . ':' . $server['port']);
$config = [
'tfo' => 1,
'remark' => $server['name'],
'alterId' => $server['alter_id']
];
if ($server['tls']) {
$config['tls'] = 1;
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
$config['allowInsecure'] = (int)$tlsSettings['allowInsecure'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['peer'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'ws') {
$config['obfs'] = "websocket";
if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
$config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
$config['obfsParam'] = $wsSettings['headers']['Host'];
}
}
if ($server['network'] === 'grpc') {
$config['obfs'] = "grpc";
if ($server['networkSettings']) {
$grpcSettings = json_decode($server['networkSettings'], true);
if (isset($grpcSettings['serviceName']) && !empty($grpcSettings['serviceName']))
$config['path'] = $grpcSettings['serviceName'];
}
if (isset($tlsSettings)) {
$config['host'] = $tlsSettings['serverName'];
} else {
$config['host'] = $server['host'];
}
}
$query = http_build_query($config, '', '&', PHP_QUERY_RFC3986);
$uri = "vmess://{$userinfo}?{$query}";
$uri .= "\r\n";
return $uri;
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}&tfo=1#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class Shadowsocks
{
public $flag = 'shadowsocks';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$configs = [];
$subs = [];
$subs['servers'] = [];
$subs['bytes_used'] = '';
$subs['bytes_remaining'] = '';
$bytesUsed = $user['u'] + $user['d'];
$bytesRemaining = $user['transfer_enable'] - $bytesUsed;
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
array_push($configs, self::SIP008($item, $user));
}
}
$subs['version'] = 1;
$subs['bytes_used'] = $bytesUsed;
$subs['bytes_remaining'] = $bytesRemaining;
$subs['servers'] = array_merge($subs['servers'] ? $subs['servers'] : [], $configs);
return json_encode($subs, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT);
}
public static function SIP008($server, $user)
{
$config = [
"id" => $server['id'],
"remarks" => $server['name'],
"server" => $server['host'],
"server_port" => $server['port'],
"password" => $user['uuid'],
"method" => $server['cipher']
];
return $config;
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class Surfboard
{
public $flag = 'surfboard';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$proxies = '';
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
// [Proxy]
$proxies .= Surfboard::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'v2ray') {
// [Proxy]
$proxies .= Surfboard::buildVmess($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
}
$defaultConfig = base_path() . '/resources/rules/default.surfboard.conf';
$customConfig = base_path() . '/resources/rules/custom.surfboard.conf';
if (\File::exists($customConfig)) {
$config = file_get_contents("$customConfig");
} else {
$config = file_get_contents("$defaultConfig");
}
// Subscription link
$subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
$config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
return $config;
}
public static function buildShadowsocks($password, $server)
{
$config = [
"{$server['name']}=custom",
"{$server['host']}",
"{$server['port']}",
"{$server['cipher']}",
"{$password}",
'https://raw.githubusercontent.com/Hackl0us/proxy-tool-backup/master/SSEncrypt.module',
'tfo=true',
'udp-relay=true'
];
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildVmess($uuid, $server)
{
$config = [
"{$server['name']}=vmess",
"{$server['host']}",
"{$server['port']}",
"username={$uuid}",
'tfo=true',
'udp-relay=true'
];
if ($server['tls']) {
array_push($config, 'tls=true');
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
array_push($config, "sni={$tlsSettings['serverName']}");
}
}
if ($server['network'] === 'ws') {
array_push($config, 'ws=true');
if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "ws-path={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
array_push($config, "ws-headers=Host:{$wsSettings['headers']['Host']}");
}
}
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class Surge
{
public $flag = 'surge';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$proxies = '';
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
// [Proxy]
$proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'v2ray') {
// [Proxy]
$proxies .= self::buildVmess($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'trojan') {
// [Proxy]
$proxies .= self::buildTrojan($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
}
$defaultConfig = base_path() . '/resources/rules/default.surge.conf';
$customConfig = base_path() . '/resources/rules/custom.surge.conf';
if (\File::exists($customConfig)) {
$config = file_get_contents("$customConfig");
} else {
$config = file_get_contents("$defaultConfig");
}
// Subscription link
$subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
$config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
return $config;
}
public static function buildShadowsocks($password, $server)
{
$config = [
"{$server['name']}=ss",
"{$server['host']}",
"{$server['port']}",
"encrypt-method={$server['cipher']}",
"password={$password}",
'tfo=true',
'udp-relay=true'
];
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildVmess($uuid, $server)
{
$config = [
"{$server['name']}=vmess",
"{$server['host']}",
"{$server['port']}",
"username={$uuid}",
'tfo=true',
'udp-relay=true'
];
if ($server['tls']) {
array_push($config, 'tls=true');
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
array_push($config, "sni={$tlsSettings['serverName']}");
}
}
if ($server['network'] === 'ws') {
array_push($config, 'ws=true');
if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "ws-path={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
array_push($config, "ws-headers=Host:{$wsSettings['headers']['Host']}");
}
}
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildTrojan($password, $server)
{
$config = [
"{$server['name']}=trojan",
"{$server['host']}",
"{$server['port']}",
"password={$password}",
$server['server_name'] ? "sni={$server['server_name']}" : "",
'tfo=true',
'udp-relay=true'
];
if (!empty($server['allow_insecure'])) {
array_push($config, $server['allow_insecure'] ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
}
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class V2rayN
{
public $flag = 'v2rayn';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$config = [
"v" => "2",
"ps" => $server['name'],
"add" => $server['host'],
"port" => (string)$server['port'],
"id" => $uuid,
"aid" => (string)$server['alter_id'],
"net" => $server['network'],
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true);
if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName'];
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
class V2rayNG
{
public $flag = 'v2rayng';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$config = [
"v" => "2",
"ps" => $server['name'],
"add" => $server['host'],
"port" => (string)$server['port'],
"id" => $uuid,
"aid" => (string)$server['alter_id'],
"net" => $server['network'],
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true);
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true);
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true);
if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName'];
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Guest;
use App\Utils\Dict;
use App\Http\Controllers\Controller;
class CommController extends Controller
{
public function config()
{
return response([
'data' => [
'tos_url' => config('v2board.tos_url'),
'is_email_verify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
'is_invite_force' => (int)config('v2board.invite_force', 0) ? 1 : 0,
'email_whitelist_suffix' => (int)config('v2board.email_whitelist_enable', 0)
? $this->getEmailSuffix()
: 0,
'is_recaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
'app_description' => config('v2board.app_description'),
'app_url' => config('v2board.app_url')
]
]);
}
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,153 +0,0 @@
<?php
namespace App\Http\Controllers\Guest;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Order;
use Omnipay\Omnipay;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use Library\BitpayX;
use Library\PayTaro;
class OrderController extends Controller
{
public function alipayNotify(Request $request)
{
// Log::info('alipayNotifyData: ' . json_encode($_POST));
$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')); // 可以是路径,也可以是密钥内容
$request = $gateway->completePurchase();
$request->setParams($_POST); //Optional
try {
/** @var \Omnipay\Alipay\Responses\AopCompletePurchaseResponse $response */
$response = $request->send();
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 {
/**
* Payment is not successful
*/
die('fail');
}
} catch (Exception $e) {
/**
* Payment is not successful
*/
die('fail');
}
}
public function stripeNotify(Request $request)
{
// Log::info('stripeNotifyData: ' . json_encode($request->input()));
\Stripe\Stripe::setApiKey(config('v2board.stripe_sk_live'));
try {
$event = \Stripe\Webhook::constructEvent(
file_get_contents('php://input'),
$_SERVER['HTTP_STRIPE_SIGNATURE'],
config('v2board.stripe_webhook_key')
);
} catch (\Stripe\Error\SignatureVerification $e) {
abort(400);
}
switch ($event->type) {
case 'source.chargeable':
$source = $event->data->object;
$charge = \Stripe\Charge::create([
'amount' => $source['amount'],
'currency' => $source['currency'],
'source' => $source['id'],
'description' => config('v2board.app_name', 'V2Board') . $source['metadata']['invoice_id'],
]);
if ($charge['status'] == 'succeeded') {
$trade_no = Cache::get($source['id']);
if (!$trade_no) {
abort(500, 'redis is not found trade no by stripe source id');
}
if (!$this->handle($trade_no, $source['id'])) {
abort(500, 'fail');
}
Cache::forget($source['id']);
die('success');
}
break;
default:
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(json_encode([
'status' => 200
]));
}
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

@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\Guest;
use App\Models\Order;
use App\Services\OrderService;
use App\Services\PaymentService;
use App\Services\TelegramService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PaymentController extends Controller
{
public function notify($method, $uuid, Request $request)
{
try {
$paymentService = new PaymentService($method, null, $uuid);
$verify = $paymentService->notify($request->input());
if (!$verify) abort(500, 'verify error');
if (!$this->handle($verify['trade_no'], $verify['callback_no'])) {
abort(500, 'handle error');
}
die(isset($paymentService->customResult) ? $paymentService->customResult : 'success');
} catch (\Exception $e) {
abort(500, 'fail');
}
}
private function handle($tradeNo, $callbackNo)
{
$order = Order::where('trade_no', $tradeNo)->first();
if (!$order) {
abort(500, 'order is not found');
}
if ($order->status === 1) return true;
$orderService = new OrderService($order);
if (!$orderService->success($callbackNo)) {
return false;
}
$telegramService = new TelegramService();
$message = sprintf(
"💰成功收款%s元\n———————————————\n订单号:%s",
$order->total_amount / 100,
$order->trade_no
);
$telegramService->sendMessageWithAdmin($message);
return true;
}
}

View File

@ -7,6 +7,7 @@ use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Utils\Helper;
use App\Services\TicketService;
class TelegramController extends Controller
{
@ -24,12 +25,13 @@ class TelegramController extends Controller
$this->msg = $this->getMessage($request->input());
if (!$this->msg) return;
try {
switch($this->msg->command) {
case '/bind': $this->bind();
switch($this->msg->message_type) {
case 'send':
$this->fromSend();
break;
case '/traffic': $this->traffic();
case 'reply':
$this->fromReply();
break;
default: $this->help();
}
} catch (\Exception $e) {
$telegramService = new TelegramService();
@ -37,6 +39,29 @@ class TelegramController extends Controller
}
}
private function fromSend()
{
switch($this->msg->command) {
case '/bind': $this->bind();
break;
case '/traffic': $this->traffic();
break;
case '/getlatesturl': $this->getLatestUrl();
break;
case '/unbind': $this->unbind();
break;
default: $this->help();
}
}
private function fromReply()
{
// ticket
if (preg_match("/[#](.*)/", $this->msg->reply_text, $match)) {
$this->replayTicket($match[1]);
}
}
private function getMessage(array $data)
{
if (!isset($data['message'])) return false;
@ -48,6 +73,11 @@ class TelegramController extends Controller
$obj->args = array_slice($text, 1);
$obj->chat_id = $data['message']['chat']['id'];
$obj->message_id = $data['message']['message_id'];
$obj->message_type = !isset($data['message']['reply_to_message']['text']) ? 'send' : 'reply';
$obj->text = $data['message']['text'];
if ($obj->message_type === 'reply') {
$obj->reply_text = $data['message']['reply_to_message']['text'];
}
return $obj;
}
@ -69,6 +99,9 @@ class TelegramController extends Controller
if (!$user) {
abort(500, '用户不存在');
}
if ($user->telegram_id) {
abort(500, '该账号已经绑定了Telegram账号');
}
$user->telegram_id = $msg->chat_id;
if (!$user->save()) {
abort(500, '设置失败');
@ -77,6 +110,24 @@ class TelegramController extends Controller
$telegramService->sendMessage($msg->chat_id, '绑定成功');
}
private function unbind()
{
$msg = $this->msg;
if (!$msg->is_private) return;
$user = User::where('telegram_id', $msg->chat_id)->first();
$telegramService = new TelegramService();
if (!$user) {
$this->help();
$telegramService->sendMessage($msg->chat_id, '没有查询到您的用户信息,请先绑定账号', 'markdown');
return;
}
$user->telegram_id = NULL;
if (!$user->save()) {
abort(500, '解绑失败');
}
$telegramService->sendMessage($msg->chat_id, '解绑成功', 'markdown');
}
private function help()
{
$msg = $this->msg;
@ -84,7 +135,9 @@ class TelegramController extends Controller
$telegramService = new TelegramService();
$commands = [
'/bind 订阅地址 - 绑定你的' . config('v2board.app_name', 'V2Board') . '账号',
'/traffic - 查询流量信息'
'/traffic - 查询流量信息',
'/getlatesturl - 获取最新的' . config('v2board.app_name', 'V2Board') . '网址',
'/unbind - 解除绑定'
];
$text = implode(PHP_EOL, $commands);
$telegramService->sendMessage($msg->chat_id, "你可以使用以下命令进行操作:\n\n$text", 'markdown');
@ -108,4 +161,40 @@ class TelegramController extends Controller
$text = "🚥流量查询\n———————————————\n计划流量:`{$transferEnable}`\n已用上行:`{$up}`\n已用下行:`{$down}`\n剩余流量:`{$remaining}`";
$telegramService->sendMessage($msg->chat_id, $text, 'markdown');
}
private function getLatestUrl()
{
$msg = $this->msg;
$user = User::where('telegram_id', $msg->chat_id)->first();
$telegramService = new TelegramService();
$text = sprintf(
"%s的最新网址是%s",
config('v2board.app_name', 'V2Board'),
config('v2board.app_url')
);
$telegramService->sendMessage($msg->chat_id, $text, 'markdown');
}
private function replayTicket($ticketId)
{
$msg = $this->msg;
if (!$msg->is_private) return;
$user = User::where('telegram_id', $msg->chat_id)->first();
if (!$user) {
abort(500, '用户不存在');
}
$ticketService = new TicketService();
if ($user->is_admin || $user->is_staff) {
$ticketService->replyByAdmin(
$ticketId,
$msg->text,
$user->id
);
}
$telegramService = new TelegramService();
$telegramService->sendMessage($msg->chat_id, "#`{$ticketId}` 的工单已回复成功", 'markdown');
$telegramService->sendMessageWithAdmin("#`{$ticketId}` 的工单已由 {$user->email} 进行回复", true);
}
}

View File

@ -14,51 +14,59 @@ use App\Models\InviteCode;
use App\Utils\Helper;
use App\Utils\Dict;
use App\Utils\CacheKey;
use ReCaptcha\ReCaptcha;
class AuthController extends Controller
{
public function register(AuthRegister $request)
{
if ((int)config('v2board.recaptcha_enable', 0)) {
$recaptcha = new ReCaptcha(config('v2board.recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
abort(500, __('Invalid code is incorrect'));
}
}
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, '邮箱后缀不处于白名单中');
abort(500, __('Email suffix is not in the Whitelist'));
}
}
if ((int)config('v2board.email_gmail_limit_enable', 0)) {
$prefix = explode('@', $request->input('email'))[0];
if (strpos($prefix, '.') !== false || strpos($prefix, '+') !== false) {
abort(500, '不支持Gmail别名邮箱');
abort(500, __('Gmail alias is not supported'));
}
}
if ((int)config('v2board.stop_register', 0)) {
abort(500, '本站已关闭注册');
abort(500, __('Registration has closed'));
}
if ((int)config('v2board.invite_force', 0)) {
if (empty($request->input('invite_code'))) {
abort(500, '必须使用邀请码才可以注册');
abort(500, __('You must use the invitation code to register'));
}
}
if ((int)config('v2board.email_verify', 0)) {
if (empty($request->input('email_code'))) {
abort(500, '邮箱验证码不能为空');
abort(500, __('Email verification code cannot be empty'));
}
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
abort(500, __('Incorrect email verification code'));
}
}
$email = $request->input('email');
$password = $request->input('password');
$exist = User::where('email', $email)->first();
if ($exist) {
abort(500, '邮箱已存在系统中');
abort(500, __('Email already exists'));
}
$user = new User();
$user->email = $email;
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->v2ray_uuid = Helper::guid(true);
$user->uuid = Helper::guid(true);
$user->token = Helper::guid();
if ($request->input('invite_code')) {
$inviteCode = InviteCode::where('code', $request->input('invite_code'))
@ -66,7 +74,7 @@ class AuthController extends Controller
->first();
if (!$inviteCode) {
if ((int)config('v2board.invite_force', 0)) {
abort(500, '邀请码无效');
abort(500, __('Invalid invitation code'));
}
} else {
$user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
@ -89,7 +97,7 @@ class AuthController extends Controller
}
if (!$user->save()) {
abort(500, '注册失败');
abort(500, __('Register failed'));
}
if ((int)config('v2board.email_verify', 0)) {
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
@ -108,22 +116,23 @@ class AuthController extends Controller
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, '用户名或密码错误');
abort(500, __('Incorrect email or password'));
}
if (!Helper::multiPasswordVerify(
$user->password_algo,
$password,
$user->password)
) {
abort(500, '用户名或密码错误');
abort(500, __('Incorrect email or password'));
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
abort(500, __('Your account has been suspended'));
}
$data = [
'token' => $user->token
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
@ -131,6 +140,10 @@ class AuthController extends Controller
$request->session()->put('is_admin', true);
$data['is_admin'] = true;
}
if ($user->is_staff) {
$request->session()->put('is_staff', true);
$data['is_staff'] = true;
}
return response([
'data' => $data
]);
@ -139,34 +152,27 @@ class AuthController extends Controller
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');
$redirect = '/#/login?verify=' . $request->input('token') . '&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);
return redirect()->to($location)->send();
}
if ($request->input('verify')) {
$key = 'token2Login_' . $request->input('verify');
$key = CacheKey::get('TEMP_TOKEN', $request->input('verify'));
$userId = Cache::get($key);
if (!$userId) {
abort(500, '令牌有误');
abort(500, __('Token error'));
}
$user = User::find($userId);
if (!$user) {
abort(500, '用户不存在');
abort(500, __('The user does not '));
}
if ($user->banned) {
abort(500, '该账户已被停止使用');
abort(500, __('Your account has been suspended'));
}
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
@ -180,6 +186,46 @@ class AuthController extends Controller
}
}
public function getTempToken(Request $request)
{
$user = User::where('token', $request->input('token'))->first();
if (!$user) {
abort(500, __('Token error'));
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
return response([
'data' => $code
]);
}
public function getQuickLoginUrl(Request $request)
{
$authData = explode(':', base64_decode($request->input('auth_data')));
if (!isset($authData[0])) abort(403, __('Token error'));
$user = User::where('email', $authData[0])
->where('password', $authData[1])
->first();
if (!$user) {
abort(500, __('Token error'));
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (config('v2board.app_url')) {
$url = config('v2board.app_url') . $redirect;
} else {
$url = url($redirect);
}
return response([
'data' => $url
]);
}
public function check(Request $request)
{
$data = [
@ -196,16 +242,16 @@ class AuthController extends Controller
public function forget(AuthForget $request)
{
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
abort(500, '邮箱验证码有误');
abort(500, __('Incorrect email verification code'));
}
$user = User::where('email', $request->input('email'))->first();
if (!$user) {
abort(500, '该邮箱不存在系统中');
abort(500, __('This email is not registered in the system'));
}
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
if (!$user->save()) {
abort(500, '重置失败');
abort(500, __('Reset failed'));
}
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
return response([

View File

@ -13,9 +13,11 @@ use App\Jobs\SendEmailJob;
use App\Models\InviteCode;
use App\Utils\Dict;
use App\Utils\CacheKey;
use ReCaptcha\ReCaptcha;
class CommController extends Controller
{
// TODO: remove on 1.5.5
public function config()
{
return response([
@ -24,7 +26,11 @@ class CommController extends Controller
'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0,
'emailWhitelistSuffix' => (int)config('v2board.email_whitelist_enable', 0)
? $this->getEmailSuffix()
: 0
: 0,
'isRecaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
'recaptchaSiteKey' => config('v2board.recaptcha_site_key'),
'appDescription' => config('v2board.app_description'),
'appUrl' => config('v2board.app_url')
]
]);
}
@ -38,12 +44,19 @@ class CommController extends Controller
public function sendEmailVerify(CommSendEmailVerify $request)
{
if ((int)config('v2board.recaptcha_enable', 0)) {
$recaptcha = new ReCaptcha(config('v2board.recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
abort(500, __('Invalid code is incorrect'));
}
}
$email = $request->input('email');
if (Cache::get(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email))) {
abort(500, '验证码已发送,请过一会再请求');
abort(500, __('Email verification code has been sent, please request again later'));
}
$code = rand(100000, 999999);
$subject = config('v2board.app_name', 'V2Board') . '邮箱验证码';
$subject = config('v2board.app_name', 'V2Board') . __('Email verification code');
SendEmailJob::dispatch([
'email' => $email,

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
@ -13,10 +14,12 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
/*
* V2ray Aurora
* Github: https://github.com/tokumeikoi/aurora
*/
class DeepbworkController extends Controller
{
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled": true,"destOverride": ["http","tls"]},"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":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
public function __construct(Request $request)
{
$token = $request->input('token');
@ -36,20 +39,19 @@ class DeepbworkController extends Controller
if (!$server) {
abort(500, 'fail');
}
Cache::put('server_last_check_at_' . $server->id, time());
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
$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,
"uuid" => $user->uuid,
"email" => sprintf("%s@v2board.user", $user->uuid),
"alter_id" => $server->alter_id,
"level" => 0,
];
unset($user['v2ray_uuid']);
unset($user['v2ray_alter_id']);
unset($user['v2ray_level']);
unset($user['uuid']);
unset($user['email']);
array_push($result, $user);
}
return response([
@ -61,7 +63,7 @@ class DeepbworkController extends Controller
// 后端提交数据
public function submit(Request $request)
{
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = Server::find($request->input('node_id'));
if (!$server) {
return response([
@ -71,26 +73,26 @@ class DeepbworkController extends Controller
}
$data = file_get_contents('php://input');
$data = json_decode($data, true);
$serverService = new ServerService();
Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'])) {
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
DB::beginTransaction();
try {
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess')) {
continue;
}
}
$serverService->log(
$item['user_id'],
$request->input('node_id'),
$item['u'],
$item['d'],
$server->rate
);
} catch (\Exception $e) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
}
DB::commit();
return response([
'ret' => 1,
@ -108,7 +110,7 @@ class DeepbworkController extends Controller
}
$serverService = new ServerService();
try {
$json = $serverService->getConfig($nodeId, $localPort);
$json = $serverService->getV2RayConfig($nodeId, $localPort);
} catch (\Exception $e) {
abort(500, $e->getMessage());
}

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
@ -13,10 +14,12 @@ use App\Models\ServerLog;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
/*
* V2ray Poseidon
* Github: https://github.com/ColetteContreras/trojan-poseidon
*/
class PoseidonController extends Controller
{
CONST SERVER_CONFIG = '{"api":{"services":["HandlerService","StatsService"],"tag":"api"},"stats":{},"inbound":{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled": true,"destOverride": ["http","tls"]},"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":{"rules":[{"inboundTag":"api","outboundTag":"api","type":"field"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
public $poseidonVersion;
public function __construct(Request $request)
@ -34,20 +37,19 @@ class PoseidonController extends Controller
if (!$server) {
return $this->error("server could not be found", 404);
}
Cache::put('server_last_check_at_' . $server->id, time());
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
$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,
"uuid" => $user->uuid,
"email" => sprintf("%s@v2board.user", $user->uuid),
"alter_id" => $server->alter_id,
"level" => 0,
];
unset($user['v2ray_uuid']);
unset($user['v2ray_alter_id']);
unset($user['v2ray_level']);
unset($user['uuid']);
unset($user['email']);
array_push($result, $user);
}
@ -58,30 +60,21 @@ class PoseidonController extends Controller
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);
$serverService = new ServerService();
Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'])) {
if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess')) {
return $this->error("user fetch fail", 500);
}
$serverService->log(
$item['user_id'],
$request->input('node_id'),
$item['u'],
$item['d'],
$server->rate
);
}
return $this->success('');
@ -100,7 +93,7 @@ class PoseidonController extends Controller
$serverService = new ServerService();
try {
$json = $serverService->getConfig($nodeId, $localPort);
$json = $serverService->getV2RayConfig($nodeId, $localPort);
$json->poseidon = [
'license_key' => (string)config('v2board.server_license'),
];
@ -143,9 +136,23 @@ class PoseidonController extends Controller
}
protected function success($data) {
$req = request();
// Only for "GET" method
if (!$req->isMethod('GET') || !$data) {
return response([
'msg' => 'ok',
'data' => $data,
]);
}
$etag = sha1(json_encode($data));
if ($etag == $req->header("IF-NONE-MATCH")) {
return response(null, 304);
}
return response([
'msg' => 'ok',
'data' => $data,
]);
])->header('ETAG', $etag);
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers\Server;
use App\Models\ServerShadowsocks;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\ServerLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
/*
* Tidal Lab Shadowsocks
* Github: https://github.com/tokumeikoi/tidalab-ss
*/
class ShadowsocksTidalabController extends Controller
{
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)
{
$nodeId = $request->input('node_id');
$server = ServerShadowsocks::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
$result = [];
foreach ($users as $user) {
array_push($result, [
'id' => $user->id,
'port' => $server->server_port,
'cipher' => $server->cipher,
'secret' => $user->uuid
]);
}
return response([
'data' => $result
]);
}
// 后端提交数据
public function submit(Request $request)
{
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = ServerShadowsocks::find($request->input('node_id'));
if (!$server) {
return response([
'ret' => 0,
'msg' => 'server is not found'
]);
}
$data = file_get_contents('php://input');
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
DB::beginTransaction();
try {
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch((float)$u, (float)$d, (int)$item['user_id'], $server, 'shadowsocks')) {
continue;
}
}
} catch (\Exception $e) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
}
DB::commit();
return response([
'ret' => 1,
'msg' => 'ok'
]);
}
}

View File

@ -0,0 +1,117 @@
<?php
namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\ServerTrojan;
use App\Models\ServerLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
/*
* Tidal Lab Trojan
* Github: https://github.com/tokumeikoi/tidalab-trojan
*/
class TrojanTidalabController extends Controller
{
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)
{
$nodeId = $request->input('node_id');
$server = ServerTrojan::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
$result = [];
foreach ($users as $user) {
$user->trojan_user = [
"password" => $user->uuid,
];
unset($user['uuid']);
unset($user['email']);
array_push($result, $user);
}
return response([
'msg' => 'ok',
'data' => $result,
]);
}
// 后端提交数据
public function submit(Request $request)
{
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = ServerTrojan::find($request->input('node_id'));
if (!$server) {
return response([
'ret' => 0,
'msg' => 'server is not found'
]);
}
$data = file_get_contents('php://input');
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
DB::beginTransaction();
try {
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'trojan')) {
continue;
}
}
} catch (\Exception $e) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
}
DB::commit();
return response([
'ret' => 1,
'msg' => 'ok'
]);
}
// 后端获取配置
public function config(Request $request)
{
$nodeId = $request->input('node_id');
$localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) {
abort(500, '参数错误');
}
$serverService = new ServerService();
try {
$json = $serverService->getTrojanConfig($nodeId, $localPort);
} catch (\Exception $e) {
abort(500, $e->getMessage());
}
die(json_encode($json, JSON_UNESCAPED_UNICODE));
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Staff;
use App\Http\Requests\Admin\NoticeSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Notice;
use Illuminate\Support\Facades\Cache;
class NoticeController extends Controller
{
public function fetch(Request $request)
{
return response([
'data' => Notice::orderBy('id', 'DESC')->get()
]);
}
public function save(NoticeSave $request)
{
$data = $request->only([
'title',
'content',
'img_url'
]);
if (!$request->input('id')) {
if (!Notice::create($data)) {
abort(500, '保存失败');
}
} else {
try {
Notice::find($request->input('id'))->update($data);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$notice = Notice::find($request->input('id'));
if (!$notice) {
abort(500, '公告不存在');
}
if (!$notice->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Staff;
use App\Http\Requests\Admin\PlanSave;
use App\Http\Requests\Admin\PlanSort;
use App\Http\Requests\Admin\PlanUpdate;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\Order;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class PlanController extends Controller
{
public function fetch(Request $request)
{
$counts = User::select(
DB::raw("plan_id"),
DB::raw("count(*) as count")
)
->where('plan_id', '!=', NULL)
->where(function ($query) {
$query->where('expired_at', '>=', time())
->orWhere('expired_at', NULL);
})
->groupBy("plan_id")
->get();
$plans = Plan::orderBy('sort', 'ASC')->get();
foreach ($plans as $k => $v) {
$plans[$k]->count = 0;
foreach ($counts as $kk => $vv) {
if ($plans[$k]->id === $counts[$kk]->plan_id) $plans[$k]->count = $counts[$kk]->count;
}
}
return response([
'data' => $plans
]);
}
}

View File

@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Staff;
use App\Services\TicketService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\TicketMessage;
class TicketController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
}
$ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
for ($i = 0; $i < count($ticket['message']); $i++) {
if ($ticket['message'][$i]['user_id'] !== $ticket->user_id) {
$ticket['message'][$i]['is_me'] = true;
} else {
$ticket['message'][$i]['is_me'] = false;
}
}
return response([
'data' => $ticket
]);
}
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$model = Ticket::orderBy('created_at', 'DESC');
if ($request->input('status') !== NULL) {
$model->where('status', $request->input('status'));
}
$total = $model->count();
$res = $model->forPage($current, $pageSize)
->get();
for ($i = 0; $i < count($res); $i++) {
if ($res[$i]['last_reply_user_id'] == $request->session()->get('id')) {
$res[$i]['reply_status'] = 0;
} else {
$res[$i]['reply_status'] = 1;
}
}
return response([
'data' => $res,
'total' => $total
]);
}
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
if (empty($request->input('message'))) {
abort(500, '消息不能为空');
}
$ticketService = new TicketService();
$ticketService->replyByAdmin(
$request->input('id'),
$request->input('message'),
$request->session()->get('id')
);
return response([
'data' => true
]);
}
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$ticket = Ticket::where('id', $request->input('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
}
$ticket->status = 1;
if (!$ticket->save()) {
abort(500, '关闭失败');
}
return response([
'data' => true
]);
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace App\Http\Controllers\Staff;
use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Staff\UserUpdate;
use App\Jobs\SendEmailJob;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Plan;
class UserController extends Controller
{
public function getUserInfoById(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$user = User::where('is_admin', 0)
->where('id', $request->input('id'))
->where('is_staff', 0)
->first();
if (!$user) abort(500, '用户不存在');
return response([
'data' => $user
]);
}
public function update(UserUpdate $request)
{
$params = $request->validated();
$user = User::find($request->input('id'));
if (!$user) {
abort(500, '用户不存在');
}
if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) {
abort(500, '邮箱已被使用');
}
if (isset($params['password'])) {
$params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
$params['password_algo'] = NULL;
} else {
unset($params['password']);
}
if (isset($params['plan_id'])) {
$plan = Plan::find($params['plan_id']);
if (!$plan) {
abort(500, '订阅计划不存在');
}
$params['group_id'] = $plan->group_id;
}
try {
$user->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function sendMail(UserSendMail $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
$users = $builder->get();
foreach ($users as $user) {
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'notify',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url'),
'content' => $request->input('content')
]
]);
}
return response([
'data' => true
]);
}
public function ban(Request $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
try {
$builder->update([
'banned' => 1
]);
} catch (\Exception $e) {
abort(500, '处理失败');
}
return response([
'data' => true
]);
}
}

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers\User;
use App\Models\Payment;
use App\Utils\Dict;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
@ -11,8 +13,23 @@ class CommController extends Controller
{
return response([
'data' => [
'isTelegram' => (int)config('v2board.telegram_bot_enable', 0)
'is_telegram' => (int)config('v2board.telegram_bot_enable', 0),
'stripe_pk' => config('v2board.stripe_pk_live'),
'withdraw_methods' => config('v2board.commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
'withdraw_close' => (int)config('v2board.withdraw_close_enable', 0)
]
]);
}
public function getStripePublicKey(Request $request)
{
$payment = Payment::where('id', $request->input('id'))
->where('payment', 'StripeCredit')
->first();
if (!$payment) abort(500, 'payment is not found');
$config = json_decode($payment->config, true);
return response([
'data' => $config['stripe_pk_live']
]);
}
}

View File

@ -11,20 +11,26 @@ class CouponController extends Controller
public function check(Request $request)
{
if (empty($request->input('code'))) {
abort(500, '优惠券码不能为空');
abort(500, __('Coupon cannot be empty'));
}
$coupon = Coupon::where('code', $request->input('code'))->first();
if (!$coupon) {
abort(500, '优惠券无效');
abort(500, __('Invalid coupon'));
}
if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) {
abort(500, '优惠券已无可用次数');
abort(500, __('This coupon is no longer available'));
}
if (time() < $coupon->started_at) {
abort(500, '优惠券还未到可用时间');
abort(500, __('This coupon has not yet started'));
}
if (time() > $coupon->ended_at) {
abort(500, '优惠券已过期');
abort(500, __('This coupon has expired'));
}
if ($coupon->limit_plan_ids) {
$limitPlanIds = json_decode($coupon->limit_plan_ids);
if (!in_array($request->input('plan_id'), $limitPlanIds)) {
abort(500, __('The coupon code cannot be used for this subscription'));
}
}
return response([
'data' => $coupon

View File

@ -14,7 +14,7 @@ class InviteController extends Controller
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, '已达到创建数量上限');
abort(500, __('The maximum number of creations has been reached'));
}
$inviteCode = new InviteCode();
$inviteCode->user_id = $request->session()->get('id');

View File

@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\UserService;
use Illuminate\Http\Request;
use App\Models\Knowledge;
class KnowledgeController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$knowledge = Knowledge::where('id', $request->input('id'))
->where('show', 1)
->first()
->toArray();
if (!$knowledge) abort(500, __('Article does not exist'));
$user = User::find($request->session()->get('id'));
$userService = new UserService();
if ($userService->isAvailable($user)) {
$appleId = config('v2board.apple_id');
$appleIdPassword = config('v2board.apple_id_password');
} else {
$appleId = __('No active subscription. Unable to use our provided Apple ID');
$appleIdPassword = __('No active subscription. Unable to use our provided Apple ID');
$this->formatAccessData($knowledge['body']);
}
$subscribeUrl = config('v2board.app_url', env('APP_URL'));
$subscribeUrls = explode(',', config('v2board.subscribe_url'));
if ($subscribeUrls) {
$subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)];
}
$subscribeUrl = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}";
$knowledge['body'] = str_replace('{{siteName}}', config('v2board.app_name', 'V2Board'), $knowledge['body']);
$knowledge['body'] = str_replace('{{appleId}}', $appleId, $knowledge['body']);
$knowledge['body'] = str_replace('{{appleIdPassword}}', $appleIdPassword, $knowledge['body']);
$knowledge['body'] = str_replace('{{subscribeUrl}}', $subscribeUrl, $knowledge['body']);
$knowledge['body'] = str_replace('{{urlEncodeSubscribeUrl}}', urlencode($subscribeUrl), $knowledge['body']);
$knowledge['body'] = str_replace(
'{{safeBase64SubscribeUrl}}',
str_replace(
array('+', '/', '='),
array('-', '_', ''),
base64_encode($subscribeUrl)
),
$knowledge['body']
);
return response([
'data' => $knowledge
]);
}
$knowledges = Knowledge::select(['id', 'category', 'title', 'updated_at'])
->where('language', $request->input('language'))
->where('show', 1)
->orderBy('sort', 'ASC')
->get()
->groupBy('category');
return response([
'data' => $knowledges
]);
}
private function formatAccessData(&$body)
{
function getBetween($input, $start, $end){$substr = substr($input, strlen($start)+strpos($input, $start),(strlen($input) - strpos($input, $end))*(-1));return $substr;}
$accessData = getBetween($body, '<!--access start-->', '<!--access end-->');
if ($accessData) {
$body = str_replace($accessData, '<div class="v2board-no-access">'. __('You must have a valid subscription to view content in this area') .'</div>', $body);
}
}
}

View File

@ -4,23 +4,24 @@ namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\OrderSave;
use App\Models\Payment;
use App\Services\CouponService;
use App\Services\OrderService;
use App\Services\PaymentService;
use App\Services\UserService;
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;
use Library\MGate;
use Library\Epay;
class OrderController extends Controller
{
@ -41,7 +42,7 @@ class OrderController extends Controller
}
}
return response([
'data' => $order
'data' => $order->makeHidden(['id', 'user_id'])
]);
}
@ -51,12 +52,12 @@ class OrderController extends Controller
->where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
abort(500, __('Order does not exist or has been paid'));
}
$order['plan'] = Plan::find($order->plan_id);
$order['try_out_plan_id'] = (int)config('v2board.try_out_plan_id');
if (!$order['plan']) {
abort(500, '订阅不存在');
abort(500, __('Subscription plan does not exist'));
}
return response([
'data' => $order
@ -67,35 +68,38 @@ class OrderController extends Controller
{
$userService = new UserService();
if ($userService->isNotCompleteOrderByUserId($request->session()->get('id'))) {
abort(500, '您有未付款或开通中的订单,请稍后或取消再试');
abort(500, __('You have an unpaid or pending order, please try again later or cancel it'));
}
$plan = Plan::find($request->input('plan_id'));
$user = User::find($request->session()->get('id'));
if (!$plan) {
abort(500, '该订阅不存在');
abort(500, __('Subscription plan does not exist'));
}
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
if ($request->input('cycle') !== 'reset_price') {
abort(500, '该订阅已售罄');
abort(500, __('This subscription has been sold out, please choose another subscription'));
}
}
if (!$plan->renew && $user->plan_id == $plan->id) {
abort(500, '该订阅无法续费,请更换其他订阅');
if (!$plan->renew && $user->plan_id == $plan->id && $request->input('cycle') !== 'reset_price') {
abort(500, __('This subscription cannot be renewed, please change to another subscription'));
}
if ($plan[$request->input('cycle')] === NULL) {
if ($request->input('cycle') === 'reset_price') {
abort(500, '该订阅当前不支持重置流量');
}
abort(500, '该订阅周期无法进行购买,请选择其他周期');
abort(500, __('This payment cycle cannot be purchased, please choose another cycle'));
}
if ($request->input('cycle') === 'reset_price' && !$user->plan_id) {
abort(500, '必须存在订阅才可以购买流量重置包');
if ($request->input('cycle') === 'reset_price') {
if ($user->expired_at <= time() || !$user->plan_id) {
abort(500, __('Subscription has expired or no active subscription, unable to purchase Data Reset Package'));
}
}
if (!$plan->show && $plan->renew && !$userService->isAvailable($user)) {
abort(500, __('This subscription has expired, please change to another subscription'));
}
DB::beginTransaction();
@ -111,8 +115,9 @@ class OrderController extends Controller
$couponService = new CouponService($request->input('coupon_code'));
if (!$couponService->use($order)) {
DB::rollBack();
abort(500, '优惠券使用失败');
abort(500, __('Coupon failed'));
}
$order->coupon_id = $couponService->getId();
}
$orderService->setVipDiscount($user);
@ -125,14 +130,14 @@ class OrderController extends Controller
if ($remainingBalance > 0) {
if (!$userService->addBalance($order->user_id, - $order->total_amount)) {
DB::rollBack();
abort(500, '余额不足');
abort(500, __('Insufficient balance'));
}
$order->balance_amount = $order->total_amount;
$order->total_amount = 0;
} else {
if (!$userService->addBalance($order->user_id, - $user->balance)) {
DB::rollBack();
abort(500, '余额不足');
abort(500, __('Insufficient balance'));
}
$order->balance_amount = $user->balance;
$order->total_amount = $order->total_amount - $user->balance;
@ -141,7 +146,7 @@ class OrderController extends Controller
if (!$order->save()) {
DB::rollback();
abort(500, '订单创建失败');
abort(500, __('Failed to create order'));
}
DB::commit();
@ -160,7 +165,7 @@ class OrderController extends Controller
->where('status', 0)
->first();
if (!$order) {
abort(500, '订单不存在或已支付');
abort(500, __('Order does not exist or has been paid'));
}
// free process
if ($order->total_amount <= 0) {
@ -172,55 +177,20 @@ class OrderController extends Controller
'data' => true
]);
}
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, '支付方式不存在');
}
$payment = Payment::find($method);
if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available'));
$paymentService = new PaymentService($payment->payment, $payment->id);
$result = $paymentService->pay([
'trade_no' => $tradeNo,
'total_amount' => $order->total_amount,
'user_id' => $order->user_id,
'stripe_token' => $request->input('token')
]);
$order->update(['payment_id' => $method]);
return response([
'type' => $result['type'],
'data' => $result['data']
]);
}
public function check(Request $request)
@ -230,7 +200,7 @@ class OrderController extends Controller
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, '订单不存在');
abort(500, __('Order does not exist'));
}
return response([
'data' => $order->status
@ -239,191 +209,38 @@ class OrderController extends Controller
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 = config('v2board.bitpayx_name', '聚合支付');
$bitpayX->method = 4;
$bitpayX->icon = 'wallet';
array_push($data, $bitpayX);
}
if ((int)config('v2board.paytaro_enable')) {
$obj = new \StdClass();
$obj->name = config('v2board.paytaro_name', '聚合支付');
$obj->method = 5;
$obj->icon = 'wallet';
array_push($data, $obj);
}
$methods = Payment::select([
'id',
'name',
'payment'
])
->where('enable', 1)->get();
return response([
'data' => $data
'data' => $methods
]);
}
public function cancel(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, '参数有误');
abort(500, __('Invalid parameter'));
}
$order = Order::where('trade_no', $request->input('trade_no'))
->where('user_id', $request->session()->get('id'))
->first();
if (!$order) {
abort(500, '订单不存在');
abort(500, __('Order does not exist'));
}
if ($order->status !== 0) {
abort(500, '只可以取消待支付订单');
abort(500, __('You can only cancel pending orders'));
}
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, '取消失败');
abort(500, __('Cancel failed'));
}
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)
{
$currency = config('v2board.stripe_currency', 'hkd');
$exchange = Helper::exchange('CNY', strtoupper($currency));
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => $currency,
'type' => 'alipay',
'statement_descriptor' => $order->trade_no,
'metadata' => [
'user_id' => $order->user_id,
'invoice_id' => $order->trade_no,
'identifier' => ''
],
'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)
{
$currency = config('v2board.stripe_currency', 'hkd');
$exchange = Helper::exchange('CNY', strtoupper($currency));
if (!$exchange) {
abort(500, '货币转换超时,请稍后再试');
}
Stripe::setApiKey(config('v2board.stripe_sk_live'));
$source = Source::create([
'amount' => floor($order->total_amount * $exchange),
'currency' => $currency,
'type' => 'wechat',
'metadata' => [
'user_id' => $order->user_id,
'invoice_id' => $order->trade_no,
'identifier' => ''
],
'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

@ -14,7 +14,7 @@ class PlanController extends Controller
$plan = Plan::where('id', $request->input('id'))
->first();
if (!$plan) {
abort(500, '该订阅不存在');
abort(500, __('Subscription plan does not exist'));
}
return response([
'data' => $plan

View File

@ -3,7 +3,9 @@
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Server;
@ -17,29 +19,14 @@ class ServerController extends Controller
public function fetch(Request $request)
{
$user = User::find($request->session()->get('id'));
$server = [];
$servers = [];
$userService = new UserService();
if ($userService->isAvailable($user)) {
$servers = Server::where('show', 1)
->orderBy('sort', 'ASC')
->get();
foreach ($servers as $item) {
$groupId = json_decode($item['group_id']);
if (in_array($user->group_id, $groupId)) {
array_push($server, $item);
}
}
}
for ($i = 0; $i < count($server); $i++) {
$server[$i]['link'] = Helper::buildVmessLink($server[$i], $user);
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']);
}
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
}
return response([
'data' => $server
'data' => $servers
]);
}

View File

@ -3,7 +3,9 @@
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\TelegramService;
use Illuminate\Http\Request;
class TelegramController extends Controller
{
@ -17,4 +19,9 @@ class TelegramController extends Controller
]
]);
}
public function unbind(Request $request)
{
$user = User::where('user_id', $request->session()->get('id'))->first();
}
}

View File

@ -7,6 +7,8 @@ use App\Http\Requests\User\TicketSave;
use App\Http\Requests\User\TicketWithdraw;
use App\Jobs\SendTelegramJob;
use App\Models\User;
use App\Services\TelegramService;
use App\Utils\Dict;
use Illuminate\Http\Request;
use App\Models\Ticket;
use App\Models\TicketMessage;
@ -21,7 +23,7 @@ class TicketController extends Controller
->where('user_id', $request->session()->get('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
abort(500, __('Ticket does not exist'));
}
$ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
for ($i = 0; $i < count($ticket['message']); $i++) {
@ -54,7 +56,7 @@ class TicketController extends Controller
{
DB::beginTransaction();
if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->count()) {
abort(500, '存在其他工单尚未处理');
abort(500, __('There are other unresolved tickets'));
}
$ticket = Ticket::create(array_merge($request->only([
'subject',
@ -65,7 +67,7 @@ class TicketController extends Controller
]));
if (!$ticket) {
DB::rollback();
abort(500, '工单创建失败');
abort(500, __('Failed to open ticket'));
}
$ticketMessage = TicketMessage::create([
'user_id' => $request->session()->get('id'),
@ -74,7 +76,7 @@ class TicketController extends Controller
]);
if (!$ticketMessage) {
DB::rollback();
abort(500, '工单创建失败');
abort(500, __('Failed to open ticket'));
}
DB::commit();
$this->sendNotify($ticket, $ticketMessage);
@ -86,22 +88,22 @@ class TicketController extends Controller
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
abort(500, __('Invalid parameter'));
}
if (empty($request->input('message'))) {
abort(500, '消息不能为空');
abort(500, __('Message cannot be empty'));
}
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->session()->get('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
abort(500, __('Ticket does not exist'));
}
if ($ticket->status) {
abort(500, '工单已关闭,无法回复');
abort(500, __('The ticket is closed and cannot be replied'));
}
if ($request->session()->get('id') == $this->getLastMessage($ticket->id)->user_id) {
abort(500, '请等待技术支持回复');
abort(500, __('Please wait for the technical enginneer to reply'));
}
DB::beginTransaction();
$ticketMessage = TicketMessage::create([
@ -112,7 +114,7 @@ class TicketController extends Controller
$ticket->last_reply_user_id = $request->session()->get('id');
if (!$ticketMessage || !$ticket->save()) {
DB::rollback();
abort(500, '工单回复失败');
abort(500, __('Ticket reply failed'));
}
DB::commit();
$this->sendNotify($ticket, $ticketMessage);
@ -125,17 +127,17 @@ class TicketController extends Controller
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
abort(500, __('Invalid parameter'));
}
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->session()->get('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
abort(500, __('Ticket does not exist'));
}
$ticket->status = 1;
if (!$ticket->save()) {
abort(500, '关闭失败');
abort(500, __('Close failed'));
}
return response([
'data' => true
@ -151,8 +153,25 @@ class TicketController extends Controller
public function withdraw(TicketWithdraw $request)
{
if ((int)config('v2board.withdraw_close_enable', 0)) {
abort(500, 'user.ticket.withdraw.not_support_withdraw');
}
if (!in_array(
$request->input('withdraw_method'),
config(
'v2board.commission_withdraw_method',
Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT
)
)) {
abort(500, __('Unsupported withdrawal method'));
}
$user = User::find($request->session()->get('id'));
$limit = config('v2board.commission_withdraw_limit', 100);
if ($limit > ($user->commission_balance / 100)) {
abort(500, __('The current required minimum withdrawal commission is', ['limit' => $limit]));
}
DB::beginTransaction();
$subject = '[提现申请]本工单由系统发出';
$subject = __('[Commission Withdrawal Request] This ticket is opened by the system');
$ticket = Ticket::create([
'subject' => $subject,
'level' => 2,
@ -161,15 +180,12 @@ class TicketController extends Controller
]);
if (!$ticket) {
DB::rollback();
abort(500, '工单创建失败');
abort(500, __('Failed to open ticket'));
}
$methodText = [
'alipay' => '支付宝',
'paypal' => '贝宝(Paypal)',
'usdt' => 'USDT',
'btc' => '比特币'
];
$message = "提现方式:{$methodText[$request->input('withdraw_method')]}\r\n提现账号:{$request->input('withdraw_account')}\r\n";
$message = sprintf("%s\r\n%s",
__('Withdrawal method') . "" . $request->input('withdraw_method'),
__('Withdrawal account') . "" . $request->input('withdraw_account')
);
$ticketMessage = TicketMessage::create([
'user_id' => $request->session()->get('id'),
'ticket_id' => $ticket->id,
@ -177,7 +193,7 @@ class TicketController extends Controller
]);
if (!$ticketMessage) {
DB::rollback();
abort(500, '工单创建失败');
abort(500, __('Failed to open ticket'));
}
DB::commit();
$this->sendNotify($ticket, $ticketMessage);
@ -188,13 +204,7 @@ class TicketController extends Controller
private function sendNotify(Ticket $ticket, TicketMessage $ticketMessage)
{
if (!config('v2board.telegram_bot_enable', 0)) return;
$users = User::where('is_admin', 1)
->where('telegram_id', '!=', NULL)
->get();
foreach ($users as $user) {
$text = "📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$ticketMessage->message}`";
SendTelegramJob::dispatch($user->telegram_id, $text);
}
$telegramService = new TelegramService();
$telegramService->sendMessageWithAdmin("📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$ticketMessage->message}`", true);
}
}

View File

@ -1,82 +0,0 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Tutorial;
use Illuminate\Support\Facades\DB;
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', 'category_id', 'title'])
->where('show', 1)
->orderBy('sort', 'ASC')
->get()
->groupBy('category_id');
$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() || $user->expired_at === NULL ? config('v2board.apple_id', '本站暂无提供AppleID信息') : '账号过期或未订阅',
'apple_id_password' => $user->expired_at > time() || $user->expired_at === NULL ? 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
// fuck support surge urlencode subscribe
$response['data']['safe_area_var']['ue_subscribe_url'] = urlencode($response['data']['safe_area_var']['subscribe_url']);
// end
return response($response);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserTransfer;
use App\Http\Requests\User\UserUpdate;
use App\Http\Requests\User\UserChangePassword;
use Illuminate\Http\Request;
@ -27,17 +28,20 @@ class UserController extends Controller
public function changePassword(UserChangePassword $request)
{
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, __('The user does not exist'));
}
if (!Helper::multiPasswordVerify(
$user->password_algo,
$request->input('old_password'),
$user->password)
) {
abort(500, '旧密码有误');
abort(500, __('The old password is wrong'));
}
$user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
if (!$user->save()) {
abort(500, '保存失败');
abort(500, __('Save failed'));
}
$request->session()->flush();
return response([
@ -54,7 +58,6 @@ class UserController extends Controller
'last_login_at',
'created_at',
'banned',
'is_admin',
'remind_expire',
'remind_traffic',
'expired_at',
@ -66,6 +69,9 @@ class UserController extends Controller
'telegram_id'
])
->first();
if (!$user) {
abort(500, __('The user does not exist'));
}
$user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon';
return response([
'data' => $user
@ -91,14 +97,34 @@ class UserController extends Controller
public function getSubscribe(Request $request)
{
$user = User::find($request->session()->get('id'));
$user = User::where('id', $request->session()->get('id'))
->select([
'id',
'plan_id',
'token',
'expired_at',
'u',
'd',
'transfer_enable',
'email'
])
->first();
if (!$user) {
abort(500, __('The user does not exist'));
}
if ($user->plan_id) {
$user['plan'] = Plan::find($user->plan_id);
if (!$user['plan']) {
abort(500, '订阅计划不存在');
abort(500, __('Subscription plan does not exist'));
}
}
$user['subscribe_url'] = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
$subscribeUrl = config('v2board.app_url', env('APP_URL'));
$subscribeUrls = explode(',', config('v2board.subscribe_url'));
if ($subscribeUrls) {
$subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)];
}
$user['subscribe_url'] = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}";
$user['reset_day'] = $this->getResetDay($user);
return response([
'data' => $user
]);
@ -107,10 +133,13 @@ class UserController extends Controller
public function resetSecurity(Request $request)
{
$user = User::find($request->session()->get('id'));
$user->v2ray_uuid = Helper::guid(true);
if (!$user) {
abort(500, __('The user does not exist'));
}
$user->uuid = Helper::guid(true);
$user->token = Helper::guid();
if (!$user->save()) {
abort(500, '重置失败');
abort(500, __('Reset failed'));
}
return response([
'data' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user->token
@ -126,12 +155,12 @@ class UserController extends Controller
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, '该用户不存在');
abort(500, __('The user does not exist'));
}
try {
$user->update($updateData);
} catch (\Exception $e) {
abort(500, '保存失败');
abort(500, __('Save failed'));
}
return response([
@ -139,25 +168,45 @@ class UserController extends Controller
]);
}
public function transfer(Request $request)
public function transfer(UserTransfer $request)
{
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, '该用户不存在');
}
if ($request->input('transfer_amount') <= 0) {
abort(500, '参数错误');
abort(500, __('The user does not exist'));
}
if ($request->input('transfer_amount') > $user->commission_balance) {
abort(500, '推广佣金余额不足');
abort(500, __('Insufficient commission balance'));
}
$user->commission_balance = $user->commission_balance - $request->input('transfer_amount');
$user->balance = $user->balance + $request->input('transfer_amount');
if (!$user->save()) {
abort(500, '划转失败');
abort(500, __('Transfer failed'));
}
return response([
'data' => true
]);
}
private function getResetDay(User $user)
{
if ($user->expired_at <= time() || $user->expired_at === NULL) return null;
$day = date('d', $user->expired_at);
$today = date('d');
$lastDay = date('d', strtotime('last day of +0 months'));
if ((int)config('v2board.reset_traffic_method') === 0) {
return $lastDay - $today;
}
if ((int)config('v2board.reset_traffic_method') === 1) {
if ((int)$day >= (int)$today && (int)$day >= (int)$lastDay) {
return $lastDay - $today;
}
if ((int)$day >= (int)$today) {
return $day - $today;
} else {
return $lastDay - $today + $day;
}
}
return null;
}
}

View File

@ -43,7 +43,7 @@ class Kernel extends HttpKernel
\Illuminate\Session\Middleware\StartSession::class,
\App\Http\Middleware\ForceJson::class,
\App\Http\Middleware\CORS::class,
'throttle:120,1',
\App\Http\Middleware\Language::class,
'bindings',
],
];
@ -68,7 +68,7 @@ class Kernel extends HttpKernel
'user' => \App\Http\Middleware\User::class,
'admin' => \App\Http\Middleware\Admin::class,
'client' => \App\Http\Middleware\Client::class,
'server' => \App\Http\Middleware\Server::class,
'staff' => \App\Http\Middleware\Staff::class,
];
/**

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\App;
class Language
{
public function handle($request, Closure $next)
{
if ($request->header('content-language')) {
App::setLocale($request->header('content-language'));
}
return $next($request);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Middleware;
use Closure;
class Staff
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (!$request->session()->get('is_staff')) {
abort(403, '权限不足');
}
return $next($request);
}
}

View File

@ -15,12 +15,15 @@ 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->input('auth_data')) {
$authData = explode(':', base64_decode($request->input('auth_data')));
if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
$user = \App\Models\User::where('password', $authData[1])
->where('email', $authData[0])
->first();
if (!$user) abort(403, '鉴权失败,请重新登入');
$request->session()->put('email', $user->email);
$request->session()->put('id', $user->id);
}
if (!$request->session()->get('id')) {
abort(403, '未登录或登陆已过期');

View File

@ -6,74 +6,6 @@ use Illuminate\Foundation\Http\FormRequest;
class ConfigSave extends FormRequest
{
CONST RULES = [
// invite & commission
'safe_mode_enable' => 'in:0,1',
'invite_force' => 'in:0,1',
'invite_commission' => 'integer',
'invite_gen_limit' => 'integer',
'invite_never_expire' => 'in:0,1',
'commission_first_time_enable' => 'in:0,1',
'commission_auto_check_enable' => 'in:0,1',
// site
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_name' => '',
'app_description' => '',
'app_url' => 'nullable|url',
'subscribe_url' => 'nullable|url',
'try_out_enable' => 'in:0,1',
'try_out_plan_id' => 'integer',
'try_out_hour' => 'numeric',
'email_whitelist_enable' => 'in:0,1',
'email_whitelist_suffix' => '',
'email_gmail_limit_enable' => 'in:0,1',
// subscribe
'plan_change_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1',
'renew_reset_traffic_enable' => 'in:0,1',
// server
'server_token' => 'nullable|min:16',
'server_license' => 'nullable',
'server_log_level' => 'nullable|in:debug,info,warning,error,none',
// alipay
'alipay_enable' => 'in:0,1',
'alipay_appid' => 'nullable|integer|min:16',
'alipay_pubkey' => 'max:2048',
'alipay_privkey' => 'max:2048',
// stripe
'stripe_alipay_enable' => 'in:0,1',
'stripe_wepay_enable' => 'in:0,1',
'stripe_sk_live' => '',
'stripe_pk_live' => '',
'stripe_webhook_key' => '',
'stripe_currency' => 'in:hkd,usd,sgd,eur,gbp',
// bitpayx
'bitpayx_name' => '',
'bitpayx_enable' => 'in:0,1',
'bitpayx_appsecret' => '',
// paytaro
'paytaro_name' => '',
'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' => 'email',
'apple_id_password' => '',
// email
'email_template' => '',
// telegram
'telegram_bot_enable' => 'in:0,1',
'telegram_bot_token' => '',
'telegram_discuss_id' => '',
'telegram_channel_id' => ''
];
/**
* Get the validation rules that apply to the request.
*
@ -81,7 +13,110 @@ class ConfigSave extends FormRequest
*/
public function rules()
{
return self::RULES;
return [
// invite & commission
'safe_mode_enable' => 'in:0,1',
'invite_force' => 'in:0,1',
'invite_commission' => 'integer',
'invite_gen_limit' => 'integer',
'invite_never_expire' => 'in:0,1',
'commission_first_time_enable' => 'in:0,1',
'commission_auto_check_enable' => 'in:0,1',
'commission_withdraw_limit' => 'nullable|numeric',
'commission_withdraw_method' => 'nullable|array',
'withdraw_close_enable' => 'in:0,1',
// site
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_name' => '',
'app_description' => '',
'app_url' => 'nullable|url',
'subscribe_url' => 'nullable',
'try_out_enable' => 'in:0,1',
'try_out_plan_id' => 'integer',
'try_out_hour' => 'numeric',
'email_whitelist_enable' => 'in:0,1',
'email_whitelist_suffix' => 'nullable|array',
'email_gmail_limit_enable' => 'in:0,1',
'recaptcha_enable' => 'in:0,1',
'recaptcha_key' => '',
'recaptcha_site_key' => '',
'tos_url' => 'nullable|url',
// subscribe
'plan_change_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1',
'surplus_enable' => 'in:0,1',
'new_order_event_id' => 'in:0,1',
'renew_order_event_id' => 'in:0,1',
'change_order_event_id' => 'in:0,1',
// server
'server_token' => 'nullable|min:16',
'server_license' => 'nullable',
'server_log_enable' => 'in:0,1',
'server_v2ray_domain' => '',
'server_v2ray_protocol' => '',
// alipay
'alipay_enable' => 'in:0,1',
'alipay_appid' => 'nullable|integer|min:16',
'alipay_pubkey' => 'max:2048',
'alipay_privkey' => 'max:2048',
// stripe
'stripe_alipay_enable' => 'in:0,1',
'stripe_wepay_enable' => 'in:0,1',
'stripe_card_enable' => 'in:0,1',
'stripe_sk_live' => '',
'stripe_pk_live' => '',
'stripe_webhook_key' => '',
'stripe_currency' => 'in:hkd,usd,sgd,eur,gbp,jpy,cad',
// bitpayx
'bitpayx_name' => '',
'bitpayx_enable' => 'in:0,1',
'bitpayx_appsecret' => '',
// mGate
'mgate_name' => '',
'mgate_enable' => 'in:0,1',
'mgate_url' => 'nullable|url',
'mgate_app_id' => '',
'mgate_app_secret' => '',
// Epay
'epay_name' => '',
'epay_enable' => 'in:0,1',
'epay_url' => 'nullable|url',
'epay_pid' => '',
'epay_key' => '',
// frontend
'frontend_theme' => '',
'frontend_theme_sidebar' => 'in:dark,light',
'frontend_theme_header' => 'in:dark,light',
'frontend_theme_color' => 'in:default,darkblue,black',
'frontend_background_url' => 'nullable|url',
'frontend_admin_path' => '',
'frontend_customer_service_method' => '',
'frontend_customer_service_id' => '',
// tutorial
'apple_id' => 'nullable|email',
'apple_id_password' => '',
// email
'email_template' => '',
'email_host' => '',
'email_port' => '',
'email_username' => '',
'email_password' => '',
'email_encryption' => '',
'email_from_address' => '',
// telegram
'telegram_bot_enable' => 'in:0,1',
'telegram_bot_token' => '',
'telegram_discuss_id' => '',
'telegram_channel_id' => '',
// app
'windows_version' => '',
'windows_download_url' => '',
'macos_version' => '',
'macos_download_url' => '',
'android_version' => '',
'android_download_url' => ''
];
}
public function messages()
@ -89,7 +124,9 @@ class ConfigSave extends FormRequest
// illiteracy prompt
return [
'app_url.url' => '站点URL格式不正确必须携带http(s)://',
'subscribe_url.url' => '订阅URL格式不正确必须携带http(s)://'
'subscribe_url.url' => '订阅URL格式不正确必须携带http(s)://',
'server_token.min' => '通讯密钥长度必须大于16位',
'tos_url.url' => '服务条款URL格式不正确'
];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class CouponGenerate extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'generate_count' => 'nullable|integer|max:500',
'name' => 'required',
'type' => 'required|in:1,2',
'value' => 'required|integer',
'started_at' => 'required|integer',
'ended_at' => 'required|integer',
'limit_use' => 'nullable|integer',
'limit_plan_ids' => 'nullable|array',
'code' => ''
];
}
public function messages()
{
return [
'generate_count.integer' => '生成数量必须为数字',
'generate_count.max' => '生成数量最大为500个',
'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' => '使用次数格式有误',
'limit_plan_ids.array' => '指定订阅格式有误'
];
}
}

View File

@ -19,7 +19,9 @@ class CouponSave extends FormRequest
'value' => 'required|integer',
'started_at' => 'required|integer',
'ended_at' => 'required|integer',
'limit_use' => 'nullable|integer'
'limit_use' => 'nullable|integer',
'limit_plan_ids' => 'nullable|array',
'code' => ''
];
}
@ -35,7 +37,8 @@ class CouponSave extends FormRequest
'started_at.integer' => '开始时间格式有误',
'ended_at.required' => '结束时间不能为空',
'ended_at.integer' => '结束时间格式有误',
'limit_use.integer' => '使用次数格式有误'
'limit_use.integer' => '使用次数格式有误',
'limit_plan_ids.array' => '指定订阅格式有误'
];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class KnowledgeCategorySave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'language' => 'required'
];
}
public function messages()
{
return [
'name.required' => '分类名称不能为空',
'language.required' => '分类语言不能为空'
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class KnowledgeCategorySort extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'knowledge_category_ids' => 'required|array'
];
}
public function messages()
{
return [
'knowledge_category_ids.required' => '分类不能为空',
'knowledge_category_ids.array' => '分类格式有误'
];
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class KnowledgeSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'category' => 'required',
'language' => 'required',
'title' => 'required',
'body' => 'required'
];
}
public function messages()
{
return [
'title.required' => '标题不能为空',
'category.required' => '分类不能为空',
'body.required' => '内容不能为空',
'language.required' => '语言不能为空'
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class KnowledgeSort extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'knowledge_ids' => 'required|array'
];
}
public function messages()
{
return [
'knowledge_ids.required' => '知识ID不能为空',
'knowledge_ids.array' => '知识ID格式有误'
];
}
}

View File

@ -17,7 +17,7 @@ class OrderAssign extends FormRequest
'plan_id' => 'required',
'email' => 'required',
'total_amount' => 'required',
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,onetime_price,reset_price'
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,two_year_price,three_year_price,onetime_price,reset_price'
];
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class OrderFetch extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'filter.*.key' => 'required|in:email,trade_no,status,commission_status,user_id,invite_user_id',
'filter.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=',
'filter.*.value' => ''
];
}
public function messages()
{
return [
'filter.*.key.required' => '过滤键不能为空',
'filter.*.key.in' => '过滤键参数有误',
'filter.*.condition.required' => '过滤条件不能为空',
'filter.*.condition.in' => '过滤条件参数有误',
];
}
}

View File

@ -15,7 +15,7 @@ class OrderUpdate extends FormRequest
{
return [
'status' => 'in:0,1,2,3',
'commission_status' => 'in:0,1,2'
'commission_status' => 'in:0,1,3'
];
}

View File

@ -6,18 +6,6 @@ use Illuminate\Foundation\Http\FormRequest;
class PlanSave extends FormRequest
{
CONST RULES = [
'name' => 'required',
'content' => '',
'group_id' => 'required',
'transfer_enable' => 'required',
'month_price' => 'nullable|integer',
'quarter_price' => 'nullable|integer',
'half_year_price' => 'nullable|integer',
'year_price' => 'nullable|integer',
'onetime_price' => 'nullable|integer',
'reset_price' => 'nullable|integer'
];
/**
* Get the validation rules that apply to the request.
*
@ -25,7 +13,20 @@ class PlanSave extends FormRequest
*/
public function rules()
{
return self::RULES;
return [
'name' => 'required',
'content' => '',
'group_id' => 'required',
'transfer_enable' => 'required',
'month_price' => 'nullable|integer',
'quarter_price' => 'nullable|integer',
'half_year_price' => 'nullable|integer',
'year_price' => 'nullable|integer',
'two_year_price' => 'nullable|integer',
'three_year_price' => 'nullable|integer',
'onetime_price' => 'nullable|integer',
'reset_price' => 'nullable|integer'
];
}
public function messages()
@ -40,6 +41,8 @@ class PlanSave extends FormRequest
'quarter_price.integer' => '季付金额格式有误',
'half_year_price.integer' => '半年付金额格式有误',
'year_price.integer' => '年付金额格式有误',
'two_year_price.integer' => '两年付金额格式有误',
'three_year_price.integer' => '三年付金额格式有误',
'onetime_price.integer' => '一次性金额有误',
'reset_price.integer' => '流量重置包金额有误'
];

View File

@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class ServerShadowsocksSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'cipher' => 'required|in:aes-128-gcm,aes-256-gcm,chacha20-ietf-poly1305',
'tags' => 'nullable|array',
'rate' => 'required|numeric'
];
}
public function messages()
{
return [
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'parent_id.integer' => '父节点格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',
'server_port.required' => '后端服务端口不能为空',
'cipher.required' => '加密方式不能为空',
'tags.array' => '标签格式不正确',
'rate.required' => '倍率不能为空',
'rate.numeric' => '倍率格式不正确'
];
}
}

View File

@ -4,25 +4,25 @@ namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class ServerSort extends FormRequest
class ServerShadowsocksUpdate extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'server_ids' => 'required|array'
'show' => 'in:0,1'
];
}
public function messages()
{
return [
'server_ids.required' => '服务器ID不能为空',
'server_ids.array' => '服务器ID格式有误'
'show.in' => '显示状态格式不正确'
];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class ServerTrojanSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'allow_insecure' => 'nullable|in:0,1',
'server_name' => 'nullable',
'tags' => 'nullable|array',
'rate' => 'required|numeric'
];
}
public function messages()
{
return [
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'parent_id.integer' => '父节点格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',
'server_port.required' => '后端服务端口不能为空',
'allow_insecure.in' => '允许不安全格式不正确',
'tags.array' => '标签格式不正确',
'rate.required' => '倍率不能为空',
'rate.numeric' => '倍率格式不正确'
];
}
}

View File

@ -4,25 +4,25 @@ namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class TutorialSort extends FormRequest
class ServerTrojanUpdate extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'tutorial_ids' => 'required|array'
'show' => 'in:0,1'
];
}
public function messages()
{
return [
'tutorial_ids.required' => '教程ID不能为空',
'tutorial_ids.array' => '教程ID格式有误'
'show.in' => '显示状态格式不正确'
];
}
}

View File

@ -4,25 +4,8 @@ namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class ServerSave extends FormRequest
class ServerV2raySave extends FormRequest
{
CONST RULES = [
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'tls' => 'required',
'tags' => 'nullable|array',
'rate' => 'required|numeric',
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic',
'networkSettings' => '',
'ruleSettings' => '',
'tlsSettings' => '',
'dnsSettings' => ''
];
/**
* Get the validation rules that apply to the request.
*
@ -30,7 +13,24 @@ class ServerSave extends FormRequest
*/
public function rules()
{
return self::RULES;
return [
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'tls' => 'required',
'tags' => 'nullable|array',
'rate' => 'required|numeric',
'alter_id' => 'required|integer',
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic,grpc',
'networkSettings' => '',
'ruleSettings' => '',
'tlsSettings' => '',
'dnsSettings' => ''
];
}
public function messages()

View File

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

View File

@ -1,34 +0,0 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class TutorialSave extends FormRequest
{
CONST RULES = [
'title' => 'required',
// 1:windows 2:macos 3:ios 4:android 5:linux 6:router
'category_id' => 'required|in:1,2,3,4,5,6',
'steps' => 'required'
];
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return self::RULES;
}
public function messages()
{
return [
'title.required' => '标题不能为空',
'category_id.required' => '分类不能为空',
'category_id.in' => '分类格式不正确',
'steps.required' => '教程步骤不能为空'
];
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class UserFetch extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id,plan_id,banned,remarks',
'filter.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=',
'filter.*.value' => 'required'
];
}
public function messages()
{
return [
'filter.*.key.required' => '过滤键不能为空',
'filter.*.key.in' => '过滤键参数有误',
'filter.*.condition.required' => '过滤条件不能为空',
'filter.*.condition.in' => '过滤条件参数有误',
'filter.*.value.required' => '过滤值不能为空'
];
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class UserGenerate extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'generate_count' => 'nullable|integer|max:500',
'expired_at' => 'nullable|integer',
'plan_id' => 'nullable|integer',
'email_prefix' => 'nullable',
'email_suffix' => 'required',
'password' => 'nullable'
];
}
public function messages()
{
return [
'generate_count.integer' => '生成数量必须为数字',
'generate_count.max' => '生成数量最大为500个'
];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class UserSendMail extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'subject' => 'required',
'content' => 'required',
];
}
public function messages()
{
return [
'subject.required' => '主题不能为空',
'content.required' => '发送内容不能为空'
];
}
}

View File

@ -6,21 +6,6 @@ use Illuminate\Foundation\Http\FormRequest;
class UserUpdate extends FormRequest
{
CONST RULES = [
'email' => 'required|email',
'password' => 'nullable',
'transfer_enable' => 'numeric',
'expired_at' => 'nullable|integer',
'banned' => 'required|in:0,1',
'plan_id' => 'nullable|integer',
'commission_rate' => 'nullable|integer|min:0|max:100',
'discount' => 'nullable|integer|min:0|max:100',
'is_admin' => 'required|in:0,1',
'u' => 'integer',
'd' => 'integer',
'balance' => 'integer',
'commission_balance' => 'integer'
];
/**
* Get the validation rules that apply to the request.
*
@ -28,7 +13,24 @@ class UserUpdate extends FormRequest
*/
public function rules()
{
return self::RULES;
return [
'email' => 'required|email',
'password' => 'nullable|min:8',
'transfer_enable' => 'numeric',
'expired_at' => 'nullable|integer',
'banned' => 'required|in:0,1',
'plan_id' => 'nullable|integer',
'commission_rate' => 'nullable|integer|min:0|max:100',
'discount' => 'nullable|integer|min:0|max:100',
'is_admin' => 'required|in:0,1',
'is_staff' => 'required|in:0,1',
'u' => 'integer',
'd' => 'integer',
'balance' => 'integer',
'commission_type' => 'integer',
'commission_balance' => 'integer',
'remarks' => 'nullable'
];
}
public function messages()
@ -42,6 +44,8 @@ class UserUpdate extends FormRequest
'banned.in' => '是否封禁格式不正确',
'is_admin.required' => '是否管理员不能为空',
'is_admin.in' => '是否管理员格式不正确',
'is_staff.required' => '是否员工不能为空',
'is_staff.in' => '是否员工格式不正确',
'plan_id.integer' => '订阅计划格式不正确',
'commission_rate.integer' => '推荐返利比例格式不正确',
'commission_rate.nullable' => '推荐返利比例格式不正确',
@ -54,7 +58,8 @@ class UserUpdate extends FormRequest
'u.integer' => '上行流量格式不正确',
'd.integer' => '下行流量格式不正确',
'balance.integer' => '余额格式不正确',
'commission_balance.integer' => '佣金格式不正确'
'commission_balance.integer' => '佣金格式不正确',
'password.min' => '密码长度最小8位'
];
}
}

View File

@ -23,11 +23,11 @@ class AuthForget extends FormRequest
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数',
'email_code.required' => '邮箱验证码不能为空'
'email.required' => __('Email can not be empty'),
'email.email' => __('Email format is incorrect'),
'password.required' => __('Password can not be empty'),
'password.min' => __('Password must be greater than 8 digits'),
'email_code.required' => __('Email verification code cannot be empty')
];
}
}

View File

@ -22,10 +22,10 @@ class AuthLogin extends FormRequest
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数'
'email.required' => __('Email can not be empty'),
'email.email' => __('Email format is incorrect'),
'password.required' => __('Password can not be empty'),
'password.min' => __('Password must be greater than 8 digits')
];
}
}

View File

@ -22,10 +22,10 @@ class AuthRegister extends FormRequest
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'password.required' => '密码不能为空',
'password.min' => '密码必须大于8位数'
'email.required' => __('Email can not be empty'),
'email.email' => __('Email format is incorrect'),
'password.required' => __('Password can not be empty'),
'password.min' => __('Password must be greater than 8 digits')
];
}
}

View File

@ -21,8 +21,8 @@ class CommSendEmailVerify extends FormRequest
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确'
'email.required' => __('Email can not be empty'),
'email.email' => __('Email format is incorrect')
];
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Http\Requests\Staff;
use Illuminate\Foundation\Http\FormRequest;
class UserUpdate extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|email',
'password' => 'nullable',
'transfer_enable' => 'numeric',
'expired_at' => 'nullable|integer',
'banned' => 'required|in:0,1',
'plan_id' => 'nullable|integer',
'commission_rate' => 'nullable|integer|min:0|max:100',
'discount' => 'nullable|integer|min:0|max:100',
'u' => 'integer',
'd' => 'integer',
'balance' => 'integer',
'commission_balance' => 'integer'
];
}
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'transfer_enable.numeric' => '流量格式不正确',
'expired_at.integer' => '到期时间格式不正确',
'banned.required' => '是否封禁不能为空',
'banned.in' => '是否封禁格式不正确',
'plan_id.integer' => '订阅计划格式不正确',
'commission_rate.integer' => '推荐返利比例格式不正确',
'commission_rate.nullable' => '推荐返利比例格式不正确',
'commission_rate.min' => '推荐返利比例最小为0',
'commission_rate.max' => '推荐返利比例最大为100',
'discount.integer' => '专属折扣比例格式不正确',
'discount.nullable' => '专属折扣比例格式不正确',
'discount.min' => '专属折扣比例最小为0',
'discount.max' => '专属折扣比例最大为100',
'u.integer' => '上行流量格式不正确',
'd.integer' => '下行流量格式不正确',
'balance.integer' => '余额格式不正确',
'commission_balance.integer' => '佣金格式不正确'
];
}
}

View File

@ -15,16 +15,16 @@ class OrderSave extends FormRequest
{
return [
'plan_id' => 'required',
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,onetime_price,reset_price'
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,two_year_price,three_year_price,onetime_price,reset_price'
];
}
public function messages()
{
return [
'plan_id.required' => '套餐ID不能为空',
'cycle.required' => '套餐周期不能为空',
'cycle.in' => '套餐周期有误'
'plan_id.required' => __('Plan ID cannot be empty'),
'cycle.required' => __('Plan cycle cannot be empty'),
'cycle.in' => __('Wrong plan cycle')
];
}
}

View File

@ -23,10 +23,10 @@ class TicketSave extends FormRequest
public function messages()
{
return [
'subject.required' => '工单主题不能为空',
'level.required' => '工单级别不能为空',
'level.in' => '工单级别格式不正确',
'message.required' => '消息不能为空'
'subject.required' => __('Ticket subject cannot be empty'),
'level.required' => __('Ticket level cannot be empty'),
'level.in' => __('Incorrect ticket level format'),
'message.required' => __('Message cannot be empty')
];
}
}

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