整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

asp.net实现图片在线上传并在线裁剪

asp.net实现图片在线上传并在线裁剪

、说明

  接上一篇文章uploadify实现多附件上传完成后,又突然用到头像上传并在线裁剪。在网上找个众多例子都没有符合要求的,有一篇文章写的不错,就是文旺老兄写的这篇Asp.Net平台下的图片在线裁剪功能的实现,大家可以看一下,写的真不错。我就是在参考了他的代码下,结合uploadify使用一般处理程序实现了这个功能,写下了这篇在asp.net实现图片在线上传并在线裁剪,有点绕口哈,废话不多说,现奉上代码,供同学们交流参考,有什么不对的地方,望请大家多多提提建议,多谢!多谢!

2、组成

  首先说明一下代码实现所用到的技术,仅供参考:

    开发工具:vs2010

    目标框架:.NET Framework3.5

    jcrop:Jcrop.js v0.9.12

    Uploadify:uploadify-v3.1

    Jquery:jquery-1.9.0.js

  最后我会将整个Demo上传,如果同学们的电脑上有开发环境可直接打开项目解决方案运行。

3、代码

  Default.aspx(测试页面)



1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ImgJcrop._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

<title>在线裁剪</title>

<link href="Scripts/jcrop/jquery.Jcrop.css" rel="stylesheet" type="text/css" />

<link href="Scripts/uploadify-v3.1/uploadify.css" rel="stylesheet" type="text/css" />

<script src="Scripts/jquery.1.9.0.min.js" type="text/javascript"></script>

<script src="Scripts/jcrop/jquery.Jcrop.js" type="text/javascript"></script>

<script src="Scripts/uploadify-v3.1/jquery.uploadify-3.1.js" type="text/javascript"></script>

<script type="text/javascript">

$(function () {

var jcrop_api, boundx, boundy;

$("#file_upload").uploadify({

"auto": true,

"buttonText": "选择图片",

"swf": "Scripts/uploadify-v3.1/uploadify.swf",

"uploader": "App_Handler/Uploadify.ashx?action=upload",

"fileTypeExts": "*.jpg; *.jpeg; *.gif; *.png; *.bmp",

"fileTypeDesc": "支持的格式:",

"multi": false,

"removeCompleted": false,

"onUploadStart": function (file) {

$("#file_upload-queue").hide();

},

"onUploadSuccess": function (file, data, response) {

var row=eval("[" + data + "]");

if (row[0]["status"]==0) {

$("#cutimg").html("<img id=\"imgOriginal\" name=\"imgOriginal\" /><div style=\"overflow: hidden; margin-top: 20px;\"><div style=\"width: 120px; height: 120px; overflow: hidden;\"><img id=\"imgPreview\" /></div><br /><input type=\"button\" id=\"btnImgCut\" onclick=\"cutSaveImg()\" value=\"裁剪并保存图片\" /></div>");

$("#cutimg img").each(function () { $(this).attr("src", row[0]["message"]); });

$("#hidImgUrl").val(row[0]["message"]);

$('#imgOriginal').Jcrop({

onChange: updatePreview,

onSelect: updatePreview,

aspectRatio: 1,

//maxSize: [120, 120],

setSelect: [0, 0, 120, 120]

}, function () {

var bounds=this.getBounds();

boundx=bounds[0];

boundy=bounds[1];

jcrop_api=this;

});

} else {

alert(row[0]["message"]);

}

}

});

function updatePreview(c) {

if (parseInt(c.w) > 0) {

var rx=120 / c.w;

var ry=120 / c.h;

$("#imgPreview").css({

width: Math.round(rx * boundx) + "px",

height: Math.round(ry * boundy) + "px",

marginLeft: "-" + Math.round(rx * c.x) + "px",

marginTop: "-" + Math.round(ry * c.y) + "px"

});

}

$("#hidXone").val(c.x);

$("#hidYone").val(c.y);

$("#hidXtwo").val(c.hidXtwo);

$("#hidYtwo").val(c.hidYtwo);

$("#hidImgWidth").val(c.w);

$("#hidImgHeight").val(c.h);

};

});

function cutSaveImg() {

$.ajax({

type: "post",

url: "App_Handler/Uploadify.ashx?action=cutsaveimg",

data: { strImgUrl: $("#imgOriginal")[0].src, hidXone: $("#hidXone").val(), hidYone: $("#hidYone").val(), hidImgWidth: $("#hidImgWidth").val(), hidImgHeight: $("#hidImgHeight").val() },

dataType: "html",

success: function (data) {

var row=eval("[" + data + "]");

if (row[0]["status"]==0) { }

alert(row[0]["message"]);

}

});

}

</script>

</head>

<body>

<form id="form1" runat="server">

<div>

