PHP 华为云H5上传文件:临时链接上传文件和POST表单直传

发布于:2025-06-25 ⋅ 阅读:(23) ⋅ 点赞:(0)

环境:PHP7.3.4 CI框架

一、完成的大概步骤:

前置工作:(1)安装集成(2)配置好华为云的桶策略、桶ACL、CORS规则【测试期间可以全部用最高权限】

1、后端利用密钥生成临时链接或者表单直传的参数

2、将后端生成的参数传递到H5前端

3、前端根据后端生成的参数或链接直接上传就好。

二、详细步骤:

(1)利用composer或者手动安装华为云插件

composer require huaweicloud/huaweicloud-sdk-php:3.0.3-beta

(2)生成临时链接URL:
官方文档链接:1、临时授权访问OBS_典型场景配置案例_权限配置指南_对象存储服务 OBS-华为云2、使用临时安全凭证直传OBS_移动应用直传_数据直传OBS_最佳实践_对象存储服务 OBS-华为云

代码:Huawei.php

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
//根据自己的实际路径引入
require_once APPPATH . 'libraries/third_party/HuaweiCos/vendor/autoload.php';

class Huawei {
    private $obsClient;
    private $bucketName;
    private $CI;
    private $cos_config;

    public function __construct() {
        $this->CI =& get_instance();
        //读入华为云的配置
        $this->CI->load->config('qcloud');
        $this->cos_config=$cos_config = $this->CI->config->item('qcloud');

        $this->bucketName = $cos_config['bucket'];
        $ak = $cos_config['secretId'];
        $sk = $cos_config['secretKey'];
        $endpoint = $cos_config['endpoint'];

        // 初始化 OBS 客户端
        $this->obsClient = \Obs\ObsClient::factory([
            'key' => $ak,
            'secret' => $sk,
            'endpoint' => $endpoint,
            'socket_timeout' => 30,
            'connect_timeout' => 10
        ]);
    }

    // 获取临时链接
    public function getFederationToken($path) {  
        $objectKey = $path;

        try {
            $res = $this->obsClient->createSignedUrl([
                'Method' => 'PUT',
                'Bucket' => $this->bucketName,
                'Key' => $objectKey,
                'Expires' => 3600,
                'Headers' => ['Content-Type' => 'application/octet-stream']
            ]);

            return [
                'success' => true,
                'domain'=>$this->cos_config['domain'],
                'signed_url' => $res['SignedUrl'],
                'object_key' => $objectKey
            ];
        } catch (Exception $e) {
            log_message('error', 'Failed to generate signed URL: ' . $e->getMessage());
            return ['success' => false, 'error' => 'Failed to generate signed URL: ' . $e->getMessage()];
        }
    }
    // 临时获取post请求相关参数
    public function getFederationTokenPost($path) {
        $objectKey = $path;
        try {

            $formParams = [
                // Set object access permission to public-read
                'x-obs-acl' => ObsClient::AclPublicRead,
                // Set object MIME type
                'content-type' => 'text/plain'
            ];
        
            // Set expiration time for the signed request (in seconds)
            $expires = 3600;
        
            // Generate POST signature
            $res = $this->obsClient->createPostSignatureSync([
                'Expires' => $expires,
                'FormParams' => $formParams
            ]);
        
            return [
                'success' => true,
                'domain'=>$this->cos_config['domain'],
                'signed_url' => $res['SignedUrl'],
                'object_key' => $objectKey
            ];
        }catch (Exception $e) {
            log_message('error', 'Failed to generate signed URL: ' . $e->getMessage());
            return ['success' => false, 'error' => 'Failed to generate signed URL: ' . $e->getMessage()];
        }
    }
}

(3)获取临时链接并发送给前端

Test.php

<?php 
class Test extends CI_Controller
{
    public function index()
    {
        $this->load->library('cdnupload/CdnFactory');
        $cdn=CdnFactory::getCdn_foster('Huawei');
        // 存储的相对链接             
        $uploadurl='Test/android/test/abc.jpg';
        $filename='abc.jpg';
        // put获取临时签名
        $res=$cdn->getFederationToken($uploadurl,$filename);
        $data['filename']=$filename;
        $data['signed_url']=$res['signed_url'];
        $data['object_key']=$res['object_key'];
        $data['domain']=$res['domain'];
        $data['UploadPath']=$uploadurl;
        $data['new_version'] = '1.0';	
        $this->load->view("test",$data);
    }
}

