开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 2963|回复: 140
收起左侧

[2025开源大赛(第八届)] ✅PHP 图片上传 全开源 快速稳定 ✅带验证 简单实用✅

[复制链接]
回帖奖励 2 枚 精币 回复本帖可获得 2 枚 精币奖励! 每人限 1 次(中奖概率 80 %)
结帖率:100% (11/11)
发表于 2025-10-18 17:02:10 | 显示全部楼层 |阅读模式   广东省茂名市
分享源码
界面截图:
是否带模块: 纯源码
备注说明: -
php 图片上传,超实用,简单稳定快速

e源码:

  
子程序名返回值类型公开备 注
上传图片文件逻辑型 上传图片到服务器
参数名类 型参考可空数组备 注
图片地址文本型
错误提示文本型
变量名类 型静态数组备 注
分隔符文本型 
时间戳文本型 
API_KEY文本型 
头信息文本型 
文件名文本型 
表单数据字节集 
上传结果字节集 
json类_json 
api地址文本型 
api地址 = http://127.0.0.1/image_api.php
' 生成随机分隔符
分隔符 = “---------------------------”取十六进制文本 (取随机数 (100000000, 999999999))
时间戳 = 时间_到时间戳 (取现行时间 (), 真, )
文件名 = 文件_取文件名 (图片地址, )
API_KEY = 校验_取md5 (到字节集 (时间戳 + “QQ516221198_QUN1071098978” + 文件名), , )
' 构造头信息
头信息 = “X-API-TIMESTAMP: ” + 时间戳 + #换行符“X-API-SIGNATURE: ” + API_KEY + #换行符“Content-Type: multipart/form-data; boundary=” + 分隔符
' 构造表单数据
表单数据 = 到字节集 (“--” + 分隔符 + #换行符“Content-Disposition: form-data; name=”#引号“image”#引号“; filename=”#引号 + 文件名 + #引号#换行符“Content-Type: application/octet-stream”#换行符#换行符 )读入文件 (图片地址)到字节集 ( #换行符“--” + 分隔符 + “--”)
' 发送POST请求
上传结果 = 编码_编码转换对象 (网页_访问_对象 (api地址, 1, , , , 头信息, , , , 表单数据, , , , , , , , , ), , , )
json.解析 (到文本 (上传结果), , )
如果 (json.取属性对象 (“success”)“true”)
图片地址 = json.取通用属性 (“['image_url']”, )
返回 ()
图片地址 = “”
错误提示 = json.取通用属性 (“message”, )
返回 ()


php 源码:

[PHP] 纯文本查看 复制代码
<?php
// 设置响应头为JSON格式
header("Content-Type: application/json; charset=utf-8");
// 允许跨域请求
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, X-API-TIMESTAMP, X-API-SIGNATURE");

// 安全配置 - 请在实际使用时修改为强密钥
$apiKey = 'QQ516221198_QUN1071098978'; // 替换为你的API密钥
$allowedTimeWindow = 30; // 允许的请求时间窗口(秒),防止重放攻击

// 检查文件签名以验证文件内容的真实性
function verifyImageSignature($filePath) {
    try {
        // 读取文件头字节进行基本验证
        $fileHeader = file_get_contents($filePath, false, null, 0, 8);
        // 检查JPG文件签名 (FF D8)
        if (substr($fileHeader, 0, 2) === "\xFF\xD8") {
            return true;
        }
        // 检查PNG文件签名
        if (substr($fileHeader, 0, 8) === "\x89PNG\r\n\x1A\n") {
            return true;
        }
    } catch (Exception $e) {
        error_log('文件签名验证错误: ' . $e->getMessage());
    }
    return false;
}

// 尝试转换非标准图片为标准JPG格式的函数
function convertToStandardJPG($sourcePath) {
    try {
        // 检查GD库是否可用
        if (!extension_loaded('gd')) {
            error_log('GD库未加载,无法转换图片');
            return false;
        }
        // 创建临时转换路径
        $targetPath = $sourcePath . '_converted.jpg';
        // 尝试使用GD库打开图片
        $image = @imagecreatefromstring(file_get_contents($sourcePath));
        if ($image === false) {
            error_log('无法从字符串创建图片');
            return false;
        }
        // 将图片保存为标准JPG格式
        $success = imagejpeg($image, $targetPath, 90);
        imagedestroy($image);
                return $success ? $targetPath : false;
    } catch (Exception $e) {
        error_log('图片转换错误: ' . $e->getMessage());
        return false;
    }
}

// 验证函数 - 只保留上传功能的验证
function validateRequest() {
    try {
        global $apiKey, $allowedTimeWindow;
        // 获取请求头中的时间戳和签名
        $timestamp = isset($_SERVER['HTTP_X_API_TIMESTAMP']) ? $_SERVER['HTTP_X_API_TIMESTAMP'] : '';
        $signature = isset($_SERVER['HTTP_X_API_SIGNATURE']) ? $_SERVER['HTTP_X_API_SIGNATURE'] : '';
        // 检查必要的头信息
        if (empty($timestamp) || empty($signature)) {
            http_response_code(401);
            $response = [
                'success' => false,
                'message' => '缺少必要的信息:时间戳或签名',
                'image_url' => ''
            ];
            echo json_encode($response, JSON_UNESCAPED_SLASHES);
            exit;
        }
        // 验证时间戳,防止重放攻击
        $currentTime = time();
        if (abs($currentTime - $timestamp) > $allowedTimeWindow) {
            http_response_code(401);
            $response = [
                'success' => false,
                'message' => '请求已过期,时间差:' . abs($currentTime - $timestamp) . '秒',
                'image_url' => ''
            ];
            echo json_encode($response, JSON_UNESCAPED_SLASHES);
            exit;
        }
        // 获取上传的文件名
        $fileName = isset($_FILES['image']['name']) ? $_FILES['image']['name'] : '';
        // 检查文件名是否存在
        if (empty($fileName)) {
            http_response_code(401);
            $response = [
                'success' => false,
                'message' => '无法获取文件名',
                'image_url' => ''
            ];
            echo json_encode($response, JSON_UNESCAPED_SLASHES);
            exit;
        }
        // 生成预期的签名:时间戳+API密钥+文件名的MD5值
        $expectedSignature = md5($timestamp . $apiKey . $fileName);
        // 验证签名
        if ($signature !== $expectedSignature) {
            http_response_code(401);
            $response = [
                'success' => false,
                'message' => '无效的签名',
                'image_url' => ''
            ];
            echo json_encode($response, JSON_UNESCAPED_SLASHES);
            exit;
        }
    } catch (Exception $e) {
        http_response_code(500);
        $response = [
            'success' => false,
            'message' => '验证过程中发生错误:' . $e->getMessage(),
            'image_url' => ''
        ];
        echo json_encode($response, JSON_UNESCAPED_SLASHES);
        exit;
    }
}

// 处理预检请求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    echo json_encode(['success' => true, 'message' => 'Preflight request successful', 'image_url' => ''], JSON_UNESCAPED_SLASHES);
    exit;
}

