$data, 'msg' => get_lang($msg), 'code' => $code], 'json', $http_code); } /** * 接口操作失败,返回信息 * @param $msg * @param array|null $data * @param int $code * @param int $http_code * @return Response */ function fail($msg = 'FAIL', ?array $data = [], int $code = 0, int $http_code = 200): Response { if (is_array($msg)) { $data = $msg; $msg = 'FAIL'; } return Response::create(['data' => $data, 'msg' => get_lang($msg), 'code' => $code], 'json', $http_code); } /** * 自动侦测语言并转化 * @param string $str * @return lang() */ function get_lang($str) { return Lang::get($str); } /** * 把返回的数据集转换成Tree * @param $list 要转换的数据集 * @param string $pk * @param string $pid * @param string $child * @param int $root * @return array */ function list_to_tree($list, $pk = 'id', $pid = 'pid', $child = 'child', $root = 0) { // 创建Tree $tree = array(); if (is_array($list)) { // 创建基于主键的数组引用 $refer = array(); foreach ($list as $key => $data) { $refer[$data[$pk]] =& $list[$key]; } foreach ($list as $key => $data) { // 判断是否存在parent $parent_id = $data[$pid]; if ($root == $parent_id) { $tree[] =& $list[$key]; } else { if (isset($refer[$parent_id])) { $parent =& $refer[$parent_id]; $parent[$child][] =& $list[$key]; } } } } return $tree; } /** * 生成加密密码 * @param $password * @param $salt 手动提供散列密码的盐值(salt)。这将避免自动生成盐值(salt)。,默认不填写将自动生成 * @return string */ function create_password($password, $salt = '') { return password_hash($password, PASSWORD_DEFAULT); } /** * 校验比对密码和加密密码是否一致 * @param $password * @param $hash * @return bool */ function check_password($password, $hash) { if (!password_verify($password, $hash)) return false; return true; } /** * 获取键对应的值 * @param array $array 源数组 * @param array $keys 要提取的键数组 * @param string $index 二维组中指定提取的字段(唯一) * @return array */ function array_keys_search($array, $keys, $index = '', $is_sort = true) { if (empty($array)) return $array; if (empty($keys)) return []; if (!empty($index) && count($array) != count($array, COUNT_RECURSIVE)) $array = array_column($array, null, $index); $list = array(); foreach ($keys as $key) { if (isset($array[$key])) { if ($is_sort) { $list[] = $array[$key]; } else { $list[$key] = $array[$key]; } } } return $list; } /** * @notes 删除目标目录 * @param $path * @param $delDir * @return bool|void */ function del_target_dir($path, $delDir) { //没找到,不处理 if (!file_exists($path)) { return false; } //打开目录句柄 $handle = opendir($path); if ($handle) { while (false !== ($item = readdir($handle))) { if ($item != "." && $item != "..") { if (is_dir("$path/$item")) { del_target_dir("$path/$item", $delDir); } else { unlink("$path/$item"); } } } closedir($handle); if ($delDir) { return rmdir($path); } } else { if (file_exists($path)) { return unlink($path); } return false; } } /** * 获取一些公共的系统参数 * @param string|null $key * @return array|mixed */ function system_name(?string $key = '') { $params = [ 'admin_token_name' => env('system.admin_token_name', 'token'),///todo !!! 注意 header参数 不能包含_ , 会自动转成 - 'api_token_name' => env('system.api_token_name', 'token'), 'channel_name' => env('system.channel_name', 'channel'), ]; if (!empty($key)) { return $params[$key]; } else { return $params; } } /** * 获取日期(默认不传参 获取当前日期) * @param int|null $time * @return string */ function get_date_by_time(?int $time = null) { return date('Y-m-d h:i:s', $time); } function get_start_and_end_time_by_day($day = '') { $date = $day ?: date('Y-m-d'); $day_start_time = strtotime($date); //当天结束之间 $day_end_time = $day_start_time + 86400; return [$day_start_time, $day_end_time]; } /** * 获取本周的 开始、结束时间 * @param data 日期 */ function get_weekinfo_by_time($date) { $idx = strftime("%u", strtotime($date)); $mon_idx = $idx - 1; $sun_idx = $idx - 7; return array( 'week_start_day' => strftime('%Y-%m-%d', strtotime($date) - $mon_idx * 86400), 'week_end_day' => strftime('%Y-%m-%d', strtotime($date) - $sun_idx * 86400), ); } /** * 路径转链接 * @param $path * @return string */ function path_to_url($path) { return trim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '.'); } /** * 链接转化路径 * @param $url * @return string */ function url_to_path($url) { if (str_contains($url, 'http://') || str_contains($url, 'https://')) return $url;//网络图片不必 return public_path() . trim(str_replace('/', DIRECTORY_SEPARATOR, $url)); } /** * 获取本地文件的对外网络路径 * @param string $path * @return string */ function get_file_url(string $path) { if (!$path) return ''; if (!str_contains($path, 'http://') && !str_contains($path, 'https://')) { return request()->domain() . '/' . path_to_url($path); } else { return path_to_url($path); } } /** * 获取图片文件的对外网络路径(get_file_url的别名) * @param string $path * @return string */ function get_image_url(string $path) { return get_file_url($path); } /** * 新增队列工作 * @param $job * @param $data * @param $delay * @param $queue * @return bool */ function create_job($job, $data = '', $delay = 0, $queue = null) { if ($delay > 0) { $is_success = Queue::later($delay, $job, $data, $queue); } else { $is_success = Queue::push($job, $data, $queue); } if ($is_success !== false) { return true; } else { return false; } } /** * 获取插件对应资源文件(插件安装后获取) * @param $addon //插件名称 * @param $file_name //文件名称(包含resource文件路径) */ function addon_resource($addon, $file_name) { return "addon/" . $addon . "/" . $file_name; } /** * 判断 文件/目录 是否可写(取代系统自带的 is_writeable 函数) * * @param string $file 文件/目录 * @return boolean */ function is_write($file) { if (is_dir($file)) { $dir = $file; if ($fp = @fopen("$dir/test.txt", 'wb')) { @fclose($fp); @unlink("$dir/test.txt"); $writeable = true; } else { $writeable = false; } } else { if ($fp = @fopen($file, 'ab+')) { @fclose($fp); $writeable = true; } else { $writeable = false; } } return $writeable; } /** * 主要用于金额格式化(用于显示) * @param $number * @return int|mixed|string */ function format_money($number) { if ($number == intval($number)) { return intval($number); } elseif ($number == sprintf('%.2f', $number)) { return sprintf('%.2f', $number); } return $number; } /** * 金额浮点数格式化 * @param $number * @param $precision * @return float|int */ function format_float_money($number, $precision = 2) { if ($precision > 0) { return sprintf('%.' . $precision . 'f', floor($number * (10 ** $precision)) / (10 ** $precision)); } else { return sprintf('%.' . $precision . 'f', floor($number)); } } /** * 金额保留小数点后*位 * @param $number * @return float */ function format_round_money($number) { return round($number, 2); } /** * 基础属性过滤(特殊字符..) * @param $string * @return void */ function filter($string) { return $string; } /** * 生成编号 * @param string $prefix * @param string $tag 业务标识 例如member_id ... * @return string * @throws Exception */ function create_no(string $prefix = '', string $tag = '') { $data_center_id = 1; $machine_id = 2; $snowflake = new Snowflake($data_center_id, $machine_id); $id = $snowflake->generateId(); return $prefix . date('Ymd') . $tag . $id; } /** * 多级目录不存在则创建 * @param $dir * @param $mode * @return bool */ function mkdirs($dir, $mode = 0777) { if (str_contains($dir, '.')) $dir = dirname($dir); if (is_dir($dir) || @mkdir($dir, $mode)) return true; if (!mkdirs(dirname($dir), $mode)) return false; return @mkdir($dir, $mode); } /** * 创建文件夹 * @param $dir * @param $mode * @return true */ function mkdirs_or_notexist($dir, $mode = 0777) { if (!is_dir($dir) && !mkdir($dir, $mode, true) && !is_dir($dir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir)); } return true; } /** * 删除缓存文件使用 * @param $dir */ function rmdirs($dir) { $dh = opendir($dir); while ($file = readdir($dh)) { if ($file != "." && $file != "..") { $fullpath = $dir . "/" . $file; if (is_dir($fullpath)) { rmdirs($fullpath); } else { unlink($fullpath); } } } closedir($dh); } /** * 获取唯一随机字符串 * @param int $len * @return string */ function unique_random($len = 10) { $str = 'qwertyuiopasdfghjklzxcvbnmasdfgh'; str_shuffle($str); return substr(str_shuffle($str), 0, $len); } /** * 校验事件结果 * @param $result * @return bool */ function check_event_result($result) { if (empty($result) || is_array($result)) { return true; } foreach ($result as $v) { if (!$v) return false; } return true; } /** * 二维数组合并 * @param array $array1 * @param array $array2 * @return array */ function array_merge2(array $array1, array $array2) { foreach ($array2 as $array2_k => $array2_v) { if (array_key_exists($array2_k, $array1)) { if (is_array($array2_v)) { foreach ($array2_v as $array2_kk => $array2_vv) { if (array_key_exists($array2_kk, $array1[$array2_k])) { if (is_array($array2_vv)) { $array1[$array2_k][$array2_kk] = array_merge($array1[$array2_k][$array2_kk], $array2_vv); } } else { $array1[$array2_k][$array2_kk] = $array2_vv; } } } else { $array1[$array2_k] = $array2_v; } } else { $array1[$array2_k] = $array2_v; } } return $array1; } /** * 通过目录获取文件结构1 * @param $dir * @return array */ function get_files_by_dir($dir) { $dh = @opendir($dir); //打开目录,返回一个目录流 $return = array(); while ($file = @readdir($dh)) { //循环读取目录下的文件 if ($file != '.' and $file != '..') { $path = $dir . DIRECTORY_SEPARATOR . $file; //设置目录,用于含有子目录的情况 if (is_dir($path)) { $return[] = $file; } } } @closedir($dh); //关闭目录流 return $return; //返回文件 } /** * 文件夹文件拷贝 * @param string $src 来源文件夹 * @param string $dst 目的地文件夹 * @param array $files 文件夹集合 * @param array $exclude_dirs 排除无需拷贝的文件夹 * @param array $exclude_files 排除无需拷贝的文件 * @return bool */ function dir_copy(string $src = '', string $dst = '', &$files = [], $exclude_dirs = [], $exclude_files = []) { if (empty($src) || empty($dst)) { return false; } if (!file_exists($src)) { return false; } $dir = opendir($src); dir_mkdir($dst); while (false !== ($file = readdir($dir))) { if (($file != '.') && ($file != '..')) { if (is_dir($src . '/' . $file)) { // 排除目录 if (count($exclude_dirs) && in_array($file, $exclude_dirs)) continue; dir_copy($src . '/' . $file, $dst . '/' . $file, $files); } else { // 排除文件 if (count($exclude_files) && in_array($file, $exclude_files)) continue; copy($src . '/' . $file, $dst . '/' . $file); $files[] = $dst . '/' . $file; } } } closedir($dir); return true; } /** * 删除文件 * @param string $dst * @param array $dirs * @return bool */ function dir_remove(string $dst = '', array $dirs = []) { if (empty($dirs) || empty($dst)) { return false; } foreach ($dirs as $v) { @unlink($dst . $v); } return true; } /** * 创建文件夹 * * @param string $path 文件夹路径 * @param int $mode 访问权限 * @param bool $recursive 是否递归创建 * @return bool */ function dir_mkdir($path = '', $mode = 0777, $recursive = true) { clearstatcache(); if (!is_dir($path)) { if (mkdir($path, $mode, $recursive)) { return chmod($path, $mode); } else { throw new \core\exception\CommonException("目录{$path}创建失败请检查是否有足够的权限"); } } return true; } /** * 分割sql语句 * @param string $content sql内容 * @param bool $string 如果为真,则只返回一条sql语句,默认以数组形式返回 * @param array $replace 替换前缀,如:['my_' => 'me_'],表示将表前缀my_替换成me_ * @return array|string 除去注释之后的sql语句数组或一条语句 */ function parse_sql($content = '', $string = false, $replace = []) { // 纯sql内容 $pure_sql = []; // 被替换的前缀 $from = ''; // 要替换的前缀 $to = ''; // 替换表前缀 if (!empty($replace)) { $to = current($replace); $from = current(array_flip($replace)); } if ($content != '') { // 多行注释标记 $comment = false; // 按行分割,兼容多个平台 $content = str_replace(["\r\n", "\r"], "\n", $content); $content = explode("\n", trim($content)); // 循环处理每一行 foreach ($content as $line) { // 跳过空行 if ($line == '') { continue; } // 跳过以#或者--开头的单行注释 if (preg_match("/^(#|--)/", $line)) { continue; } // 跳过以/**/包裹起来的单行注释 if (preg_match("/^\/\*(.*?)\*\//", $line)) { continue; } // 多行注释开始 if (str_starts_with($line, '/*')) { $comment = true; continue; } // 多行注释结束 if (str_ends_with($line, '*/')) { $comment = false; continue; } // 多行注释没有结束,继续跳过 if ($comment) { continue; } // 替换表前缀 if ($from != '') { $line = str_replace('`' . $from, '`' . $to, $line); } // sql语句 $pure_sql[] = $line; } // 只返回一条语句 if ($string) { return implode("", $pure_sql); } // 以数组形式返回sql语句 $pure_sql = implode("\n", $pure_sql); $pure_sql = explode(";\n", $pure_sql); } return $pure_sql; } /** * 递归查询目录下所有文件 * @param $path * @param $data * @param $search * @return void */ function search_dir($path, &$data, $search = '') { if (is_dir($path)) { $path .= DIRECTORY_SEPARATOR; $fp = dir($path); while ($file = $fp->read()) { if ($file != '.' && $file != '..') { search_dir($path . $file, $data, $search); } } $fp->close(); } if (is_file($path)) { if ($search) $path = str_replace($search, '', $path); $data[] = $path; } } function remove_empty_dir($dirs) { } /** * 获取文件地图 * @param $path * @param array $arr * @return array */ function getFileMap($path, $arr = []) { if (is_dir($path)) { $dir = scandir($path); foreach ($dir as $file_path) { if ($file_path != '.' && $file_path != '..') { $temp_path = $path . '/' . $file_path; if (is_dir($temp_path)) { $arr[$temp_path] = $file_path; $arr = getFileMap($temp_path, $arr); } else { $arr[$temp_path] = $file_path; } } } return $arr; } } /** * 如果不存在则写入缓存 * @param string|null $name * @param $value * @param $tag * @param $options * @return mixed|string */ function cache_remember(string $name = null, $value = '', $tag = null, $options = null) { if (!empty($hit = Cache::get($name)))//可以用has return $hit; if ($value instanceof Closure) { // 获取缓存数据 $value = Container::getInstance()->invokeFunction($value); } if (is_null($tag)) { Cache::set($name, $value, $options['expire'] ?? null); } else { Cache::tag($tag)->set($name, $value, $options['expire'] ?? null); } return $value; } /** * 项目目录 * @return string */ function project_path() { return dirname(root_path()) . DIRECTORY_SEPARATOR; } /** * 图片转base64 * @param string $path * @param $is_delete 转换后是否删除原图 * @return string */ function image_to_base64(string $path, $is_delete = false) { if (!file_exists($path)) return 'image not exist'; $mime = getimagesize($path)['mime']; $image_data = file_get_contents($path); // 将图片转换为 base64 $base64_data = base64_encode($image_data); if ($is_delete) @unlink($path); return "data:$mime;base64,$base64_data"; } /** * 获取缩略图 * @param $image * @param string $thumb_type * @param bool $is_throw_exception * @return mixed * @throws Exception */ function get_thumb_images($image, $thumb_type = 'all', bool $is_throw_exception = false) { return (new CoreImageService())->thumb($image, $thumb_type, $is_throw_exception); } /** * 版本号转整数 例如1.0.0=001.000.000=001000000=1000000 * @param $version * @return int */ function version_to_int($version) { $version_array = explode(".", $version); $v1 = sprintf('%03s', (int)$version_array[0] ?? 0); $v2 = sprintf('%03s', (int)$version_array[1] ?? 0); $v3 = sprintf('%03s', (int)$version_array[2] ?? 0); return (int)"{$v1}{$v2}{$v3}"; } /** * 整数版本号转字符串例如 1000000=001000000=001.000.000=1.0.0 * @param int $ver * @return string */ function version_to_string($ver) { if ($ver > 999) { if ($ver > 999999) { $ver .= ""; $v3 = (int)substr($ver, -3); $v2 = (int)substr($ver, -6, 3); $v1 = (int)substr($ver, 0, -6); } else { $ver .= ""; $v3 = (int)substr($ver, -3); $v2 = (int)substr($ver, 0, -3); $v1 = 0; } } else { $v3 = $ver; $v2 = 0; $v1 = 0; } return "{$v1}.{$v2}.{$v3}"; } /** * 检测文件是否是本地图片 * @param string $file_path * @return void */ function check_file_is_remote(string $file_path) { return str_contains($file_path, 'https://') || str_contains($file_path, 'http://') || str_contains($file_path, '.com'); } /** * 文件拷贝 * @param string $source_file * @param string $to_file * @return void */ function file_copy(string $source_file, string $to_file) { if (!file_exists($source_file)) return false; // 检查目标文件是否存在 if (!file_exists($to_file)) { // 创建目录 $directory = dirname($to_file); if (!file_exists($directory)) { mkdir($directory, 0777, true); } } if (copy($source_file, $to_file)) { return true; } else { return false; } } /** * 创建并生成二维码 * @param $url * @param $dir * @param $file_path * @param $channel * @param $size * @return string */ function qrcode($url, $page, $data, $dir = '', $channel = 'h5', $style = ['is_transparent' => true], $outfile = true) { if ($outfile) { $dir = $dir ?: 'upload' . '/' . 'qrcode/';//二维码默认存储位置 if (!is_dir($dir) && !mkdir($dir, 0777, true) && !is_dir($dir)) { throw new \RuntimeException(sprintf('Directory "%s" was not created', $dir)); } $file_path = md5($url . $page . serialize($data) . serialize($style) . $channel); $path = $dir . '/' . $file_path . '.png'; if (file_exists($path)) { return $path; } } $result = array_values(array_filter(event('GetQrcodeOfChannel', [ 'filepath' => $path ?? '', 'url' => $url, 'page' => $page, 'data' => $data, 'channel' => $channel, 'outfile' => $outfile ]))); if (!empty($result[0])) { $path = $result[0]; } return $path; } /** * 获取海报 * @param $id * @param $type * @param array $param * @param string $channel * @param bool $is_throw_exception * @return string|void */ function poster($id, $type, array $param = [], string $channel = '', bool $is_throw_exception = true) { return (new \app\service\core\poster\CorePosterService())->get($id, $type, $param, $channel, $is_throw_exception); } /** * 是否是url链接 * @param unknown $string * @return boolean */ function is_url($string) { if (strstr($string, 'http://') === false && strstr($string, 'https://') === false) { return false; } else { return true; } } /** * 获取站点插件 * @return array */ function get_site_addons(): array { $addons = Cache::get("local_install_addons"); return is_null($addons) ? [] : $addons; } function get_wap_domain() { $wap_url = (new CoreSysConfigService())->getSceneDomain()['wap_url']; return $wap_url; } /** * $str为要进行截取的字符串,$length为截取长度(汉字算一个字,字母算半个字 * @param $str * @param int $length * @param bool $is_need_apostrophe * @return string */ function str_sub($str, $length = 10, $is_need_apostrophe = true) { return mb_substr($str, 0, $length, 'UTF-8') . ($is_need_apostrophe ? '...' : ''); } /** * 使用正则表达式匹配特殊字符 * @param $str * @return bool */ function is_special_character($str) { $pattern = '/[!@#$%^&*()\[\]{}<>\|?:;"]/'; if (preg_match($pattern, $str)) { return true; } else { return false; } } /** * 时间格式转换 * @param $time * @return string */ function get_last_time($time = null) { $text = ''; $time = $time === null || $time > time() ? time() : intval($time); $t = time() - $time; //时间差 (秒) $y = date('Y', $time) - date('Y', time());//是否跨年 switch ($t) { case 0: $text = '刚刚'; break; case $t < 60: $text = $t . '秒前'; // 一分钟内 break; case $t < 60 * 60: $text = floor($t / 60) . '分钟前'; //一小时内 break; case $t < 60 * 60 * 24: $text = floor($t / (60 * 60)) . '小时前'; // 一天内 break; case $t < 60 * 60 * 24 * 3: $text = floor($time / (60 * 60 * 24)) == 1 ? '昨天' . date('H:i', $time) : '前天' . date('H:i', $time); //昨天和前天 break; case $t < 60 * 60 * 24 * 30: $text = date('m-d H:i', $time); //一个月内 break; case $t < 60 * 60 * 24 * 365 && $y == 0: $text = date('m-d', $time); //一年内 break; default: $text = date('Y-m-d', $time); //一年以前 break; } return $text; } function get_campus_where($user_id, $field = "campus_id") { $where = []; $person = new Personnel(); $campusPersonRole = new CampusPersonRole(); if ($user_id > 1) { $person_id = $person->where(['sys_user_id' => $user_id])->value("id"); $role_info = $campusPersonRole->where(['person_id' => $person_id])->find(); if (!in_array($role_info['role_id'], [1, 7, 8])) { if ($role_info['campus_id']) { $where[] = [$field, '=', $role_info['campus_id']]; } } } return $where; } function getModifiedFields(array $oldData, array $newData): array { $modifiedFields = []; $oldValues = []; $newValues = []; foreach ($newData as $key => $newValue) { if (!array_key_exists($key, $oldData)) { continue; } if ($oldData[$key] != $newValue) { $modifiedFields[] = $key; $oldValues[$key] = $oldData[$key]; $newValues[$key] = $newValue; } } return [ 'is_save' => !empty($modifiedFields), 'modified_fields' => json_encode($modifiedFields, JSON_UNESCAPED_UNICODE), 'old_values' => json_encode($oldValues, JSON_UNESCAPED_UNICODE), 'new_values' => json_encode($newValues, JSON_UNESCAPED_UNICODE), ]; } // 获取员工编号 function getEmployeeNumber() { $personnel = new Personnel(); // 获取最新id $max_id = $personnel->max('id') + 1; $max_id = str_pad($max_id, 5, '0', STR_PAD_LEFT); return date('Ymd') . $max_id; } function return_pay_config($campus_id, $order_id) { $campus_pay = new \app\model\campus_pay\CampusPay(); $pay_config = $campus_pay->where(['campus_id' => $campus_id])->find(); /** * [ * "id" => 3 * "campus_id" => 3 * "mchid" => "1683015573" * "pay_sign_key" => "eMxxEaxmzWo0FeMasMGPZDAC2vEZtVpE" * "apiclient_key" => "upload/attachment/document/cert/202508/08/apiclient_cert.pem" * "apiclient_cert" => "upload/attachment/document/cert/202508/08/apiclient_key.pem" * "wx_pay_key" => "upload/attachment/document/cert/202508/09/pub_key.pem" * "wx_pay_key_id" => "PUB_KEY_ID_0116830155732025080800112116001001" * "created_at" => "2025-08-08 19:34:38" * "updated_at" => "2025-08-09 00:02:13" * ] */ $sysConfig = new SysConfig(); $vx_config = $sysConfig->where(['config_key' => 'weapp'])->value("value"); if (!$vx_config || !$pay_config) { throw new \Exception('当前校区支付配置不存在'); } $config = [ // 必填-商户号 'mch_id' => $pay_config['mchid'], // 必填-商户私钥 字符串或路径 'mch_secret_cert' => $pay_config['apiclient_cert'], // 必填-商户公钥证书路径 'mch_public_cert_path' => $pay_config['apiclient_key'], // 必填 'notify_url' => request()->domain() . '/api/pay/qrcodenotify/order_id/' . $order_id, // 选填-公众号 的 app_id 'mp_app_id' => $vx_config['app_id'], // 选填-小程序 的 app_id 'mini_app_id' => '', 'mch_secret_key' => $pay_config['pay_sign_key'], 'wechat_public_cert_path' => $pay_config['wx_pay_key'] ?? '', 'wechat_public_cert_id' => $pay_config['wx_pay_key_id'] ?? '', ]; return $config; } function getCurrentDomain() { $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443 ? "https://" : "http://"; $host = $_SERVER['HTTP_HOST']; // 包括域名和端口(如存在) return $scheme . $host . '/'; } function decryptWechatPayNotify($ciphertext, $nonce, $associatedData, $key) { $ciphertext = base64_decode($ciphertext); $authTag = substr($ciphertext, -16); $ciphertext = substr($ciphertext, 0, -16); return openssl_decrypt( $ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $authTag, $associatedData ); } /** * 判断是否为手机号 */ function isPhone($mobile) { return preg_match('/^1[3456789]\d{9}$/', $mobile); } /** * 体测评分 * @param int $age年龄 * @param int $gender性别( 1:男,2:女) * @param int $height身高 * @param int $weight体重 */ function calculateChildHealthScore($age, $gender, $height, $weight) { // WHO 标准数据(简化版)——单位:cm/kg // 示例使用中位数及上下限估算(实际可使用更精确的Z-score或百分位表) $reference = [ '1' => [ 3 => ['height_median' => 96.5, 'height_low' => 91.5, 'height_high' => 101.5, 'weight_median' => 14.2, 'weight_low' => 12.3, 'weight_high' => 16.5], 5 => ['height_median' => 110.0, 'height_low' => 104.5, 'height_high' => 115.5, 'weight_median' => 18.0, 'weight_low' => 15.6, 'weight_high' => 21.0], 7 => ['height_median' => 123.0, 'height_low' => 117.0, 'height_high' => 129.0, 'weight_median' => 22.5, 'weight_low' => 19.5, 'weight_high' => 26.0], 10 => ['height_median' => 138.0, 'height_low' => 131.0, 'height_high' => 145.0, 'weight_median' => 29.5, 'weight_low' => 25.5, 'weight_high' => 34.0], 12 => ['height_median' => 148.5, 'height_low' => 141.0, 'height_high' => 156.0, 'weight_median' => 36.0, 'weight_low' => 31.0, 'weight_high' => 42.0], 15 => ['height_median' => 164.0, 'height_low' => 155.0, 'height_high' => 173.0, 'weight_median' => 50.0, 'weight_low' => 43.0, 'weight_high' => 58.0], 18 => ['height_median' => 170.0, 'height_low' => 161.0, 'height_high' => 179.0, 'weight_median' => 58.0, 'weight_low' => 50.0, 'weight_high' => 67.0], ], '2' => [ 3 => ['height_median' => 95.5, 'height_low' => 90.5, 'height_high' => 100.5, 'weight_median' => 13.8, 'weight_low' => 12.0, 'weight_high' => 16.0], 5 => ['height_median' => 109.0, 'height_low' => 103.5, 'height_high' => 114.5, 'weight_median' => 17.8, 'weight_low' => 15.3, 'weight_high' => 20.5], 7 => ['height_median' => 122.0, 'height_low' => 116.0, 'height_high' => 128.0, 'weight_median' => 22.0, 'weight_low' => 19.0, 'weight_high' => 25.5], 10 => ['height_median' => 137.0, 'height_low' => 130.0, 'height_high' => 144.0, 'weight_median' => 29.0, 'weight_low' => 25.0, 'weight_high' => 33.5], 12 => ['height_median' => 150.0, 'height_low' => 142.0, 'height_high' => 158.0, 'weight_median' => 37.0, 'weight_low' => 31.5, 'weight_high' => 43.0], 15 => ['height_median' => 158.0, 'height_low' => 150.0, 'height_high' => 166.0, 'weight_median' => 48.0, 'weight_low' => 41.0, 'weight_high' => 56.0], 18 => ['height_median' => 159.0, 'height_low' => 152.0, 'height_high' => 166.0, 'weight_median' => 53.0, 'weight_low' => 46.0, 'weight_high' => 61.0], ] ]; if (!isset($reference[$gender][$age])) { return 0; // 年龄不在范围内 } $data = $reference[$gender][$age]; // 身高评分(线性插值) $height_score = 100; if ($height < $data['height_low']) { $height_score = max(0, 50 * ($height - $data['height_low']) / ($data['height_median'] - $data['height_low'])); } elseif ($height > $data['height_high']) { $height_score = max(0, 50 + 50 * ($data['height_high'] - $height) / ($data['height_high'] - $data['height_median'])); } else { $height_score = 50 + 50 * (abs($height - $data['height_median']) / ($data['height_median'] - $data['height_low'])) * ($height > $data['height_median'] ? 1 : -1); $height_score = max(0, min(100, $height_score)); } // 体重评分 $weight_score = 100; if ($weight < $data['weight_low']) { $weight_score = max(0, 50 * ($weight - $data['weight_low']) / ($data['weight_median'] - $data['weight_low'])); } elseif ($weight > $data['weight_high']) { $weight_score = max(0, 50 + 50 * ($data['weight_high'] - $weight) / ($data['weight_high'] - $data['weight_median'])); } else { $weight_score = 50 + 50 * (abs($weight - $data['weight_median']) / ($data['weight_median'] - $data['weight_low'])) * ($weight > $data['weight_median'] ? 1 : -1); $weight_score = max(0, min(100, $weight_score)); } // 综合评分 $total_score = ($height_score + $weight_score) / 2; // 四舍五入取整 return round($total_score); } function get_dict_value($key, $value) { $dict = new \app\model\dict\Dict(); $field = 'id,name,key,dictionary,memo,create_time,update_time'; $info = $dict->field($field)->where([['key', '=', $key]])->findOrEmpty()->toArray(); if ($info['dictionary'] == null) { $info['dictionary'] = []; } // 如果dictionary是字符串,尝试解析为数组 if (is_string($info['dictionary'])) { // 处理双重编码的JSON字符串 $dictionary_str = $info['dictionary']; // 先尝试直接解析 $dictionary_data = json_decode($dictionary_str, true); // 如果解析结果仍然是字符串,说明是双重编码,需要再解析一次 if (is_string($dictionary_data)) { $dictionary_data = json_decode($dictionary_data, true); } $info['dictionary'] = is_array($dictionary_data) ? $dictionary_data : []; } $map = []; if (is_array($info['dictionary'])) { foreach ($info['dictionary'] as $item) { $map[$item['value']] = $item['name']; } } return $map[$value] ?? ''; } function get_dict_name($key, $value) { $dict = new \app\model\dict\Dict(); $field = 'id,name,key,dictionary,memo,create_time,update_time'; $info = $dict->field($field)->where([['key', '=', $key]])->findOrEmpty()->toArray(); if ($info['dictionary'] == null) { $info['dictionary'] = []; } $map = []; foreach ($info['dictionary'] as $item) { $map[$item['name']] = $item['value']; } return $map[$value] ?? ''; } //$date = 年月 $campus_person_role_id 人员角色表id //$field complete_num 完成数量 expire_num 到期数量 renew_num 续费数量 resource_num 分配的资源数量 visit_num 到访数量 //$num 增加数量 默认1 function set_summary($date, $campus_person_role_id, $field, $num = 1) { $personnel_summary = new PersonnelSummary(); $personnel_summary->where([ ['task_date', '=', $date], ['campus_person_role_id', '=', $campus_person_role_id] ])->inc($field, $num)->update(); return true; } /** * 根据角色id获取角色类型 */ function get_role_type($role_id) { $role_type = [ 1 => 'market', 2 => 'sale', 3 => 'teacher' ]; $dept = \app\model\sys\SysRole::find($role_id); return $role_type[$dept->dept_id] ?? 'other'; } /** * 获取顶级部门 */ function isDateInThisWeek($date) { $timestamp = strtotime($date); // 获取本周开始和结束时间戳(周一到周日) $monday = strtotime('monday this week'); $sunday = strtotime('sunday this week 23:59:59'); return $timestamp >= $monday && $timestamp <= $sunday; } function isDateInLastWeek($date) { $timestamp = strtotime($date); // 获取上周的周一(开始)和周日(结束) $startOfLastWeek = strtotime('monday last week'); $endOfLastWeek = strtotime('sunday last week 23:59:59'); return $timestamp >= $startOfLastWeek && $timestamp <= $endOfLastWeek; } function getChineseWeekday($date) { $weekdays = ['日', '一', '二', '三', '四', '五', '六']; return '星期' . $weekdays[$date->format('w')]; } /** * 添加绩效汇总记录 * @param array $data 绩效数据 * - staff_id: 员工ID (必填) * - resource_id: 资源ID (必填) * - order_id: 订单ID (可选) * - order_status: 订单状态 (可选,默认pending) * - performance_type: 绩效类型 (必填,如sales) * - performance_value: 绩效金额 (必填) * - remarks: 备注 (可选) * @return int|string 插入的ID,失败返回0 */ function add_performance_summary(array $data) { try { $performanceService = new PerformanceService(); return $performanceService->addPerformance($data); } catch (\Exception $e) { \think\facade\Log::write('添加绩效汇总记录失败:' . $e->getMessage()); return 0; } } /** * 批量添加绩效汇总记录 * @param array $dataList 绩效数据列表 * @return int 插入的记录数 */ function add_batch_performance_summary(array $dataList) { try { $performanceService = new PerformanceService(); return $performanceService->addBatchPerformance($dataList); } catch (\Exception $e) { \think\facade\Log::write('批量添加绩效汇总记录失败:' . $e->getMessage()); return 0; } } /** * 获取员工绩效总额 * @param int $staffId 员工ID * @param string $performanceType 绩效类型 (可选) * @param string $orderStatus 订单状态 (可选) * @param string $startDate 开始日期 (可选,格式:Y-m-d) * @param string $endDate 结束日期 (可选,格式:Y-m-d) * @return float 绩效总额 */ function get_staff_performance_total(int $staffId, string $performanceType = '', string $orderStatus = '', string $startDate = '', string $endDate = '') { try { $performanceService = new PerformanceService(); return $performanceService->calculateStaffPerformanceTotal($staffId, $performanceType, $orderStatus, $startDate, $endDate); } catch (\Exception $e) { \think\facade\Log::write('获取员工绩效总额失败:' . $e->getMessage()); return 0; } } function httpGet($url) { $curl = curl_init(); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_TIMEOUT, 500); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_URL, $url); $res = curl_exec($curl); curl_close($curl); return $res; } function getAccessToken() { $appId = 'wxe48c268ac1c97ab9'; $appSecret = 'fc355f42f55e693595d31e0e644bd11c'; $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appId&secret=$appSecret"; $res = json_decode(httpGet($url)); $access_token = $res->access_token; return $access_token; } //$touser 用户的openid //$template_id 模板id //$value 发送的 消息 数组 function sendMessage($touser, $template_id, $value) { // 构建请求参数 $accessToken = getAccessToken(); $url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" . $accessToken; if ($template_id == 'K5YS7UBbD18PZvqUmnBKfX_j8tKP_AUAKa75mwUsX0A') { $data = [ 'touser' => $touser, 'template_id' => $template_id, 'data' => [ 'thing9' => ['value' => $value['thing9'], 'color' => '#173177'], //课程名称 'time8' => ['value' => $value['time8'], 'color' => '#173177'],//课程周期 例子 2023年10月11日~2023年11月30日 'number4' => ['value' => $value['number4'], 'color' => '#173177'],//课程数量 'amount5' => ['value' => $value['amount5'], 'color' => '#173177']//课程码 课程金额 ], 'miniprogram' => [ "appid" => "wxaee2df4a4b31df05", "pagepath" => $value['pagepath'] ] ]; } if ($template_id == 'FrAKIn9zs0qdxWFW1R9vnR2WDJgil2KCg-9cLFaHFWM') { $data = [ 'touser' => $touser, 'template_id' => $template_id, 'data' => [ 'thing5' => ['value' => $value['thing5'], 'color' => '#173177'], 'thing19' => ['value' => $value['thing19'], 'color' => '#173177'] ], 'miniprogram' => [ "appid" => "wxaee2df4a4b31df05", "pagepath" => $value['pagepath'] ] ]; } // 发送 POST 请求 $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); $response = curl_exec($ch); curl_close($ch); } /** * 通过 id 获取员工姓名 */ function getStaffNameById($id) { //获取全部员工的 id 和 name $staffs = Personnel::column('id,name'); $staffs = array_column($staffs, 'name', 'id'); return $staffs[$id] ?? ''; } /** * 获取学员有效的课程 */ function getValidCourseByStudentId($studentId) { $course = \app\model\student_courses\StudentCourses::where('student_id', $studentId) ->where('status', 1) ->order('id', 'desc') ->find(); return $course ?? []; } /** * 根据校区ID和部门ID获取对应角色的人员列表 * @param int $campus_id 校区ID(必填) * @param array $dept_ids 部门ID数组,例如:[1, 2, 23] 对应市场、教务、教练部门 * @return array 人员列表,包含person_id、name、phone等信息 * * 使用示例: * // 获取校区1的市场人员 * $market_staff = get_personnel_by_campus_dept(1, [1]); * * // 获取校区2的教练和教务人员 * $coaches_and_educators = get_personnel_by_campus_dept(2, [24, 2]); */ function get_personnel_by_campus_dept($campus_id, $dept_ids = []) { if (empty($campus_id) || empty($dept_ids)) { return []; } try { // 1. 根据部门ID获取对应的角色ID,使用正确的表名 $role_ids = \think\facade\Db::table('school_sys_role')->where([ ['dept_id', 'in', $dept_ids], ['status', '=', 1] ])->column('role_id'); if (empty($role_ids)) { return []; } // 2. 根据校区ID和角色ID获取人员 $personnel_list = \think\facade\Db::table('school_campus_person_role') ->alias('cpr') ->leftJoin('school_personnel p', 'cpr.person_id = p.id') ->where([ ['cpr.campus_id', '=', $campus_id], ['cpr.role_id', 'in', $role_ids], ['cpr.deleted_at', '=', 0], ['p.status', '=', 2] ]) ->field('cpr.person_id, p.name, p.phone, cpr.role_id, cpr.dept_id') ->select() ->toArray(); return $personnel_list; } catch (\Exception $e) { \think\facade\Log::write('获取校区部门人员失败:' . $e->getMessage()); return []; } } /** * 根据校区ID和角色ID获取人员列表 * @param int $campus_id 校区ID * @param array $role_ids 角色ID数组 * @return array 人员列表 * * 使用示例: * // 获取指定校区的教练主管和普通教练 * $coaches = get_personnel_by_campus_role(1, [1, 5]); */ function get_personnel_by_campus_role($campus_id, $role_ids = []) { if (empty($campus_id) || empty($role_ids)) { return []; } try { // 根据校区ID和角色ID直接获取人员 $personnel_list = \think\facade\Db::table('school_campus_person_role') ->alias('cpr') ->leftJoin('school_personnel p', 'cpr.person_id = p.id') ->where([ ['cpr.campus_id', '=', $campus_id], ['cpr.role_id', 'in', $role_ids], ['cpr.deleted_at', '=', 0], ['p.status', '=', 2] ]) ->field('cpr.person_id, p.name, p.phone, cpr.role_id, cpr.dept_id') ->select() ->toArray(); return $personnel_list; } catch (\Exception $e) { \think\facade\Log::write('获取校区角色人员失败:' . $e->getMessage()); return []; } } // ==================== 合同系统函数 ==================== /** * 获取当前日期 * @return string */ function get_current_date() { return date('Y-m-d'); } /** * 获取当前时间 * @return string */ function get_current_time() { return date('H:i:s'); } /** * 获取当前日期时间 * @return string */ function get_current_datetime() { return date('Y-m-d H:i:s'); } /** * 获取当前年份 * @return string */ function get_current_year() { return date('Y'); } /** * 获取当前月份 * @return string */ function get_current_month() { return date('m'); } /** * 获取当前日 * @return string */ function get_current_day() { return date('d'); } /** * 获取当前周 * @return string */ function get_current_week() { return date('W'); } /** * 获取当前年份(别名函数,兼容配置中的函数名) * @return string */ function current_year() { return get_current_year(); } /** * 获取当前月份(别名函数,兼容配置中的函数名) * @return string */ function current_month() { return get_current_month(); } /** * 获取当前日(别名函数,兼容配置中的函数名) * @return string */ function current_day() { return get_current_day(); } /** * 获取当前季度 * @return string */ function get_current_quarter() { $month = (int)date('m'); return ceil($month / 3); } /** * 获取当前校区名称 * @param int $campus_id 校区ID,为空时尝试从当前用户获取 * @return string */ function get_current_campus($campus_id = 0) { try { if (empty($campus_id)) { // 尝试从当前登录用户获取校区信息 // 这里需要根据实际的用户会话机制来获取 return '默认校区'; } $campus = \think\facade\Db::table('school_campus') ->where('id', $campus_id) ->value('campus_name'); return $campus ?: '未知校区'; } catch (\Exception $e) { return '获取校区失败'; } } /** * 获取当前登录用户信息 * @return string */ function get_current_login_user() { try { // 这里需要根据实际的用户会话机制来获取当前用户 // 可以从JWT token或session中获取 return '当前用户'; } catch (\Exception $e) { return '获取用户失败'; } } /** * 获取当前员工信息 * @return string */ function get_current_personnel() { try { // 这里需要根据实际的员工会话机制来获取当前员工 return '当前员工'; } catch (\Exception $e) { return '获取员工失败'; } } /** * 获取合同生成时间 * @return string */ function get_contract_generate_time() { return date('Y-m-d H:i:s'); } /** * 获取合同签署时间 * @return string */ function get_contract_sign_time() { return date('Y-m-d H:i:s'); } /** * 获取系统名称 * @return string */ function get_system_name() { return '智慧教务系统'; } /** * 获取系统版本 * @return string */ function get_system_version() { return '1.0.0'; } /** * 生成随机数字 * @param int $length 数字长度,默认6位 * @return string */ function get_random_number($length = 6) { $min = pow(10, $length - 1); $max = pow(10, $length) - 1; return str_pad(rand($min, $max), $length, '0', STR_PAD_LEFT); } /** * 生成合同序号 * @param string $prefix 前缀,默认为HT * @return string */ function get_contract_sequence($prefix = 'HT') { return $prefix . date('Ymd') . get_random_number(4); } /** * 格式化货币 * @param float $amount 金额 * @param string $currency 货币符号,默认为¥ * @return string */ function format_currency($amount, $currency = '¥') { return $currency . number_format($amount, 2); } /** * 上课规则配置函数 * @param array $params 配置参数 * @return array */ function get_class_schedule_rules($params = []) { // 默认上课规则配置 $default_rules = [ 'class_duration' => 45, // 课时长度(分钟) 'break_duration' => 15, // 课间休息时间(分钟) 'start_time' => '08:00', // 开始时间 'end_time' => '18:00', // 结束时间 'lunch_break_start' => '12:00', // 午休开始时间 'lunch_break_end' => '14:00', // 午休结束时间 'weekend_enabled' => true, // 是否允许周末上课 'holiday_enabled' => false, // 是否允许节假日上课 'max_students_per_class' => 20, // 每班最大学生数 'min_students_per_class' => 5, // 每班最小学生数 'advance_booking_days' => 7, // 提前预约天数 'cancel_deadline_hours' => 24, // 取消课程截止时间(小时) ]; // 合并自定义参数 return array_merge($default_rules, $params); } /** * 生成上课时间表 * @param string $start_date 开始日期 * @param string $end_date 结束日期 * @param array $rules 上课规则 * @return array */ function generate_class_schedule($start_date, $end_date, $rules = []) { $rules = get_class_schedule_rules($rules); $schedule = []; $current_date = strtotime($start_date); $end_timestamp = strtotime($end_date); while ($current_date <= $end_timestamp) { $date_str = date('Y-m-d', $current_date); $day_of_week = date('w', $current_date); // 检查是否为周末 if (!$rules['weekend_enabled'] && ($day_of_week == 0 || $day_of_week == 6)) { $current_date = strtotime('+1 day', $current_date); continue; } // 生成当天的课程时间段 $daily_schedule = generate_daily_time_slots($date_str, $rules); if (!empty($daily_schedule)) { $schedule[$date_str] = $daily_schedule; } $current_date = strtotime('+1 day', $current_date); } return $schedule; } /** * 生成单日课程时间段 * @param string $date 日期 * @param array $rules 规则配置 * @return array */ function generate_daily_time_slots($date, $rules) { $slots = []; $current_time = strtotime($date . ' ' . $rules['start_time']); $end_time = strtotime($date . ' ' . $rules['end_time']); $lunch_start = strtotime($date . ' ' . $rules['lunch_break_start']); $lunch_end = strtotime($date . ' ' . $rules['lunch_break_end']); $slot_number = 1; while ($current_time < $end_time) { $slot_end = $current_time + ($rules['class_duration'] * 60); // 检查是否与午休时间冲突 if ($current_time < $lunch_end && $slot_end > $lunch_start) { $current_time = $lunch_end; continue; } $slots[] = [ 'slot_number' => $slot_number, 'start_time' => date('H:i', $current_time), 'end_time' => date('H:i', $slot_end), 'duration' => $rules['class_duration'], 'available' => true ]; $current_time = $slot_end + ($rules['break_duration'] * 60); $slot_number++; } return $slots; } /** * 验证上课时间冲突 * @param string $date 日期 * @param string $start_time 开始时间 * @param string $end_time 结束时间 * @param int $teacher_id 教师ID * @param int $classroom_id 教室ID * @return bool */ function check_class_time_conflict($date, $start_time, $end_time, $teacher_id = 0, $classroom_id = 0) { try { $where = [ ['date', '=', $date], ['status', '=', 1], // 正常状态 ]; // 时间冲突检查:新课程的开始时间在已有课程时间范围内,或新课程的结束时间在已有课程时间范围内 $time_conflict = [ ['start_time', '<', $end_time], ['end_time', '>', $start_time] ]; $where = array_merge($where, $time_conflict); // 检查教师冲突 if ($teacher_id > 0) { $teacher_conflict = \think\facade\Db::table('school_class_schedule') ->where($where) ->where('teacher_id', $teacher_id) ->count(); if ($teacher_conflict > 0) { return true; // 有冲突 } } // 检查教室冲突 if ($classroom_id > 0) { $classroom_conflict = \think\facade\Db::table('school_class_schedule') ->where($where) ->where('classroom_id', $classroom_id) ->count(); if ($classroom_conflict > 0) { return true; // 有冲突 } } return false; // 无冲突 } catch (\Exception $e) { \think\facade\Log::write('检查上课时间冲突失败:' . $e->getMessage()); return true; // 异常情况下认为有冲突,确保安全 } } // ==================== 签名图片处理函数 ==================== /** * 获取校区印章图片URL * @param int $campus_id 校区ID * @return string 印章图片URL */ function get_campus_seal_image($campus_id) { try { if (empty($campus_id)) { return ''; } $seal_image = \think\facade\Db::table('school_campus') ->where('id', $campus_id) ->value('seal_image'); if (empty($seal_image)) { return ''; } // 如果已经是完整URL,直接返回 if (str_contains($seal_image, 'http://') || str_contains($seal_image, 'https://')) { return $seal_image; } // 转换为完整的URL路径 return get_file_url($seal_image); } catch (\Exception $e) { \think\facade\Log::write('获取校区印章图片失败:' . $e->getMessage()); return ''; } } /** * 验证并处理上传的签名图片 * @param array $file 上传的文件信息 * @param array $options 配置选项 * @return array 处理结果 */ function process_signature_image($file, $options = []) { try { // 默认配置 $default_options = [ 'max_size' => 2 * 1024 * 1024, // 2MB 'allowed_types' => ['jpg', 'jpeg', 'png', 'gif'], 'save_path' => 'upload/signature/', 'compress_quality' => 80, // 图片压缩质量 'max_width' => 800, // 最大宽度 'max_height' => 600, // 最大高度 ]; $config = array_merge($default_options, $options); // 验证文件大小 if ($file['size'] > $config['max_size']) { return [ 'success' => false, 'message' => '图片文件大小不能超过' . ($config['max_size'] / 1024 / 1024) . 'MB' ]; } // 验证文件类型 $file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($file_ext, $config['allowed_types'])) { return [ 'success' => false, 'message' => '只支持' . implode('、', $config['allowed_types']) . '格式的图片' ]; } // 创建保存目录 $save_dir = $config['save_path'] . date('Y/m/d') . '/'; if (!is_dir($save_dir) && !mkdir($save_dir, 0755, true)) { return [ 'success' => false, 'message' => '创建保存目录失败' ]; } // 生成文件名 $filename = 'signature_' . date('YmdHis') . '_' . uniqid() . '.' . $file_ext; $save_path = $save_dir . $filename; // 移动文件 if (!move_uploaded_file($file['tmp_name'], $save_path)) { return [ 'success' => false, 'message' => '文件保存失败' ]; } // 压缩图片(如果需要) $compressed_path = compress_signature_image($save_path, $config); if ($compressed_path) { $save_path = $compressed_path; } return [ 'success' => true, 'message' => '签名图片上传成功', 'data' => [ 'file_path' => $save_path, 'file_url' => get_file_url($save_path), 'file_size' => filesize($save_path), 'original_name' => $file['name'] ] ]; } catch (\Exception $e) { \think\facade\Log::write('处理签名图片失败:' . $e->getMessage()); return [ 'success' => false, 'message' => '处理签名图片失败:' . $e->getMessage() ]; } } /** * 压缩签名图片 * @param string $source_path 源文件路径 * @param array $config 压缩配置 * @return string|false 压缩后的文件路径 */ function compress_signature_image($source_path, $config) { try { // 获取图片信息 $image_info = getimagesize($source_path); if (!$image_info) { return false; } [$width, $height, $type] = $image_info; // 如果图片尺寸已经符合要求,不需要压缩 if ($width <= $config['max_width'] && $height <= $config['max_height']) { return $source_path; } // 计算新尺寸 $ratio = min($config['max_width'] / $width, $config['max_height'] / $height); $new_width = intval($width * $ratio); $new_height = intval($height * $ratio); // 创建源图像资源 switch ($type) { case IMAGETYPE_JPEG: $source_image = imagecreatefromjpeg($source_path); break; case IMAGETYPE_PNG: $source_image = imagecreatefrompng($source_path); break; case IMAGETYPE_GIF: $source_image = imagecreatefromgif($source_path); break; default: return false; } if (!$source_image) { return false; } // 创建新图像 $new_image = imagecreatetruecolor($new_width, $new_height); // 保持透明度(PNG/GIF) if ($type == IMAGETYPE_PNG || $type == IMAGETYPE_GIF) { imagealphablending($new_image, false); imagesavealpha($new_image, true); $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127); imagefill($new_image, 0, 0, $transparent); } // 重新采样 imagecopyresampled($new_image, $source_image, 0, 0, 0, 0, $new_width, $new_height, $width, $height); // 生成压缩后的文件路径 $compressed_path = str_replace('.', '_compressed.', $source_path); // 保存压缩后的图片 $saved = false; switch ($type) { case IMAGETYPE_JPEG: $saved = imagejpeg($new_image, $compressed_path, $config['compress_quality']); break; case IMAGETYPE_PNG: $saved = imagepng($new_image, $compressed_path); break; case IMAGETYPE_GIF: $saved = imagegif($new_image, $compressed_path); break; } // 释放资源 imagedestroy($source_image); imagedestroy($new_image); if ($saved) { // 删除原文件 unlink($source_path); return $compressed_path; } return false; } catch (\Exception $e) { \think\facade\Log::write('压缩签名图片失败:' . $e->getMessage()); return false; } } /** * 获取用户签名记录 * @param int $user_id 用户ID * @param string $user_type 用户类型(staff/student/member) * @return array 签名记录 */ function get_user_signature_records($user_id, $user_type = 'staff') { try { if (empty($user_id)) { return []; } $signatures = \think\facade\Db::table('school_user_signatures') ->where([ ['user_id', '=', $user_id], ['user_type', '=', $user_type], ['status', '=', 1], // 有效状态 ['deleted_at', '=', 0] ]) ->order('created_at', 'desc') ->select() ->toArray(); // 处理签名图片URL foreach ($signatures as &$signature) { if (!empty($signature['signature_image'])) { $signature['signature_url'] = get_file_url($signature['signature_image']); } } return $signatures; } catch (\Exception $e) { \think\facade\Log::write('获取用户签名记录失败:' . $e->getMessage()); return []; } } /** * 保存用户签名记录 * @param array $data 签名数据 * @return int|false 签名记录ID */ function save_user_signature($data) { try { $required_fields = ['user_id', 'user_type', 'signature_image']; foreach ($required_fields as $field) { if (empty($data[$field])) { throw new \Exception("缺少必要字段:{$field}"); } } $insert_data = [ 'user_id' => $data['user_id'], 'user_type' => $data['user_type'], 'signature_image' => $data['signature_image'], 'signature_name' => $data['signature_name'] ?? '用户签名', 'signature_type' => $data['signature_type'] ?? 'image', // image/canvas/handwrite 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'status' => 1, 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'deleted_at' => 0 ]; $signature_id = \think\facade\Db::table('school_user_signatures') ->insertGetId($insert_data); if ($signature_id) { \think\facade\Log::write('保存用户签名记录成功:' . json_encode($insert_data)); return $signature_id; } return false; } catch (\Exception $e) { \think\facade\Log::write('保存用户签名记录失败:' . $e->getMessage()); return false; } }