mirror of
https://github.com/cedar2025/Xboard.git
synced 2025-01-23 02:58:14 -05:00
Compare commits
4 Commits
1926d6a867
...
3bd4cf05dd
Author | SHA1 | Date | |
---|---|---|---|
|
3bd4cf05dd | ||
|
e41c47beeb | ||
|
8ec568c24d | ||
|
c89b57422a |
@ -121,6 +121,7 @@ class UniProxyController extends Controller
|
|||||||
: $protocolSettings['reality_settings']
|
: $protocolSettings['reality_settings']
|
||||||
],
|
],
|
||||||
'hysteria' => [
|
'hysteria' => [
|
||||||
|
'server_port' => (int) $serverPort,
|
||||||
'version' => (int) $protocolSettings['version'],
|
'version' => (int) $protocolSettings['version'],
|
||||||
'host' => $host,
|
'host' => $host,
|
||||||
'server_name' => $protocolSettings['tls']['server_name'],
|
'server_name' => $protocolSettings['tls']['server_name'],
|
||||||
|
@ -215,11 +215,11 @@ class OrderController extends Controller
|
|||||||
$orderService = new OrderService($order);
|
$orderService = new OrderService($order);
|
||||||
$order->user_id = $user->id;
|
$order->user_id = $user->id;
|
||||||
$order->plan_id = $plan->id;
|
$order->plan_id = $plan->id;
|
||||||
$order->period = $request->input('period');
|
$order->period = PlanService::getPeriodKey($request->input('period'));
|
||||||
$order->trade_no = Helper::guid();
|
$order->trade_no = Helper::guid();
|
||||||
$order->total_amount = $request->input('total_amount');
|
$order->total_amount = $request->input('total_amount');
|
||||||
|
|
||||||
if ($order->period === 'reset_price') {
|
if (PlanService::getPeriodKey($order->period) === Plan::PERIOD_RESET_TRAFFIC) {
|
||||||
$order->type = Order::TYPE_RESET_TRAFFIC;
|
$order->type = Order::TYPE_RESET_TRAFFIC;
|
||||||
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
|
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
|
||||||
$order->type = Order::TYPE_UPGRADE;
|
$order->type = Order::TYPE_UPGRADE;
|
||||||
|
@ -29,7 +29,7 @@ class PlanController extends Controller
|
|||||||
'id' => 'nullable|integer',
|
'id' => 'nullable|integer',
|
||||||
'name' => 'required|string',
|
'name' => 'required|string',
|
||||||
'content' => 'nullable|string',
|
'content' => 'nullable|string',
|
||||||
'period_reset_method' => 'integer|required_if:type,0',
|
'reset_traffic_method' => 'integer|nullable',
|
||||||
'transfer_enable' => 'integer|required',
|
'transfer_enable' => 'integer|required',
|
||||||
'prices' => 'array|nullable',
|
'prices' => 'array|nullable',
|
||||||
'group_id' => 'integer|nullable',
|
'group_id' => 'integer|nullable',
|
||||||
|
@ -68,71 +68,123 @@ class Server extends Model
|
|||||||
'updated_at' => 'timestamp'
|
'updated_at' => 'timestamp'
|
||||||
];
|
];
|
||||||
|
|
||||||
private const DEFAULT_PROTOCOL_SETTINGS = [
|
private const PROTOCOL_CONFIGURATIONS = [
|
||||||
self::TYPE_TROJAN => [
|
self::TYPE_TROJAN => [
|
||||||
'allow_insecure' => false,
|
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
||||||
'server_name' => null,
|
'server_name' => ['type' => 'string', 'default' => null],
|
||||||
'network' => null,
|
'network' => ['type' => 'string', 'default' => null],
|
||||||
'network_settings' => null
|
'network_settings' => ['type' => 'array', 'default' => null]
|
||||||
],
|
],
|
||||||
self::TYPE_VMESS => [
|
self::TYPE_VMESS => [
|
||||||
'tls' => 0,
|
'tls' => ['type' => 'integer', 'default' => 0],
|
||||||
'network' => null,
|
'network' => ['type' => 'string', 'default' => null],
|
||||||
'rules' => null,
|
'rules' => ['type' => 'array', 'default' => null],
|
||||||
'network_settings' => null,
|
'network_settings' => ['type' => 'array', 'default' => null],
|
||||||
'tls_settings' => null
|
'tls_settings' => ['type' => 'array', 'default' => null]
|
||||||
],
|
],
|
||||||
self::TYPE_VLESS => [
|
self::TYPE_VLESS => [
|
||||||
'tls' => false,
|
'tls' => ['type' => 'integer', 'default' => 0],
|
||||||
'tls_settings' => null,
|
'tls_settings' => ['type' => 'array', 'default' => null],
|
||||||
'flow' => null,
|
'flow' => ['type' => 'string', 'default' => null],
|
||||||
'network' => null,
|
'network' => ['type' => 'string', 'default' => null],
|
||||||
'network_settings' => null,
|
'network_settings' => ['type' => 'array', 'default' => null],
|
||||||
'reality_settings' => [
|
'reality_settings' => [
|
||||||
'allow_insecure' => false,
|
'type' => 'object',
|
||||||
'server_port' => null,
|
'fields' => [
|
||||||
'server_name' => null,
|
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
||||||
'public_key' => null,
|
'server_port' => ['type' => 'integer', 'default' => null],
|
||||||
'private_key' => null,
|
'server_name' => ['type' => 'string', 'default' => null],
|
||||||
'short_id' => null
|
'public_key' => ['type' => 'string', 'default' => null],
|
||||||
|
'private_key' => ['type' => 'string', 'default' => null],
|
||||||
|
'short_id' => ['type' => 'string', 'default' => null]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
self::TYPE_SHADOWSOCKS => [
|
self::TYPE_SHADOWSOCKS => [
|
||||||
'cipher' => null,
|
'cipher' => ['type' => 'string', 'default' => null],
|
||||||
'obfs' => null,
|
'obfs' => ['type' => 'string', 'default' => null],
|
||||||
'obfs_settings' => null
|
'obfs_settings' => ['type' => 'array', 'default' => null]
|
||||||
],
|
],
|
||||||
self::TYPE_HYSTERIA => [
|
self::TYPE_HYSTERIA => [
|
||||||
'version' => 2,
|
'version' => ['type' => 'integer', 'default' => 2],
|
||||||
'bandwidth' => [
|
'bandwidth' => [
|
||||||
'up' => null,
|
'type' => 'object',
|
||||||
'down' => null
|
'fields' => [
|
||||||
|
'up' => ['type' => 'integer', 'default' => null],
|
||||||
|
'down' => ['type' => 'integer', 'default' => null]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
'obfs' => [
|
'obfs' => [
|
||||||
'open' => false,
|
'type' => 'object',
|
||||||
'type' => 'salamander',
|
'fields' => [
|
||||||
'password' => null
|
'open' => ['type' => 'boolean', 'default' => false],
|
||||||
|
'type' => ['type' => 'string', 'default' => 'salamander'],
|
||||||
|
'password' => ['type' => 'string', 'default' => null]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
'tls' => [
|
'tls' => [
|
||||||
'server_name' => null,
|
'type' => 'object',
|
||||||
'allow_insecure' => false
|
'fields' => [
|
||||||
|
'server_name' => ['type' => 'string', 'default' => null],
|
||||||
|
'allow_insecure' => ['type' => 'boolean', 'default' => false]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
self::TYPE_TUIC => [
|
self::TYPE_TUIC => [
|
||||||
'congestion_control' => 'cubic',
|
'congestion_control' => ['type' => 'string', 'default' => 'cubic'],
|
||||||
'alpn' => ['h3'],
|
'alpn' => ['type' => 'array', 'default' => ['h3']],
|
||||||
'udp_relay_mode' => 'native',
|
'udp_relay_mode' => ['type' => 'string', 'default' => 'native'],
|
||||||
'allow_insecure' => false,
|
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
||||||
'tls_settings' => null
|
'tls_settings' => ['type' => 'array', 'default' => null]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private function castValueWithConfig($value, array $config)
|
||||||
|
{
|
||||||
|
if ($value === null) {
|
||||||
|
return $config['default'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return match($config['type']) {
|
||||||
|
'integer' => (int) $value,
|
||||||
|
'boolean' => (bool) $value,
|
||||||
|
'string' => (string) $value,
|
||||||
|
'array' => (array) $value,
|
||||||
|
'object' => is_array($value) ?
|
||||||
|
$this->castSettingsWithConfig($value, $config['fields']) :
|
||||||
|
$config['default'],
|
||||||
|
default => $value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function castSettingsWithConfig(array $settings, array $configs): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($configs as $key => $config) {
|
||||||
|
$value = $settings[$key] ?? null;
|
||||||
|
$result[$key] = $this->castValueWithConfig($value, $config);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDefaultSettings(array $configs): array
|
||||||
|
{
|
||||||
|
$defaults = [];
|
||||||
|
foreach ($configs as $key => $config) {
|
||||||
|
if ($config['type'] === 'object') {
|
||||||
|
$defaults[$key] = $this->getDefaultSettings($config['fields']);
|
||||||
|
} else {
|
||||||
|
$defaults[$key] = $config['default'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $defaults;
|
||||||
|
}
|
||||||
|
|
||||||
public function getProtocolSettingsAttribute($value)
|
public function getProtocolSettingsAttribute($value)
|
||||||
{
|
{
|
||||||
$settings = json_decode($value, true) ?? [];
|
$settings = json_decode($value, true) ?? [];
|
||||||
$defaultSettings = self::DEFAULT_PROTOCOL_SETTINGS[$this->type] ?? [];
|
$configs = self::PROTOCOL_CONFIGURATIONS[$this->type] ?? [];
|
||||||
|
return $this->castSettingsWithConfig($settings, $configs);
|
||||||
return array_replace_recursive($defaultSettings, $settings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setProtocolSettingsAttribute($value)
|
public function setProtocolSettingsAttribute($value)
|
||||||
@ -141,10 +193,10 @@ class Server extends Model
|
|||||||
$value = json_decode($value, true);
|
$value = json_decode($value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
$defaultSettings = self::DEFAULT_PROTOCOL_SETTINGS[$this->type] ?? [];
|
$configs = self::PROTOCOL_CONFIGURATIONS[$this->type] ?? [];
|
||||||
$mergedSettings = array_replace_recursive($defaultSettings, $value ?? []);
|
$castedSettings = $this->castSettingsWithConfig($value ?? [], $configs);
|
||||||
|
|
||||||
$this->attributes['protocol_settings'] = json_encode($mergedSettings);
|
$this->attributes['protocol_settings'] = json_encode($castedSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadParentCreatedAt(): void
|
public function loadParentCreatedAt(): void
|
||||||
|
@ -44,10 +44,10 @@ class OrderService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
switch ((string) $order->period) {
|
switch ((string) $order->period) {
|
||||||
case 'onetime_price':
|
case Plan::PERIOD_ONETIME:
|
||||||
$this->buyByOneTime($plan);
|
$this->buyByOneTime($plan);
|
||||||
break;
|
break;
|
||||||
case 'reset_price':
|
case Plan::PERIOD_RESET_TRAFFIC:
|
||||||
$this->buyByResetTraffic();
|
$this->buyByResetTraffic();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -88,7 +88,7 @@ class OrderService
|
|||||||
public function setOrderType(User $user)
|
public function setOrderType(User $user)
|
||||||
{
|
{
|
||||||
$order = $this->order;
|
$order = $this->order;
|
||||||
if ($order->period === 'reset_price') {
|
if ($order->period === Plan::PERIOD_RESET_TRAFFIC) {
|
||||||
$order->type = Order::TYPE_RESET_TRAFFIC;
|
$order->type = Order::TYPE_RESET_TRAFFIC;
|
||||||
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id && ($user->expired_at > time() || $user->expired_at === NULL)) {
|
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id && ($user->expired_at > time() || $user->expired_at === NULL)) {
|
||||||
if (!(int) admin_setting('plan_change_enable', 1))
|
if (!(int) admin_setting('plan_change_enable', 1))
|
||||||
@ -170,7 +170,7 @@ class OrderService
|
|||||||
private function getSurplusValueByOneTime(User $user, Order $order)
|
private function getSurplusValueByOneTime(User $user, Order $order)
|
||||||
{
|
{
|
||||||
$lastOneTimeOrder = Order::where('user_id', $user->id)
|
$lastOneTimeOrder = Order::where('user_id', $user->id)
|
||||||
->where('period', 'onetime_price')
|
->where('period', Plan::PERIOD_ONETIME)
|
||||||
->where('status', Order::STATUS_COMPLETED)
|
->where('status', Order::STATUS_COMPLETED)
|
||||||
->orderBy('id', 'DESC')
|
->orderBy('id', 'DESC')
|
||||||
->first();
|
->first();
|
||||||
@ -185,7 +185,7 @@ class OrderService
|
|||||||
$trafficUnitPrice = $paidTotalAmount / $nowUserTraffic;
|
$trafficUnitPrice = $paidTotalAmount / $nowUserTraffic;
|
||||||
$notUsedTraffic = $nowUserTraffic - (($user->u + $user->d) / 1073741824);
|
$notUsedTraffic = $nowUserTraffic - (($user->u + $user->d) / 1073741824);
|
||||||
$result = $trafficUnitPrice * $notUsedTraffic;
|
$result = $trafficUnitPrice * $notUsedTraffic;
|
||||||
$orderModel = Order::where('user_id', $user->id)->where('period', '!=', 'reset_price')->where('status', Order::STATUS_COMPLETED);
|
$orderModel = Order::where('user_id', $user->id)->where('period', '!=', Plan::PERIOD_RESET_TRAFFIC)->where('status', Order::STATUS_COMPLETED);
|
||||||
$order->surplus_amount = $result > 0 ? $result : 0;
|
$order->surplus_amount = $result > 0 ? $result : 0;
|
||||||
$order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id');
|
$order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id');
|
||||||
}
|
}
|
||||||
@ -193,7 +193,7 @@ class OrderService
|
|||||||
private function getSurplusValueByPeriod(User $user, Order $order)
|
private function getSurplusValueByPeriod(User $user, Order $order)
|
||||||
{
|
{
|
||||||
$orders = Order::where('user_id', $user->id)
|
$orders = Order::where('user_id', $user->id)
|
||||||
->whereNotIn('period', ['reset_price', 'onetime_price'])
|
->whereNotIn('period', [Plan::PERIOD_RESET_TRAFFIC, Plan::PERIOD_ONETIME])
|
||||||
->where('status', Order::STATUS_COMPLETED)
|
->where('status', Order::STATUS_COMPLETED)
|
||||||
->get()
|
->get()
|
||||||
->toArray();
|
->toArray();
|
||||||
|
@ -59,7 +59,7 @@ class PlanService
|
|||||||
{
|
{
|
||||||
// 如果是续费
|
// 如果是续费
|
||||||
if ($user->plan_id === $plan->id) {
|
if ($user->plan_id === $plan->id) {
|
||||||
return $plan->sell && $plan->renew;
|
return $plan->renew;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是新购
|
// 如果是新购
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use App\Models\Plan;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
/**
|
||||||
|
* 旧的价格字段到新周期的映射关系
|
||||||
|
*/
|
||||||
|
private const PERIOD_MAPPING = [
|
||||||
|
'month_price' => 'monthly',
|
||||||
|
'quarter_price' => 'quarterly',
|
||||||
|
'half_year_price' => 'half_yearly',
|
||||||
|
'year_price' => 'yearly',
|
||||||
|
'two_year_price' => 'two_yearly',
|
||||||
|
'three_year_price' => 'three_yearly',
|
||||||
|
'onetime_price' => 'onetime',
|
||||||
|
'reset_price' => 'reset_traffic'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// 批量更新订单的周期字段
|
||||||
|
foreach (self::PERIOD_MAPPING as $oldPeriod => $newPeriod) {
|
||||||
|
DB::table('v2_order')
|
||||||
|
->where('period', $oldPeriod)
|
||||||
|
->update(['period' => $newPeriod]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否还有未转换的记录
|
||||||
|
$unconvertedCount = DB::table('v2_order')
|
||||||
|
->whereNotIn('period', array_values(self::PERIOD_MAPPING))
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if ($unconvertedCount > 0) {
|
||||||
|
\Log::warning("Found {$unconvertedCount} orders with unconverted period values");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// 回滚操作 - 将新的周期值转换回旧的价格字段名
|
||||||
|
foreach (self::PERIOD_MAPPING as $oldPeriod => $newPeriod) {
|
||||||
|
DB::table('v2_order')
|
||||||
|
->where('period', $newPeriod)
|
||||||
|
->update(['period' => $oldPeriod]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@ -78,7 +78,7 @@ docker compose up -d
|
|||||||
### 4. 版本更新
|
### 4. 版本更新
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose pull && docker compose up -d
|
docker compose pull && docker compose run -it --rm web php artisan xboard:update && docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### 注意事项
|
### 注意事项
|
||||||
|
@ -82,7 +82,7 @@ location ^~ / {
|
|||||||
### 4. 版本更新
|
### 4. 版本更新
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose pull && docker compose up -d
|
docker compose pull && docker compose run -it --rm web php artisan xboard:update && docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### 注意事项
|
### 注意事项
|
||||||
|
4
public/assets/admin/assets/index.js
vendored
4
public/assets/admin/assets/index.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user