// 处理非POST请求
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['success' => false, 'message' => '不支持的请求方法', 'image_url' => ''], JSON_UNESCAPED_SLASHES);
    exit;
}

// 全局变量用于跟踪转换状态
global $isConverted, $convertedTmpPath, $originalMimeType;
$isConverted = false;
$convertedTmpPath = null;
$originalMimeType = null;

// 图片配置
$uploadDir = 'uploads';  // 简化目录名,不带斜杠
$maxFileSize = 5 * 1024 * 1024;  // 最大文件大小:5MB

// 确保上传目录存在,如果不存在则创建
if (!file_exists($uploadDir)) {
    mkdir($uploadDir, 0777, true);
    @chmod($uploadDir, 0777);
}

// 确保目录路径格式正确
$uploadDir = rtrim($uploadDir, '/\\') . DIRECTORY_SEPARATOR;

// 获取当前日期,用于创建日期目录
$currentDate = date('Y-m-d');
$dateDir = $uploadDir . $currentDate . DIRECTORY_SEPARATOR;

// 确保日期目录存在,如果不存在则创建
if (!file_exists($dateDir)) {
    mkdir($dateDir, 0777, true);
    @chmod($dateDir, 0777);
}

// 获取当前API的基础URL(用于生成图片URL)
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http";
$host = $_SERVER['HTTP_HOST'];
$port = $_SERVER['SERVER_PORT'];

