<?php
/*
 * @author: CloudZA(云之安) <2922619853@qq.com>
 * @hitokoto: 一场秋雨一场凉，秋心酌满泪为霜。
 * Copyright (c) 2022 by CloudZA, All Rights Reserved.
 */

include_once 'class/db.class.php';
require_once 'config.php';
include_once 'class/mailer.class.php';
require_once 'class/Snowflake/SnowflakeIdWorker.php';
require_once 'class/Redis/CheckRedis.php';
require_once 'class/OnlineUpdater/OnlineUpdater.php';
require_once 'class/pay/epay/EpayCore.class.php';
require_once 'class/pay/alipay.php';
require_once 'class/pay/WxpayService.php';
require_once 'class/RandomCode/RandomCode.php';
// 常量文件
require_once( "app.config.php" );
require_once( 'authcode.php' );

define('host' , $param[ 'host' ]);
define('port' , $param[ 'port' ]);
define('user' , $param[ 'user' ]);
define('pwd' , $param[ 'pwd' ]);
define('dbname' , $param[ 'dbname' ]);
define('charset' , $param[ 'charset' ]);
/**
 * 显示支付方式
 *
 * @param string $type 支付方式
 *
 * @return string
 */
function displayPayType ($type)
{
    if ($type === 'wxpay') {
        return '微信支付';
    } else if ($type === 'alipay') {
        return '支付宝';
    } else {
        return 'NULL';
    }
}

/**
 * 显示支付状态
 *
 * @param int $stu 支付状态
 *
 * @return string
 */
function displayStutas (int $stu)
{
    if ($stu === 1) {
        return '已支付';
    } else if ($stu === '2') {
        return '已取消';
    } else {
        return '未支付';
    }
}

/**
 * 净化字符串，防止 SQL 注入和 XSS 攻击
 *
 * @param string|array $string 要净化的字符串或字符串数组
 * @param bool         $trim   是否去除空格，默认为 true
 * @param bool         $filter 是否过滤危险字符，默认为 true
 * @param bool|int     $force  是否强制净化，默认为 false
 * @param bool         $strip  是否去除反斜杠，默认为 false
 *
 * @return string|array
 */
function purge ($string , $trim = true , $filter = true , $force = false , $strip = false)
{
    // 检测字符编码并转换为 UTF-8
    $encode = mb_detect_encoding($string , [
        "ASCII" ,
        "UTF-8" ,
        "GB2312" ,
        "GBK" ,
        "BIG5"
    ]);
    if ($encode != 'UTF-8') {
        $string = iconv($encode , 'UTF-8' , $string);
    }

    // 去除空格
    if ($trim) {
        $string = preg_replace('/\s+/' , '' , $string);
    }

    // 过滤危险字符
    if ($filter) {
        $farr   = [
            "/<(\\/?)(script|i?frame|style|html|body|title|link|meta|object|\\?|\\%)([^>]*?)>/isU" ,
            "/(<[^>]*)on[a-zA-Z]+\s*=([^>]*>)/isU" ,
            "/select |insert |and |or |create |update |delete |alter |count |\'|\/\*|\*|\.\.\/|\.\/|\^|union |into |load_file|outfile |dump/is"
        ];
        $string = preg_replace($farr , '' , $string);
    }

    // 处理反斜杠
    if ( !defined('MAGIC_QUOTES_GPC')) {
        define('MAGIC_QUOTES_GPC' , !ini_get('magic_quotes_gpc'));
    }
    if ( !MAGIC_QUOTES_GPC || $force) {
        if (is_array($string)) {
            foreach ($string as $key => $val) {
                $string[ $key ] = sanitize_input($val , $force , $strip);
            }
        } else {
            $string = addslashes($strip ? stripslashes($string) : $string);
        }
    }

    return $string;
}

/**
 * 发送邮件
 *
 * @param $email string|array 收信邮箱，为数组就群发
 * @param $title string 发送的标题
 * @param $body  mixed 发送的内容
 *
 * @return   bool
 */