(4)前端执行直传操作

test.html

<!DOCTYPE html>
<html>
<head>
    <title>H5 File Upload to Huawei Cloud OBS</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/10.32.0/css/jquery.fileupload.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/10.32.0/css/jquery.fileupload-ui.css">
    <link href="/statics/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
    <style>
        .progress-bar { transition: width 0.3s ease-in-out; }
        .upload-container { max-width: 800px; margin: 50px auto; }
        .fileupload-buttonbar { margin-bottom: 20px; }
        .files tr td { vertical-align: middle; }
        .progress-cell { width: 200px; }
        .size-cell { width: 100px; }
        .operation-cell { width: 150px; }
        .retry-btn { margin-left: 10px; }
        .status-alert { margin-top: 20px; }
        .upload-speed { font-size: 12px; color: #666; }
        .table-striped tbody tr:hover { background-color: #f8f9fa; }
    </style>
</head>
<body>
    <div class="container upload-container">
        <h3 class="text-center"><?php echo htmlspecialchars($game_info['name'], ENT_QUOTES, 'UTF-8'); ?>母包上传</h3>
        <form id="fileupload" method="PUT" >
            <div class="fileupload-buttonbar">
                <div class="fileupload-buttons">
                    <span class="fileinput-button btn btn-primary">
                        <span>选择文件</span>
                        <input type="file" name="files[]" accept=".ipa,.apk">
                    </span>
                    <button type="submit" class="btn btn-success start" disabled>
                        <span>开始上传</span>
                    </button>
                </div>
            </div>
            <table role="presentation" class="table table-striped">
                <thead>
                    <tr>
                        <th>游戏名</th>
                        <th>版本号</th>
                        <th>文件名</th>
                        <th>文件大小</th>
                        <th>上传进度</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody class="files"></tbody>
            </table>
        </form>
        <div id="status" class="alert status-alert" style="display:none;"></div>
    </div>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="/assets/libs/fastadmin-layer/dist/layer.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/10.32.0/js/vendor/jquery.ui.widget.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/10.32.0/js/jquery.iframe-transport.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/10.32.0/js/jquery.fileupload.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/10.32.0/js/jquery.fileupload-process.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/10.32.0/js/jquery.fileupload-validate.js"></script>
    <script>
        $(function () {
            'use strict';
            var cdnDomain = 'http://<?php echo htmlspecialchars($domain, ENT_QUOTES, 'UTF-8'); ?>';
            var filenameURL="<?=$filename?>";
            var signedUrl = '<?php echo htmlspecialchars($signed_url, ENT_QUOTES, 'UTF-8'); ?>';
            var objectKey = '<?php echo htmlspecialchars($object_key, ENT_QUOTES, 'UTF-8'); ?>';
            var isUploading = false;

            if (!signedUrl || !objectKey) {
                layer.msg('错误:无效的签名 URL 或对象键', {icon: 2, time: 2000});
                $('#status').addClass('alert-danger').text('错误:无效的签名 URL 或对象键。').show();
                $('.start').prop('disabled', true);
                return;
            }

            $('#fileupload').fileupload({
                url: signedUrl,
                type: 'PUT',
                dataType: 'xml',
                acceptFileTypes: /(\.|\/)(ipa|apk)$/i,
                maxFileSize: 2 * 1024 * 1024 * 1024, // 2GB
                forceIframeTransport: false,
                multipart: false, // 禁用 multipart/form-data
                add: function (e, data) {
                    if (isUploading) {
                        layer.msg('正在上传,请勿重复操作', {icon: 2, time: 2000});
                        return;
                    }

                    var file = data.files[0];
                    var ext = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase();
                    var expectedExt = objectKey.substring(objectKey.lastIndexOf('.') + 1).toLowerCase();
                   

                    $('.files').empty();
                    data.context = $('<tr/>').appendTo('.files');
                    $('<td/>').text(objectKey.split('/').pop()).appendTo(data.context);
                    $('<td/>').addClass('size-cell').text((file.size / 1024 / 1024).toFixed(2) + ' MB').appendTo(data.context);
                    $('<td/>').addClass('progress-cell').append(
                        $('<div/>').addClass('progress').append(
                            $('<div/>').addClass('progress-bar progress-bar-success').attr('role', 'progressbar').css('width', '0%')
                        ).append(                            
                        )
                    ).appendTo(data.context);
                    $('<td/>').addClass('operation-cell').append(
                        $('<button/>').addClass('btn btn-sm btn-warning retry-btn').text('重试').hide().click(function () {
                            if (!isUploading) {
                                data.context.find('.progress-bar').removeClass('progress-bar-danger').css('width', '0%').text('0%');                              
                                $('#status').hide();
                                data.submit();
                            }
                        })
                    ).appendTo(data.context);

                    $('.start').prop('disabled', false);
                    data.context.data('fileData', data);
                },
                beforeSend: function (xhr, data) {
                    var file = data.files[0];
                    var contentType = 'application/octet-stream';
                    xhr.setRequestHeader('Content-Type', contentType);
                    data.startTime = new Date().getTime();
                    isUploading = true;
                    $('.start').prop('disabled', true);
                },
                progress: function (e, data) {
                    var progress = parseInt(data.loaded / data.total * 100, 10) || 0;
                    data.context.find('.progress-bar').css('width', progress + '%').text(progress + '%');

                    var elapsed = (new Date().getTime() - (data.startTime || new Date().getTime())) / 1000;
                    var speed = 0;
                    if (elapsed > 0.1 && data.loaded && data.total) {
                        speed = data.loaded / elapsed / 1024;
                    }
                    // data.context.find('.upload-speed').text(speed.toFixed(2) + ' KB/s');
                },
                done: function (e, data) {
                    isUploading = false;
                    layer.msg('上传成功', {icon: 1, time: 2000});
                    updateVersionCallback(data.context);
                    var cdnUrl = cdnDomain + '/' + objectKey;
                    data.context.find('.progress-cell .progress-bar').addClass('progress-bar-success').text('已完成');
                    data.context.find('.operation-cell').html(
                        $('<a/>').addClass('btn btn-sm btn-primary').attr('href', cdnUrl).attr('target', '_blank').text('下载')
                    );
                    $('.start').prop('disabled', true);
                },
                fail: function (e, data) {
                    isUploading = false;
                    var errorMsg = '上传失败:' + (data.jqXHR && data.jqXHR.responseXML ? 
                        $(data.jqXHR.responseXML).find('Message').text() : '未知错误');
                    layer.msg(errorMsg, {icon: 2, time: 3000});
                    $('#status').removeClass('alert-success').addClass('alert-danger').text(errorMsg).show();
                    data.context.find('.progress-cell .progress-bar').addClass('progress-bar-danger').text('失败');
                    data.context.find('.retry-btn').show();
                    $('.start').prop('disabled', false);
                }
            });

            $('.start').on('click', function (e) {
                if (isUploading) {
                    layer.msg('正在上传,请勿重复点击', {icon: 2, time: 2000});
                    return;
                }
                $('#status').html('<span style="color:red">上传处理过程中,请勿关闭页面</span>').show();
                e.preventDefault();
                $('.files tr').each(function () {
                    var data = $(this).data('fileData');
                    if (data) {
                        data.submit();
                    }
                });
            });

            function updateVersionCallback(context) {
                // 上传完成后的操作
            }
        });
    </script>
</body>
</html>

post上传:上述Huawei.php中的getFederationTokenPost就是获取post直传的相关参数

post.html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>

<form action="http://bucketname.your-endpoint/" method="post" enctype="multipart/form-data">
Object key
<!-- 对象名 -->
<input type="text" name="key" value="objectname" />
<p>
ACL
<!-- 对象ACL权限 -->
<input type="text" name="x-obs-acl" value="public-read" />
<p>
Content-Type
<!-- 对象MIME类型 -->
<input type="text" name="content-type" value="text/plain" />
<p>
<!-- policy的base64编码值 -->
<input type="hidden" name="policy" value="*** Provide your policy ***" />
<!-- AK -->
<input type="hidden" name="AccessKeyId" value="*** Provide your access key ***"/>
<!-- 签名串信息 -->
<input type="hidden" name="signature" value="*** Provide your signature ***"/>

<input name="file" type="file" />
<input name="submit" value="Upload" type="submit" />
</form>
</body>
</html>

post直传的相关文档:
1、Web端通过PostObject接口直传OBS_数据直传OBS_最佳实践_对象存储服务 OBS-华为云

里面的代码用AI转化为自己需要的语言应该不难,有不同意见的可以评论或私信交流


网站公告

今日签到

点亮在社区的每一天
去签到