where('name', $name)->find(); return $config ? $config['value'] : null; } if ($group) { $temp = $configModel::cache('group' . $group, null, $configModel::$cacheTag)->where('group', $group)->select()->toArray(); } else { $temp = $configModel::cache('sys_config_all', null, $configModel::$cacheTag)->order('weigh desc')->select()->toArray(); } if ($concise) { $config = []; foreach ($temp as $item) { $config[$item['name']] = $item['value']; } return $config; } return $temp; } return config("sys_config.{$name}", null); } } if (!function_exists('clear_config_cache')) { /** * 清理配置缓存(Config 写入后调用) */ function clear_config_cache(): void { $cachePath = base_path() . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR . 'cache'; if (!is_dir($cachePath)) { return; } $files = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($cachePath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST ); foreach ($files as $file) { if ($file->isFile()) { @unlink($file->getRealPath()); } } } } if (!function_exists('ip_check')) { /** * IP 检查 * @param string|null $ip 要检查的 IP,null 时从 request 获取 * @param \Webman\Http\Request|null $request * @return Response|null 禁止访问时返回 Response,否则 null */ function ip_check(?string $ip = null, $request = null): ?Response { if ($ip === null && $request) { $ip = $request->getRealIp(); } if (!$ip) { return null; } $noAccess = get_sys_config('no_access_ip'); $noAccess = !$noAccess ? [] : array_filter(explode("\n", str_replace("\r\n", "\n", (string) $noAccess))); if ($noAccess && IpUtils::checkIp($ip, $noAccess)) { return response(json_encode(['msg' => 'No permission request']), 403, ['Content-Type' => 'application/json']); } return null; } } if (!function_exists('get_auth_token')) { /** * 获取鉴权 token * @param array $names 如 ['ba', 'token'] * @param \Webman\Http\Request|null $request */ function get_auth_token(array $names = ['ba', 'token'], $request = null): string { $request = $request ?? (function_exists('request') ? request() : null); if (!$request) { return ''; } $separators = [ 'header' => ['', '-'], 'param' => ['', '-', '_'], 'server' => ['_'], ]; $tokens = []; foreach ($separators as $source => $sps) { foreach ($sps as $sp) { $key = ($source === 'server' ? 'http_' : '') . implode($sp, $names); if ($source === 'header') { $val = $request->header($key); } elseif ($source === 'param') { $val = $request->get($key) ?? $request->post($key); } else { $val = $_SERVER[$key] ?? null; } if ($val) { $tokens[] = $val; } } } return $tokens[0] ?? ''; } } if (!function_exists('get_controller_path')) { /** * 从 Request 或路由获取控制器路径(等价于 ThinkPHP controllerPath) * 优先从 $request->controller(Webman 路由匹配时设置)解析,否则从 path 解析 * @param \Webman\Http\Request|null $request * @return string|null 如 auth/admin、user/user */ function get_controller_path($request = null): ?string { $request = $request ?? (function_exists('request') ? request() : null); if (!$request) { return null; } // 优先从路由匹配的 controller 解析(Webman 在路由匹配后设置) $controller = $request->controller ?? null; if ($controller && is_string($controller)) { foreach (['app\\admin\\controller\\', 'app\\api\\controller\\'] as $prefix) { if (str_starts_with($controller, $prefix)) { $relative = substr($controller, strlen($prefix)); $parts = explode('\\', $relative); $path = []; foreach ($parts as $p) { $path[] = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $p)); } return implode('/', $path); } } } // 回退:从 path 解析(如 admin/auth/admin/index -> auth/admin) $path = trim($request->path(), '/'); if (!$path) { return null; } $parts = explode('/', $path); if (count($parts) < 2) { return $parts[0] ?? null; } return implode('/', array_slice($parts, 1, -1)) ?: $parts[1]; } } if (!function_exists('action_in_arr')) { /** * 检测当前方法是否在数组中(用于 noNeedLogin、noNeedPermission) * @param array $arr * @param string|null $action 当前 action,null 时从 request path 解析 */ function action_in_arr(array $arr, ?string $action = null): bool { if (!$arr) { return false; } $arr = array_map('strtolower', $arr); if (in_array('*', $arr)) { return true; } if ($action === null && function_exists('request')) { $req = request(); $path = trim($req->path(), '/'); $parts = explode('/', $path); $action = $parts[array_key_last($parts)] ?? ''; } return $action ? in_array(strtolower($action), $arr) : false; } } if (!function_exists('event_trigger')) { /** * 触发事件(BuildAdmin 兼容,替代 Event::trigger) * @param string $event * @param mixed ...$args */ function event_trigger(string $event, mixed ...$args): void { $listeners = config("events.listen.{$event}", []); foreach ($listeners as $listener) { try { if (is_string($listener) && class_exists($listener)) { $instance = new $listener(); if (method_exists($instance, 'handle')) { $instance->handle(...$args); } } elseif (is_callable($listener)) { $listener(...$args); } } catch (\Throwable $e) { if (class_exists(\support\Log::class)) { \support\Log::warning("[event_trigger] {$event}: " . $e->getMessage()); } } } } } if (!function_exists('set_timezone')) { /** * 设置时区 */ function set_timezone(?string $timezone = null): void { $defaultTimezone = config('app.default_timezone', 'Asia/Shanghai'); $timezone = $timezone ?? get_sys_config('time_zone'); if ($timezone && $defaultTimezone !== $timezone) { date_default_timezone_set($timezone); } } } if (!function_exists('encrypt_password')) { /** * 加密密码(兼容旧版 md5) * @deprecated 使用 hash_password 代替 */ function encrypt_password(string $password, string $salt = '', string $encrypt = 'md5'): string { return $encrypt($encrypt($password) . $salt); } } if (!function_exists('hash_password')) { /** * 创建密码散列(hash) */ function hash_password(string $password): string { return password_hash($password, PASSWORD_DEFAULT); } } if (!function_exists('verify_password')) { /** * 验证密码是否和散列值匹配 */ function verify_password(string $password, string $hash, array $extend = []): bool { if (str_starts_with($hash, '$') || password_get_info($hash)['algoName'] !== 'unknown') { return password_verify($password, $hash); } return encrypt_password($password, $extend['salt'] ?? '') === $hash; } } if (!function_exists('full_url')) { /** * 获取资源完整 url 地址 */ function full_url(string $relativeUrl = '', string|bool $domain = true, string $default = ''): string { $cdnUrl = config('buildadmin.cdn_url'); if (!$cdnUrl && function_exists('request')) { $req = request(); $cdnUrl = $req ? '//' . $req->host() : '//localhost'; } elseif (!$cdnUrl) { $cdnUrl = '//localhost'; } if ($domain === true) { $domain = $cdnUrl; } elseif ($domain === false) { $domain = ''; } $relativeUrl = $relativeUrl ?: $default; if (!$relativeUrl) { return $domain; } $regex = "/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i"; if (preg_match('/^http(s)?:\/\//', $relativeUrl) || preg_match($regex, $relativeUrl) || $domain === false) { return $relativeUrl; } $url = $domain . $relativeUrl; $cdnUrlParams = config('buildadmin.cdn_url_params'); if ($domain === $cdnUrl && $cdnUrlParams) { $separator = str_contains($url, '?') ? '&' : '?'; $url .= $separator . $cdnUrlParams; } return $url; } } if (!function_exists('parse_name')) { /** * 命名转换(ThinkPHP 兼容) * @param string $name 名称 * @param int $type 0=转小写+下划线 1=驼峰 2=首字母大写驼峰 * @param string $delimiter 分隔符 */ function parse_name(string $name, int $type = 0, string $delimiter = '_'): string { if ($type === 0) { return strtolower(preg_replace('/([A-Z])/', $delimiter . '$1', lcfirst($name))); } if ($type === 1) { $name = str_replace($delimiter, ' ', $name); return lcfirst(str_replace(' ', '', ucwords($name))); } if ($type === 2) { $name = str_replace($delimiter, ' ', $name); return str_replace(' ', '', ucwords($name)); } return $name; } } if (!function_exists('root_path')) { /** * 根路径(BuildAdmin 兼容,等价于 base_path) * 无参数时返回带尾部分隔符的路径,确保 root_path() . 'app' 拼接正确 * @param string $path 子路径 */ function root_path(string $path = ''): string { $base = base_path($path); if ($path === '' && $base !== '') { return rtrim($base, DIRECTORY_SEPARATOR . '/') . DIRECTORY_SEPARATOR; } return $base; } } if (!function_exists('app_path')) { /** * 应用目录路径(Webman 兼容,用于 CRUD Helper 等) * @param string $path 子路径 */ function app_path(string $path = ''): string { $base = rtrim(base_path(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'app'; return $path ? $base . DIRECTORY_SEPARATOR . ltrim(str_replace('/', DIRECTORY_SEPARATOR, $path), DIRECTORY_SEPARATOR) : $base; } } if (!function_exists('get_controller_list')) { /** * 获取控制器文件列表(递归) * @param string $app 应用名,默认 admin */ function get_controller_list(string $app = 'admin'): array { $controllerDir = root_path() . 'app' . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR . 'controller' . DIRECTORY_SEPARATOR; return (class_exists(\ba\Filesystem::class) && is_dir($controllerDir)) ? \ba\Filesystem::getDirFiles($controllerDir) : []; } } if (!function_exists('get_route_remark')) { /** * 获取当前路由后台菜单规则的备注信息 * 使用控制器 domain 翻译,以支持不同控制器对同一 key(如 Remark lang)的不同翻译 */ function get_route_remark(): string { $controllerPath = get_controller_path() ?? ''; $actionName = ''; if (function_exists('request')) { $req = request(); if ($req) { $path = trim($req->path(), '/'); $parts = explode('/', $path); $actionName = $parts[array_key_last($parts)] ?? ''; } } $path = str_replace('.', '/', $controllerPath); $names = [$path]; if ($actionName) { $names[] = $path . '/' . $actionName; } $remark = \support\think\Db::name('admin_rule') ->where('name', 'in', $names) ->value('remark'); $remarkStr = (string) ($remark ?? ''); if (!$remarkStr) { return ''; } return function_exists('trans') ? trans($remarkStr, [], $controllerPath ?: null) : $remarkStr; } } if (!function_exists('keys_to_camel_case')) { function keys_to_camel_case(array $array, array $keys = []): array { $result = []; foreach ($array as $key => $value) { $camelCaseKey = ($keys && in_array($key, $keys)) ? parse_name($key, 1, '_') : $key; if (is_array($value)) { $result[$camelCaseKey] = keys_to_camel_case($value); } else { $result[$camelCaseKey] = $value; } } return $result; } } if (!function_exists('get_upload_config')) { function get_upload_config($request = null): array { event_trigger('uploadConfigInit', null); $uploadConfig = config('upload', []); $uploadConfig['max_size'] = \ba\Filesystem::fileUnitToByte($uploadConfig['max_size'] ?? '10M'); $request = $request ?? (function_exists('request') ? request() : null); $upload = $request && isset($request->upload) ? $request->upload : null; if (!$upload) { $uploadConfig['mode'] = 'local'; return $uploadConfig; } unset($upload['cdn']); return array_merge($upload, $uploadConfig); } } if (!function_exists('filter')) { function filter(string $string): string { $string = trim($string); $string = strip_tags($string); return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8'); } } if (!function_exists('clean_xss')) { function clean_xss(string $string): string { if (class_exists(\voku\helper\AntiXSS::class)) { $antiXss = new \voku\helper\AntiXSS(); $antiXss->removeEvilAttributes(['style']); $antiXss->setReplacement('cleanXss'); return $antiXss->xss_clean($string); } return strip_tags($string); } } if (!function_exists('htmlspecialchars_decode_improve')) { function htmlspecialchars_decode_improve(string $string, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401): string { return htmlspecialchars_decode($string, $flags); } } if (!function_exists('get_ba_client')) { /** * 获取请求 BuildAdmin 开源社区的 Guzzle Client(用于云端 CRUD 历史等) */ function get_ba_client(): \GuzzleHttp\Client { return new \GuzzleHttp\Client([ 'base_uri' => config('buildadmin.api_url', 'https://api.buildadmin.com'), 'timeout' => 30, 'connect_timeout' => 30, 'verify' => false, 'http_errors' => false, 'headers' => [ 'X-REQUESTED-WITH' => 'XMLHttpRequest', 'User-Agent' => 'BuildAdminClient', ] ]); } } if (!function_exists('str_attr_to_array')) { function str_attr_to_array(string $attr): array { if (!$attr) return []; $attr = explode("\n", trim(str_replace("\r\n", "\n", $attr))); $attrTemp = []; foreach ($attr as $item) { $item = explode('=', $item); if (isset($item[0]) && isset($item[1])) { $attrVal = $item[1]; if ($item[1] === 'false' || $item[1] === 'true') { $attrVal = !($item[1] === 'false'); } elseif (is_numeric($item[1])) { $attrVal = (float)$item[1]; } if (strpos($item[0], '.') !== false) { $attrKey = explode('.', $item[0]); if (isset($attrKey[0]) && isset($attrKey[1])) { $attrTemp[$attrKey[0]][$attrKey[1]] = $attrVal; continue; } } $attrTemp[$item[0]] = $attrVal; } } return $attrTemp; } } if (!function_exists('hsv2rgb')) { function hsv2rgb($h, $s, $v): array { $r = $g = $b = 0; $i = floor($h * 6); $f = $h * 6 - $i; $p = $v * (1 - $s); $q = $v * (1 - $f * $s); $t = $v * (1 - (1 - $f) * $s); switch ($i % 6) { case 0: $r = $v; $g = $t; $b = $p; break; case 1: $r = $q; $g = $v; $b = $p; break; case 2: $r = $p; $g = $v; $b = $t; break; case 3: $r = $p; $g = $q; $b = $v; break; case 4: $r = $t; $g = $p; $b = $v; break; case 5: $r = $v; $g = $p; $b = $q; break; } return [floor($r * 255), floor($g * 255), floor($b * 255)]; } } if (!function_exists('build_suffix_svg')) { function build_suffix_svg(string $suffix = 'file', ?string $background = null): string { $suffix = mb_substr(strtoupper($suffix), 0, 4); $total = unpack('L', hash('adler32', $suffix, true))[1]; $hue = $total % 360; [$r, $g, $b] = hsv2rgb($hue / 360, 0.3, 0.9); $background = $background ?: "rgb($r,$g,$b)"; return ' ' . $suffix . ' '; } } if (!function_exists('get_account_verification_type')) { /** * 获取可用的账户验证方式 * @return string[] email=电子邮件,mobile=手机短信验证 */ function get_account_verification_type(): array { $types = []; $sysMailConfig = get_sys_config('', 'mail'); $configured = true; if (is_array($sysMailConfig)) { foreach ($sysMailConfig as $item) { if (!$item) { $configured = false; break; } } } else { $configured = false; } if ($configured) { $types[] = 'email'; } if (class_exists(\app\admin\library\module\Server::class)) { $sms = \app\admin\library\module\Server::getIni(\ba\Filesystem::fsFit(root_path() . 'modules/sms/')); if ($sms && ($sms['state'] ?? 0) == 1) { $types[] = 'mobile'; } } return $types; } } if (!function_exists('get_area')) { function get_area($request = null): array { $request = $request ?? (function_exists('request') ? request() : null); $province = $request ? $request->get('province', '') : ''; $city = $request ? $request->get('city', '') : ''; $where = ['pid' => 0, 'level' => 1]; if ($province !== '') { $where['pid'] = $province; $where['level'] = 2; if ($city !== '') { $where['pid'] = $city; $where['level'] = 3; } } return \support\think\Db::name('area') ->where($where) ->field('id as value,name as label') ->select() ->toArray(); } }