1432 lines
40 KiB
PHP
1432 lines
40 KiB
PHP
<?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;
|
||
}
|