发布页: https://bri6.cn/archives/405.html
开源主仓库: https://github.com/XiaoFeng-QWQ/zicheng-web-chat-room
开源镜像: https://gitee.com/XiaoFengQWQ/zichen-web-chat-room (可能不保证实时更新)

更改:

  1. 聊天支持上传文件,指令列表新增投票
  2. 部分CSS、布局优化
  3. 优化部分代码

实现方法:

支持文件上传:

1. 检查文件是否上传成功

if (isset($_FILES['file']) && $_FILES['file']['error'] === UPLOAD_ERR_OK) {
  • isset($_FILES['file']): 检查是否存在上传的文件。
  • $_FILES['file']['error'] === UPLOAD_ERR_OK: 检查文件上传是否成功,UPLOAD_ERR_OK 表示没有错误发生。$_FILES['file']['error'] 是文件上传的错误码。

2. 获取文件的 MIME 类型

$fileType = mime_content_type($_FILES['file']['tmp_name']);
  • mime_content_type($_FILES['file']['tmp_name']):通过 mime_content_type 函数获取文件的 MIME 类型。该函数根据文件内容而不是文件扩展名来判断文件类型。

3. 检查文件的 MIME 类型是否在允许范围内

$allowed = false;
if (in_array($fileType, $chatConfig->uploadFile['allowTypes'])) {
    $allowed = true;
}
if (!$allowed) {
    respondWithJson(ChatController::STATUS_WARNING, '无效的文件类型');
}
  • $chatConfig->uploadFile['allowTypes']:这是一个允许的文件类型列表(通常是数组),可能包含诸如 'image/jpeg', 'image/png' 等类型。
  • in_array($fileType, $chatConfig->uploadFile['allowTypes']):检查文件的 MIME 类型是否在允许的类型数组中。
  • 如果文件类型不被允许,返回一个警告信息 '无效的文件类型'

4. 检查文件的大小是否超过限制

if ($_FILES['file']['size'] > $chatConfig->uploadFile['maxSize']) {
    respondWithJson(ChatController::STATUS_WARNING, '文件太大');
}
  • $_FILES['file']['size']:获取上传文件的大小(单位字节)。
  • $chatConfig->uploadFile['maxSize']:最大文件大小限制。
  • 如果文件大小超出了限制,返回 '文件太大' 的警告信息。

5. 创建上传目录(如果不存在)

$uploadDir = FRAMEWORK_DIR . "/StaticResources/uploads/" . date('Y/m/d') . "/u_{$userInfo['user_id']}/";
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0777, true)) {
    respondWithJson(ChatController::STATUS_ERROR, '无法创建上传目录');
}
  • 通过当前日期(年、月、日)来组织上传文件的目录结构:/StaticResources/uploads/Y/m/d/u_user_id/
  • is_dir($uploadDir): 检查目录是否存在。
  • mkdir($uploadDir, 0777, true): 如果目录不存在,创建该目录,权限为 0777true 表示递归创建不存在的目录。
  • 如果创建失败,返回错误信息 '无法创建上传目录'

6. 生成唯一的文件名并保存文件

$fileName = time() . "_" . uniqid() . '.' . pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$filePath = $uploadDir . $fileName;
if (!move_uploaded_file($_FILES['file']['tmp_name'], $filePath)) {
    respondWithJson(ChatController::STATUS_ERROR, '上传失败');
}
  • time() . "_" . uniqid():使用当前时间戳和一个唯一 ID 生成文件名,确保文件名不重复。
  • pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION):获取上传文件的扩展名。
  • move_uploaded_file($_FILES['file']['tmp_name'], $filePath):将文件从临时目录移动到指定的目标路径。
  • 如果文件上传失败,返回 '上传失败' 的错误信息。

7. 生成文件的相对路径

$relativeImagePath = $helpres->getCurrentUrl() . "/StaticResources/uploads/" . date('Y/m/d') . "/u_{$userInfo['user_id']}/$fileName";
  • 生成文件的相对 URL 路径,用于在前端显示或下载文件。$helpres->getCurrentUrl() 获取当前的基础 URL。

8. 生成文件信息模板

function generateFileTemplate($fileData)
{
    $template = '[!file(';
    foreach ($fileData as $key => $value) {
        $template .= $key . '="' . $value . '", ';
    }
    $template = rtrim($template, ', ');
    $template .= ')]';
    return $template;
}
  • 这是一个用于生成文件信息模板的函数。它会将传入的文件数据(如路径、名称、类型、大小等)转化为特定的模板格式,最终返回 [!file(path="xxx", name="xxx", type="xxx", size="xxx", download="true")] 这样的格式。

9. 构建文件信息并添加到消息中

$message .= generateFileTemplate([
    'path' => $relativeImagePath,
    'name' => $_FILES['file']['name'],
    'type' => $_FILES['file']['type'],
    'size' => round($_FILES['file']['size'] / 1024, 2) . 'KB',
    'download' => 'true'
]);

10. 解析

/**
 * 解析文件模板字符串,提取其中的文件参数。
 * 
 * @param {string} template - 文件模板字符串,格式应为 [!file(key1="value1", key2="value2", ...)]
 * @returns {Object|null} - 返回一个包含文件参数的对象,如果模板格式不匹配,返回 null。
 */
function parseFileTemplate(template) {
    // 定义正则表达式,用于匹配模板字符串中的内容
    const pattern = /\[!file\((.*?)\)\]/;
    // 使用正则表达式匹配模板字符串
    const match = template.match(pattern);
    // 如果匹配成功
    if (match) {
        // 获取括号内的参数部分,match[1] 是正则匹配到的第一个捕获组
        const paramsString = match[1];
        // 将参数字符串分割成单个参数,格式为 key=value
        const params = paramsString.split(',').reduce((acc, param) => {
            // 分割每个参数为 key 和 value
            const [key, value] = param.split('=');
            
            // 去除键值对两端的空白字符,去除值两端的引号
            acc[key.trim()] = value.trim().replace(/^"|"$/g, '');
            return acc;
        }, {}); // 初始值为空对象,用于存储解析后的参数

        // 返回解析出的参数对象
        return params;
    }
    // 如果没有匹配到符合格式的模板,返回 null
    return null;
}

样式优化

{tabs}
{tabs-pane label="前台样式优化"}

前台样式优化

{/tabs-pane}
{tabs-pane label="后台部分布局优化"}

主页优化 检测更新优化

{/tabs-pane}
{/tabs}