function send ($email = "" , $title = '' , $body = '')
{
    $row         = Db::table('app_web')
                     ->find();
    $mailer      = new mailer();
    $mail_Config = [
        'Host'     => $row[ 'send_smtp_host' ] ,
        'Port'     => $row[ 'send_smtp_port' ] ,
        'FromName' => $row[ 'send_from_name' ] ,
        'Username' => $row[ 'send_username' ] ,
        'Password' => $row[ 'send_password' ]
    ];
    $send_res    = $mailer->send($mail_Config , $email , $title , $body);
    if ($send_res) {
        return true;
    } else {
        return false;
    }
}

/**
 * @param $msg       string 信息描述
 * @param $type      string 提示类型
 * @param $back      bool 是否返回指定界面 默认：FALSE
 * @param $backTitle string 返回指定页面的按钮文字
 *
 * @return void
 */
function infoMessage (string $msg , string $type , bool $back = false , $backTitle = false)
{
    switch ($type) {
        case 1:
            $panel = "success";
            break;
        case 2:
            $panel = "info";
            break;
        case 3:
            $panel = "warning";
            break;
        case 4:
            $panel = "danger";
            break;
    }
    if ( !$backTitle) {
        $backTitle = '返回上一页';
    }
    if ($back) {
        $A = '<a class="btn btn-alt-' . $panel . ' fs-sm" href="' . $back . '"><i class="fa fa-arrow-left opacity-50 me-1"></i>' . $backTitle . '</a>';
    } else {
        $A = '<a class="btn btn-alt-' . $panel . ' fs-sm" href="javascript:history.back(-1)"><i class="fa fa-arrow-left opacity-50 me-1"></i> 返回上一页</a>';
    }
    echo '<div class="content">
	<div class="hero bg-body-extra-light block block-rounded" style="height: auto;">
		<div class="hero-inner">
			<div class="content content-full">
				<div class="py-4 text-center">
					<i class="si si-ghost text-primary display-3 text-' . $panel . '"></i>
					<h1 class="h2 fw-bold mt-4 mb-2 text-' . $panel . '">Tips Info</h1>
					<h2 class="h5 fw-medium  mb-5 text-' . $panel . '">' . $msg . '</h2>
					' . $A . '
				</div>
			</div>
		</div>
	</div>
</div>';
    exit;
}

/**
 * 从URL中提取域名部分。
 *
 * @param string $url 包含HTTP/HTTPS协议和可选路径的URL。
 *
 * @return string URL的域名部分，不包括协议或路径。
 */
function filterDomain (string $url)
{
    // 删除 URL 中的协议和查询字符串
    $url = preg_replace('/^https?:\/\//' , '' , $url);
    $url = preg_replace('/\/.*$/' , '' , $url);
    return $url;
}

/**
 * 比较两个数组是否相同
 *
 * @param $array1 array 第一个数组
 * @param $array2 array 第二个数组
 *
 * @return bool 相同则true
 */
function array_compare (array $array1 , array $array2)
{
    // 如果数组长度不同，直接返回false
    if (count($array1) != count($array2)) {
        return false;
    }

    // 对两个数组进行排序
    sort($array1);
    sort($array2);

    // 逐个元素比对
    for ($i = 0; $i < count($array1); $i++) {
        if ($array1[ $i ] !== $array2[ $i ]) {
            return false;
        }
    }

    // 如果所有元素都相同，返回true
    return true;
}

function txt_Arr ($txt): array
{
    //文本转数组
    $arr   = explode('&' , $txt);
    $array = [];
    foreach ($arr as $value) {
        $tmp_arr = explode('=' , $value);
        if (is_array($tmp_arr) && count($tmp_arr) == 2) {
            $array = array_merge($array , [$tmp_arr[ 0 ] => $tmp_arr[ 1 ]]);
        }
    }
    return $array;
}

function txt_zhong ($str , $leftStr , $rightStr)
{
    //取文本中间
    $left = strpos($str , $leftStr);
    //echo '左边:'.$left;
    $right = strpos($str , $rightStr , $left);
    //echo '<br>右边:'.$right;
    if ($left < 0 or $right < $left) return '';
    return substr($str , $left + strlen($leftStr) , $right - $left - strlen($leftStr));
}

