链接管理模块
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
/**
|
||||
* ISO 3166-1 alpha-2 国家代码与中文名称
|
||||
*/
|
||||
class CountryIso
|
||||
{
|
||||
/**
|
||||
* 常用投放国家 ISO2 => 中文名
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private const COUNTRIES = [
|
||||
'CN' => '中国',
|
||||
'HK' => '中国香港',
|
||||
'MO' => '中国澳门',
|
||||
'TW' => '中国台湾',
|
||||
'US' => '美国',
|
||||
'CA' => '加拿大',
|
||||
'GB' => '英国',
|
||||
'DE' => '德国',
|
||||
'FR' => '法国',
|
||||
'IT' => '意大利',
|
||||
'ES' => '西班牙',
|
||||
'NL' => '荷兰',
|
||||
'BE' => '比利时',
|
||||
'CH' => '瑞士',
|
||||
'AT' => '奥地利',
|
||||
'SE' => '瑞典',
|
||||
'NO' => '挪威',
|
||||
'DK' => '丹麦',
|
||||
'FI' => '芬兰',
|
||||
'IE' => '爱尔兰',
|
||||
'PT' => '葡萄牙',
|
||||
'PL' => '波兰',
|
||||
'CZ' => '捷克',
|
||||
'HU' => '匈牙利',
|
||||
'RO' => '罗马尼亚',
|
||||
'GR' => '希腊',
|
||||
'RU' => '俄罗斯',
|
||||
'UA' => '乌克兰',
|
||||
'TR' => '土耳其',
|
||||
'IL' => '以色列',
|
||||
'SA' => '沙特阿拉伯',
|
||||
'AE' => '阿联酋',
|
||||
'QA' => '卡塔尔',
|
||||
'KW' => '科威特',
|
||||
'IN' => '印度',
|
||||
'PK' => '巴基斯坦',
|
||||
'BD' => '孟加拉国',
|
||||
'TH' => '泰国',
|
||||
'VN' => '越南',
|
||||
'MY' => '马来西亚',
|
||||
'SG' => '新加坡',
|
||||
'ID' => '印度尼西亚',
|
||||
'PH' => '菲律宾',
|
||||
'JP' => '日本',
|
||||
'KR' => '韩国',
|
||||
'AU' => '澳大利亚',
|
||||
'NZ' => '新西兰',
|
||||
'BR' => '巴西',
|
||||
'MX' => '墨西哥',
|
||||
'AR' => '阿根廷',
|
||||
'CL' => '智利',
|
||||
'CO' => '哥伦比亚',
|
||||
'PE' => '秘鲁',
|
||||
'ZA' => '南非',
|
||||
'EG' => '埃及',
|
||||
'NG' => '尼日利亚',
|
||||
'KE' => '肯尼亚',
|
||||
];
|
||||
|
||||
/**
|
||||
* 下拉选项 ISO2 => 中文名
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function getOptions(): array
|
||||
{
|
||||
return self::COUNTRIES;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验是否为合法 ISO2 代码
|
||||
*/
|
||||
public static function isValidCode(string $code): bool
|
||||
{
|
||||
$code = strtoupper(trim($code));
|
||||
return isset(self::COUNTRIES[$code]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化并过滤国家代码列表
|
||||
*
|
||||
* @param array<int, string>|string $codes
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function normalizeCodes($codes): array
|
||||
{
|
||||
if (is_string($codes)) {
|
||||
$codes = explode(',', $codes);
|
||||
}
|
||||
if (!is_array($codes)) {
|
||||
return [];
|
||||
}
|
||||
$result = [];
|
||||
foreach ($codes as $code) {
|
||||
$code = strtoupper(trim((string)$code));
|
||||
if ($code !== '' && self::isValidCode($code) && !in_array($code, $result, true)) {
|
||||
$result[] = $code;
|
||||
}
|
||||
}
|
||||
sort($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 逗号分隔 ISO 代码转中文展示
|
||||
*/
|
||||
public static function codesToText(string $codes): string
|
||||
{
|
||||
if ($codes === '') {
|
||||
return '';
|
||||
}
|
||||
$parts = [];
|
||||
foreach (explode(',', $codes) as $code) {
|
||||
$code = strtoupper(trim($code));
|
||||
if ($code === '') {
|
||||
continue;
|
||||
}
|
||||
$parts[] = self::COUNTRIES[$code] ?? $code;
|
||||
}
|
||||
return implode(',', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组转存储字符串
|
||||
*
|
||||
* @param array<int, string> $codes
|
||||
*/
|
||||
public static function codesToStorage(array $codes): string
|
||||
{
|
||||
return implode(',', self::normalizeCodes($codes));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\model;
|
||||
|
||||
use think\Model;
|
||||
@@ -92,12 +94,25 @@ class Config extends Model
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取分类分组列表
|
||||
* 读取分类分组列表(优先数据库 configgroup,避免 site.php 未同步时分组缺失)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getGroupList()
|
||||
{
|
||||
$groupList = config('site.configgroup');
|
||||
if (!is_array($groupList)) {
|
||||
$groupList = [];
|
||||
}
|
||||
try {
|
||||
$dbJson = \think\Db::name('config')->where('name', 'configgroup')->value('value');
|
||||
$dbList = json_decode((string) $dbJson, true);
|
||||
if (is_array($dbList) && $dbList !== []) {
|
||||
$groupList = $dbList;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// 数据库不可用时回退 site.php
|
||||
}
|
||||
foreach ($groupList as $k => &$v) {
|
||||
$v = __($v);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
/**
|
||||
* 分流链接自动回复语句(一行一条)
|
||||
*/
|
||||
class SplitAutoReplyService
|
||||
{
|
||||
/** 最多条数 */
|
||||
private const MAX_LINES = 200;
|
||||
|
||||
/** 单条最大字符数 */
|
||||
private const MAX_LINE_LENGTH = 500;
|
||||
|
||||
/**
|
||||
* 解析为多行文本数组
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function parseLines(string $raw): array
|
||||
{
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
$parts = preg_split('/\r\n|\r|\n/', $raw) ?: [];
|
||||
$lines = [];
|
||||
foreach ($parts as $part) {
|
||||
$line = trim((string) $part);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
if (strlen($line) > self::MAX_LINE_LENGTH) {
|
||||
$line = mb_substr($line, 0, self::MAX_LINE_LENGTH, 'UTF-8');
|
||||
}
|
||||
$lines[] = $line;
|
||||
if (count($lines) >= self::MAX_LINES) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化为数据库存储(换行分隔)
|
||||
*
|
||||
* @param string|array<int, string> $value
|
||||
*/
|
||||
public static function formatStorage($value): string
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$lines = $value;
|
||||
} else {
|
||||
$lines = self::parseLines((string) $value);
|
||||
}
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* 供表单 textarea 回显
|
||||
*/
|
||||
public static function formatDisplay(string $stored): string
|
||||
{
|
||||
$lines = self::parseLines($stored);
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\admin\model\split\Link;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
* 分流链接码生成服务
|
||||
*/
|
||||
class SplitLinkCodeService
|
||||
{
|
||||
private const CODE_LENGTH = 9;
|
||||
|
||||
private const CHARSET = 'abcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
private const MAX_ATTEMPTS = 50;
|
||||
|
||||
/**
|
||||
* 规范化链接码(小写、去空格)
|
||||
*/
|
||||
public static function normalize(string $code): string
|
||||
{
|
||||
return strtolower(trim($code));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为合法 9 位小写字母链接码
|
||||
*/
|
||||
public static function isValidFormat(string $code): bool
|
||||
{
|
||||
return (bool) preg_match('/^[a-z]{9}$/', $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 链接码是否未被占用
|
||||
*/
|
||||
public static function isAvailable(string $code, int $excludeId = 0): bool
|
||||
{
|
||||
$code = self::normalize($code);
|
||||
$query = Link::where('link_code', $code);
|
||||
if ($excludeId > 0) {
|
||||
$query->where('id', '<>', $excludeId);
|
||||
}
|
||||
return !$query->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一 9 位小写字母链接码
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function generateUnique(): string
|
||||
{
|
||||
for ($i = 0; $i < self::MAX_ATTEMPTS; $i++) {
|
||||
$code = $this->generateRandom();
|
||||
if (!Link::where('link_code', $code)->find()) {
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
throw new Exception('分流链接生成失败,请稍后重试');
|
||||
}
|
||||
|
||||
private function generateRandom(): string
|
||||
{
|
||||
$chars = self::CHARSET;
|
||||
$max = strlen($chars) - 1;
|
||||
$code = '';
|
||||
for ($i = 0; $i < self::CODE_LENGTH; $i++) {
|
||||
$code .= $chars[random_int(0, $max)];
|
||||
}
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\admin\model\Domain as DomainModel;
|
||||
|
||||
/**
|
||||
* 平台分配域名解析(支持多域名,一行一个)
|
||||
*/
|
||||
class SplitPlatformDomainService
|
||||
{
|
||||
/**
|
||||
* 解析配置文本为合法根域名列表(去重、小写)
|
||||
*
|
||||
* @param string $raw 换行或逗号分隔的域名文本
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function parseList(string $raw): array
|
||||
{
|
||||
$raw = trim($raw);
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parts = preg_split('/[\r\n,]+/', $raw) ?: [];
|
||||
$domains = [];
|
||||
foreach ($parts as $part) {
|
||||
$domain = DomainModel::normalizeDomain(trim((string) $part));
|
||||
if ($domain === '' || isset($domains[$domain])) {
|
||||
continue;
|
||||
}
|
||||
if (!DomainModel::isValidRootDomain($domain)) {
|
||||
continue;
|
||||
}
|
||||
$domains[$domain] = $domain;
|
||||
}
|
||||
|
||||
return array_values($domains);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将域名列表格式化为配置存储文本(一行一个)
|
||||
*
|
||||
* @param array<int, string> $domains
|
||||
*/
|
||||
public static function formatList(array $domains): string
|
||||
{
|
||||
$lines = [];
|
||||
foreach ($domains as $domain) {
|
||||
$domain = DomainModel::normalizeDomain((string) $domain);
|
||||
if ($domain !== '' && DomainModel::isValidRootDomain($domain)) {
|
||||
$lines[$domain] = $domain;
|
||||
}
|
||||
}
|
||||
return implode("\n", array_values($lines));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user