Web开发--上传文件

最近有个项目要求做到通过web方式在线更新一款嵌入式(snmp agent)设备的应用程序。所以做了以下调试,然后权衡利弊后选择第三种方式

这里介绍三种方式实现web上传文件:

*1. 使用插件*
下面这个连接介绍了十种插件。但是由于官方下载的插件例子都是php作为后台处理脚本,我做的嵌入式应用是不支持PHP的,只能支持C和shell。
http://www.chinaz.com/free/2013/0409/298937.shtml
upload.html

<html>
<head>
<title>My Uploadify Implementation</title>

<link rel="stylesheet" type="text/css" href="commJS/uploadify/uploadify.css" />
<script type="text/javascript" src="commJS/jquery-1.8.2.min.js"></script>
<script type="text/javascript" src="commJS/uploadify/jquery.uploadify.js"></script>
<script type="text/javascript" src="commJS/uploadify/jquery.uploadify.min.js"></script>
<script type="text/javascript">
        $(function() {
            $('#file_upload').uploadify({
                'swf'      : 'commJS/uploadify/uploadify.swf',
                'uploader' : 'commJS/uploadify/uploadify.php',
                'script': 'commJS/uploadify/uploadify.sh', 
                'fileTypeDesc':'Image Files',
                'fileTypeExts':'*.jpg;*.jpeg;*.gif;*.png;*.bmp',
                'buttonText' : 'BROWSE...',
                 'debug' : true,
                 'auto'                 : true, //是否自动上传,   
                 'cancelImage' : 'commJS/uploadify/uploadify-cancel.png',
                 //修改formData数据   
                'onUploadStart' : function(file) {
                    $("#file_upload").uploadify("settings", "someOtherKey", 2);   
                },

                 //上传成功   
                'onUploadSuccess' : function(file, data,
                        response) {
                        alert(file.name + ' | ' + response + ':' + data);   
                    },
                 'onUploadComplete' : function(file) {
                                    alert('The file ' + file.name + ' finished processing.');   
                                }
                // Your options here
            });
        });
</script>
</head>
<body>
    <input type="file" name="file_upload" id="file_upload" />
</body>
</html>

PHP:

<?php
/*
Uploadify
Copyright (c) 2012 Reactive Apps, Ronnie Garcia
Released under the MIT License <http://www.opensource.org/licenses/mit-license.php> 
*/

// Define a destination
$targetFolder = '/uploads'; // Relative to the root

$verifyToken = md5('unique_salt' . $_POST['timestamp']);

if (!empty($_FILES) && $_POST['token'] == $verifyToken) {
    $tempFile = $_FILES['Filedata']['tmp_name'];
    $targetPath = $_SERVER['DOCUMENT_ROOT'] . $targetFolder;
    $targetFile = rtrim($targetPath,'/') . '/' . $_FILES['Filedata']['name'];

    // Validate the file type
    $fileTypes = array('jpg','jpeg','gif','png'); // File extensions
    $fileParts = pathinfo($_FILES['Filedata']['name']);

    if (in_array($fileParts['extension'],$fileTypes)) {
        move_uploaded_file($tempFile,$targetFile);
        echo '1';
    } else {
        echo 'Invalid file type.';
    }
}
?>

*2. 使用Post方式*
经过测试发现这种方式一次只能传送3KB大小的文件,而我要上传的文件有5M左右,所以此方式感觉行不通,或者可以修改代码实现分段上传;但是感觉实现起来太麻烦就放弃了。
参考:代码做了一下修改以实现文件成功上传。
http://www.justwinit.cn/post/6314/
http://wenku.baidu.com/link?url=XDk_VErYp8H-y4Rjw8a5g832FDEK6U0N-P_PrMSn_BCdCKTuHszrTM06xVOuos41olCNAyIXLMC_0lJrSDH6_uQgzeTRmS8CxUS1X-Xesju

upload.html

<FORM METHOD="POST" id="form1" name="form1" ENCTYPE="multipart/form-data" ACTION="cgi-bin/setUpgrade.cgi">
select file:<INPUT TYPE="FILE" NAME="FILE1" id="FILE1"><INPUT TYPE="button" onclick="upload_file()" VALUE="上传" ><br>
<center>
<div id="uploadInfo"></div>
</center>
</FORM>