// 标准端口不需要重复添加
$standardPorts = ['80', '443'];
$portSuffix = in_array($port, $standardPorts) ? '' : ":$port";

// 确保host不包含端口号,避免重复
if (strpos($host, ':') !== false) {
    $host = explode(':', $host)[0];
}

$baseUrl = "$protocol://$host$portSuffix{$_SERVER['REQUEST_URI']}";
$baseUrl = dirname($baseUrl) . '/';

try {
    // 检查是否收到文件
    if (!isset($_FILES['image'])) {
        http_response_code(400);
        $response = [
            'success' => false,
            'message' => '没有收到上传的文件',
            'image_url' => ''
        ];
        echo json_encode($response, JSON_UNESCAPED_SLASHES);
        exit;
    }
    
    // 验证请求签名
    validateRequest();
    $file = $_FILES['image'];
    
    // 检查文件上传错误
    if ($file['error'] !== UPLOAD_ERR_OK) {
        http_response_code(400);
        $response = [
            'success' => false,
            'message' => '文件上传错误,错误代码: ' . $file['error'],
            'image_url' => ''
        ];
        echo json_encode($response, JSON_UNESCAPED_SLASHES);
        exit;
    }
    
    // 检查文件大小
    if ($file['size'] > $maxFileSize) {
        http_response_code(400);
        $response = [
            'success' => false,
            'message' => '文件过大,最大允许5MB',
            'image_url' => ''
        ];
        echo json_encode($response, JSON_UNESCAPED_SLASHES);
        exit;
    }
    
    // 检查文件类型(MIME类型验证)
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mimeType = $finfo->file($file['tmp_name']);
    
    // 获取文件扩展名进行辅助验证
    $fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    $allowedExtensions = ['jpg', 'jpeg', 'png'];
    
    // 添加更多可能的图片MIME类型
    $supportedTypes = [
        'image/jpeg', 'image/jpg', 'image/png', 'image/x-png', 'application/png', 'image/vnd.png'
    ];
    
    // 主验证逻辑:优先MIME类型验证,MIME类型不匹配时进行文件签名验证
    $isValidImage = in_array($mimeType, $supportedTypes);
    
    // 保存原始MIME类型用于响应
    $originalMimeType = $mimeType;
    
    // 如果MIME类型不匹配但扩展名为允许的类型,进行文件签名验证
    if (!$isValidImage && in_array($fileExtension, $allowedExtensions)) {
        $isValidImage = verifyImageSignature($file['tmp_name']);
    }
    
    // 如果MIME类型不匹配且不是有效图片签名,但文件扩展名是图片类型,尝试转换
    if (!$isValidImage && in_array($fileExtension, $allowedExtensions)) {
        // 尝试转换为标准JPG
        $convertedPath = convertToStandardJPG($file['tmp_name']);
        if ($convertedPath) {
            // 验证转换后的文件
            $convertedMimeType = $finfo->file($convertedPath);
            if (in_array($convertedMimeType, $supportedTypes)) {
                // 替换原始临时文件路径
                $file['tmp_name'] = $convertedPath;
                $mimeType = $convertedMimeType;
                $fileExtension = 'jpg';
                $isValidImage = true;
                
                // 设置转换状态
                $isConverted = true;
                $convertedTmpPath = $convertedPath;
            } else {
                // 转换失败,删除临时文件
                @unlink($convertedPath);
            }
        }
    }
    
    if (!$isValidImage) {
        http_response_code(400);
        $response = [
            'success' => false,
            'message' => '不支持的文件类型,只允许JPG、PNG。检测到类型:' . $mimeType,
            'image_url' => ''
        ];
        echo json_encode($response, JSON_UNESCAPED_SLASHES);
        exit;
    }
    
    // 生成唯一的文件名
    $originalExtension = pathinfo($file['name'], PATHINFO_EXTENSION);
    $extension = !empty($originalExtension) ? strtolower($originalExtension) : 'jpg';
    $filename = bin2hex(random_bytes(16)) . '.' . $extension;
    // 构建目标路径,使用日期目录
    $targetPath = $dateDir . $filename;
    // 作为最后的备选方案,尝试使用当前脚本所在目录
    $scriptDir = __DIR__ . DIRECTORY_SEPARATOR;
    $backupTargetPath = $scriptDir . 'uploads_' . basename($targetPath);
    
    // 移动上传的文件并设置权限
    if (move_uploaded_file($file['tmp_name'], $targetPath)) {
        // 设置文件权限
        @chmod($targetPath, 0644);
        // 返回成功信息和图片URL
        $webPath = 'uploads/' . $currentDate . '/' . $filename;
        $fullImageUrl = $baseUrl . $webPath;
        // 构建简化的响应数据
        $message = $isConverted ? '图片上传成功(已转换为标准格式)' : '图片上传成功';
        if ($isConverted) {
            $message .= ',原始类型:' . $originalMimeType;
        }
        $response = [
            'success' => true,
            'message' => $message,
            'image_url' => $fullImageUrl
        ];
        // 确保JSON格式正确且不转义斜杠
        $jsonResponse = json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        echo $jsonResponse;
        // 如果有转换的临时文件,尝试删除
        if ($isConverted && isset($convertedTmpPath) && file_exists($convertedTmpPath)) {
            @unlink($convertedTmpPath);
        }
    } else {
        // 尝试备选路径
        if (move_uploaded_file($file['tmp_name'], $backupTargetPath)) {
            @chmod($backupTargetPath, 0644);
            // 备选路径也使用日期目录结构
            $backupDateDir = $scriptDir . 'uploads_' . $currentDate . DIRECTORY_SEPARATOR;
            if (!file_exists($backupDateDir)) {
                mkdir($backupDateDir, 0777, true);
                @chmod($backupDateDir, 0777);
            }
            $newBackupTargetPath = $backupDateDir . basename($backupTargetPath);
            // 移动到日期子目录
            rename($backupTargetPath, $newBackupTargetPath);
            // 构建完整的图片URL
            $fullImageUrl = $baseUrl . 'uploads_' . $currentDate . '/' . basename($backupTargetPath);
            // 构建简化的响应数据
            $message = $isConverted ? '图片上传成功(已转换为标准格式,使用备选路径)' : '图片上传成功(使用备选路径)';
            if ($isConverted) {
                $message .= ',原始类型:' . $originalMimeType;
            }
            $response = [
                'success' => true,
                'message' => $message,
                'image_url' => $fullImageUrl
            ];
            // 确保JSON格式正确且不转义斜杠
            $jsonResponse = json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            echo $jsonResponse;
            // 如果有转换的临时文件,尝试删除
            if ($isConverted && isset($convertedTmpPath) && file_exists($convertedTmpPath)) {
                @unlink($convertedTmpPath);
            }
        } else {
            http_response_code(500);
            // 确保JSON输出正确,对路径进行处理以避免JSON格式问题
            $debugInfo = [
                'target_path' => str_replace('\\', '/', $targetPath),
                'backup_target_path' => str_replace('\\', '/', $backupTargetPath),
                'directory_exists' => file_exists($uploadDir),
                'directory_writable' => is_writable($uploadDir),
                'script_directory_writable' => is_writable($scriptDir),
                'error_message' => error_get_last() ? error_get_last()['message'] : '无错误信息'
            ];
            $response = [
                'success' => false,
                'message' => '无法保存上传的文件,请检查目录权限。路径:' . $targetPath,
                'image_url' => ''
            ];
            echo json_encode($response, JSON_UNESCAPED_SLASHES);
        }
    }
} catch (Exception $e) {
    // 全局异常处理
    http_response_code(500);
    $response = [
            'success' => false,
            'message' => '服务器处理错误:' . $e->getMessage(),
            'image_url' => ''
        ];
    echo json_encode($response, JSON_UNESCAPED_SLASHES);
}
// 确保脚本结束
exit;
?>