function txt_you ($str , $leftStr)
{
    //取文本右边
    $left = strpos($str , $leftStr);
    return substr($str , $left + strlen($leftStr));
}

function txt_zuo ($str , $rightStr)
{
    //取文本左边
    $right = strpos($str , $rightStr);
    return substr($str , 0 , $right);
}

function ip_city_str ($str)
{
    return str_replace([
        '省' ,
        '市'
    ] , '' , $str);
}

//获取当个ip所在的省份
function getIpAddress ($ip)
{
    $url  = 'https://whois.pconline.com.cn/ipJson.jsp?json=true&ip=';
    $city = curl_get($url . $ip);
    $city = mb_convert_encoding($city , "UTF-8" , "GB2312");
    $city = json_decode($city , true);
    if ($city[ 'city' ]) {
        $location = ip_city_str($city[ 'pro' ]) . ip_city_str($city[ 'city' ]);
    } else {
        $location = ip_city_str($city[ 'pro' ]);
    }
    if ($location) {
        return $location;
    } else {
        return $city[ 'addr' ];
    }
}


/**
 * 获取用户真实IP
 *
 * @param int  $type
 * @param bool $adv
 *
 * @return mixed
 */
function get_ip (int $type = 0 , bool $adv = true)
{
    $type = $type ? 1 : 0;
    $ip   = NULL;
    if (NULL !== $ip) {
        return $ip[ $type ];
    }
    if ($adv) {
        if (isset($_SERVER[ 'HTTP_X_FORWARDED_FOR' ])) {
            $arr = explode(',' , $_SERVER[ 'HTTP_X_FORWARDED_FOR' ]);
            $pos = array_search('unknown' , $arr);
            if (false !== $pos) {
                unset($arr[ $pos ]);
            }
            $ip = trim(current($arr));
        } elseif (isset($_SERVER[ 'HTTP_CLIENT_IP' ])) {
            $ip = $_SERVER[ 'HTTP_CLIENT_IP' ];
        } elseif (isset($_SERVER[ 'REMOTE_ADDR' ])) {
            $ip = $_SERVER[ 'REMOTE_ADDR' ];
        }
    } elseif (isset($_SERVER[ 'HTTP_X_FORWARDED_FOR' ])) {
        $arr = explode(',' , $_SERVER[ 'HTTP_X_FORWARDED_FOR' ]);
        $pos = array_search('unknown' , $arr);
        if (false !== $pos) {
            unset($arr[ $pos ]);
        }
        $ip = trim(current($arr));
    } elseif (isset($_SERVER[ 'REMOTE_ADDR' ])) {
        $ip = $_SERVER[ 'REMOTE_ADDR' ];
    }
    // IP地址合法验证
    $long = sprintf("%u" , ip2long($ip));
    $ip   = $long ? [
        $ip ,
        $long
    ] : [
        '0.0.0.0' ,
        0
    ];
    return $ip[ $type ];
}

/**
 * 生成随机字符串
 *
 * @param $length int 长度
 *
 * @return string|null
 */
function getRand (int $length): ?string
{
    $str    = NULL;
    $strPol = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $max    = strlen($strPol) - 1;
    for ($i = 0; $i < $length; $i++) {
        $str .= $strPol[ rand(0 , $max) ];
    }
    return $str;
}

// url参数转数组
function toarr ($para)
{
    $str = mb_substr($para , stripos($para , "?") + 1);
    parse_str($str , $arr);
    return $arr;
}


function curl_get ($url)
{
    $ch = curl_init($url);
    curl_setopt($ch , CURLOPT_SSL_VERIFYPEER , false);
    curl_setopt($ch , CURLOPT_SSL_VERIFYHOST , false);
    curl_setopt($ch , CURLOPT_RETURNTRANSFER , true);
    curl_setopt($ch , CURLOPT_USERAGENT , 'Mozilla/5.0 (Linux; U; Android 4.4.1; zh-cn; R815T Build/JOP40D) AppleWebKit/533.1 (KHTML, like Gecko)Version/4.0 MQQBrowser/4.5 Mobile Safari/533.1');
    curl_setopt($ch , CURLOPT_TIMEOUT , 30);
    $content = curl_exec($ch);
    curl_close($ch);
    return ( $content );
}