setUpgrade.c

#include   <stdio.h> 
#include   <stdlib.h> 
#include   <string.h> 
#include   <ctype.h> 
#include   <fcntl.h> 

#include <volcfg.h>
#include <shmram.h>
#include <volmibvar.h>
#include <s_share.h>
#include <cgi.h>
#include <gsemaphore.h>
#include <volSNTPTask.h>


#define UPLOAD_PATH "/root/upgrade.tar.gz"  //"../htdocs/ac483/ac483_bak"

void fit(char *,unsigned size);

int   main(int   argc,   char   *argv[]) 
{ 
    printf("Content-Type:text/html\n\n");     
    char   *pMethod   =   getenv("REQUEST_METHOD");
    //printf("pMethod=%s\n",pMethod); 
    if(pMethod   ==   NULL   ||   *pMethod   ==   0) 
    { 
        printf("No   Any   Method!\n"); 
        return   0; 
    } 

    if(strcmp(pMethod,   "GET")   ==   0) 
        return   1; 

    if(strcmp(pMethod,   "POST")   ==   0) 
    { 
        char   *pCntLen   =   getenv("CONTENT_LENGTH"); 
        //printf("pCntLen=%s\n",pCntLen);
        if(!pCntLen) 
        { 
            printf("Can't   get   Content_Length!\n"); 
            return   0; 
        } 
        if(*pCntLen   ==   0) 
        { 
            printf("Can't   get   Content_Length!\n"); 
            return   0; 
        } 
        int   StrLen   =   atoi(pCntLen);
        // printf("StrLen=%d\n",StrLen); 
        if(StrLen   <=   0) 
        { 
            printf("String   Length   <=   0\n"); 
            return   0; 
        } 
        //char *ph = getenv("CONTENT_TYPE");
        //printf("ph=%s\n",ph);

        char *readstr=(char   *)malloc(StrLen+1);
        fread(readstr,StrLen,1,stdin);
        // printf("readstr=%s\n",readstr);
        int n=0;
        int firstLineMark=0;
        int firstLineCount=0;
        int headCount=0;
        while(n<4)
        {
            if(*(readstr++)=='\n')
            {
                firstLineMark++;
                n++;
            }
            if(firstLineMark==0)
            {
                firstLineCount++;
            }
            headCount++;

        }

        StrLen = StrLen-headCount;
        fit(readstr,13);
        /*if(strcmp(readstr,ACHEAD)!=0)
        {
            printf("<SCRIPT language=JavaScript>alert('file format not support!');javascript:history.go(-1)</SCRIPT>");

            return 0;
        }
        */
        FILE *fp;
        if((fp=fopen(UPLOAD_PATH,"wb"))==NULL)
        {
            printf("error open file\n");
            return 0;
        }
        fwrite(readstr+13,StrLen-13-firstLineCount-5,1,fp);
        fclose(fp);
        printf("<SCRIPT language=JavaScript>alert('congratulations!');javascript:history.go(-1)</SCRIPT>");

    } 
    return   0; 
}   
void fit(char *string,unsigned size)
{
    if(strlen(string)>size)
        *(string+size)='\0';
}

*3. 使用websocket方式上传文件*
文件上传过程:先建立sock连接,web client通过sock发送要上传的文件名字,收到服务器返回的ACK后就开始发送文件,每次发送1KB并等待服务器返回ACK后发送下1KB直到全部发送完成,最后发送Send success。
web界面参考了下面的连接
http://www.cnblogs.com/tianma3798/p/5852527.html
服务器端则参考了下面的连接:对于服务器程序有两个地方我是建议修改的,一个是每次调用analyData();函数的时候要先释放掉前一次占用的内存空间(加入代码 if(payloadData !=NULL) free(payloadData); );二是response(); 中的
if(!data ) //这里还是个野指针。
{
return;
}

改为
if(!message) // data 改为 message
{
return;
}

http://blog.csdn.net/xxdddail/article/details/19070149