<input type="file" id="file_upload" name="file_upload" />

</div>

<div id="cutimg">

</div>

<asp:HiddenField ID="hidXone" runat="server" />

<asp:HiddenField ID="hidYone" runat="server" />

<asp:HiddenField ID="hidXtwo" runat="server" />

<asp:HiddenField ID="hidYtwo" runat="server" />

<asp:HiddenField ID="hidImgWidth" runat="server" />

<asp:HiddenField ID="hidImgHeight" runat="server" />

<asp:HiddenField ID="hidImgUrl" runat="server" />

</form>

</body>

</html>



Uploadify.ashx(一般处理程序)



1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

<%@ WebHandler Language="C#" Class="UploadifyUpload" %>

using System;

using System.Collections;

using System.Data;

using System.Web;

using System.Linq;

using System.Web.Services;

using System.Web.Services.Protocols;

using System.Web.SessionState;

using System.IO;

using System.Collections.Generic;

using System.Web.UI.WebControls;

using System.Text;

using System.Drawing;

using System.Drawing.Imaging;

public class UploadifyUpload : IHttpHandler, IRequiresSessionState

{

public void ProcessRequest(HttpContext context)

{

context.Response.ContentType="text/plain";

context.Response.Charset="utf-8";

string action=context.Request["action"];

switch (action)

{

case "upload":

//上传图片

upload(context);

break;

case "cutsaveimg":

//裁剪并保存

cutsaveimg(context);

break;

}

context.Response.End();

}

/// <summary>

/// 上传图片

/// </summary>

/// <param name="context"></param>

private void upload(HttpContext context)

{

HttpPostedFile postedFile=context.Request.Files["Filedata"];

if (postedFile !=null)

{

string fileName, fileExtension;

int fileSize;

fileName=postedFile.FileName;

fileSize=postedFile.ContentLength;

if (fileName !="")

{

fileExtension=postedFile.FileName.Substring(postedFile.FileName.LastIndexOf('.'));

string strPath=context.Server.MapPath("/") + "\App_File\Upload\";//设置文件的路径

string strFileName="upload" + DateTime.Now.ToString("yyyyMMddHHmmss") + fileExtension;

string strFileUrl=strPath + strFileName;//保存文件路径

if (!Directory.Exists(strPath))

{

Directory.CreateDirectory(strPath);

}

postedFile.SaveAs(strFileUrl);//先保存源文件

context.Response.Write("{\"status\":0,\"message\":\"/App_File/Upload/" + strFileName + "\"}");

}

else

{

context.Response.Write("{\"status\":1,\"message\":\"上传失败!\"}");

}

}

else

{

context.Response.Write("{\"status\":1,\"message\":\"上传失败!\"}");

}

}

/// <summary>

/// 裁剪并保存图片

/// </summary>

/// <param name="context"></param>

private void cutsaveimg(HttpContext context)

{

string strImgUrl=context.Request["strImgUrl"];

string strXone=context.Request["hidXone"];

string strYone=context.Request["hidYone"];

string strImgWidth=context.Request["hidImgWidth"];

string strImgHeight=context.Request["hidImgHeight"];

string[] urls=strImgUrl.Split('/');

string str_url=urls.Last();

try

{

string strOldFiel=context.Server.MapPath("~/App_File/Upload/");

string strNewFiel=context.Server.MapPath("~/App_File/Cut/");

string strOldUrl=Path.Combine(strOldFiel, str_url);

string strNewUrl=Path.Combine(strNewFiel, "cut" + DateTime.Now.ToString("yyyyMMddHHmmss") + "." + str_url.Split('.')[1]);

if (!Directory.Exists(strNewFiel))

{

Directory.CreateDirectory(strNewFiel);

}

int intStartX=int.Parse(strXone);

int intStartY=int.Parse(strYone);

int intWidth=int.Parse(strImgWidth);

int intHeight=int.Parse(strImgHeight);

CutGeneratedImage(intStartX, intStartY, intWidth, intHeight, strOldUrl, strNewUrl);

context.Response.Write("{\"status\":0,\"message\":\"裁剪成功并保存!\"}");

}

catch

{

context.Response.Write("{\"status\":1,\"message\":\"裁剪失败!\"}");

}

}

/// <summary>

/// 裁剪图片

/// </summary>

/// <param name="intWidth">要缩小裁剪图片宽度</param>

/// <param name="intHeight">要缩小裁剪图片长度</param>

/// <param name="strOldImgUrl">要处理图片路径</param>

/// <param name="strNewImgUrl">处理完毕图片路径</param>

public void CutGeneratedImage(int intStartX, int intStartY, int intWidth, int intHeight, string strOldImgUrl, string strNewImgUrl)

{

//上传标准图大小

int intStandardWidth=120;

int intStandardHeight=120;

int intReduceWidth=0; // 缩小的宽度

int intReduceHeight=0; // 缩小的高度

int intCutOutWidth=0; // 裁剪的宽度

int intCutOutHeight=0; // 裁剪的高度

int level=100; //缩略图的质量 1-100的范围

//获得缩小,裁剪大小

if (intStandardHeight * intWidth / intStandardWidth > intHeight)

{

intReduceWidth=intWidth;

intReduceHeight=intStandardHeight * intWidth / intStandardWidth;

intCutOutWidth=intWidth;

intCutOutHeight=intHeight;

}

else if (intStandardHeight * intWidth / intStandardWidth < intHeight)

{

intReduceWidth=intStandardWidth * intHeight / intStandardHeight;

intReduceHeight=intHeight;

intCutOutWidth=intWidth;

intCutOutHeight=intHeight;

}

else

{

intReduceWidth=intWidth;

intReduceHeight=intHeight;

intCutOutWidth=intWidth;

intCutOutHeight=intHeight;

}

//通过连接创建Image对象

//System.Drawing.Image oldimage=System.Drawing.Image.FromFile(strOldImgUrl);

//oldimage.Save(Server.MapPath("tepm.jpg"));

//oldimage.Dispose();

//缩小图片

Bitmap bm=new Bitmap(strOldImgUrl);

//处理JPG质量的函数

ImageCodecInfo[] codecs=ImageCodecInfo.GetImageEncoders();

ImageCodecInfo ici=null;

foreach (ImageCodecInfo codec in codecs)

{

if (codec.MimeType=="image/jpeg")

{

ici=codec;

break;

}

}

EncoderParameters ep=new EncoderParameters();

ep.Param[0]=new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)level);