/**
 * 返回带数据
 *
 * @param $code int 响应代码
 * @param $msg  string 描述字符串
 * @param $data array 返回的数据
 *
 * @return false|string json
 */
function return_msg (int $code , string $msg , array $data = [])
{
    $result = [
        'code' => $code ,
        'msg'  => $msg ,
        'data' => $data
    ];
    return json_encode($result);
}

/**
 * 成功返回不带数据
 *
 * @param $msg string 描述
 */
function ReturnSuccess (string $msg)
{
    $result = [
        'code' => 200 ,
        'msg'  => $msg ,
    ];
    return json_encode($result);
}

/**
 * 失败返回
 *
 * @param $msg string 描述
 */
function ReturnError (string $msg)
{
    $result = [
        'code' => 201 ,
        'msg'  => $msg ,
    ];
    return json_encode($result);
}

/**
 * 随机昵称
 *
 * @return string
 */
function generateNickname ()
{
    $words    = [
        '琴' ,
        '瑶' ,
        '凰' ,
        '璃' ,
        '鸾' ,
        '杏' ,
        '鹿' ,
        '华' ,
        '岳' ,
        '芸' ,
        '雪' ,
        '霞' ,
        '露' ,
        '珂' ,
        '砚' ,
        '云' ,
        '岚' ,
        '泓' ,
        '琅' ,
        '蕊' ,
        '灵' ,
        '泪' ,
        '若' ,
        '凌' ,
        '绯' ,
        '梓' ,
        '萱' ,
        '曼' ,
        '蝶' ,
        '莲' ,
        '婉' ,
        '桂' ,
        '倩' ,
        '涟' ,
        '瑾' ,
        '霭' ,
        '媛' ,
        '晴' ,
        '夏' ,
        '秋' ,
        '冬' ,
        '春' ,
        '彤' ,
        '沁' ,
        '菡' ,
        '蔚' ,
        '菁' ,
        '蓝' ,
        '靖' ,
        '逸' ,
        '峰' ,
        '岚' ,
        '波' ,
        '芷' ,
        '静' ,
        '慕' ,
        '珊' ,
        '丹' ,
        '莎' ,
        '桃' ,
        '梅' ,
        '莉' ,
        '柏' ,
        '菊' ,
        '柳' ,
        '杉' ,
        '竹' ,
        '榆' ,
        '枫' ,
        '檀' ,
        '橙' ,
        '柚' ,
        '荔' ,
        '橘' ,
        '栀' ,
        '桔' ,
        '柠' ,
        '荷' ,
        '蓝' ,
        '菱' ,
        '萍' ,
        '蒲' ,
        '芍' ,
        '芦' ,
        '荻' ,
        '蒙' ,
        '草' ,
        '薇' ,
        '葵' ,
        '蕉' ,
        '蔷' ,
        '莎' ,
        '薰' ,
        '蓉' ,
        '蓓' ,
        '蕾' ,
        '萝' ,
        '莎' ,
        '莉' ,
        '菲' ,
        '曦' ,
        '阳' ,
        '峰' ,
        '雨' ,
        '霖' ,
        '翰' ,
        '绮' ,
        '婧' ,
        '洛' ,
        '洁' ,
        '雁' ,
        '南' ,
        '北' ,
        '东' ,
        '西' ,
        '风' ,
        '月' ,
        '霜' ,
        '雪' ,
        '寒' ,
        '冰' ,
        '瑶' ,
        '琳' ,
        '琼' ,
        '晶' ,
        '珠' ,
        '璇' ,
        '瑰' ,
        '桂' ,
        '苑' ,
        '茜' ,
        '蓝' ,
        '黛' ,
        '妙' ,
        '音' ,
        '涵' ,
        '雅' ,
        '舒' ,
        '娅' ,
        '嫣' ,
        '芳' ,
        '莎' ,
        '苔' ,
        '葳' ,
        '莺' ,
        '萤' ,
        '盈' ,
        '娜' ,
        '妍' ,
        '艳' ,
        '倚' ,
        '恬' ,
        '净' ,
        '红' ,
        '玫' ,
        '佩' ,
        '梦' ,
        '翠' ,
        '芬' ,
        '芸' ,
        '芷' ,
        '芹'
    ];
    $nickname = '';
    for ($i = 0; $i < 2; $i++) {
        $nickname .= $words[ rand(0 , count($words) - 1) ]; // 随机选择一个字词
    }

    return $nickname;
}