**

Code:

**
upload.html

<html>

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

    </head>
<body>
    <div >
        <div >
            <div >Block read the file:</div>
            <div  id="bodyOne">
                <input type="file" value="Browse..." id="file"  multiple/>
                <input type="button" onclick="upgrade()"  value="Upgrade"/><br />
            </div>
        </div>
    </div>
</body>
    <script type="text/javascript">

    //封装 单个文件上传实例
    (function () {
        var ip_addr = document.location.hostname;
        var url = 'ws://'+ip_addr+":8000";
        //指定上传文件,创建上传操作对象
        function uploadOperate(file) {
            var _this = this;
            this.reader = new FileReader();//读取文件对象
            this.step = 1024 * 1;//每次读取文件字节数
            this.curLoaded = 0; //当前读取位置
            this.file = file;   //当前文件对象
            this.enableRead = true; //指定是否可读取,
            this.total = file.size;  //当前文件总大小
            this.startTime = new Date(); //开始读取时间
            //创建显示
            this.createItem();
            this.initWebSocket(function () {
                _this.bindReader();
            });
            console.info('file size:' + this.total);
        }
        uploadOperate.prototype = {
            //绑定读取事件
            bindReader: function () {
                var _this = this;
                var reader = this.reader;
                var ws = this.ws;
                if (_this.curLoaded <= 0)
                {
                    ws.send(_this.file.name);
                    console.log('----ws.send(_this.file.name);...');
                }
                reader.onload = function (e) {
                    //判断是否能再次读取
                    if (_this.enableRead == false) return;
                    //根据当前缓冲区 控制读取速度
                    if (ws.bufferedAmount >= _this.step * 20) {
                        setTimeout(function () {
                            _this.loadSuccess(e.loaded);
                        }, 5);
                        console.info('----wait...');
                    } else {
                        _this.loadSuccess(e.loaded);
                    }
                }
                //开始读取
                //_this.readBlob();
            },
            //读取成功,操作处理
            loadSuccess: function (loaded) {
                var _this = this;
                var ws = _this.ws;
                //使用WebSocket 将二进制输出上传到服务器
                var blob = _this.reader.result;

                ws.send(blob);//send to server
                console.log('----ws.send(blob);...');

                //当前发送完成,继续读取
                _this.curLoaded += loaded;
                if (_this.curLoaded < _this.total) {

                } else {
                    //发送读取完成
                    ws.send('Send success.');
                    console.log('----ws.send(\'Send success.\');...');
                    //读取完成
                    console.log('Total upload:' + _this.curLoaded + ',Total time:' + (new Date().getTime() - _this.startTime.getTime()) / 1000);
                }
                //显示进度等
                _this.showProgress();
            },
            //创建显示项
            createItem: function () {
                var _this = this;
                var blockquote = document.createElement('blockquote');
                var abort = document.createElement('input');
                abort.type = 'button';
                abort.value = 'Pause';
                abort.onclick = function () {
                    _this.stop();
                };
                blockquote.appendChild(abort);

                var containue = document.createElement('input');
                containue.type = 'button';
                containue.value = 'Continue';
                containue.onclick = function () {
                    _this.containue();
                };
                blockquote.appendChild(containue);

                var progress = document.createElement('progress');
                progress.style.width = '400px';
                progress.max = 100;
                progress.value = 0;
                blockquote.appendChild(progress);
                _this.progressBox = progress;

                var status = document.createElement('p');
                status.id = 'Status';
                blockquote.appendChild(status);
                _this.statusBox = status;

                document.getElementById('bodyOne').appendChild(blockquote);
            },
            //显示进度
            showProgress: function () {
                var _this = this;
                var percent = (_this.curLoaded / _this.total) * 100;
                _this.progressBox.value = percent;
                _this.statusBox.innerHTML = percent;
            },
            //执行读取文件
            readBlob: function () {
                var blob = this.file.slice(this.curLoaded, this.curLoaded + this.step);
                this.reader.readAsArrayBuffer(blob);
            },
            //中止读取
            stop: function () {
                this.enableRead = false;
                this.reader.abort();
                console.log('Stop read,curLoaded:' + this.curLoaded);
            },
            //继续读取
            containue: function () {
                this.enableRead = true;
                this.readBlob();
                console.log('Continue read,curLoaded:' + this.curLoaded);
            },
            //初始化 绑定创建连接
            initWebSocket: function (onSuccess) {
                var _this = this;
                var ws = this.ws = new WebSocket(url); //初始化上传对象
                ws.onopen = function () {// 三次握手---horsen
                    console.log('connect creat success');
                    if (onSuccess)
                        onSuccess();
                }
                ws.onmessage = function (e) {
                    var data = e.data;
                    if (isNaN(data) == false) {
                        console.log('Sever receive success:' + data);
                    } else {
                        console.log(data);
                        _this.readBlob();-----------------------------收到服务器返回的ok,才发送下一个数据
                    }
                }
                ws.onclose = function (e) {
                    //中止读取
                    _this.stop();
                    console.log('connect is broken');
                }
                ws.onerror = function (e) {
                    //中止读取
                    _this.stop();
                    console.log('Exception occurs:' + e.message);
                }
            }
        };
        window.uploadOperate = uploadOperate;
    })();
    </script>
    <script type="text/javascript">
    //var fileBox = document.getElementById('file');
    //  fileBox.onChange = function () {
        function upgrade() {
            var fileBox = document.getElementById('file');
            var files = fileBox.files;
            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                var operate = new uploadOperate(file);
            }
        }
  </script>