//裁剪图片

Rectangle cloneRect=new Rectangle(intStartX, intStartY, intCutOutWidth, intCutOutHeight);

PixelFormat format=bm.PixelFormat;

Bitmap cloneBitmap=bm.Clone(cloneRect, format);

//保存图片

cloneBitmap.Save(strNewImgUrl, ici, ep);

bm.Dispose();

}

public bool IsReusable

{

get

{

return false;

}

}

}



4、最后奉上Demo

 https://files.cnblogs.com/files/lengzhan/ImgJcrop.zip

读目录

  • 利用表单实现文件上传
  • 表单异步上传(jquery.form插件)
  • 模拟表单数据上传(FormData)
  • 分片上传
  • 使用HTML5 拖拽、粘贴上传
  • 上传插件(WebUploader)
  • 总结

作为程序员的我们,经常要用到文件的上传和下载功能。到了需要用的时候,各种查资料。有木有..有木有...。为了方便下次使用,这里来做个总结和备忘。

利用表单实现文件上传

最原始、最简单、最粗暴的文件上传。
前端代码:

//方式1
<form action="/Home/SaveFile1" method="post" enctype="multipart/form-data">
     <input type="file" class="file1" name="file1" />
     <button type="submit" class="but1">上传</button>
</form>

【注意】

  • 1、需要post提交
  • 2、enctype="multipart/form-data" (传输文件)
  • 3、需要提交的表单元素需要设置 name 属性

后台代码:

public ActionResult SaveFile1()
{
    if (Request.Files.Count > 0)
    {
        Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Request.Files[0].FileName);
        return Content("保存成功");
    }
    return Content("没有读到文件");
}

表单异步上传(jquery.form插件)

虽然上面的方式简单粗暴,但是不够友好。页面必然会刷新。难以实现停留在当前页面,并给出文件上传成功的提示。
随着时间的流逝,技术日新月异。ajax的出现,使得异步文件提交变得更加容易。
下面我们利用jquery.form插件来实现文件的异步上传。
首先我们需要导入
jquery.jsjquery.form.js
前端代码:

<form id="form2" action="/Home/SaveFile2" method="post" enctype="multipart/form-data">
    <input type="file" class="file1" name="file1" />
    <button type="submit" class="but1">上传1</button>
    <button type="button" class="but2">上传2</button>
</form>

//方式2(通过ajaxForm绑定ajax操作)
$(function () {
    $('#form2').ajaxForm({
        success: function (responseText) {
            alert(responseText);
        }
    });
});

//方式3(通过ajaxSubmit直接执行ajax操作)
$(function () {
    $(".but2").click(function () {
        $('#form2').ajaxSubmit({
            success: function (responseText) {
                alert(responseText);
            }
        });
    });
});

后台代码:

public string SaveFile2()
{
    if (Request.Files.Count > 0)
    {                
        Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName));
        return "保存成功";
    }
    return "没有读到文件";
}