/**
 * 查询OpenAi Api Key额度使用情况
 *
 * @param $apiUrl string API_URL
 * @param $apiKey string API_KEY
 *
 * @return mixed|string
 */
function sendCurlRequest (string $apiUrl , string $apiKey)
{
    $headers = [
        'content-type: application/json' ,
        'Authorization: Bearer ' . $apiKey
    ];

    $n = curl_init();
    curl_setopt($n , CURLOPT_SSL_VERIFYPEER , false);
    curl_setopt($n , CURLOPT_SSL_VERIFYHOST , false);
    curl_setopt($n , CURLOPT_URL , $apiUrl . "/v1/dashboard/billing/subscription");
    curl_setopt($n , CURLOPT_HTTPHEADER , $headers);
    curl_setopt($n , CURLOPT_RETURNTRANSFER , true);
    $response = curl_exec($n);
    if ($response) {
        $data        = json_decode($response , true);
        $accessUntil = $data[ 'access_until' ];
        $endDate     = date('Y-m-d' , $accessUntil);
        $startDate   = date('Y-m-d' , strtotime('-3 months' , $accessUntil));
        $url         = $apiUrl . "/v1/dashboard/billing/usage?start_date=" . $startDate . "&end_date=" . $endDate;
        curl_setopt($n , CURLOPT_URL , $url);
        $response = curl_exec($n);
        if ($response) {
            $usage          = json_decode($response , true);
            $plan           = ( $data[ 'plan' ][ 'title' ] == 'Pay-as-you-go' ) ? 'Pay-as-you-go' : $data[ 'plan' ][ 'id' ];
            $totalGranted   = $data[ 'hard_limit_usd' ];
            $totalUsed      = $usage[ 'total_usage' ] / 100;
            $totalAvailable = $totalGranted - $totalUsed;
            if ($plan === NULL && $totalGranted === NULL) {
                return return_msg(201 , '查询失败' , ['key' => $apiKey]);
            }
            return return_msg(200 , '查询成功' , [
                // key
                'key'          => $apiKey ,
                // 类型
                'plan'         => $plan ,
                // 总额度
                'granted'      => number_format($totalGranted , 2) ,
                // 已用额度
                'used'         => number_format($totalUsed , 2) ,
                // 剩余额度
                'available'    => number_format($totalAvailable , 2) ,
                // Key起始时间
                'effective_at' => $startDate ,
                // Key过期时间
                'expires_at'   => $endDate ,
            ]);
        } else {
            $error = curl_error($n);
            return $error;
        }
    } else {
        $error = curl_error($n);
        return $error;
    }

    curl_close($n);
}

/**
 * 生成随机盐值
 *
 * @param int $length 盐值长度，默认为 8
 *
 * @return string 返回生成的随机盐值
 */
function generateSalt ($length = 8)
{
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#_+=-';
    $salt  = '';
    for ($i = 0; $i < $length; $i++) {
        $salt .= substr($chars , mt_rand(0 , strlen($chars) - 1) , 1);
    }
    return $salt;
}

/**
 * 对密码进行盐值加密
 *
 * @param string $password 原始密码
 *
 * @return string 返回加密后的密码字符串
 */
function encryptPassword ($password)
{
    $salt              = generateSalt();
    $hash              = hash('sha256' , $password . $salt);
    $encryptedPassword = $salt . $hash;
    return $encryptedPassword;
}

/**
 * 验证密码是否正确
 *
 * @param string $password          用户输入的密码
 * @param string $encryptedPassword 加密后的密码字符串
 *
 * @return bool 如果密码匹配则返回 true，否则返回 false
 */