好了,源码
php图片上传.rar (234.08 KB, 下载次数: 30, 售价: 2 枚 精币)

点评

上传进度是易语言发送端的事,自己更换下那些带上传进度的,测试用的 网页访问_对象   广东省茂名市  发表于 2025-10-19 23:57
非常好的东西可以跨平台但是我测试了如果大文件没有上传进度是否可以看到或是判断上传进度呢小文件当然不需要这个我说的是大文件   浙江省嘉兴市  发表于 2025-10-19 20:08
新版!支持主流图片格式,支持所有文件上传 https://bbs.ijingyi.com/forum.php?mod=viewthread&tid=14866070   广东省茂名市  发表于 2025-10-19 11:02
@***ftt 任何文件,断点续传,在 61 楼 置顶回复中...   广东省茂名市  发表于 2025-10-19 09:46
能加个支持***传文件的吗 支持任意格式的***支持断点传输的   浙江省杭州市  发表于 2025-10-18 19:21
NX线程池、Mysql连接、PHP中间件(多数据库) 全开源 在线验证 https://bbs.ijingyi.com/forum.php?mod=viewthread&tid=14865869   广东省茂名市  发表于 2025-10-18 18:06

评分

参与人数 5精币 +5 收起 理由
文西哥 + 1 感谢分享,很给力!~
chuanqibuding + 1 感谢分享,很给力!~
cui870222829 + 1 感谢分享,很给力!~
kyo9766 + 1 感谢分享,很给力!~
远古石桥 + 1 支持开源~!感谢分享

