mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-15 12:30:51 +08:00
Compare commits
28 Commits
3744ebcd5a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13756956a6 | ||
|
|
121511523f | ||
|
|
1fe6531924 | ||
|
|
38ea7d0067 | ||
|
|
58ef46f754 | ||
|
|
ec49ba3fd1 | ||
|
|
b7c8b31a91 | ||
|
|
f3fd40008b | ||
|
|
94fc5f6942 | ||
|
|
c5a8c836c0 | ||
|
|
048530a893 | ||
|
|
7ed5fc8fd3 | ||
|
|
5f1afe4bdc | ||
|
|
0cd20d12dd | ||
|
|
b4a94d1605 | ||
|
|
7879a9ef85 | ||
|
|
d6a3614d98 | ||
|
|
a58d66d72e | ||
|
|
daf3055b42 | ||
|
|
6cac241144 | ||
|
|
f6abc362fd | ||
|
|
c327fecb49 | ||
|
|
0446f88e9e | ||
|
|
a01151130e | ||
|
|
9ca8da045c | ||
|
|
1ebf86b510 | ||
|
|
9e35d16fa6 | ||
|
|
051813d39d |
@@ -73,6 +73,12 @@ docker compose up -d
|
||||
|
||||
This project is for learning and communication purposes only. Users are responsible for any consequences of using this project.
|
||||
|
||||
## ❤️ Support The Project
|
||||
|
||||
If this project has helped you, donations are appreciated. They help support ongoing maintenance and would make me very happy.
|
||||
|
||||
TRC20: `TLypStEWsVrj6Wz9mCxbXffqgt5yz3Y4XB`
|
||||
|
||||
## 🌟 Maintenance Notice
|
||||
|
||||
This project is currently under light maintenance. We will:
|
||||
|
||||
@@ -111,6 +111,94 @@ class ManageController extends Controller
|
||||
return $this->success(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除节点
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function batchDelete(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
]);
|
||||
|
||||
$ids = $request->input('ids');
|
||||
if (empty($ids)) {
|
||||
return $this->fail([400, '请选择要删除的节点']);
|
||||
}
|
||||
|
||||
try {
|
||||
$deleted = Server::whereIn('id', $ids)->delete();
|
||||
if ($deleted === false) {
|
||||
return $this->fail([500, '批量删除失败']);
|
||||
}
|
||||
return $this->success(true);
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
return $this->fail([500, '批量删除失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置节点流量
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function resetTraffic(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'id' => 'required|integer',
|
||||
]);
|
||||
|
||||
$server = Server::find($request->id);
|
||||
if (!$server) {
|
||||
return $this->fail([400202, '服务器不存在']);
|
||||
}
|
||||
|
||||
try {
|
||||
$server->u = 0;
|
||||
$server->d = 0;
|
||||
$server->save();
|
||||
|
||||
Log::info("Server {$server->id} ({$server->name}) traffic reset by admin");
|
||||
return $this->success(true);
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
return $this->fail([500, '重置失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量重置节点流量
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function batchResetTraffic(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'ids' => 'required|array',
|
||||
'ids.*' => 'integer',
|
||||
]);
|
||||
|
||||
$ids = $request->input('ids');
|
||||
if (empty($ids)) {
|
||||
return $this->fail([400, '请选择要重置的节点']);
|
||||
}
|
||||
|
||||
try {
|
||||
Server::whereIn('id', $ids)->update([
|
||||
'u' => 0,
|
||||
'd' => 0,
|
||||
]);
|
||||
|
||||
Log::info("Servers " . implode(',', $ids) . " traffic reset by admin");
|
||||
return $this->success(true);
|
||||
} catch (\Exception $e) {
|
||||
Log::error($e);
|
||||
return $this->fail([500, '批量重置失败']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制节点
|
||||
@@ -120,12 +208,17 @@ class ManageController extends Controller
|
||||
public function copy(Request $request)
|
||||
{
|
||||
$server = Server::find($request->input('id'));
|
||||
$server->show = 0;
|
||||
$server->code = null;
|
||||
if (!$server) {
|
||||
return $this->fail([400202, '服务器不存在']);
|
||||
}
|
||||
Server::create($server->toArray());
|
||||
|
||||
$copiedServer = $server->replicate();
|
||||
$copiedServer->show = 0;
|
||||
$copiedServer->code = null;
|
||||
$copiedServer->u = 0;
|
||||
$copiedServer->d = 0;
|
||||
$copiedServer->save();
|
||||
|
||||
return $this->success(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,6 +363,12 @@ class UserController extends Controller
|
||||
public function generate(UserGenerate $request)
|
||||
{
|
||||
if ($request->input('email_prefix')) {
|
||||
// If generate_count is specified with email_prefix, generate multiple users with incremented emails
|
||||
if ($request->input('generate_count')) {
|
||||
return $this->multiGenerateWithPrefix($request);
|
||||
}
|
||||
|
||||
// Single user generation with email_prefix
|
||||
$email = $request->input('email_prefix') . '@' . $request->input('email_suffix');
|
||||
|
||||
if (User::byEmail($email)->exists()) {
|
||||
@@ -459,6 +465,87 @@ class UserController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
private function multiGenerateWithPrefix(Request $request)
|
||||
{
|
||||
$userService = app(UserService::class);
|
||||
$usersData = [];
|
||||
$emailPrefix = $request->input('email_prefix');
|
||||
$emailSuffix = $request->input('email_suffix');
|
||||
$generateCount = $request->input('generate_count');
|
||||
|
||||
// Check if any of the emails with prefix already exist
|
||||
for ($i = 1; $i <= $generateCount; $i++) {
|
||||
$email = $emailPrefix . '_' . $i . '@' . $emailSuffix;
|
||||
if (User::where('email', $email)->exists()) {
|
||||
return $this->fail([400201, '邮箱 ' . $email . ' 已存在于系统中']);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate user data for batch creation
|
||||
for ($i = 1; $i <= $generateCount; $i++) {
|
||||
$email = $emailPrefix . '_' . $i . '@' . $emailSuffix;
|
||||
$usersData[] = [
|
||||
'email' => $email,
|
||||
'password' => $request->input('password') ?? $email,
|
||||
'plan_id' => $request->input('plan_id'),
|
||||
'expired_at' => $request->input('expired_at'),
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
$users = [];
|
||||
foreach ($usersData as $userData) {
|
||||
$user = $userService->createUser($userData);
|
||||
$user->save();
|
||||
$users[] = $user;
|
||||
}
|
||||
DB::commit();
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return $this->fail([500, '生成失败']);
|
||||
}
|
||||
|
||||
// 判断是否导出 CSV
|
||||
if ($request->input('download_csv')) {
|
||||
$headers = [
|
||||
'Content-Type' => 'text/csv',
|
||||
'Content-Disposition' => 'attachment; filename="users.csv"',
|
||||
];
|
||||
$callback = function () use ($users, $request) {
|
||||
$handle = fopen('php://output', 'w');
|
||||
fputcsv($handle, ['账号', '密码', '过期时间', 'UUID', '创建时间', '订阅地址']);
|
||||
foreach ($users as $user) {
|
||||
$user = $user->refresh();
|
||||
$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 = Helper::getSubscribeUrl($user['token']);
|
||||
fputcsv($handle, [$user['email'], $password, $expireDate, $user['uuid'], $createDate, $subscribeUrl]);
|
||||
}
|
||||
fclose($handle);
|
||||
};
|
||||
return response()->streamDownload($callback, 'users.csv', $headers);
|
||||
}
|
||||
|
||||
// 默认返回 JSON
|
||||
$data = collect($users)->map(function ($user) use ($request) {
|
||||
return [
|
||||
'email' => $user['email'],
|
||||
'password' => $request->input('password') ?? $user['email'],
|
||||
'expired_at' => $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']),
|
||||
'uuid' => $user['uuid'],
|
||||
'created_at' => date('Y-m-d H:i:s', $user['created_at']),
|
||||
'subscribe_url' => Helper::getSubscribeUrl($user['token']),
|
||||
];
|
||||
});
|
||||
return response()->json([
|
||||
'code' => 0,
|
||||
'message' => '批量生成成功',
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendMail(UserSendMail $request)
|
||||
{
|
||||
ini_set('memory_limit', '-1');
|
||||
|
||||
@@ -71,6 +71,10 @@ class ServerSave extends FormRequest
|
||||
'network' => 'required|string',
|
||||
'network_settings' => 'nullable|array',
|
||||
'flow' => 'nullable|string',
|
||||
'encryption' => 'nullable|array',
|
||||
'encryption.enabled' => 'nullable|boolean',
|
||||
'encryption.encryption' => 'nullable|string',
|
||||
'encryption.decryption' => 'nullable|string',
|
||||
'tls_settings.server_name' => 'nullable|string',
|
||||
'tls_settings.allow_insecure' => 'nullable|boolean',
|
||||
'reality_settings.allow_insecure' => 'nullable|boolean',
|
||||
@@ -128,6 +132,7 @@ class ServerSave extends FormRequest
|
||||
'rate_time_ranges.*.end' => 'required_with:rate_time_ranges|string|date_format:H:i',
|
||||
'rate_time_ranges.*.rate' => 'required_with:rate_time_ranges|numeric|min:0',
|
||||
'protocol_settings' => 'array',
|
||||
'transfer_enable' => 'nullable|integer|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -200,6 +205,8 @@ class ServerSave extends FormRequest
|
||||
'protocol_settings.*.string' => ':attribute 必须是字符串',
|
||||
'protocol_settings.*.integer' => ':attribute 必须是整数',
|
||||
'protocol_settings.*.in' => ':attribute 的值不合法',
|
||||
'transfer_enable.integer' => '流量上限必须是整数',
|
||||
'transfer_enable.min' => '流量上限不能小于0',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,9 @@ class AdminRoute
|
||||
$router->post('/drop', [ManageController::class, 'drop']);
|
||||
$router->post('/copy', [ManageController::class, 'copy']);
|
||||
$router->post('/sort', [ManageController::class, 'sort']);
|
||||
$router->post('/batchDelete', [ManageController::class, 'batchDelete']);
|
||||
$router->post('/resetTraffic', [ManageController::class, 'resetTraffic']);
|
||||
$router->post('/batchResetTraffic', [ManageController::class, 'batchResetTraffic']);
|
||||
});
|
||||
|
||||
// Order
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StatServer;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -59,12 +61,23 @@ class StatServerJob implements ShouldQueue
|
||||
|
||||
try {
|
||||
$this->processServerStat($u, $d, $recordAt);
|
||||
$this->updateServerTraffic($u, $d);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('StatServerJob failed for server ' . $this->server['id'] . ': ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function updateServerTraffic(int $u, int $d): void
|
||||
{
|
||||
DB::table('v2_server')
|
||||
->where('id', $this->server['id'])
|
||||
->incrementEach(
|
||||
['u' => $u, 'd' => $d],
|
||||
['updated_at' => Carbon::now()]
|
||||
);
|
||||
}
|
||||
|
||||
protected function processServerStat(int $u, int $d, int $recordAt): void
|
||||
{
|
||||
$driver = config('database.default');
|
||||
|
||||
@@ -52,6 +52,10 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
* @property int|null $d 下行流量
|
||||
* @property int|null $total 总流量
|
||||
* @property-read array|null $load_status 负载状态(包含CPU、内存、交换区、磁盘信息)
|
||||
*
|
||||
* @property int $transfer_enable 流量上限,0或者null表示不限制
|
||||
* @property int $u 当前上传流量
|
||||
* @property int $d 当前下载流量
|
||||
*/
|
||||
class Server extends Model
|
||||
{
|
||||
@@ -124,6 +128,9 @@ class Server extends Model
|
||||
'updated_at' => 'timestamp',
|
||||
'rate_time_ranges' => 'array',
|
||||
'rate_time_enable' => 'boolean',
|
||||
'transfer_enable' => 'integer',
|
||||
'u' => 'integer',
|
||||
'd' => 'integer',
|
||||
];
|
||||
|
||||
private const MULTIPLEX_CONFIGURATION = [
|
||||
@@ -196,6 +203,15 @@ class Server extends Model
|
||||
'tls' => ['type' => 'integer', 'default' => 0],
|
||||
'tls_settings' => ['type' => 'array', 'default' => null],
|
||||
'flow' => ['type' => 'string', 'default' => null],
|
||||
'encryption' => [
|
||||
'type' => 'object',
|
||||
'default' => null,
|
||||
'fields' => [
|
||||
'enabled' => ['type' => 'boolean', 'default' => false],
|
||||
'encryption' => ['type' => 'string', 'default' => null], // 客户端公钥
|
||||
'decryption' => ['type' => 'string', 'default' => null], // 服务端私钥
|
||||
]
|
||||
],
|
||||
'network' => ['type' => 'string', 'default' => null],
|
||||
'network_settings' => ['type' => 'array', 'default' => null],
|
||||
...self::REALITY_CONFIGURATION,
|
||||
|
||||
@@ -332,6 +332,10 @@ class ClashMeta extends AbstractProtocol
|
||||
'cipher' => 'auto',
|
||||
'udp' => true,
|
||||
'flow' => data_get($protocol_settings, 'flow'),
|
||||
'encryption' => match (data_get($protocol_settings, 'encryption.enabled')) {
|
||||
true => data_get($protocol_settings, 'encryption.encryption', 'none'),
|
||||
default => 'none'
|
||||
},
|
||||
'tls' => false
|
||||
];
|
||||
|
||||
|
||||
@@ -151,7 +151,10 @@ class General extends AbstractProtocol
|
||||
$config = [
|
||||
'mode' => 'multi', //grpc传输模式
|
||||
'security' => '', //传输层安全 tls/reality
|
||||
'encryption' => 'none', //加密方式
|
||||
'encryption' => match (data_get($protocol_settings, 'encryption.enabled')) {
|
||||
true => data_get($protocol_settings, 'encryption.encryption', 'none'),
|
||||
default => 'none'
|
||||
},
|
||||
'type' => data_get($server, 'protocol_settings.network'), //传输协议
|
||||
'flow' => data_get($protocol_settings, 'flow'),
|
||||
];
|
||||
|
||||
@@ -15,6 +15,7 @@ class Loon extends AbstractProtocol
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_HYSTERIA,
|
||||
Server::TYPE_VLESS,
|
||||
Server::TYPE_ANYTLS,
|
||||
];
|
||||
|
||||
protected $protocolRequirements = [
|
||||
@@ -47,6 +48,9 @@ class Loon extends AbstractProtocol
|
||||
if ($item['type'] === Server::TYPE_VLESS) {
|
||||
$uri .= self::buildVless($item['password'], $item);
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||
$uri .= self::buildAnyTLS($item['password'], $item);
|
||||
}
|
||||
}
|
||||
return response($uri)
|
||||
->header('content-type', 'text/plain')
|
||||
@@ -325,4 +329,29 @@ class Loon extends AbstractProtocol
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildAnyTLS($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
|
||||
$config = [
|
||||
"{$server['name']}=anytls",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"{$password}",
|
||||
"udp=true"
|
||||
];
|
||||
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$config[] = "sni={$serverName}";
|
||||
}
|
||||
// ✅ 跳过证书校验
|
||||
if (data_get($protocol_settings, 'tls.allow_insecure')) {
|
||||
$config[] = 'skip-cert-verify=true';
|
||||
}
|
||||
|
||||
$config = array_filter($config);
|
||||
|
||||
return implode(',', $config) . "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class Surfboard extends AbstractProtocol
|
||||
Server::TYPE_SHADOWSOCKS,
|
||||
Server::TYPE_VMESS,
|
||||
Server::TYPE_TROJAN,
|
||||
Server::TYPE_ANYTLS,
|
||||
];
|
||||
const CUSTOM_TEMPLATE_FILE = 'resources/rules/custom.surfboard.conf';
|
||||
const DEFAULT_TEMPLATE_FILE = 'resources/rules/default.surfboard.conf';
|
||||
@@ -36,7 +37,10 @@ class Surfboard extends AbstractProtocol
|
||||
'aes-128-gcm',
|
||||
'aes-192-gcm',
|
||||
'aes-256-gcm',
|
||||
'chacha20-ietf-poly1305'
|
||||
'chacha20-ietf-poly1305',
|
||||
'2022-blake3-aes-128-gcm',
|
||||
'2022-blake3-aes-256-gcm',
|
||||
'2022-blake3-chacha20-poly1305'
|
||||
])
|
||||
) {
|
||||
// [Proxy]
|
||||
@@ -56,6 +60,10 @@ class Surfboard extends AbstractProtocol
|
||||
// [Proxy Group]
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
if ($item['type'] === Server::TYPE_ANYTLS) {
|
||||
$proxies .= self::buildAnyTLS($item['password'], $item);
|
||||
$proxyGroup .= $item['name'] . ', ';
|
||||
}
|
||||
}
|
||||
|
||||
$config = subscribe_template('surfboard');
|
||||
@@ -190,4 +198,32 @@ class Surfboard extends AbstractProtocol
|
||||
$uri .= "\r\n";
|
||||
return $uri;
|
||||
}
|
||||
|
||||
public static function buildAnyTLS($password, $server)
|
||||
{
|
||||
$protocol_settings = data_get($server, 'protocol_settings', []);
|
||||
|
||||
$config = [
|
||||
"{$server['name']}=anytls",
|
||||
"{$server['host']}",
|
||||
"{$server['port']}",
|
||||
"password={$password}",
|
||||
"tfo=true",
|
||||
"udp-relay=true"
|
||||
];
|
||||
|
||||
// SNI
|
||||
if ($serverName = data_get($protocol_settings, 'tls.server_name')) {
|
||||
$config[] = "sni={$serverName}";
|
||||
}
|
||||
|
||||
// 跳过证书校验
|
||||
if (data_get($protocol_settings, 'tls.allow_insecure')) {
|
||||
$config[] = "skip-cert-verify=true";
|
||||
}
|
||||
|
||||
$config = array_filter($config);
|
||||
|
||||
return implode(',', $config) . "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class MailLinkService
|
||||
|
||||
$this->sendMailLinkEmail($user, $link);
|
||||
|
||||
return [true, $link];
|
||||
return [true, true];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,11 @@ class ServerService
|
||||
{
|
||||
$servers = Server::whereJsonContains('group_ids', (string) $user->group_id)
|
||||
->where('show', true)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('transfer_enable')
|
||||
->orWhere('transfer_enable', 0)
|
||||
->orWhereRaw('u + d < transfer_enable');
|
||||
})
|
||||
->orderBy('sort', 'ASC')
|
||||
->get()
|
||||
->append(['last_check_at', 'last_push_at', 'online', 'is_online', 'available_status', 'cache_key', 'server_key']);
|
||||
@@ -178,6 +183,10 @@ class ServerService
|
||||
...$baseConfig,
|
||||
'tls' => (int) $protocolSettings['tls'],
|
||||
'flow' => $protocolSettings['flow'],
|
||||
'decryption' => match (data_get($protocolSettings, 'encryption.enabled')) {
|
||||
true => data_get($protocolSettings, 'encryption.decryption'),
|
||||
default => null,
|
||||
},
|
||||
'tls_settings' => match ((int) $protocolSettings['tls']) {
|
||||
2 => $protocolSettings['reality_settings'],
|
||||
default => $protocolSettings['tls_settings'],
|
||||
@@ -244,10 +253,10 @@ class ServerService
|
||||
default => [],
|
||||
};
|
||||
|
||||
$response = array_filter(
|
||||
$response,
|
||||
static fn ($value) => $value !== null
|
||||
);
|
||||
// $response = array_filter(
|
||||
// $response,
|
||||
// static fn ($value) => $value !== null
|
||||
// );
|
||||
|
||||
if (!empty($node['route_ids'])) {
|
||||
$response['routes'] = self::getRoutes($node['route_ids']);
|
||||
|
||||
@@ -86,6 +86,7 @@ class Helper
|
||||
case 'md5': return md5($password) === $hash;
|
||||
case 'sha256': return hash('sha256', $password) === $hash;
|
||||
case 'md5salt': return md5($password . $salt) === $hash;
|
||||
case 'sha256salt': return hash('sha256', $password . $salt) === $hash;
|
||||
default: return password_verify($password, $hash);
|
||||
}
|
||||
}
|
||||
@@ -229,4 +230,14 @@ class Helper
|
||||
{
|
||||
return $transfer_enable / 1073741824;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义 Telegram Markdown 特殊字符
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
public static function escapeMarkdown(string $text): string
|
||||
{
|
||||
return str_replace(['_', '*', '`', '['], ['\_', '\*', '\`', '\['], $text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 统计需要转换的记录数
|
||||
$count = DB::table('v2_user')
|
||||
->whereNotNull('email')
|
||||
->whereRaw('email != LOWER(email)')
|
||||
->count();
|
||||
|
||||
if ($count > 0) {
|
||||
Log::info("Converting {$count} email(s) to lowercase");
|
||||
DB::table('v2_user')
|
||||
->whereNotNull('email')
|
||||
->whereRaw('email != LOWER(email)')
|
||||
->update(['email' => DB::raw('LOWER(email)')]);
|
||||
|
||||
Log::info("Email lowercase conversion completed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// 无法恢复原始大小写
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('v2_server', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('v2_server', 'transfer_enable')) {
|
||||
$table->bigInteger('transfer_enable')
|
||||
->default(null)
|
||||
->nullable()
|
||||
->after('rate')
|
||||
->comment('Traffic limit , 0 or null=no limit');
|
||||
}
|
||||
if (!Schema::hasColumn('v2_server', 'u')) {
|
||||
$table->bigInteger('u')
|
||||
->default(0)
|
||||
->after('transfer_enable')
|
||||
->comment('upload traffic');
|
||||
}
|
||||
if (!Schema::hasColumn('v2_server', 'd')) {
|
||||
$table->bigInteger('d')
|
||||
->default(0)
|
||||
->after('u')
|
||||
->comment('donwload traffic');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('v2_server', function (Blueprint $table) {
|
||||
$table->dropColumn(['transfer_enable', 'u', 'd']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -58,8 +58,8 @@ class Plugin extends AbstractPlugin
|
||||
"支付渠道:%s\n" .
|
||||
"本站订单:`%s`",
|
||||
$order->total_amount / 100,
|
||||
$payment->payment,
|
||||
$payment->name,
|
||||
Helper::escapeMarkdown($payment->payment),
|
||||
Helper::escapeMarkdown($payment->name),
|
||||
$order->trade_no
|
||||
);
|
||||
$this->telegramService->sendMessageWithAdmin($message, true);
|
||||
@@ -92,7 +92,7 @@ class Plugin extends AbstractPlugin
|
||||
$TGmessage .= "📍 位置: `{$region}`\n";
|
||||
|
||||
if ($plan) {
|
||||
$TGmessage .= "📦 套餐: `{$plan->name}`\n";
|
||||
$TGmessage .= "📦 套餐: `" . Helper::escapeMarkdown($plan->name) . "`\n";
|
||||
$TGmessage .= "📊 流量: `{$remaining_traffic}G / {$transfer_enable}G` (剩余/总计)\n";
|
||||
$TGmessage .= "⬆️⬇️ 已用: `{$u}G / {$d}G`\n";
|
||||
$TGmessage .= "⏰ 到期: `{$expired_at}`\n";
|
||||
@@ -103,8 +103,8 @@ class Plugin extends AbstractPlugin
|
||||
$TGmessage .= "💰 余额: `{$money}元`\n";
|
||||
$TGmessage .= "💸 佣金: `{$affmoney}元`\n";
|
||||
$TGmessage .= "━━━━━━━━━━━━━━━━━━━━\n";
|
||||
$TGmessage .= "📝 *主题*: `{$ticket->subject}`\n";
|
||||
$TGmessage .= "💬 *内容*: `{$message->message}`";
|
||||
$TGmessage .= "📝 *主题*: `" . Helper::escapeMarkdown($ticket->subject) . "`\n";
|
||||
$TGmessage .= "💬 *内容*: `" . Helper::escapeMarkdown($message->message) . "`";
|
||||
$this->telegramService->sendMessageWithAdmin($TGmessage, true);
|
||||
}
|
||||
|
||||
|
||||
Submodule public/assets/admin updated: 89c0577191...ee5c965558
12
update.sh
12
update.sh
@@ -10,7 +10,17 @@ if ! command -v git &> /dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git config --global --add safe.directory $(pwd)
|
||||
repo_root="$(pwd)"
|
||||
|
||||
add_safe_directory() {
|
||||
local dir="$1"
|
||||
|
||||
git config --global --get-all safe.directory | grep -Fx "$dir" > /dev/null || git config --global --add safe.directory "$dir"
|
||||
}
|
||||
|
||||
add_safe_directory "$repo_root"
|
||||
add_safe_directory "$repo_root/public/assets/admin"
|
||||
|
||||
git fetch --all && git reset --hard origin/master && git pull origin master
|
||||
rm -rf composer.lock composer.phar
|
||||
wget https://github.com/composer/composer/releases/latest/download/composer.phar -O composer.phar
|
||||
|
||||
Reference in New Issue
Block a user