</html>

uploadserver.c

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include   <fcntl.h> 

#include <volcfg.h>
#include <shmram.h>
#include <volmibvar.h>
#include <s_share.h>
#include <cgi.h>
#include <gsemaphore.h>
#include <volSNTPTask.h>

#include "base64.h"  
#include "sha1.h"  
#include "intLib.h"  

#define UPLOAD_PATH "/root/"

#define REQUEST_LEN_MAX 1024*10 
#define DEFEULT_SERVER_PORT 8000  
#define WEB_SOCKET_KEY_LEN_MAX 256  
#define RESPONSE_HEADER_LEN_MAX 1024  
#define LINE_MAX 256  

#if(1)
void shakeHand(int connfd,const char *serverKey);  
char * fetchSecKey(const char * buf);  
char * computeAcceptKey(const char * buf);  
char * analyData(const char * buf,const int bufLen, int* datalen);  
char * packData(const char * message,unsigned long * len);  
void response(const int connfd,const char * message);  
int saveFile(FILE **fp,char* buf,int length);


#endif

int main(int argc, char *argv[])  
{  
    struct sockaddr_in servaddr, cliaddr;  
    socklen_t cliaddr_len;  
    int listenfd, connfd;  
    char buf[REQUEST_LEN_MAX]; 
    char tempbuf[32]={"\0"};
    char *data;  
    char str[INET_ADDRSTRLEN];  
    char *secWebSocketKey;  
    int i,n,datalength,datalengthAll=0;  
    int connected=0;//0:not connect.1:connected.  
    int port= DEFEULT_SERVER_PORT;  

    FILE *fp;

    if(argc>1)  
    {  
        port=atoi(argv[1]);  
    }  
    if(port<=0||port>0xFFFF)  
    {  
        printf("Port(%d) is out of range(1-%d)\n",port,0xFFFF);  
        return;  
    }  
    listenfd = socket(AF_INET, SOCK_STREAM, 0);  

    bzero(&servaddr, sizeof(servaddr));  
    servaddr.sin_family = AF_INET;  
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
    servaddr.sin_port = htons(port);  

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));  

    listen(listenfd, 20);  

    printf("Listen %d\nAccepting connections ...\n",port);  
    cliaddr_len = sizeof(cliaddr);  
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);  
    printf("From %s at PORT %d\n",  
        inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),  
        ntohs(cliaddr.sin_port));  

    while (1)  
    {  

        memset(buf,0,REQUEST_LEN_MAX);  
        //printf("--------read..-------------\n");  
        n = read(connfd, buf, REQUEST_LEN_MAX);   
        if(0==n) break; 

        if(0==connected)  
        {  
            //printf("read:%d\n%s\n",n,buf);  
            secWebSocketKey=computeAcceptKey(buf);    
            shakeHand(connfd,secWebSocketKey);  
            connected=1;  
            continue;  
        }  

        //printf("******************data analy*****************\n");  

        data=analyData(buf,n,&datalength); 
        fflush(stdout);
        datalengthAll += datalength;
        //printf("reveice size:%d\r",datalengthAll);

        if(memcmp(data,"Send success.",13)==0) {
            printf("Receive success!\n"); 
            sprintf(tempbuf,"%d",datalengthAll);
            response(connfd,tempbuf);
            sleep(2);
            break;
        }

        saveFile(&fp,data,datalength);

        response(connfd,"ACK\n");

    }

    fclose(fp);
    close(connfd);  

}  