原理:
我们很多时候使用了插件,就不管其他三七二十一呢。
如果有点好奇心,想想这个插件是怎么实现的。随便看了看源码一千五百多行。我的妈呀,不就是个异步上传的吗,怎么这么复杂。
难以看出个什么鬼来,直接断点调试下吧。


原来插件内部有iframe和FormData不同方式来上传,来适应更多版本浏览器。

模拟表单数据上传(FormData)

iframe这东西太恶心。我们看到上面可以利用FormData来上传文件,这个是Html 5 才有的。下面我们自己也来试试吧。
前端代码:

<input id="fileinfo" type="file" class="notFormFile" />
<button type="button" class="btnNotForm">上传4</button>
//方式4
$(".btnNotForm").click(function () {
    var formData=new FormData();//初始化一个FormData对象
    formData.append("files", $(".notFormFile")[0].files[0]);//将文件塞入FormData
    $.ajax({
        url: "/Home/SaveFile2",
        type: "POST",
        data: formData,
        processData: false,  // 告诉jQuery不要去处理发送的数据
        contentType: false,   // 告诉jQuery不要去设置Content-Type请求头
        success: function (responseText) {
            alert(responseText);
        }
    });
});

后面的代码:(不变,还是上例代码)

public string SaveFile2()
{
    if (Request.Files.Count > 0)
    {                
        Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName));
        return "保存成功";
    }
    return "没有读到文件";
}

我们看到,FormData对象也只是在模拟一个原始的表单格式的数据。那有没有可能利用表单或表单格式来上传文件呢?答案是肯定的。(下面马上揭晓)
前端代码:

<input type="file"  id="file5" multiple>
<button type="button" class="btnFile5">上传5</button>    

//方式5
$(".btnFile5").click(function () {
    $.ajax({
        url: "/Home/SaveFile4",
        type: "POST",
        data: $("#file5")[0].files[0],
        processData: false,  // 告诉jQuery不要去处理发送的数据
        contentType: false,   // 告诉jQuery不要去设置Content-Type请求头
        success: function (responseText) {
            alert(responseText);
        }
    });;       
});       
     

后台代码:

public string SaveFile4()
{
    //这里发现只能得到一个网络流,没有其他信息了。(比如,文件大小、文件格式、文件名等)
    Request.SaveAs(Server.MapPath("~/App_Data/SaveFile4.data") + "", false);
    return "保存成功";
}