function verifyPassword ($password , $encryptedPassword)
{
    $salt    = substr($encryptedPassword , 0 , 8);
    $hash    = substr($encryptedPassword , 8);
    $newHash = hash('sha256' , $password . $salt);
    return $newHash === $hash;
}

/**
 * 根据已用额度和总额度计算使用百分比。
 *
 * @param float $used           已用额度
 * @param float $total          总额度
 * @param int   $decimal_places 结果中小数点后保留的位数，默认为 2
 *
 * @return string 格式化后的百分比字符串，包括指定数量的小数位数
 */
function calculateUsagePercentage ($used , $total , $decimal_places = 2)
{
    if ($total == 0) {
        return '0%';
    }
    $percentage = ( $used / $total ) * 100;
    return number_format($percentage , $decimal_places) . '%';
}

/**
 * 将字符串的换行转成br
 *
 * @param $text string 带转换的字符串
 *
 * @return string|string[]
 */
function replaceEnterWithBr (string $text)
{
    return str_replace("\n" , "<br>" , $text);
}

/**
 * 测试指定API的响应时间
 *
 * @param string $api_url API地址
 *
 * @return mixed 如果请求成功，返回响应时间（单位：毫秒)
 */
function test_api_speed ($api_url)
{
    $ch = curl_init();
    curl_setopt($ch , CURLOPT_URL , $api_url);
    curl_setopt($ch , CURLOPT_RETURNTRANSFER , true);  // 返回响应结果
    curl_setopt($ch , CURLOPT_TIMEOUT , 10);           // 设置超时时间为10秒
    curl_setopt($ch , CURLOPT_SSL_VERIFYPEER , false); // 禁用 SSL 证书验证
    curl_setopt($ch , CURLOPT_SSL_VERIFYHOST , false);
    $start_time = microtime(true);
    $response   = curl_exec($ch);
    $end_time   = microtime(true);
    curl_close($ch);

    if ($response === false) {
        return false; // 请求失败，返回 false
    }

    $time_diff     = $end_time - $start_time;
    $response_time = round($time_diff * 1000 , 2) . 'ms'; // 转换为毫秒并保留两位小数 + ms

    return $response_time;
}


/**
 * 比较两个关联数组中的所有值是否相同
 *
 * @param array $old_array 旧数组
 * @param array $new_array 新数组
 *
 * @return bool 如果两个数组中的所有值相同，则返回false；否则返回true
 */
function compare_arrays (array $old_array , array $new_array): bool
{
    foreach ($old_array as $key => $value) {
        if ( !isset($new_array[ $key ]) || $new_array[ $key ] !== $value) {
            return true;
        }
    }
    return false;
}


/**
 * 获取所有的会员套餐信息
 *
 * @return array 套餐信息数组
 */
function getPlans (): array
{
    return Db::table('app_plans')
             ->select();
}


/**
 * 根据套餐ID获取套餐信息
 *
 * @param int $plan_id 套餐ID
 *
 * @return array|null 套餐信息数组（不存在则返回null）
 */
function getPlanById (int $plan_id): ?array
{
    return Db::table('app_plans')
             ->where(['id' => $plan_id])
             ->find();
}

/**
 * 根据卡密获取卡密信息
 *
 * @param string $coupon 卡密
 *
 * @return array|null 卡密信息数组（不存在则返回null）
 */
function getCouponByCode (string $coupon): ?array
{
    return Db::table('app_plan_coupons')
             ->where(['coupon' => $coupon])
             ->find();
}

/**
 * 更新卡密状态
 *
 * @param int  $coupon_id 卡密ID
 * @param bool $is_used   是否已使用
 *
 * @return bool 是否更新成功
 */
function updateCouponStatus (int $coupon_id , bool $is_used): bool
{
    return Db::table('app_plan_coupons')
             ->where(['id' => $coupon_id])
             ->update([
                 'is_used' => $is_used ,
                 'used_at' => date('Y-m-d H:i:s')
             ]);
}