#if(1)
int saveFile(FILE **fp,char* buf,int length){
    int res=0;
    if(*fp==NULL){
        char sfile[64]={"\0"};
        sprintf(sfile,"%s%s",UPLOAD_PATH,buf);

        if(access(sfile,R_OK) == 0){// if file have exist ,remove it.
            if(unlink(sfile)<0){
                printf("unlink %s error !\n",sfile);
                return 0;
            }
        }
        printf("sfile:%s--buflen(%d)!\n",sfile,strlen(buf));
        if((*fp=fopen(sfile,"wb"))==NULL)// creat a file.
        {
            printf("error open file %s\n",sfile);
            return 0;
        }
        printf("saveFile creat file %s success! %d  !\n",sfile,length);
        return 1;
    }

    res= fwrite(buf,length,1,*fp);
    //printf("res=%d---length=%d--\n",res,length);//debug-----------------
    return length;
}
/*******************************************************/
char * fetchSecKey(const char * buf)  
{  
    char *key;  
    char *keyBegin;  
    char *flag="Sec-WebSocket-Key: ";  
    int i=0, bufLen=0;  

    key=(char *)malloc(WEB_SOCKET_KEY_LEN_MAX);  
    memset(key,0, WEB_SOCKET_KEY_LEN_MAX);  
    if(!buf)  
    {  
        return NULL;  
    }  

    keyBegin=strstr(buf,flag);  
    if(!keyBegin)  
    {  
        return NULL;  
    }  
    keyBegin+=strlen(flag);  

    bufLen=strlen(buf);  
    for(i=0;i<bufLen;i++)  
    {  
        if(keyBegin[i]==0x0A||keyBegin[i]==0x0D)  
        {  
            break;  
        }  
        key[i]=keyBegin[i];  
    }  

    return key;  
}  

char * computeAcceptKey(const char * buf)  
{  
    char * clientKey;  
    char * serverKey;   
    char * sha1DataTemp;  
    char * sha1Data;  
    short temp;  
    int i,n;  
    const char * GUID="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";  


    if(!buf)  
    {  
        return NULL;  
    }  
    clientKey=(char *)malloc(LINE_MAX);  
    memset(clientKey,0,LINE_MAX);  
    clientKey=fetchSecKey(buf);  

    if(!clientKey)  
    {  
        return NULL;  
    }  


    strcat(clientKey,GUID);  

    sha1DataTemp=sha1_hash(clientKey);  
    n=strlen(sha1DataTemp);  


    sha1Data=(char *)malloc(n/2+1);  
    memset(sha1Data,0,n/2+1);  

    for(i=0;i<n;i+=2)  
    {        
        sha1Data[i/2]=htoi(sha1DataTemp,i,2);      
    }   

    serverKey = base64_encode(sha1Data, strlen(sha1Data));   

    return serverKey;  
}  

