webman_duanju/app/functions.php

1432 lines
40 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// 应用公共文件
use support\Log;
use support\think\Cache;
use Webman\RedisQueue\Client;
if (!function_exists('__')) {
/**
* 语言翻译
* @param string $name 被翻译字符
* @param array $vars 替换字符数组
* @param string $lang 翻译语言
* @return mixed
*/
function __(string $name, array $vars = [], string $lang = ''): mixed
{
if (is_numeric($name) || !$name) {
return $name;
}
return Lang::get($name, $vars, $lang);
}
}
if (!function_exists('filter')) {
/**
* 输入过滤
* 富文本反XSS请使用 clean_xss也就不需要及不能再 filter 了
* @param string $string 要过滤的字符串
* @return string
*/
function filter(string $string): string
{
// 去除字符串两端空格(对防代码注入有一定作用)
$string = trim($string);
// 过滤html和php标签
$string = strip_tags($string);
// 特殊字符转实体
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8');
}
}
if (!function_exists('clean_xss')) {
/**
* 清理XSS
* 通常只用于富文本,比 filter 慢
* @param string $string
* @return string
*/
function clean_xss(string $string): string
{
$antiXss = new AntiXSS();
// 允许 style 属性style="list-style-image: url(javascript:alert(0))" 任然可被正确过滤)
$antiXss->removeEvilAttributes(['style']);
// 检查到 xss 代码之后使用 cleanXss 替换它
$antiXss->setReplacement('cleanXss');
return $antiXss->xss_clean($string);
}
}
if (!function_exists('htmlspecialchars_decode_improve')) {
/**
* html解码增强
* 被 filter函数 内的 htmlspecialchars 编码的字符串,需要用此函数才能完全解码
* @param string $string
* @param int $flags
* @return string
*/
function htmlspecialchars_decode_improve(string $string, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401): string
{
return htmlspecialchars_decode($string, $flags);
}
}
if (!function_exists('get_sys_config')) {
/**
* 获取站点的系统配置,不传递参数则获取所有配置项
* @param string $name 变量名
* @param string $group 变量分组,传递此参数来获取某个分组的所有配置项
* @param bool $concise 是否开启简洁模式,简洁模式下,获取多项配置时只返回配置的键值对
* @return mixed
* @throws Throwable
*/
function get_sys_config(string $name = '', string $group = '', bool $concise = true): mixed
{
if ($name) {
// 直接使用->value('value')不能使用到模型的类型格式化
$config = configModel::cache($name, null, configModel::$cacheTag)->where('name', $name)->find();
if ($config) $config = $config['value'];
} else {
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'];
}
} else {
$config = $temp;
}
}
return $config;
}
}
if (!function_exists('get_route_remark')) {
/**
* 获取当前路由后台菜单规则的备注信息
* @return string
*/
function get_route_remark(): string
{
$controllerName = request()->controller;
$actionName = request()->action;
$path = str_replace('.', '/', $controllerName);
$remark = Db::name('admin_rule')
->where('name', $path)
->whereOr('name', $path . '/' . $actionName)
->value('remark');
return __((string)$remark);
}
}
if (!function_exists('full_url')) {
/**
* 获取资源完整url地址若安装了云存储或 config/buildadmin.php 配置了CdnUrl则自动使用对应的CdnUrl
* @param string $relativeUrl 资源相对地址 不传入则获取域名
* @param string|bool $domain 是否携带域名 或者直接传入域名
* @param string $default 默认值
* @return string
*/
function full_url(string $relativeUrl = '', string|bool $domain = true, string $default = ''): string
{
// 存储/上传资料配置
Event::trigger('uploadConfigInit', App::getInstance());
$cdnUrl = Config::get('buildadmin.cdn_url');
if (!$cdnUrl) {
$cdnUrl = request()->upload['cdn'] ?? '//' . request()->host();
}
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::get('buildadmin.cdn_url_params');
if ($domain === $cdnUrl && $cdnUrlParams) {
$separator = str_contains($url, '?') ? '&' : '?';
$url .= $separator . $cdnUrlParams;
}
return $url;
}
}
if (!function_exists('encrypt_password')) {
/**
* 加密密码
* @deprecated 使用 hash_password 代替
*/
function encrypt_password($password, $salt = '', $encrypt = 'md5')
{
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')) {
/**
* 验证密码是否和散列值匹配
* @param string $password 密码
* @param string $hash 散列值
* @param array $extend 扩展数据
*/
function verify_password(string $password, string $hash, array $extend = []): bool
{
// 第一个表达式直接检查是否为 password_hash 函数创建的 hash 的典型格式,即:$algo$cost$salt.hash
if (str_starts_with($hash, '$') || password_get_info($hash)['algoName'] != 'unknown') {
return password_verify($password, $hash);
} else {
// 兼容旧版 md5 加密的密码
return encrypt_password($password, $extend['salt'] ?? '') === $hash;
}
}
}
if (!function_exists('str_attr_to_array')) {
/**
* 将字符串属性列表转为数组
* @param string $attr 属性一行一个无需引号比如class=input-class
* @return 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], '.')) {
$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('action_in_arr')) {
/**
* 检测一个方法是否在传递的数组内
* @param array $arr
* @return bool
*/
function action_in_arr(array $arr = []): bool
{
$arr = is_array($arr) ? $arr : explode(',', $arr);
if (!$arr) {
return false;
}
$arr = array_map('strtolower', $arr);
if (in_array(strtolower(request()->action), $arr) || in_array('*', $arr)) {
return true;
}
return false;
}
}
if (!function_exists('build_suffix_svg')) {
/**
* 构建文件后缀的svg图片
* @param string $suffix 文件后缀
* @param ?string $background 背景颜色rgb(255,255,255)
* @return string
*/
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 '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<path style="fill:#E2E5E7;" d="M128,0c-17.6,0-32,14.4-32,32v448c0,17.6,14.4,32,32,32h320c17.6,0,32-14.4,32-32V128L352,0H128z"/>
<path style="fill:#B0B7BD;" d="M384,128h96L352,0v96C352,113.6,366.4,128,384,128z"/>
<polygon style="fill:#CAD1D8;" points="480,224 384,128 480,128 "/>
<path style="fill:' . $background . ';" d="M416,416c0,8.8-7.2,16-16,16H48c-8.8,0-16-7.2-16-16V256c0-8.8,7.2-16,16-16h352c8.8,0,16,7.2,16,16 V416z"/>
<path style="fill:#CAD1D8;" d="M400,432H96v16h304c8.8,0,16-7.2,16-16v-16C416,424.8,408.8,432,400,432z"/>
<g><text><tspan x="220" y="380" font-size="124" font-family="Verdana, Helvetica, Arial, sans-serif" fill="white" text-anchor="middle">' . $suffix . '</tspan></text></g>
</svg>';
}
}
if (!function_exists('get_area')) {
/**
* 获取省份地区数据
* @throws Throwable
*/
function get_area(): array
{
$province = request()->get('province', '');
$city = 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 Db::name('area')
->where($where)
->field('id as value,name as label')
->select()
->toArray();
}
}
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('ip_check')) {
/**
* IP检查
* @throws Throwable
*/
function ip_check($ip = null): void
{
$ip = is_null($ip) ? request()->ip : $ip;
$noAccess = get_sys_config('no_access_ip');
$noAccess = !$noAccess ? [] : array_filter(explode("\n", str_replace("\r\n", "\n", $noAccess)));
if ($noAccess && IpUtils::checkIp($ip, $noAccess)) {
$response = Response::create(['msg' => 'No permission request'], 'json', 403);
throw new HttpResponseException($response);
}
}
}
if (!function_exists('set_timezone')) {
/**
* 设置时区
* @throws Throwable
*/
function set_timezone($timezone = null): void
{
// $defaultTimezone = Config::get('app.default_timezone');
// $timezone = is_null($timezone) ? get_sys_config('time_zone') : $timezone;
// if ($timezone && $defaultTimezone != $timezone) {
// Config::set([
// 'app.default_timezone' => $timezone
// ]);
// date_default_timezone_set($timezone);
// }
date_default_timezone_set('Asia/Shanghai');
}
}
if (!function_exists('env')) {
/**
* 获取环境变量值
* @access public
* @param string $name 环境变量名(支持二级 .号分割)
* @param string $default 默认值
* @return mixed
*/
function env(?string $name = null, $default = null)
{
return getenv($name, $default);
}
}
if (!function_exists('get_upload_config')) {
/**
* 获取上传配置
* @return array
*/
function get_upload_config(): array
{
// 存储/上传资料配置
Event::trigger('uploadConfigInit', App::getInstance());
$uploadConfig = Config::get('upload');
$uploadConfig['max_size'] = Filesystem::fileUnitToByte($uploadConfig['max_size']);
$upload = request()->upload;
if (!$upload) {
$uploadConfig['mode'] = 'local';
return $uploadConfig;
}
unset($upload['cdn']);
return array_merge($upload, $uploadConfig);
}
}
if (!function_exists('get_auth_token')) {
/**
* 获取鉴权 token
* @param array $names
* @return string
*/
function get_auth_token(array $names = ['ba', 'token']): string
{
$separators = [
'header' => ['', '-'], // batoken、ba-token【ba_token 不在 header 的接受列表内因为兼容性不高,改用 http_ba_token】
'param' => ['', '-', '_'], // batoken、ba-token、ba_token
'server' => ['_'], // http_ba_token
];
$tokens = [];
$request = request();
foreach ($separators as $fun => $sps) {
foreach ($sps as $sp) {
$tokens[] = $request->$fun(($fun == 'server' ? 'http_' : '') . implode($sp, $names));
}
}
$tokens = array_filter($tokens);
return array_values($tokens)[0] ?? '';
}
}
if (!function_exists('keys_to_camel_case')) {
/**
* 将数组 key 的命名方式转换为小写驼峰
* @param array $array 被转换的数组
* @param array $keys 要转换的 key默认所有
* @return array
*/
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, false) : $key;
if (is_array($value)) {
// 如果值是数组,递归转换
$result[$camelCaseKey] = keys_to_camel_case($value);
} else {
$result[$camelCaseKey] = $value;
}
}
return $result;
}
}
if (!function_exists('p')) {
/**
* 将数组 key 的命名方式转换为小写驼峰
* @param array $array 被转换的数组
* @param array $keys 要转换的 key默认所有
* @return array
*/
function p(...$p)
{
if(count($p) > 1) {
foreach ($p as $k => $v) {
print_r($v);
print_r('---');
}
}else {
print_r($p[0]);
}
die;
}
}
if (!function_exists('returnErrorData')) {
function returnErrorData($msg, $code = -1)
{
return ['code' => $code, 'message' => $msg, 'msg' => $msg, 'data' => []];
}
}
function getYesterdayFiles($dirPath)
{
// 1. 获取昨天的日期前缀(格式:两位数日期,如 08
$prefix = date('d', strtotime('-1 day'));
// 2. 定义要读取的文件夹路径
// 示例:读取 public 目录下的 files 文件夹
// 若读取其他目录,可使用 app_path()、root_path() 等助手函数
// $dirPath = app_path() . 'data/'; // app/data/ 目录
// 3. 验证文件夹是否存在
if (!is_dir($dirPath)) {
return "文件夹不存在:{$dirPath}";
}
// 4. 读取文件夹中的所有文件,并筛选以指定前缀开头的文件
$fileNames = [];
// 打开目录
$dirHandle = opendir($dirPath);
if ($dirHandle) {
// 循环读取目录中的文件
while (($fileName = readdir($dirHandle)) !== false) {
// 排除 . 和 .. 目录
if ($fileName != '.' && $fileName != '..') {
// 检查文件名是否以昨天日期前缀开头
if (strpos($fileName, $prefix) === 0) {
$fileNames[] = $fileName;
}
}
}
closedir($dirHandle); // 关闭目录句柄
}
// 5. 输出结果
return [
'prefix' => $prefix,
'dir' => $dirPath,
'files' => $fileNames,
'count' => count($fileNames)
];
}
if (!function_exists('returnSuccessData')) {
function returnSuccessData($data = [], $arr_data = [])
{
if(!empty($arr_data)) {
$return = $arr_data;
$return['code'] = 0;
$return['message'] = 'ok';
return $return;
}else {
return ['code' => 0, 'message' => 'ok', 'data' => $data];
}
}
}
if (!function_exists('maskPhoneNumber')) {
/**
* 隐藏手机号中间部分
* @param string $phone 手机号
* @param int $start 开始位置
* @param int $length 替换长度
* @return string 处理后的手机号
*/
function maskPhoneNumber(string $phone, int $start = 3, int $length = 4): string
{
if (strlen($phone) < $start + $length) {
return $phone; // 长度不足,直接返回原号码
}
return substr_replace($phone, str_repeat('*', $length), $start, $length);
}
}
if (!function_exists('sha256Hex')) {
function sha256Hex($data) {
return hash('sha256', $data);
}
}
if (!function_exists('toSerialCode')) {
function toSerialCode($id) {
$r = str_split("0123456789ABCDEFGHIJKLMNOPQRSTUV");
$binLen = count($r); // 进制长度通常为32
$s = 8; // 目标字符串长度
$e = "00000000"; // 补全字符通常为8个'0'
$buf = array_fill(0, 32, '');
$charPos = 32;
$id = (string) $id;
while (intdiv($id, $binLen) > 0) {
$ind = (int) ($id % $binLen);
$buf[--$charPos] = $r[$ind];
$id = intdiv($id, $binLen);
}
$buf[--$charPos] = $r[(int) ($id % $binLen)];
$str = implode(array_slice($buf, $charPos));
// 长度不足时补全
if (strlen($str) < $s) {
$str = substr($e, 0, $s - strlen($str)) . $str;
}
return strtoupper($str);
}
}
function toSnakeCase($string) {
// 将驼峰或帕斯卡命名法转为下划线命名
$string = preg_replace('/([a-z])([A-Z])/', '$1_$2', $string);
$string = preg_replace('/([A-Z])([A-Z][a-z])/', '$1_$2', $string);
return strtolower($string);
}
function arrayKeysToSnakeCase($array) {
$result = [];
foreach ($array as $key => $value) {
$newKey = is_string($key) ? toSnakeCase($key) : $key;
if (is_array($value)) {
$value = arrayKeysToSnakeCase($value); // 递归处理
}
$result[$newKey] = $value;
}
return $result;
}
function getNormalDate(string $time = '')
{
return $time ? date($time) : date('Y-m-d H:i:s');
}
/**
* ThinkPHP 防抖函数,基于 cache() 助手函数实现
*
* @param string $key 唯一操作标识符(例如 user_id_action
* @param int $waitMs 等待毫秒数(支持毫秒)
* @param callable $callback 要执行的操作
* @return mixed|null 等待时间内重复调用将返回 null
*/
function debounceAndRun(string $key, callable $callback,int $waitMs = 2000)
{
// cache() 默认单位是秒,我们转成浮点秒数
$ttl = $waitMs / 1000;
// 尝试设置缓存,存在时不执行
if (!cache($key)) {
cache($key, 1, $ttl); // 设置一个短暂缓存用于防抖
return $callback();
}
throw new Exception("操作过于频繁,请稍后再试");
}
function debounce(string $key,int $waitMs = 20)
{
// cache() 默认单位是秒,我们转成浮点秒数
$ttl = $waitMs;
// 尝试设置缓存,存在时不执行
if (!cache($key)) {
cache($key, 1, $ttl); // 设置一个短暂缓存用于防抖
}else{
throw new \app\exception\SysException("操作过于频繁,请稍后再试");
}
}
/**
* 类似 Java 的 StrUtil.format("{}, {}") 占位符替换函数
*
* @param string $template 模板字符串
* @param mixed ...$args 依次替换的值
* @return string
*/
function format(string $template, ...$args): string
{
foreach ($args as $value) {
if (!is_string($value)) {
$value = json_encode($value);
}
$template = preg_replace('/\{}/', (string)$value, $template, 1);
}
return $template;
}
if (!function_exists('uuid')) {
function uuid():string
{
$data = random_bytes(16);
$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
return vsprintf('%s%s%s%s%s%s%s%s', str_split(bin2hex($data), 4));
}
}
if (!function_exists('formatTo4Decimal')) {
/**
* 使用sprintf保留4位小数
* @param float $number 输入的浮点数
* @return string 格式化后的字符串
*/
function formatTo4Decimal(float $number): string
{
// 先乘以10000取整再除以10000最后格式化为4位小数
$truncated = (int)($number * 10000) / 10000;
return sprintf('%.4f', $truncated);
}
}
function hasEmpty(...$args): bool
{
foreach ($args as $arg) {
if (empty($arg)) {
return true;
}
}
return false;
}
if(!function_exists('shuffleMultiArray')) {
/**
* 打乱多维数组的顶层元素
* @param array $array 输入的多维数组
* @return array 打乱顺序后的数组
*/
function shuffleMultiArray(array $array): array {
$keys = array_keys($array);
shuffle($keys); // 随机打乱键名顺序
$shuffled = [];
foreach ($keys as $key) {
$shuffled[$key] = $array[$key];
}
return $shuffled;
}
}
if(!function_exists('convertToCamelCase')) {
/**
* 下划线转驼峰
*/
function convertToCamelCase(array | null $data): array | null {
if (!$data) {
return $data;
}
$result = [];
foreach ($data as $k => $item) {
$camelItem = [];
if(is_array($item)) {
foreach ($item as $key => $value) {
$camelKey = lcfirst(preg_replace_callback('/_([a-z])/', function ($match) {
return strtoupper($match[1]);
}, $key));
$camelItem[$camelKey] = $value;
}
$result[] = $camelItem;
}else {
$camelKey = lcfirst(preg_replace_callback('/_([a-z])/', function ($match) {
return strtoupper($match[1]);
}, $k));
$result[$camelKey] = $item;
}
}
return $result;
}
}
if(!function_exists('apiconvertToCamelCase')) {
/**
* 下划线转驼峰
*/
function apiconvertToCamelCase(array $data): array {
$result = [];
foreach ($data as $k => $item) {
$camelItem = [];
if(is_array($item)) {
foreach ($item as $key => $value) {
$camelKey = lcfirst(preg_replace_callback('/_([a-z])/', function ($match) {
return strtoupper($match[1]);
}, $key));
$camelItem[$camelKey] = $value;
}
$result[] = $camelItem;
}else {
$camelKey = lcfirst(preg_replace_callback('/_([a-z])/', function ($match) {
return strtoupper($match[1]);
}, $k));
$result[$camelKey] = $item;
}
}
$result = convertUserIdToString($result);
return $result;
}
}
if(!function_exists('page')) {
/**
* 分页计算
*/
function page($page = 1, $limit = 10) {
return ($page - 1) * $limit;
}
}
/**
* 自动加锁执行回调逻辑,结束后自动释放锁
*
* @param string $key 锁的键名
* @param int $expire 锁过期时间(秒)
* @param \Closure $callback 要执行的回调函数
* @return mixed 回调函数返回值,失败返回 false
*/
function runWithLock(string $key, int $expire, \Closure $callback)
{
$lockValue = uniqid();
$val = cache($key);
if ($val) {
return false;
}
// 加锁NX不存在时设置EX过期时间
$acquired = cache($key, $lockValue, $expire);
if (!$acquired) {
return false; // 获取锁失败
}
try {
return $callback(); // 执行传入的方法
} finally {
release($key, $lockValue);
}
}
if (!function_exists('cache')) {
/**
* 缓存管理
* @param string $name 缓存名称
* @param mixed $value 缓存值
* @param mixed $options 缓存参数
* @param string $tag 缓存标签
* @return mixed
*/
function cache(?string $name = null, $value = '', $options = null, $tag = null)
{
if (is_null($name)) {
return app('cache');
}
if ('' === $value) {
// 获取缓存
return str_starts_with($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name);
} elseif (is_null($value)) {
// 删除缓存
return Cache::delete($name);
}
// 缓存数据
if (is_array($options)) {
$expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间
} else {
$expire = $options;
}
if (is_null($tag)) {
return Cache::set($name, $value, $expire);
} else {
return Cache::tag($tag)->set($name, $value, $expire);
}
}
}
/**
* 解锁用Lua防止误删
*/
function release(string $key, string $value)
{
$lua = <<<LUA
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
LUA;
cache($lua, [$key, $value], 1);
}
if(!function_exists('extract_user_ids')) {
/**
* 从多维数组中提取所有 user_id 的值
*
* @param array $array 要处理的多维数组
* @return array 包含所有 user_id 值的数组,如果没有找到则返回空数组
*/
function extract_user_ids($array) {
$userIds = [];
// 使用递归函数遍历多维数组
$traverse = function($item) use (&$traverse, &$userIds) {
if (is_array($item)) {
// 检查当前数组是否包含 user_id 键
if (array_key_exists('user_id', $item)) {
$userIds[] = $item['user_id'];
}
// 递归遍历数组的每个元素
foreach ($item as $value) {
$traverse($value);
}
}
};
$traverse($array);
return $userIds;
}
}
if(!function_exists('extract_target_user_ids')) {
/**
* 从多维数组中提取所有 user_id 的值
*
* @param array $array 要处理的多维数组
* @return array 包含所有 user_id 值的数组,如果没有找到则返回空数组
*/
function extract_target_user_ids($array) {
$userIds = [];
// 使用递归函数遍历多维数组
$traverse = function($item) use (&$traverse, &$userIds) {
if (is_array($item)) {
// 检查当前数组是否包含 user_id 键
if (array_key_exists('target_user_id', $item)) {
$userIds[] = $item['target_user_id'];
}
// 递归遍历数组的每个元素
foreach ($item as $value) {
$traverse($value);
}
}
};
$traverse($array);
return $userIds;
}
}
function pushQueue($class, $data=[], $seconds=0)
{
$queue = class_basename($class);
$data[] = [
'queueId' => uuid(),
];
Log::info("消息队列发送消息,对列名: $queue, 携带数据: ".json_encode($data).', 延时时间: '.$seconds);
// 投递延迟消息消息会在60秒后处理
Client::send($queue, $data, $seconds);
}
if(!function_exists('daysBetween')) {
/**
* 计算两个日期之间的天数差(纯原生函数实现)
* @param string $startDate 开始日期格式YYYY-MM-DD
* @param string $endDate 结束日期格式YYYY-MM-DD默认当前日期
* @return int 天数差(绝对值)
*/
function daysBetween(string $startDate, string $endDate = null): int {
// 解析开始日期
$startTimestamp = strtotime($startDate);
if ($startTimestamp === false) {
throw new InvalidArgumentException("无效的开始日期格式: $startDate");
}
// 处理结束日期(默认为当前日期)
if ($endDate === null) {
$endTimestamp = time();
} else {
$endTimestamp = strtotime($endDate);
if ($endTimestamp === false) {
throw new InvalidArgumentException("无效的结束日期格式: $endDate");
}
}
// 计算天数差(忽略时区影响,仅计算日期差)
$startDay = strtotime(date('Y-m-d', $startTimestamp));
$endDay = strtotime(date('Y-m-d', $endTimestamp));
$daysDiff = ($endDay - $startDay) / (24 * 60 * 60); // 86400秒/天
return (int)$daysDiff;
}
}
if(!function_exists('buildFlowDays')) {
/**
* 生成从开始日期起的连续日期列表
* @param string $beginDay 开始日期格式YYYY-MM-DD
* @param int $activeDays 连续天数
* @return array 日期字符串列表格式YYYY-MM-DD
*/
function buildFlowDays(string $beginDay, int $activeDays)
{
$flowDays = [];
$timestamp = strtotime($beginDay); // 转换为时间戳
for ($i = 0; $i < $activeDays; $i++) {
// 计算偏移后的时间戳每天86400秒
$currentTimestamp = $timestamp + ($i * 86400);
// 格式化为YYYY-MM-DD并添加到列表
$flowDays[] = date('Y-m-d', $currentTimestamp);
}
return $flowDays;
}
}
if(!function_exists('buildFlowDaysTwo')) {
/**
* 生成从开始日期到结束日期的连续日期列表(纯原生函数实现)
* @param string $beginDay 开始日期格式YYYY-MM-DD
* @param string $endDay 结束日期格式YYYY-MM-DD
* @return array 日期字符串列表格式YYYY-MM-DD
*/
function buildFlowDaysTwo(string $beginDay, string $endDay): array
{
$flowDays = [];
$currentTimestamp = strtotime($beginDay);
$endTimestamp = strtotime($endDay);
while (true) {
// 将当前日期添加到列表(每次循环开始时添加)
$flowDays[] = date('Y-m-d', $currentTimestamp);
// 判断是否达到结束日期
if ($currentTimestamp === $endTimestamp) {
break;
}
// 未达到结束日期,增加一天
$currentTimestamp += 86400; // 86400秒 = 1天
}
return $flowDays;
}
}
if(!function_exists('todayAfterSecond')) {
/**
* 获取当日剩余秒数
* @return int 剩余秒数
*/
function todayAfterSecond()
{
// 获取当前时间戳
$now = time();
// 获取当天结束时间23:59:59的时间戳
$endOfDay = strtotime('tomorrow -1 second');
// 计算剩余秒数
$diffSeconds = $endOfDay - $now;
return $diffSeconds;
}
}
if(!function_exists('generateRedisKey')) {
function generateRedisKey($key, $id)
{
return 'sys:limit:' . $key . ':' . $id;
}
}
if(!function_exists('bankCard')) {
function bankCard($bankCardNo)
{
if (empty($bankCardNo)) {
return $bankCardNo;
}
$bankCardNo = trim($bankCardNo);
$length = strlen($bankCardNo);
if ($length < 9) {
return $bankCardNo;
}
$midLength = $length - 8; // 中间需要脱敏的长度
$buf = '';
// 保留前4位
$buf .= substr($bankCardNo, 0, 4);
// 中间部分用*替换每4位加一个空格
for ($i = 0; $i < $midLength; $i++) {
if ($i % 4 === 0) {
$buf .= ' ';
}
$buf .= '*';
}
// 保留后4位前面加一个空格
$buf .= ' ' . substr($bankCardNo, -4);
return $buf;
}
}
if(!function_exists('email')) {
function email($email)
{
if (empty($email)) {
return '';
}
$index = strpos($email, '@');
if ($index <= 1) {
return $email;
}
// 保留第一个字符,中间部分用*替换,保留@及后面的域名
$prefix = substr($email, 0, 1);
$suffix = substr($email, $index);
$maskLength = $index - 1;
$maskedPart = str_repeat('*', $maskLength);
return $prefix . $maskedPart . $suffix;
}
}
if(!function_exists('idCardNum')) {
/**
* 对身份证号进行脱敏处理
* @param string $idCardNum 身份证号码
* @param int $front 保留前几位
* @param int $end 保留后几位
* @return string 脱敏后的身份证号
*/
function idCardNum($idCardNum, $front, $end)
{
// 身份证不能为空
if (empty($idCardNum)) {
return '';
}
// 需要截取的长度不能大于身份证号长度
if (($front + $end) > strlen($idCardNum)) {
return '';
}
// 需要截取的位数不能小于0
if ($front < 0 || $end < 0) {
return '';
}
// 保留前$front位和后$end位中间用*替换
$maskLength = strlen($idCardNum) - $front - $end;
$maskedPart = str_repeat('*', $maskLength);
return substr($idCardNum, 0, $front) . $maskedPart . substr($idCardNum, -$end);
}
}
if(!function_exists('http_post')) {
function http_post($url, $data, $headers = [], $timeout = 30, $contentType = 'application/json')
{
// 初始化cURL
$ch = curl_init();
// 处理请求数据
if ($contentType === 'application/json') {
$data = json_encode($data);
} elseif (is_array($data)) {
$data = http_build_query($data);
}
// 设置请求头
$defaultHeaders = [
"Content-Type: $contentType",
"Content-Length: " . strlen($data)
];
$headers = array_merge($defaultHeaders, $headers);
// 设置cURL选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
// 执行请求
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
// 关闭cURL
curl_close($ch);
// 处理错误
if ($response === false) {
return $error;
}
return $response;
}
}
function shiro_simple_hash_hex_salt(string $algorithm, string $source, ?string $salt = null, int $iterations = 1): string
{
if ($iterations < 1) {
$iterations = 1;
}
$salt = $salt ?? ''; // 将 null 转为空字符串
$hexSalt = bin2hex($salt);
// 转为字节
$sourceBytes = is_string($source) ? $source : strval($source);
$saltBytes = $hexSalt !== '' ? hex2bin($hexSalt) : '';
if ($saltBytes === false && $hexSalt !== '') {
throw new InvalidArgumentException("Invalid hex salt: $hexSalt");
}
// 初始 hash含 salt
$digest = hash_init($algorithm);
if ($saltBytes !== '') {
hash_update($digest, $saltBytes);
}
hash_update($digest, $sourceBytes);
$result = hash_final($digest, true);
// 后续迭代(不加盐)
for ($i = 1; $i < $iterations; $i++) {
$result = hash($algorithm, $result, true); // binary
}
return bin2hex($result);
}
if(!function_exists('get_master_connect_name')) {
function get_master_connect_name()
{
return config('think-orm.z_library');
}
}
if(!function_exists('get_slave_connect_name')) {
function get_slave_connect_name()
{
return config('think-orm.search_library');
}
}
if(!function_exists('get_file_info')) {
function get_file_info($file)
{
return file_get_contents($file);
}
}
function buildPageInfo(array $info, bool $isRecord=false)
{
$size = count($info);
return [
'totalCount' => $size,
'pageSize' => $size,
'totalPage' => 1,
'currPage' => 1,
$isRecord ? 'records' : 'list' => $info
];
}
/**
* 将日期补全为当天的开始时间00:00:00
* @param string $date 输入日期(支持格式:'2023-10-05'、'2023/10/05'、'2023-10-05 12:30:45' 等)
* @return string 补全后的时间字符串(格式:'Y-m-d 00:00:00'
* @throws \Exception 若日期格式无效则抛出异常
*/
function completeStartTime($date) {
// 将输入日期转换为时间戳(支持多种格式)
$timestamp = strtotime($date);
if ($timestamp === false) {
throw new \Exception("无效的日期格式:{$date},请使用类似 'YYYY-MM-DD' 的格式");
}
// 格式化时间戳为 "YYYY-MM-DD 00:00:00"
return date('Y-m-d 00:00:00', $timestamp);
}
/**
* 模糊删除 Redis 中匹配指定模式的所有 key使用 SCAN
*
* @param string $pattern 匹配模式,如 "user_*"
* @param int $count 每次扫描数量,默认 100
* @return int 删除的 key 数量
*/
function deleteRedisKeysByPattern(string $pattern, int $count = 100): int
{
$redis = Cache::store('redis')->handler();
$iterator = null;
$deleted = 0;
do {
$keys = $redis->scan($iterator, $pattern, $count);
if (!empty($keys)) {
$redis->del(...$keys);
$deleted += count($keys);
}
} while ($iterator > 0);
return $deleted;
}
/**
* 将多维数组中的user_id或userId键的值转换为字符串类型
* @param array $data 待处理的多维数组
* @return array 处理后的数组
*/
function convertUserIdToString(array $data): array {
$result = [];
foreach ($data as $key => $value) {
// 处理键为user_id或userId的情况
$id_list = [
'user_id',
'userId',
'id',
'roleId',
'courseDetailsId',
'courseId',
'ordersId',
];
if (in_array($key, $id_list)) {
$result[$key] = (string)$value;
continue;
}
// 递归处理子数组
if (is_array($value)) {
$result[$key] = convertUserIdToString($value);
} else {
// 其他类型的值保持不变
$result[$key] = $value;
}
}
return $result;
}