/**
 * 获取用户当前的会员状态信息
 *
 * @param int $user_id 用户ID
 *
 * @return array|null 会员状态信息数组（不存在则返回null）
 */
function getUserMemberInfo (int $user_id): ?array
{
    return Db::table('app_user' , 'AS U')
             ->field('U.id,U.plans,U.plans_id,U.plans_name,U.chat_limit,U.effective_at,U.expires_at')
             ->where(['id' => $user_id])
             ->find();
}

/**
 * 更新用户会员信息
 *
 * @param int   $user_id 用户ID
 * @param array $data    更新数据数组
 *
 * @return bool 是否更新成功
 */
function updateUserMemberInfo (int $user_id , array $data): bool
{
    return Db::table('app_user')
             ->where(['id' => $user_id])
             ->update($data);
}

/**
 * 开通会员
 *
 * @param int         $user_id 用户ID
 * @param string|null $coupon  卡密（可选）
 * @param int         $plan_id 套餐ID
 *
 * @return bool 是否开通成功
 */
function openMember (int $user_id , ?string $coupon , int $plan_id): bool
{
    // 根据套餐ID查询套餐信息
    $plan = getPlanById($plan_id);
    // 设置对话次数
    $chat_limit = $plan[ 'chat_limit' ];
    // 查询当前用户的信息
    $user_chat_linut = getUserMemberInfo($user_id)[ 'chat_limit' ];
    if ( !$plan) {
        return false;
    }

    // 如果使用卡密，则判断卡密是否有效并未使用过，否则直接生成会员信息
    if ($coupon) {
        $couponInfo = getCouponByCode($coupon);
        if ( !$couponInfo || $couponInfo[ 'is_used' ]) {
            return false;
        }
        // 设置卡密状态为已使用
        updateCouponStatus($couponInfo[ 'id' ] , true);
    }

    $now = time();
    // 获取当前时间作为生效时间
    $effective_time = date('Y-m-d H:i:s' , $now);
    // 获取过期时间
    $expires_time = date('Y-m-d H:i:s' , strtotime("+{$plan['period']} days" , $now));


    $chat_limit += $user_chat_linut;

    $userData = [
        'plans'        => 1 ,
        'plans_id'     => $plan[ 'id' ] ,
        'plans_name'   => $plan[ 'name' ] ,
        'chat_limit'   => $chat_limit ,
        'effective_at' => $effective_time ,
        'expires_at'   => $expires_time ,
    ];

    // 更新用户表中的会员信息
    updateUserMemberInfo($user_id , $userData);

    return true;
}

/**
 * 续费会员并增加对话额度
 *
 * @param int $user_id             用户ID
 * @param int $days                续费天数
 * @param int $chat_limit_increase 增加的对话额度
 *
 * @return bool 是否续费成功
 */
function renewMemberWithChatLimit (int $user_id , int $days , int $chat_limit_increase): bool
{
    // 获取用户当前的会员状态信息
    $userInfo  = getUserMemberInfo($user_id);
    $plan_info = getPlanById($userInfo[ 'plans_id' ]);
    if ( !$plan_info) {
        return false;
    }
    if ( !$userInfo || $userInfo[ 'plans' ] === 0) {
        return false;
    }

    // 计算续费后的过期时间
    $expires_time   = date('Y-m-d H:i:s' , strtotime("+{$days} days" , strtotime($userInfo[ 'expires_at' ])));
    $new_chat_limit = $userInfo[ 'chat_limit' ] + $plan_info[ 'chat_limit' ] + $chat_limit_increase;
    $userData       = [
        'chat_limit'   => $new_chat_limit ,
        'effective_at' => $userInfo[ 'effective_at' ] ,
        'expires_at'   => $expires_time ,
    ];

    // 更新用户表中的会员信息
    if ( !updateUserMemberInfo($user_id , $userData)) {
        return false;
    }

    return true;
}


/**
 * 增加对话额度
 *
 * @param int $user_id        用户ID
 * @param int $add_chat_limit 新增的对话额度
 *
 * @return bool 是否增加成功
 */