void shakeHand(int connfd,const char *serverKey)  
{  
    char responseHeader [RESPONSE_HEADER_LEN_MAX];  

    if(!connfd)  
    {  
        return;  
    }  

    if(!serverKey)  
    {  
        return;  
    }  

    memset(responseHeader,'\0',RESPONSE_HEADER_LEN_MAX);  

    sprintf(responseHeader, "HTTP/1.1 101 Switching Protocols\r\n");  
    sprintf(responseHeader, "%sUpgrade: websocket\r\n", responseHeader);  
    sprintf(responseHeader, "%sConnection: Upgrade\r\n", responseHeader);  
    sprintf(responseHeader, "%sSec-WebSocket-Accept: %s\r\n\r\n", responseHeader, serverKey);  

    printf("Response Header:%s\n",responseHeader);  

    write(connfd,responseHeader,strlen(responseHeader));  
}  

/***********************************************************************/
char * analyData(const char * buf,const int bufLen, int* datalen)  
{  
    char * data;  
    char fin, maskFlag,masks[4];  
    static char * payloadData=NULL;  
    char temp[8];  
    unsigned long n, payloadLen=0;  
    unsigned short usLen=0;  
    int i=0;   

    if(payloadData !=NULL) free(payloadData);// 释放上次读取数据的内存空间,数据已经保存到文件里面去了

    if (bufLen < 2)   
    {  
        return NULL;  
    }  

    fin = (buf[0] & 0x80) == 0x80; // 1bit,1表示最后一帧    
    if (!fin)  
    {  
        return NULL;// 超过一帧暂不处理   
    }  

    maskFlag = (buf[1] & 0x80) == 0x80; // 是否包含掩码    
    if (!maskFlag)  
    {  
        return NULL;// 不包含掩码的暂不处理  
    }  

    payloadLen = buf[1] & 0x7F; // 数据长度   
    if (payloadLen == 126)  
    {        
        memcpy(masks,buf+4, 4);        
        payloadLen =(buf[2]&0xFF) << 8 | (buf[3]&0xFF);    
        payloadData=(char *)malloc(payloadLen);  
        memset(payloadData,0,payloadLen);  
        memcpy(payloadData,buf+8,payloadLen);  
    }  
    else if (payloadLen == 127)  
    {  
        memcpy(masks,buf+10,4);    
        for ( i = 0; i < 8; i++)  
        {  
            temp[i] = buf[9 - i];  
        }   

        memcpy(&n,temp,8);    
        payloadData=(char *)malloc(n);   
        memset(payloadData,0,n);   
        memcpy(payloadData,buf+14,n);//toggle error(core dumped) if data is too long.  
        payloadLen=n;      
    }  
    else  
    {     
        memcpy(masks,buf+2,4);      
        payloadData=(char *)malloc(payloadLen);  
        memset(payloadData,0,payloadLen);  
        memcpy(payloadData,buf+6,payloadLen);   
    }  

    for (i = 0; i < payloadLen; i++)  
    {  
        payloadData[i] = (char)(payloadData[i] ^ masks[i % 4]);  
    }  

    //printf("data(%d):%s\n",(int)payloadLen,payloadData);  
    *datalen = (int)payloadLen;
    return payloadData;  
}  

char *  packData(const char * message,unsigned long * len)  
{  
    char * data=NULL;  
    unsigned long n;  

    n=strlen(message);  
    if (n < 126)  
    {  
        data=(char *)malloc(n+2);  
        memset(data,0,n+2);      
        data[0] = 0x81;  
        data[1] = n;  
        memcpy(data+2,message,n);  
        *len=n+2;  
    }  
    else if (n < 0xFFFF)  
    {  
        data=(char *)malloc(n+4);  
        memset(data,0,n+4);  
        data[0] = 0x81;  
        data[1] = 126;  
        data[2] = (n>>8 & 0xFF);  
        data[3] = (n & 0xFF);  
        memcpy(data+4,message,n);      
        *len=n+4;  
    }  
    else  
    {  

        // 暂不处理超长内容    
        *len=0;  
    }  


    return data;  
}  

void response(int connfd,const char * message)  
{  
    char * data;  
    unsigned long n=0;  
    int i;  
    if(!connfd)  
    {  
        return;  
    }  

    if(!message)  // data 改为 message
    {  
        return;  
    }  
    data=packData(message,&n);   

    if(!data||n<=0)  
    {  
        printf("data is empty!\n");  
        return;  
    }   

    write(connfd,data,n);  

}  
#endif
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

horsen_duan

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值