feat: xxbb
This commit is contained in:
@@ -8,37 +8,31 @@ use Illuminate\Support\Facades\File;
|
|||||||
|
|
||||||
class RawLogController extends Controller
|
class RawLogController extends Controller
|
||||||
{
|
{
|
||||||
|
/** @var list<string> */
|
||||||
|
private const KINDS = ['c2', 'xxbb'];
|
||||||
|
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$dir = public_path('log/c2');
|
$kind = $this->kind($request);
|
||||||
$files = [];
|
|
||||||
if (is_dir($dir)) {
|
|
||||||
foreach (File::files($dir) as $file) {
|
|
||||||
if ($file->getExtension() !== 'log') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$files[] = [
|
|
||||||
'name' => $file->getFilename(),
|
|
||||||
'size' => $file->getSize(),
|
|
||||||
'mtime' => date('Y-m-d H:i:s', $file->getMTime()),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
usort($files, fn ($a, $b) => strcmp($b['name'], $a['name']));
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = trim((string) $request->query('path', ''));
|
$path = trim((string) $request->query('path', ''));
|
||||||
$device = trim((string) $request->query('device', ''));
|
$device = trim((string) $request->query('device', ''));
|
||||||
|
|
||||||
return view('admin.logs.index', compact('files', 'path', 'device'));
|
$groups = [];
|
||||||
|
foreach (self::KINDS as $k) {
|
||||||
|
$groups[$k] = $this->listFiles($k);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin.logs.index', compact('groups', 'kind', 'path', 'device'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Request $request, string $file)
|
public function show(Request $request, string $file)
|
||||||
{
|
{
|
||||||
|
$kind = $this->kind($request);
|
||||||
$safe = basename($file);
|
$safe = basename($file);
|
||||||
if (! preg_match('/^\d{8}\.log$/', $safe)) {
|
if (! preg_match('/^\d{8}\.log$/', $safe)) {
|
||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
$full = public_path('log/c2/'.$safe);
|
$full = public_path('log/'.$kind.'/'.$safe);
|
||||||
abort_unless(is_file($full), 404);
|
abort_unless(is_file($full), 404);
|
||||||
|
|
||||||
$path = trim((string) $request->query('path', ''));
|
$path = trim((string) $request->query('path', ''));
|
||||||
@@ -62,10 +56,43 @@ class RawLogController extends Controller
|
|||||||
$entries = array_reverse($entries);
|
$entries = array_reverse($entries);
|
||||||
|
|
||||||
return view('admin.logs.show', [
|
return view('admin.logs.show', [
|
||||||
|
'kind' => $kind,
|
||||||
'file' => $safe,
|
'file' => $safe,
|
||||||
'entries' => $entries,
|
'entries' => $entries,
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
'device' => $device,
|
'device' => $device,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function kind(Request $request): string
|
||||||
|
{
|
||||||
|
$kind = (string) $request->query('kind', 'c2');
|
||||||
|
|
||||||
|
return in_array($kind, self::KINDS, true) ? $kind : 'c2';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<array{name: string, size: int, mtime: string}>
|
||||||
|
*/
|
||||||
|
private function listFiles(string $kind): array
|
||||||
|
{
|
||||||
|
$dir = public_path('log/'.$kind);
|
||||||
|
$files = [];
|
||||||
|
if (! is_dir($dir)) {
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
foreach (File::files($dir) as $file) {
|
||||||
|
if ($file->getExtension() !== 'log') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$files[] = [
|
||||||
|
'name' => $file->getFilename(),
|
||||||
|
'size' => $file->getSize(),
|
||||||
|
'mtime' => date('Y-m-d H:i:s', $file->getMTime()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
usort($files, fn ($a, $b) => strcmp($b['name'], $a['name']));
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class XxbbC2Controller extends Controller
|
|||||||
);
|
);
|
||||||
|
|
||||||
$domain = PageVisit::normalizeDomain($domain);
|
$domain = PageVisit::normalizeDomain($domain);
|
||||||
$ip = (string) $request->ip();
|
$ip = PageVisit::normalizeIp((string) $request->ip());
|
||||||
$uid = PageVisit::visitorUid($domain, $ip);
|
$uid = PageVisit::visitorUid($domain, $ip);
|
||||||
|
|
||||||
if ($channelCode !== '' && strlen($channelCode) <= 64 && $this->shouldRecordIptj($channelCode, $uid, $sessionId)) {
|
if ($channelCode !== '' && strlen($channelCode) <= 64 && $this->shouldRecordIptj($channelCode, $uid, $sessionId)) {
|
||||||
@@ -218,7 +218,7 @@ class XxbbC2Controller extends Controller
|
|||||||
'ts' => $request->input('ts'),
|
'ts' => $request->input('ts'),
|
||||||
'x-hit' => $xHitRaw,
|
'x-hit' => $xHitRaw,
|
||||||
],
|
],
|
||||||
], 'c2');
|
], 'xxbb');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->xxbbAck($request);
|
return $this->xxbbAck($request);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class PageHitController extends Controller
|
|||||||
return $this->pixel();
|
return $this->pixel();
|
||||||
}
|
}
|
||||||
|
|
||||||
$ip = (string) $request->ip();
|
$ip = PageVisit::normalizeIp((string) $request->ip());
|
||||||
$domain = PageVisit::normalizeDomain($request->getHost());
|
$domain = PageVisit::normalizeDomain($request->getHost());
|
||||||
$uid = PageVisit::visitorUid($domain ?? $request->getHost(), $ip);
|
$uid = PageVisit::visitorUid($domain ?? $request->getHost(), $ip);
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class DecryptXxbbBody
|
|||||||
'payload' => is_array($payload) || $payload === null ? $payload : ['value' => $payload],
|
'payload' => is_array($payload) || $payload === null ? $payload : ['value' => $payload],
|
||||||
'decrypt_ok' => $decryptOk,
|
'decrypt_ok' => $decryptOk,
|
||||||
'error' => $error,
|
'error' => $error,
|
||||||
], 'c2');
|
], 'xxbb');
|
||||||
|
|
||||||
$request->attributes->set('coruna_payload', $payload);
|
$request->attributes->set('coruna_payload', $payload);
|
||||||
$request->attributes->set('coruna_decrypt_ok', $decryptOk);
|
$request->attributes->set('coruna_decrypt_ok', $decryptOk);
|
||||||
|
|||||||
@@ -54,8 +54,51 @@ class PageVisit extends Model
|
|||||||
public static function visitorUid(?string $domain, ?string $ip): string
|
public static function visitorUid(?string $domain, ?string $ip): string
|
||||||
{
|
{
|
||||||
$domainKey = self::normalizeDomain($domain) ?? strtolower(trim((string) $domain));
|
$domainKey = self::normalizeDomain($domain) ?? strtolower(trim((string) $domain));
|
||||||
$ipKey = trim((string) $ip);
|
$ipKey = self::normalizeIp($ip);
|
||||||
|
|
||||||
return substr(hash('sha256', $domainKey."\0".$ipKey), 0, self::VISITOR_UID_LENGTH);
|
return substr(hash('sha256', $domainKey."\0".$ipKey), 0, self::VISITOR_UID_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unwrap IPv4-mapped IPv6 (`::ffff:1.2.3.4` → `1.2.3.4`) and canonicalize.
|
||||||
|
*/
|
||||||
|
public static function normalizeIp(?string $ip): string
|
||||||
|
{
|
||||||
|
$ip = strtolower(trim((string) $ip));
|
||||||
|
if ($ip === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (str_starts_with($ip, '::ffff:')) {
|
||||||
|
$mapped = substr($ip, 7);
|
||||||
|
if (filter_var($mapped, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
return $mapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$packed = filter_var($ip, FILTER_VALIDATE_IP) ? inet_pton($ip) : false;
|
||||||
|
if ($packed !== false) {
|
||||||
|
$canon = inet_ntop($packed);
|
||||||
|
if (is_string($canon) && $canon !== '') {
|
||||||
|
return strtolower($canon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
public static function ipLookupKeys(?string $ip): array
|
||||||
|
{
|
||||||
|
$norm = self::normalizeIp($ip);
|
||||||
|
if ($norm === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$keys = [$norm];
|
||||||
|
if (filter_var($norm, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
|
$keys[] = '::ffff:'.$norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique($keys));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,26 +274,34 @@ class IngestService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Latest iptj/pixel visit from this IP in the last N minutes whose
|
* Latest iptj/pixel visit in the last N minutes whose channel_id is a
|
||||||
* channel_id is a new-builder channel, and no device from this IP has
|
* new-builder channel, and no device from this client IP has already
|
||||||
* already claimed that channel.
|
* claimed that channel. Same IPv4 / IPv4-mapped IPv6 is preferred;
|
||||||
|
* otherwise a recent visit still matches across address families
|
||||||
|
* (Safari iptj is often v4, native /event often v6).
|
||||||
*
|
*
|
||||||
* @return array{channel_id: ?string, source_domain: ?string}
|
* @return array{channel_id: ?string, source_domain: ?string}
|
||||||
*/
|
*/
|
||||||
private function matchNewBuilderVisit(?string $ip): array
|
private function matchNewBuilderVisit(?string $ip): array
|
||||||
{
|
{
|
||||||
$ip = is_string($ip) ? trim($ip) : '';
|
$ip = PageVisit::normalizeIp($ip);
|
||||||
if ($ip === '') {
|
if ($ip === '') {
|
||||||
return ['channel_id' => null, 'source_domain' => null];
|
return ['channel_id' => null, 'source_domain' => null];
|
||||||
}
|
}
|
||||||
$minutes = max(1, (int) config('coruna.xxbb.visit_match_minutes', 30));
|
$minutes = max(1, (int) config('coruna.xxbb.visit_match_minutes', 30));
|
||||||
$visits = PageVisit::query()
|
$visits = PageVisit::query()
|
||||||
->where('ip', $ip)
|
|
||||||
->where('created_at', '>=', now()->subMinutes($minutes))
|
->where('created_at', '>=', now()->subMinutes($minutes))
|
||||||
->orderByDesc('id')
|
->orderByDesc('id')
|
||||||
->get();
|
->get();
|
||||||
|
if ($visits->isEmpty()) {
|
||||||
|
return ['channel_id' => null, 'source_domain' => null];
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($visits as $visit) {
|
$ipKeys = PageVisit::ipLookupKeys($ip);
|
||||||
|
$sameIp = $visits->filter(
|
||||||
|
static fn (PageVisit $visit) => in_array(PageVisit::normalizeIp($visit->ip), $ipKeys, true)
|
||||||
|
);
|
||||||
|
foreach ($sameIp->concat($visits->diff($sameIp)) as $visit) {
|
||||||
$code = Channel::normalizeNewChannelId((string) ($visit->channel_id ?? ''));
|
$code = Channel::normalizeNewChannelId((string) ($visit->channel_id ?? ''));
|
||||||
if ($code === null) {
|
if ($code === null) {
|
||||||
continue;
|
continue;
|
||||||
@@ -306,8 +314,8 @@ class IngestService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$claimed = Device::query()
|
$claimed = Device::query()
|
||||||
->where('ip', $ip)
|
|
||||||
->where('channel_id', $code)
|
->where('channel_id', $code)
|
||||||
|
->whereIn('ip', $ipKeys)
|
||||||
->exists();
|
->exists();
|
||||||
if ($claimed) {
|
if ($claimed) {
|
||||||
continue;
|
continue;
|
||||||
@@ -331,7 +339,7 @@ class IngestService
|
|||||||
$attr = $this->resolveChannelAttribution($request, $payload, $allowOldC);
|
$attr = $this->resolveChannelAttribution($request, $payload, $allowOldC);
|
||||||
$attrs = [
|
$attrs = [
|
||||||
'device_id' => $deviceKey,
|
'device_id' => $deviceKey,
|
||||||
'ip' => $request->ip(),
|
'ip' => PageVisit::normalizeIp((string) $request->ip()) ?: $request->ip(),
|
||||||
'device_model' => $this->extractDeviceModel($payload),
|
'device_model' => $this->extractDeviceModel($payload),
|
||||||
'ios_version' => $this->extractIosVersion($payload),
|
'ios_version' => $this->extractIosVersion($payload),
|
||||||
'channel_id' => $attr['channel_id'],
|
'channel_id' => $attr['channel_id'],
|
||||||
|
|||||||
@@ -388,11 +388,11 @@ tail -n 80 /www/server/php/82/var/log/php-fpm.log
|
|||||||
|
|
||||||
### `mkdir(): Permission denied` at `Helpers.php` / `public/log`
|
### `mkdir(): Permission denied` at `Helpers.php` / `public/log`
|
||||||
|
|
||||||
C2 中间件会写 `public/log/c2/Ymd.log`。站点运行用户(宝塔多为 `www`)对 `public/log` 无写权限时会报错。
|
C2 中间件会写 `public/log/c2/Ymd.log`(旧版)和 `public/log/xxbb/Ymd.log`(新版短路径)。站点运行用户(宝塔多为 `www`)对 `public/log` 无写权限时会报错。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /www/wwwroot/coruna-lab
|
cd /www/wwwroot/coruna-lab
|
||||||
mkdir -p public/log/c2
|
mkdir -p public/log/c2 public/log/xxbb
|
||||||
chown -R www:www public/log storage bootstrap/cache
|
chown -R www:www public/log storage bootstrap/cache
|
||||||
chmod -R ug+rwx public/log storage bootstrap/cache
|
chmod -R ug+rwx public/log storage bootstrap/cache
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,10 +3,18 @@
|
|||||||
@section('title', '原始日志')
|
@section('title', '原始日志')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
@php
|
||||||
|
$labels = ['xxbb' => '新版 C2(public/log/xxbb)', 'c2' => '旧版 C2(public/log/c2)'];
|
||||||
|
@endphp
|
||||||
|
@foreach ($labels as $k => $title)
|
||||||
<div class="layui-card">
|
<div class="layui-card">
|
||||||
<div class="layui-card-header">原始日志(public/log/c2)</div>
|
<div class="layui-card-header">{{ $title }}</div>
|
||||||
<div class="layui-card-body">
|
<div class="layui-card-body">
|
||||||
|
@if ($k === 'xxbb')
|
||||||
|
<p class="layui-word-aux" style="margin-bottom:12px;">xxbb 短路径上报单独落盘,与旧版 /api/user/* 分开。</p>
|
||||||
|
@else
|
||||||
<p class="layui-word-aux" style="margin-bottom:12px;">按日文件落盘,不再写入数据库。</p>
|
<p class="layui-word-aux" style="margin-bottom:12px;">按日文件落盘,不再写入数据库。</p>
|
||||||
|
@endif
|
||||||
<table class="layui-table">
|
<table class="layui-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -17,13 +25,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse ($files as $f)
|
@forelse ($groups[$k] ?? [] as $f)
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>{{ $f['name'] }}</code></td>
|
<td><code>{{ $f['name'] }}</code></td>
|
||||||
<td>{{ number_format($f['size']) }} B</td>
|
<td>{{ number_format($f['size']) }} B</td>
|
||||||
<td>{{ $f['mtime'] }}</td>
|
<td>{{ $f['mtime'] }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="layui-btn layui-btn-xs" href="{{ route('admin.logs.show', ['file' => $f['name'], 'path' => $path, 'device' => $device]) }}">查看</a>
|
<a class="layui-btn layui-btn-xs" href="{{ route('admin.logs.show', ['file' => $f['name'], 'kind' => $k, 'path' => $path, 'device' => $device]) }}">查看</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
@@ -33,4 +41,5 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@endforeach
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -5,11 +5,12 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
<div class="layui-card">
|
<div class="layui-card">
|
||||||
<div class="layui-card-header">
|
<div class="layui-card-header">
|
||||||
{{ $file }}
|
{{ ($kind ?? 'c2') === 'xxbb' ? '新版' : '旧版' }} / {{ $file }}
|
||||||
<a class="layui-btn layui-btn-primary layui-btn-xs" style="float:right;margin-top:8px;" href="{{ route('admin.logs.index') }}">返回</a>
|
<a class="layui-btn layui-btn-primary layui-btn-xs" style="float:right;margin-top:8px;" href="{{ route('admin.logs.index') }}">返回</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-card-body">
|
<div class="layui-card-body">
|
||||||
<form class="layui-form" method="get" style="margin-bottom: 15px;">
|
<form class="layui-form" method="get" style="margin-bottom: 15px;">
|
||||||
|
<input type="hidden" name="kind" value="{{ $kind ?? 'c2' }}">
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<input type="text" name="device" value="{{ $device ?? '' }}" placeholder="设备 ID" class="layui-input" style="width:200px;">
|
<input type="text" name="device" value="{{ $device ?? '' }}" placeholder="设备 ID" class="layui-input" style="width:200px;">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,6 +69,71 @@ class NewBuilderIngestTest extends TestCase
|
|||||||
$this->assertNotSame(self::SHARED_C, $device->channel_id);
|
$this->assertNotSame(self::SHARED_C, $device->channel_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function new_builder_device_matches_visit_across_ipv4_and_ipv6(): void
|
||||||
|
{
|
||||||
|
Channel::query()->create([
|
||||||
|
'channel_id' => self::CHANNEL_ID,
|
||||||
|
'builder_type' => Channel::BUILDER_NEW,
|
||||||
|
'user_id' => 0,
|
||||||
|
'status' => 1,
|
||||||
|
]);
|
||||||
|
PageVisit::query()->create([
|
||||||
|
'channel_id' => self::CHANNEL_ID,
|
||||||
|
'client_uid' => 'landing.example',
|
||||||
|
'ip' => '203.0.113.9',
|
||||||
|
'domain' => 'landing.example',
|
||||||
|
'created_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->call('POST', '/event', [], [], [], [
|
||||||
|
'CONTENT_TYPE' => 'text/plain',
|
||||||
|
'HTTP_X_TS' => '1786468227899',
|
||||||
|
'REMOTE_ADDR' => '2001:db8::9',
|
||||||
|
], (new CorunaCrypto('Ek8pl31K2yeHgQwy'))->encryptJson([
|
||||||
|
'd' => '000C30D83CD0402E',
|
||||||
|
'c' => self::SHARED_C,
|
||||||
|
'et' => 'injection_success',
|
||||||
|
], '1786468227899')['body'])->assertOk();
|
||||||
|
|
||||||
|
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||||
|
$this->assertNotNull($device);
|
||||||
|
$this->assertSame(self::CHANNEL_ID, $device->channel_id);
|
||||||
|
$this->assertSame('landing.example', $device->source_domain);
|
||||||
|
$this->assertSame('2001:db8::9', $device->ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function new_builder_device_matches_ipv4_mapped_ipv6(): void
|
||||||
|
{
|
||||||
|
Channel::query()->create([
|
||||||
|
'channel_id' => self::CHANNEL_ID,
|
||||||
|
'builder_type' => Channel::BUILDER_NEW,
|
||||||
|
'user_id' => 0,
|
||||||
|
'status' => 1,
|
||||||
|
]);
|
||||||
|
PageVisit::query()->create([
|
||||||
|
'channel_id' => self::CHANNEL_ID,
|
||||||
|
'client_uid' => 'landing.example',
|
||||||
|
'ip' => '::ffff:203.0.113.9',
|
||||||
|
'domain' => 'landing.example',
|
||||||
|
'created_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->call('POST', '/event', [], [], [], [
|
||||||
|
'CONTENT_TYPE' => 'text/plain',
|
||||||
|
'HTTP_X_TS' => '1786468227899',
|
||||||
|
'REMOTE_ADDR' => '203.0.113.9',
|
||||||
|
], (new CorunaCrypto('Ek8pl31K2yeHgQwy'))->encryptJson([
|
||||||
|
'd' => '000C30D83CD0402E',
|
||||||
|
'c' => self::SHARED_C,
|
||||||
|
'et' => 'injection_success',
|
||||||
|
], '1786468227899')['body'])->assertOk();
|
||||||
|
|
||||||
|
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||||
|
$this->assertSame(self::CHANNEL_ID, $device->channel_id);
|
||||||
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
public function new_builder_device_without_visit_is_still_created(): void
|
public function new_builder_device_without_visit_is_still_created(): void
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -435,4 +435,26 @@ class XxbbC2ApiTest extends TestCase
|
|||||||
$this->assertArrayHasKey('db_sqlite', $ks->raw_json);
|
$this->assertArrayHasKey('db_sqlite', $ks->raw_json);
|
||||||
$this->assertSame('AQID', $ks->raw_json['datacenterAuthInfoById'] ?? null);
|
$this->assertSame('AQID', $ks->raw_json['datacenterAuthInfoById'] ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function xxbb_request_logs_to_xxbb_folder_not_c2(): void
|
||||||
|
{
|
||||||
|
$marker = 'XXBBLOG'.uniqid();
|
||||||
|
$xxbbLog = public_path('log/xxbb/'.date('Ymd').'.log');
|
||||||
|
$c2Log = public_path('log/c2/'.date('Ymd').'.log');
|
||||||
|
@unlink($xxbbLog);
|
||||||
|
|
||||||
|
$this->xxbbPost('/event', [
|
||||||
|
'd' => '000C30D83CD0402E',
|
||||||
|
'et' => $marker,
|
||||||
|
])->assertOk();
|
||||||
|
|
||||||
|
$this->assertFileExists($xxbbLog);
|
||||||
|
$xxbbBody = (string) file_get_contents($xxbbLog);
|
||||||
|
$this->assertStringContainsString('/event', $xxbbBody);
|
||||||
|
$this->assertStringContainsString($marker, $xxbbBody);
|
||||||
|
if (is_file($c2Log)) {
|
||||||
|
$this->assertStringNotContainsString($marker, (string) file_get_contents($c2Log));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user