细心的你发现没有了表单格式,我们除了可以上传文件流数据外,不能再告诉后台其他信息了(如文件格式)。
到这里,我似乎明白了以前上传文件为什么非得要用个form包起来,原来这只是和后台约定的一个传输格式而已。
其实我们单纯地用jq的ajax传输文本数据的时候,最后也是组装成了form格式的数据,如:

 $.ajax({
    data: { "userName": "张三" } 

分片上传

在知道了上面的各种上传之后,我们是不是就满于现状了呢?no,很多时候我们需要传输大文件,一般服务器都会有一定的大小限制。
某天,你发现了一个激情小电影想要分享给大家。无奈,高清文件太大传不了,怎么办?我们可以化整为零,一部分一部分的传嘛,也就是所谓的分片上传。
前端代码:

<input type="file" id="file6" multiple>
<button type="button" class="btnFile6">分片上传6</button>
<div class="result"></div>

//方式6
 $(".btnFile6").click(function () { 
     var upload=function (file, skip) {
         var formData=new FormData();//初始化一个FormData对象
         var blockSize=1000000;//每块的大小
         var nextSize=Math.min((skip + 1) * blockSize, file.size);//读取到结束位置             
         var fileData=file.slice(skip * blockSize, nextSize);//截取 部分文件 块
         formData.append("file", fileData);//将 部分文件 塞入FormData
         formData.append("fileName", file.name);//保存文件名字
         $.ajax({
             url: "/Home/SaveFile6",
             type: "POST",
             data: formData,
             processData: false,  // 告诉jQuery不要去处理发送的数据
             contentType: false,   // 告诉jQuery不要去设置Content-Type请求头
             success: function (responseText) {
                 $(".result").html("已经上传了" + (skip + 1) + "块文件");
                 if (file.size <=nextSize) {//如果上传完成,则跳出继续上传
                     alert("上传完成");
                     return;
                 }
                 upload(file, ++skip);//递归调用
             }
         });
     };

     var file=$("#file6")[0].files[0];
     upload(file, 0);
 }); 

后台代码:

public string SaveFile6()
{
    //保存文件到根目录 App_Data + 获取文件名称和格式
    var filePath=Server.MapPath("~/App_Data/") + Request.Form["fileName"];
    //创建一个追加(FileMode.Append)方式的文件流
    using (FileStream fs=new FileStream(filePath, FileMode.Append, FileAccess.Write))
    {
        using (BinaryWriter bw=new BinaryWriter(fs))
        {
            //读取文件流
            BinaryReader br=new BinaryReader(Request.Files[0].InputStream);
            //将文件留转成字节数组
            byte[] bytes=br.ReadBytes((int)Request.Files[0].InputStream.Length);
            //将字节数组追加到文件
            bw.Write(bytes);
        }
    }
    return "保存成功";
}

相对而言,代码量多了一点,复杂了一点。不过相对于网上的其他分片上传的代码应该要简单得多(因为这里没有考虑多文件块同时上传、断点续传。那样就需要在后台把文件块排序,然后上传完成按序合并,然后删除原来的临时文件。有兴趣的同学可以自己试试,稍候在分析上传插件webuploader的时候也会实现)。
效果图:


【说明】:如果我们想要上传多个文件怎么办?其实H5中也提供了非常简单的方式。直接在
input里面标记multiple<input type="file" id="file6" multiple>,然后我们后台接收的也是一个数组Request.Files

使用HTML5 拖拽、粘贴上传

只能说H5真是强大啊,权限越来越大,操作越来越牛逼。
前端代码(拖拽上传):

<textarea class="divFile7" style="min-width:800px;height:150px" placeholder="请将文件拖拽或直接粘贴到这里"></textarea>
//方式7
 $(".divFile7")[0].ondrop=function (event) {

     event.preventDefault();//不要执行与事件关联的默认动作
     var files=event.dataTransfer.files;//获取拖上来的文件

     //以下代码不变
     var formData=new FormData();//初始化一个FormData对象
     formData.append("files", files[0]);//将文件塞入FormData
     $.ajax({
         url: "/Home/SaveFile2",
         type: "POST",
         data: formData,
         processData: false,  // 告诉jQuery不要去处理发送的数据
         contentType: false,   // 告诉jQuery不要去设置Content-Type请求头
         success: function (responseText) {
             alert(responseText);
         }
     });
 };

后台代码:
略(和之前的SaveFile2一样)

前端代码(粘贴上传 限图片格式):

//方式8
$(".divFile7")[0].onpaste=function (event) {
    event.preventDefault();//不要执行与事件关联的默认动作
    var clipboard=event.clipboardData.items[0];//剪贴板数据
    if (clipboard.kind=='file' || clipboard.type.indexOf('image') > -1) {//判断是图片格式
        var imageFile=clipboard.getAsFile();//获取文件

        //以下代码不变
        var formData=new FormData;
        formData.append('files', imageFile);
        formData.append('fileName', "temp.png");//这里给文件命个名(或者直接在后台保存的时候命名)
        $.ajax({
            url: "/Home/SaveFile8",
            type: "POST",
            data: formData,
            processData: false,  // 告诉jQuery不要去处理发送的数据
            contentType: false,   // 告诉jQuery不要去设置Content-Type请求头
            success: function (responseText) {
                alert(responseText);
            }
        });
    }
};

后台代码:

public string SaveFile8()
{
    //保存文件到根目录 App_Data + 获取文件名称和格式
    var filePath=Server.MapPath("~/App_Data/") + Request.Form["fileName"];      
    if (Request.Files.Count > 0)
    {
        Request.Files[0].SaveAs(filePath);
        return "保存成功";
    }
    return "没有读到文件";
}

效果图:

上传插件(WebUploader)

已经列举分析了多种上传文件的方式,我想总有一种适合你。不过,上传这个功能比较通用,而我们自己写的可能好多情况没有考虑到。接下来简单介绍下百度的WebUploader插件。
比起我们自己写的简单上传,它的优势:稳定、兼容性好(有flash切换,所以支持IE)、功能多、并发上传、断点续传(主要还是靠后台配合)。
官网:http://fex.baidu.com/webuploader/
插件下载:https://github.com/fex-team/webuploader/releases/download/0.1.5/webuploader-0.1.5.zip
下面开始对WebUploader的使用
第一种,简单粗暴
前端代码:

<div id="picker">选择文件</div>
<button id="ctlBtn" class="btn btn-default">开始上传</button>

<!--引用webuploader的js和css-->
<link href="~/Scripts/webuploader-0.1.5/webuploader.css" rel="stylesheet" />
<script src="~/Scripts/webuploader-0.1.5/webuploader.js"></script>
<script type="text/javascript">
    var uploader=WebUploader.create({

        // (如果是新浏览器 可以不用 flash)
        //swf: '/Scripts/webuploader-0.1.5/Uploader.swf',

        // 文件接收服务端。
        server: '/Webuploader/SaveFile',

        // 选择文件的按钮。可选。
        // 内部根据当前运行是创建,可能是input元素,也可能是flash.
        pick: '#picker'
    });

    $("#ctlBtn").click(function () {
        uploader.upload();
    });

    uploader.on('uploadSuccess', function (file) {
        alert("上传成功");
    });

</script>

后台代码:

public string SaveFile()
{
    if (Request.Files.Count > 0)
    {
        Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName));
        return "保存成功";
    }
    return "没有读到文件";
}