function addChatLimit (int $user_id , int $add_chat_limit): bool
{
    // 获取用户当前的会员状态信息
    $userInfo = getUserMemberInfo($user_id);

    if ( !$userInfo || $userInfo[ 'plans' ] === 0) {
        return false;
    }

    $new_chat_limit = $userInfo[ 'chat_limit' ] + $add_chat_limit;

    $userData = [
        'chat_limit' => $new_chat_limit ,
    ];

    // 更新用户表中的会员信息
    updateUserMemberInfo($user_id , $userData);

    return true;
}

/**
 * 减少对话额度
 *
 * @param int $user_id           用户ID
 * @param int $reduce_chat_limit 减少的对话额度
 *
 * @return bool 是否减少成功
 */
function reduceChatLimit (int $user_id , int $reduce_chat_limit): bool
{
    // 获取用户当前的会员状态信息
    $userInfo = getUserMemberInfo($user_id);

    if ( !$userInfo || $userInfo[ 'plans' ] === 0) {
        return false;
    }

    // 计算减少后的对话额度不能小于0
    $new_chat_limit = max($userInfo[ 'chat_limit' ] - $reduce_chat_limit , 0);

    $userData = [
        'chat_limit' => $new_chat_limit ,
    ];

    // 更新用户表中的会员信息
    updateUserMemberInfo($user_id , $userData);

    return true;
}


/**
 * 检查用户名是否存在
 *
 * @param string $username 用户名
 *
 * @return bool 如果存在则返回true
 */
function checkUsernameExistence (string $username): bool
{
    if (Db::table('app_user')
          ->where(['username' => $username])
          ->find()) {
        return true;
    } else {
        return false;
    }
}

/**
 * 检查用户名是否符合规则：只能为4-20位英文、下划线和小数点组成
 *
 * @param string $username 待检查的用户名
 *
 * @return bool 返回值为true表示用户名符合规则，否则为false
 */
function checkUsername ($username): bool
{
    return preg_match('/^[a-zA-Z0-9.]{4,20}$/' , $username) === 1;
}


/**
 * 检查密码是否符合规则：长度为8到20位，只能包含英文字母和数字
 *
 * @param string $password 待检查的密码
 *
 * @return bool 返回值为true表示密码符合规则，否则为false
 */
function checkPassword ($password)
{
    return preg_match('/^[a-zA-Z0-9]{8,20}$/' , $password) === 1;
}


/**
 * 检查邮箱地址是否合法
 *
 * @param string $email 待检查的邮箱地址
 *
 * @return bool 返回值为true表示邮箱地址合法，否则为false
 */
function checkEmail ($email)
{
    return filter_var($email , FILTER_VALIDATE_EMAIL) !== false;
}

/**
 * 检查邮箱验证码是否正确
 *
 * @param $uuid string UUID
 * @param $code string CODE
 *
 * @return false|string
 */
function checkEmailCode ($uuid , $code)
{
    RandomCode::setRedisConfig(__RedisHost__ , __RedisPort__ , __RedisPass__);
    if ( !RandomCode::isExpired($uuid)) {
        return ReturnError('验证码已失效，请刷新页面重新获取');
    }
    if ( !RandomCode::validate($uuid , $code)) {
        return ReturnError('验证码不正确，请重新输入');
    }
    return ReturnSuccess('验证成功');
}

/**
 * 检测文本中是否包含违禁词
 *
 * @param string $text 待检测内容
 *
 * @return bool 如果包含违禁词则返回true，否则返回false
 */
function checkBadWords ($text): bool
{
    if (is_string(__BAD_WORDS__)) {
        // 如果 $badWords 是一个字符串，将其按照逗号分隔符转换成一个违禁词数组
        $badWords = array_map('trim' , explode(',' , __BAD_WORDS__));
    }
    $text = preg_replace('/[^\p{L}\p{N}\s]/u' , '' , $text);
    foreach ($badWords as $word) {
        $pattern = '/' . implode('|' , array_map('preg_quote' , $badWords , array_fill(0 , count($badWords) , '/'))) . '/u';
        if (preg_match($pattern , $text)) {
            return true;
        }
    }
    return false;
}