查看全部评分


结帖率:100% (11/11)

签到天数: 18 天

 楼主| 发表于 2025-10-19 09:44:48 | 显示全部楼层   广东省茂名市
本帖最后由 jcos 于 2025-10-21 11:36 编辑

想要支持任何文件?支持断点续传?
新版!支持主流图片格式,支持所有文件上传


https://bbs.ijingyi.com/forum.php?mod=viewthread&tid=14866070


新版本,升级支持更多图片格式,同时支持更高的php版本
优化了性能等等。

回复 支持 反对

使用道具 举报

结帖率:25% (1/4)

签到天数: 18 天

发表于 2025-12-5 12:01:23 | 显示全部楼层   四川省泸州市
图片上传 全开源 快速稳定
回复 支持 反对

使用道具 举报

签到天数: 7 天

发表于 2025-11-28 10:13:24 | 显示全部楼层   重庆市重庆市

回帖奖励 +2 枚 精币

看看能不能用
回复 支持 反对

使用道具 举报

结帖率:0% (0/2)

签到天数: 12 天

发表于 2025-11-22 16:19:44 | 显示全部楼层   上海市上海市

回帖奖励 +2 枚 精币

感谢分享
回复 支持 反对

使用道具 举报

结帖率:100% (9/9)

签到天数: 13 天

发表于 2025-11-20 14:59:28 | 显示全部楼层   辽宁省盘锦市

回帖奖励 +2 枚 精币

感谢分享,很给力!~
回复 支持 反对

使用道具 举报

发表于 2025-11-13 01:57:36 | 显示全部楼层   江苏省扬州市

回帖奖励 +2 枚 精币

感谢分享,很给力!~
回复 支持 反对

使用道具 举报

签到天数: 1 天

发表于 2025-11-12 13:51:53 | 显示全部楼层   四川省乐山市
这个不是法国一次了
回复 支持 反对

使用道具 举报

签到天数: 3 天

发表于 2025-11-10 22:52:41 | 显示全部楼层   四川省自贡市
怎么学习,依然都是在追赶,从未超越
回复 支持 反对

使用道具 举报

签到天数: 18 天

发表于 2025-11-9 00:28:53 | 显示全部楼层   山西省忻州市

回帖奖励 +2 枚 精币

谢谢楼主分享
回复 支持 反对

使用道具 举报

结帖率:67% (2/3)

签到天数: 3 天

发表于 2025-11-8 23:29:43 | 显示全部楼层   福建省泉州市
支持开源~!感谢分享
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

关闭

精易论坛 - 有你更精彩上一条 /1 下一条

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报QQ: 793400750,邮箱:wp@125.la
网站简介:精易论坛成立于2009年,是一个程序设计学习交流技术论坛,隶属于揭阳市揭东区精易科技有限公司所有。
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备2025452707号) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表