第二种,分片上传。和我们之前自己写的效果差不多。
前端代码:

var uploader=WebUploader.create({ 
    //兼容老版本IE
    swf: '/Scripts/webuploader-0.1.5/Uploader.swf', 
    // 文件接收服务端。
    server: '/Webuploader/SveFile2', 
    // 开起分片上传。
    chunked: true, 
    //分片大小
    chunkSize: 1000000, 
    //上传并发数
    threads: 1,
    // 选择文件的按钮。 
    pick: '#picker'
});

// 点击触发上传
$("#ctlBtn").click(function () {
    uploader.upload();
});

uploader.on('uploadSuccess', function (file) {
    alert("上传成功");
});

后台代码:

public string SveFile2()
{
    //保存文件到根目录 App_Data + 获取文件名称和格式
    var filePath=Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName);
    //创建一个追加(FileMode.Append)方式的文件流
    using (FileStream fs=new FileStream(filePath, FileMode.Append, FileAccess.Write))
    {
        using (BinaryWriter bw=new BinaryWriter(fs))
        {
            //读取文件流
            BinaryReader br=new BinaryReader(Request.Files[0].InputStream);
            //将文件留转成字节数组
            byte[] bytes=br.ReadBytes((int)Request.Files[0].InputStream.Length);
            //将字节数组追加到文件
            bw.Write(bytes);
        }
    }
    return "保存成功";
}

我们看到了有个参数threads: 1上传并发数,如果我们改成大于1会怎样?前端会同时发起多个文件片上传。后台就会报错,多个进程同时操作一个文件。
那如果我们想要多线程上传怎么办?改代码吧(主要是后台逻辑)。
前端代码:

//并发上传(多线程上传)
var uploader=WebUploader.create({
    //兼容老版本IE
    swf: '/Scripts/webuploader-0.1.5/Uploader.swf',
    // 文件接收服务端。
    server: '/Webuploader/SveFile3',
    // 开起分片上传。
    chunked: true,
    //分片大小
    chunkSize: 1000000,
    //上传并发数
    threads: 10,
    // 选择文件的按钮。
    pick: '#picker'
});

// 点击触发上传
$("#ctlBtn").click(function () {
    uploader.upload();
});

uploader.on('uploadSuccess', function (file) {
    //上传完成后,给后台发送一个合并文件的命令
    $.ajax({
        url: "/Webuploader/FileMerge",
        data: { "fileName": file.name },
        type: "post",
        success: function () {
            alert("上传成功");
        }
    });
});

后台代码:

public string SveFile3()
{
    var chunk=Request.Form["chunk"];//当前是第多少片 

    var path=Server.MapPath("~/App_Data/") + Path.GetFileNameWithoutExtension(Request.Files
    if (!Directory.Exists(path))//判断是否存在此路径,如果不存在则创建
    {
        Directory.CreateDirectory(path);
    }
    //保存文件到根目录 App_Data + 获取文件名称和格式
    var filePath=path + "/" + chunk;
    //创建一个追加(FileMode.Append)方式的文件流
    using (FileStream fs=new FileStream(filePath, FileMode.Append, FileAccess.Write))
    {
        using (BinaryWriter bw=new BinaryWriter(fs))
        {
            //读取文件流
            BinaryReader br=new BinaryReader(Request.Files[0].InputStream);
            //将文件留转成字节数组
            byte[] bytes=br.ReadBytes((int)Request.Files[0].InputStream.Length);
            //将字节数组追加到文件
            bw.Write(bytes);
        }
    }           
    return "保存成功";
}
/// <summary>
/// 合并文件
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public bool FileMerge()
{
    var fileName=Request.Form["fileName"];
    var path=Server.MapPath("~/App_Data/") + Path.GetFileNameWithoutExtension(fileName);

    //这里排序一定要正确,转成数字后排序(字符串会按1 10 11排序,默认10比2小)
    foreach (var filePath in Directory.GetFiles(path).OrderBy(t=> int.Parse(Path.GetFileNameWithoutExtension(t))))
    {
        using (FileStream fs=new FileStream(Server.MapPath("~/App_Data/") + fileName, FileMode.Append, FileAccess.Write))
        {
            byte[] bytes=System.IO.File.ReadAllBytes(filePath);//读取文件到字节数组
            fs.Write(bytes, 0, bytes.Length);//写入文件
        }
        System.IO.File.Delete(filePath);
    }
    Directory.Delete(path);
    return true;
}

到这里你以为就结束了吗?错,还有好多情况没有考虑到。如果多个用户上传的文件名字一样会怎样?如何实现断点续传?还没实现选择多个文件?不过,这里不打算继续贴代码了(再贴下去,代码量越来越多了),自己也来练习练习吧。
提供一个思路,上传前先往数据库插入一条数据。数据包含文件要存的路径、文件名(用GUID命名,防止同名文件冲突)、文件MD5(用来识别下次续传和秒传)、临时文件块存放路径、文件是否完整上传成功等信息。
然后如果我们断网后再传,首先获取文件MD5值,看数据库里面有没上传完成的文件,如果有就实现秒传。如果没有,看是不是有上传了部分的。如果有接着传,如果没有则重新传一个新的文件。

总结

之前我一直很疑惑,为什么上传文件一定要用form包起来,现在算是大概明白了。
最开始在javascript还不流行时,我们就可以直接使用submit按钮提交表单数据了。表单里面可以包含文字和文件。然后随着js和ajax的流行,可以利用ajax直接异步提交部分表单数据。这里开始我就纠结了,为什么ajax可以提交自己组装的数据。那为什么不能直接提交文件呢。这里我错了,ajax提交的并不是随意的数据,最后还是组装成了表单格式(因为后台技术对表单格式数据的支持比较普及)。但是现有的技术还不能通过js组装一个文件格式的表单数据。直到H5中的FormData出现,让前端js组装一个包含文件的表单格式数据成为了可能。所以说表单只是为了满足和后台“约定”的数据格式而已。

相关推荐

  • http://www.cnblogs.com/fish-li/archive/2011/07/17/2108884.html
  • http://javascript.ruanyifeng.com/htmlapi/file.html

demo

  • https://github.com/zhaopeiym/BlogDemoCode/tree/master/上传下载

cikit-learn 的 datasets 模块包含测试数据相关函数,主要包括三类:

  • datasets.load_*():获取小规模数据集。数据包含在 datasets 里
  • datasets.fetch_*():获取大规模数据集。需要从网络上下载,函数的第一个参数是 data_home,表示数据集下载的目录,默认是 ~/scikit_learn_data/。要修改默认目录,可以修改环境变量SCIKIT_LEARN_DATA。数据集目录可以通过datasets.get_data_home()获取。clear_data_home(data_home=None)删除所有下载数据。
  • datasets.make_*():本地生成数据集。

数据集格式

  • tuple(X, y)
    本地生成的数据函数
    make_*load_svmlight_* 返回的数据是 tuple(X, y) 格式
  • Bunch
    load_*fetch_* 函数返回的数据类型是 datasets.base.Bunch,本质上是一个 dict,它的一个键值对,可用通过对象的属性方式访问。主要包含以下属性:

data:特征数据数组,是 n_samples * n_features的二维 numpy.ndarray 数组

target:标签数组,是 n_samples 的一维 numpy.ndarray 数组

DESCR:数据描述

feature_names:特征名

target_names:标签名

获取小数据集

  • load_boston():
    房屋特征-房价,用于regression
  • load_diabetes():
    糖尿病数据,用于regression
  • load_linnerud():
    Linnerud数据集,有多个标签,用于 multilabel regression
  • load_iris():
    鸢尾花特征和类别,用于classification
  • load_digits([n_class]):
    手写数字识别
  • load_sample_images():
    载入图片数据集,共两张图
  • load_sample_image(name):
    载入图片数据集中的一张图
  • load_files(container_path, description=None, categories=None, load_content=True, shuffle=True, encoding=None, decode_error='strict', random_state=0):
    从本地目录获取文本数据,并根据二级目录做分类

获取大数据集

  • load_mlcomp(name_or_id, set_='raw', mlcomp_root=None, **kwargs):
    从 http://mlcomp.org/ 上下载数据集
  • fetch_california_housing(data_home=None, download_if_missing=True)
  • fetch_olivetti_faces(data_home=None, shuffle=False, random_state=0, download_if_missing=True):
    Olivetti 脸部图片数据集
  • fetch_lfw_people(data_home=None, funneled=True, resize=0.5, min_faces_per_person=0, color=False, slice_=(slice(70, 195, None), slice(78, 172, None)), download_if_missing=True):
  • fetch_lfw_pairs(subset='train', data_home=None, funneled=True, resize=0.5, color=False, slice_=(slice(70, 195, None), slice(78, 172, None)), download_if_missing=True):
    Labeled Faces in the Wild (LFW) 数据集,参考 LFW
  • fetch_20newsgroups(data_home=None, subset='train', categories=None, shuffle=True, random_state=42, remove=(), download_if_missing=True)
  • fetch_20newsgroups_vectorized(subset='train', remove=(), data_home=None):
    新闻分类数据集,数据集包含 ‘train’ 部分和 ‘test’ 部分。
  • fetch_rcv1(data_home=None, subset='all', download_if_missing=True, random_state=None, shuffle=False):
    路透社新闻语聊数据集
  • fetch_mldata(dataname, target_name='label', data_name='data', transpose_data=True, data_home=None):
    从 mldata.org 中下载数据集。参考 PASCAL network
  • mldata_filename(dataname):
    将 mldata 的数据集名转换为下载的数据文件名
  • fetch_covtype(data_home=None, download_if_missing=True, random_state=None, shuffle=False)
    Forest covertypes 数据集

本地生成数据

回归(regression)

  • make_regression(n_samples=100, n_features=100, n_informative=10, n_targets=1, bias=0.0, effective_rank=None, tail_strength=0.5, noise=0.0, shuffle=True, coef=False, random_state=None)
  • make_sparse_uncorrelated(n_samples=100, n_features=10, random_state=None)
  • make_friedman1(n_samples=100, n_features=10, noise=0.0, random_state=None)
  • make_friedman2(n_samples=100, noise=0.0, random_state=None)
  • make_friedman3(n_samples=100, noise=0.0, random_state=None)

分类(classification)

单标签

  • make_classification(n_samples=100, n_features=20, n_informative=2, n_redundant=2, n_repeated=0, n_classes=2, n_clusters_per_class=2, weights=None, flip_y=0.01, class_sep=1.0, hypercube=True, shift=0.0, scale=1.0, shuffle=True, random_state=None):
    生成 classification 数据集。包含所有的设置,可以包含噪声,偏斜的数据集
  • make_blobs(n_samples=100, n_features=2, centers=3, cluster_std=1.0, center_box=(-10.0, 10.0), shuffle=True, random_state=None):
    生成 classification 数据集。数据服从高斯分布
    centers 可以是整数,表示中心点个数,或者用列表给出每个中心点的特征值
    cluster_std 也可以是浮点数或浮点数列表
    random_state 可以是整数,表示随机起始 seed,或者 RandomState 对象,默认使用 np.random
  • make_gaussian_quantiles(mean=None, cov=1.0, n_samples=100, n_features=2, n_classes=3, shuffle=True, random_state=None):
  • make_hastie_10_2(n_samples=12000, random_state=None):
  • make_circles(n_samples=100, shuffle=True, noise=None, random_state=None, factor=0.8):
  • make_moons(n_samples=100, shuffle=True, noise=None, random_state=None):

多标签

  • make_multilabel_classification(n_samples=100, n_features=20, n_classes=5, n_labels=2, length=50, allow_unlabeled=True, sparse=False, return_indicator='dense', return_distributions=False, random_state=None):
    生成 multilabel classification 数据集。

双聚类(bicluster)

  • make_biclusters(shape, n_clusters, noise=0.0, minval=10, maxval=100, shuffle=True, random_state=None):
  • make_checkerboard(shape, n_clusters, noise=0.0, minval=10, maxval=100, shuffle=True, random_state=None):

流形学习(manifold learning)

  • make_s_curve(n_samples=100, noise=0.0, random_state=None)
  • make_swiss_roll(n_samples=100, noise=0.0, random_state=None)、

可降维(decomposition)数据

  • make_low_rank_matrix(n_samples=100, n_features=100, effective_rank=10, tail_strength=0.5, random_state=None)
  • make_sparse_coded_signal(n_samples, n_components, n_features, n_nonzero_coefs, random_state=None)
  • make_spd_matrix(n_dim, random_state=None)
  • make_sparse_spd_matrix(dim=1, alpha=0.95, norm_diag=False, smallest_coef=0.1, largest_coef=0.9, random_state=None)

处理 svmlight / libsvm 格式数据

提供 svmlight / libsvm 格式数据的导入或导出。

  • load_svmlight_file(f, n_features=None, dtype=numpy.float64, multilabel=False, zero_based='auto', query_id=False):
    返回 (X, y, [query_id]),其中 X 是 scipy.sparse matrix,y 是 numpy.ndarray
  • load_svmlight_files(files, n_features=None, dtype=numpy.float64, multilabel=False, zero_based='auto', query_id=False)
  • dump_svmlight_file(X, y, f, zero_based=True, comment=None, query_id=None, multilabel=False)

其他数据集网站

UCI Machine Learning Repository:http://archive.ics.uci.edu/ml/datasets.html
UCI KDD:http://kdd.ics.uci.edu/summary.data.type.html
Kaggle:https://www.kaggle.com/datasets

参考

官方datasets包文档:http://scikit-learn.org/stable/datasets/index.html
API列表:http://scikit-learn.org/stable/modules/classes.html#module-sklearn.datasets