本篇将要和朋友们分享的是几个上传文件的例子和逻辑步奏及自定义个简单的js上传插件我取名为shenniu.upfile-0.0.1.js;近来在讨论组中很有几个朋友咨询上传文件的代码和怎么下载上传的文件,所以写了此篇文章,希望能为朋友们解答一些疑惑或能带来帮助,也谢谢各位多多支持点赞。
以上是个人的看法,下面来正式分享今天的文章吧:
- 使用iis发布保存上传文件的文件夹
- 示例A - 普通表单上传文件,Request.Files获取上传文件
- 示例B - 普通表单上传文件,HttpPostedFileBase获取上传文件
- 示例C - ajax+FormData上传文件,Request.Files获取上传文件
- 示例D - 自定义上传插件,Request.Files获取上传文件并提交表单内容
下面一步一个脚印的来分享:
. 使用iis发布保存上传文件的文件夹
首先,因为我们上传文件后,需要查看或者下载这个文件,通常分为两种形式;一种把上传的文件上传到程序的根目录中的某个文件夹中,这种就可以直接通过站点的虚拟路径来访问,这种其实也是使用了iis发布的;第二种是通过程序上传到服务器的某个磁盘中,这种路径通常就不在上传程序站点的目录下,因为想让用户通过其他的url地址访问下载文件,iis发布只是其中一种;其实用iss发布保存上传文件的文件夹和发布程序一样差不多;
我们随便找个某磁盘下面的文件夹当做保存上传文件的文件夹,我这里磁盘路径是:D:\D\TTest这个TTest文件夹,然后放几张测试的图片,并复制这个路径,在iis中创建一个站点物理路径指向刚才的复制的路径,端口我这里是1010,我这里没有域名只有用本机的ip代替,这里默认是localhost,配置好后,查看高级设置如:
然后点击"测试文件地址"这个站点-》内容视图 能看到如图的效果
然后,鼠标指上去右键其中某一张图片-》浏览 这样浏览器马上就能看到如图所示的效果:
这样就完成了iis发布文件夹,当然通过http访问图片只是多种方式的一种;有些后缀的文件可能访问不了,需要手动在iis中增加mime类型:
. 示例A - 普通表单上传文件,Request.Files获取上传文件
首先,我们来看下html主要代码:
<fieldset> <legend>示例A - 普通表单上传文件,Request.Files获取上传文件</legend> <form action="/Error/A" method="post" enctype="multipart/form-data"> <input type="file" name="fileA" multiple /> <button type="submit">提交</button> </form> </fieldset>
看是简单简单及简短的代码,其实是上传的重要部分必不可少的,下面就来简单总结下:
- form元素的action属性:表单提交地址,这里是文件上传的接口地址
- 必须采用method="post"提交方式
- 表单提交数据的格式是enctype="multipart/form-data"
- type="file"元素必须要有属性name,这里例子是name="fileA"
- type="file"元素的multiple属性可选,意思是可以选择多个上传文件不用再增加file标签元素来支持上传多个文件了,这个在近几年移动端火热的时候几乎所有最新版本浏览器都支持
再来,我们一起看下后端怎么接受表单提交过来的文件信息,先来看代码:
[HttpPost] public ActionResult A() { var sbLog = new StringBuilder(string.Empty); var fileCount = Request.Files.Count; //访问上传文件地址 var path = @"http://localhost:1010/{0}"; //保存文件地址 var uploadPath = @"D:\D\TTest"; sbLog.AppendFormat("上传文件目录:{0}<br/>", uploadPath); sbLog.AppendFormat("上传文件量:{0}<br/>", fileCount); for (int i = 0; i < fileCount; i++) { var file = Request.Files[i]; if (file == null || string.IsNullOrEmpty(file.FileName)) { continue; } var fileName = file.FileName; var fileNewName = DateTime.Now.Ticks + fileName; sbLog.AppendFormat("第:{0}个文件名称:{1}新名称:{2}下载地址:<a href='{3}' target='_blank'>{2}</a><br/>", i + 1, fileName, fileNewName, string.Format(path, fileNewName)); file.SaveAs(Path.Combine(uploadPath, fileNewName)); } return Content(sbLog.ToString()); }
这段代码的总要点列举如下:
- Request.Files:用来获取上传的文件,所有上传文件都以此来获取,什么文件名称也包含在列表中
- path变量:就是咋们在前面配置的iis发布文件夹的url路径,只需要传递上传的文件的名称就可以访问
- uploadPath:保存文件地址
- 通过for循环获取上传上来的文件信息和通过file.SaveAs保存到uploadPath指定的文件夹中去
- Path.Combine方法把传递进来的参数拼接长一个磁盘路径如第一个参数值是D:/第二个参数是test.png那么这个方法返回的结果是D:/test.png
最终我这个Action返回的是ContentResult,因为我想在页面输入一些信息好截图哈哈,好了咋们通过复制上面的代码运行起来点"提交"按钮得到的效果如图:
文件夹中选择上的两张图片,就是我们页面上传上来的,通过点击Action返回给页面的下载地址,我们可以在浏览器中浏览图片;好这个上传图片,浏览图片的过程描述大概就是这样,朋友们有收获一些么
. 示例B - 普通表单上传文件,HttpPostedFileBase获取上传文件
经过示例A描述的上传,下载图片流程应该都很清楚了,这个小节主要是换了一种获取上传文件信息的方式,我们通过HttpPostedFileBase获取(其实示例ARequest.Files的最底层就是这个),因为可能上传多个文件所以使用了IEnumerable<HttpPostedFileBase>类型来当做Action的参数,试图部分我们使用HtmlHelper的BeginForm方法来生成form标签,具体代码如:
<fieldset> <legend>示例B - 普通表单上传文件,HttpPostedFileBase获取上传文件</legend> @using (Html.BeginForm("B", "Error", FormMethod.Post, new { enctype = "multipart/form-data" })) { <input type="file" name="files" multiple /> <button>提交</button> } </fieldset>
这里使用@html.BeginForm写法主要是满足使用mvc的同学,简单说下这里使用的BeginForm参数:BeginForm(Action,Controller,表单提交方式,增加form属性enctype);好再来看下Controller的代码如:
[HttpPost] public ActionResult B(IEnumerable<HttpPostedFileBase> files) { var sbLog = new StringBuilder(string.Empty); var fileCount = files == null "http://localhost:1010/{0}"; //保存文件地址 var uploadPath = @"D:\D\TTest"; sbLog.AppendFormat("上传文件目录:{0}<br/>", uploadPath); sbLog.AppendFormat("上传文件量:{0}<br/>", fileCount); var i = 0; foreach (var file in files) { if (file == null || string.IsNullOrEmpty(file.FileName)) { continue; } var fileName = file.FileName; var fileNewName = DateTime.Now.Ticks + fileName; sbLog.AppendFormat("第:{0}个文件名称:{1}新名称:{2}下载地址:<a href='{3}' target='_blank'>{2}</a><br/>", i + 1, fileName, fileNewName, string.Format(path, fileNewName)); file.SaveAs(Path.Combine(uploadPath, fileNewName)); } return Content(sbLog.ToString()); }
上面的代码与示例A对比一下,可以看出处理方式几乎是一模一样呢,那这里就没什么说的了,主要是展示下两种获取file文件信息的方式,但下面的将与众不同
. 示例C - ajax+FormData上传文件,Request.Files获取上传文件
首先,来了解下FormData这个近几年才被几乎所有浏览器支持的js对象,它的构造函数:new FormData (form"htmlcode">
<fieldset> <legend>示例C - ajax+FormData上传文件,Request.Files获取上传文件</legend> @using (Html.BeginForm("C", "Error", FormMethod.Post, new { enctype = "multipart/form-data", @id = "form_c" })) { <span>账号:</span><input type="text" name="userName" value="神牛步行3" /><br /> <span>密码:</span><input type="password" name="userPwd" value="神牛" /><br /> <span>文件:</span><input type="file" name="filesc" multiple /><br /> <button type="button">提交</button><br /> <span id="msg_c"></span> } </fieldset>
<script> $(function () { //示例C代码 $("#form_c button[type='button']").click(function () { var msg = $("#msg_c"); var btnSave = $(this); var formData = new FormData(document.getElementById("form_c")); console.log(formData); $.ajax({ url: '/Error/C', type: 'post', beforeSend: function (e) { btnSave.attr("disabled", "disabled"); msg.html("上传中..."); }, success: function (data) { console.log(data); var alertMsg = "上传失败"; if (data) { alertMsg = data.Status == 1 "disabled"); }, error: function (data) { msg.html("上传失败"); btnSave.removeAttr("disabled"); }, data: formData, cache: false, contentType: false, processData: false }); }); }) </script>
上面的html代码与示例B的格式差不多,只是增加了两个input元素方便我们来测试;重点在js代码中,由上面介绍可以看到FormData的构造只能是HTMLFormElement,所以我们通过document.getElementById("form_c")来获取这个表单的对象传递给它;
然后,我们用普通的$.ajax提交FormData对象到后台进行上传,至于后台我们参照示例A的方式来获取文件信息,不用做什么特殊额处理,只是我们返回的信息是Json数据,代码如:
[HttpPost] public JsonResult C() { Thread.Sleep(1000 * 5); var response = new MoResponse(); var sbLog = new StringBuilder("开始处理..."); try { sbLog.AppendFormat("账号:{0}<br/>", Request.Params["userName"]); sbLog.AppendFormat("密码:{0}<br/>", Request.Params["userPwd"]); var fileCount = Request.Files.Count; //访问上传文件地址 var path = @"http://localhost:1010/{0}"; //保存文件地址 var uploadPath = @"D:\D\TTest"; sbLog.AppendFormat("上传文件目录:{0}<br/>", uploadPath); sbLog.AppendFormat("上传文件量:{0}<br/>", fileCount); for (int i = 0; i < fileCount; i++) { var file = Request.Files[i]; if (file == null || string.IsNullOrEmpty(file.FileName)) { continue; } var fileName = file.FileName; var fileNewName = DateTime.Now.Ticks + fileName; sbLog.AppendFormat("第:{0}个文件名称:{1}新名称:{2}下载地址:<a href='{3}' target='_blank'>{2}</a><br/>", i + 1, fileName, fileNewName, string.Format(path, fileNewName)); file.SaveAs(Path.Combine(uploadPath, fileNewName)); response.Status = 1; } } catch (Exception ex) { sbLog.AppendFormat("异常信息:{0}", ex.Message); } finally { response.Data = sbLog.ToString(); } return Json(response); }
下面我们看一下添加"提交"按钮执行后返回的效果,可以看出提交后没有跳转到action指向的路由,这样用户体验就好多了:
. 示例D - 自定义上传插件,Request.Files获取上传文件并提交表单内容
首先,我发个插件下载地址shenniu.upfile-0.0.1.js,只需要在试图或者html页面引用jquery-1.10.2.min.js库和这个插件,我们就可以这样使用:
<script> $(function () { //示例D代码 //初始化插件 var shenniu = new shenniu_UpFile({ url: "/Error/D_A", fileEleName: "filesd" }); //提交事件 $("#form_d button[type='button']").click(function () { var msg = $("#msg_d"); var btnSave = $(this); btnSave.attr("disabled", "disabled"); msg.html("上传中.."); //上传文件 shenniu.submitFun(function (data) { try { var result = JSON.parse(data); //上传成功,保存数据 if (result.Status == 1) { //增加上传成功的文件名称到表单 var hidFileNames = $("#form_d input[name='hidFileNames']"); if (hidFileNames.length <= 0) { $("#form_d").append("<input type='hidden' name='hidFileNames' value='" + result.Data + "'/>"); else { hidFileNames.val(result.Data); } //获取表单键值对,提交表单数据 var param = $("#form_d").serialize(); var action = $("#form_d").attr("action"); $.post(action, param, function (res) { msg.html(res.Status == 1 "保存成功<br/>" + res.Data : "保存失败"); }); } else { msg.html(result.Data); } } catch (e) { console.log(e.message); } finally { btnSave.removeAttr("disabled"); } }); }); }) </script>
使用插件的地方和js关键点已经通过代码注释备注好了,朋友们可仔细阅读下,这里要将的是插件上传文件和提交表单数据的逻辑;我们分析下:
- form表单中通常有其他的属性数据
- form表单的提交数据的后台地址可能和上传文件的后台地址不一样
- 需要单独上传文件后,返回是否上传成功,再提交form表单的其他数据到后台
由上面几点插件的参数定义为:
var defOption = { url: "http://www.cnblogs.com/wangrudong003/", //上传路由地址,注:1.目前通过该地址上传文件成功后,返回的信息是text/plain 2.跨域暂未考虑 fileEleName: "fileName", //上传input file控件的唯一名称 uid: new Date().getTime(), //防重复 backFun: function () { } //回调函数 };
这个自定义插件原理和几个重点是:
- 自动创建一个iframe(用来做无刷新体验),iframe里面创建一个form表单,form表单里面只包含要上传的文件file对象,最终把iframe加入到视图页面中
- 通过iframe.load()方法来获取上传文件后返回到contentWindow.document.body中的信息,并且执行自定义回调函数,把参数传递给自定义方法方便使用者自由的控制体验效果
- 创建的form表单里面只能使用页面选择文件上传的文件file对象,使用jquery的clone()函数无法获取到选择的文件对象(这是一个悲剧),所以我这个使用append把用户使用的那个file对象直接包含到创建的form中去,然后在创建一个初始化的file元素对象到原始的视图中去代替被移除掉的file元素,代码如(这个还真花了我2个小时的时间尝试):
//清空自定义form多余的file元素 form.html(""); var files = $("input[name='" + defOption.fileEleName + "']"); //复制上传控件对象 var filesClone = files.clone(true); filesClone.insertAfter(files); form.append(files);
- 使用该插件提交原始表单数据的顺序是:用户点击页面的保存按钮-》通过插件创建的上传文件的form表单,提交上传文件-》上传文件返回成功与否的信息-》收到上传成功新文件名称信息,创建个hidden保存新文件名称到原始form中去-》再真实提交原始form表单的其他数据
下面来看下效果图:
示例D的后台代码分为两部分:1.上传文件的Action 2.真实获取表单form参数的Action,代码如:
/// <summary> /// 保存form提交的表单数据 /// </summary> /// <returns></returns> [HttpPost] public JsonResult D() { var response = new MoResponse(); var sbLog = new StringBuilder(string.Empty); try { //访问上传文件地址 var path = @"http://localhost:1010/{0}"; sbLog.AppendFormat("账号:{0}<br/>", Request.Params["userName"]); sbLog.AppendFormat("密码:{0}<br/>", Request.Params["userPwd"]); foreach (var item in Request.Params["hidFileNames"].Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries)) { sbLog.AppendFormat("文件新名称:{0};下载地址:<a href='{1}' target='_blank'>{0}</a><br/>", item, string.Format(path, item)); } response.Status = 1; } catch (Exception ex) { sbLog.AppendFormat("异常信息:{0}", ex.Message); } finally { response.Data = sbLog.ToString(); } return Json(response); } /// <summary> /// 获取上传文件信息 /// </summary> /// <returns></returns> [HttpPost] public ContentResult D_A() { var response = new MoResponse(); response.Data = "上传失败"; try { Thread.Sleep(1000 * 3); var fileCount = Request.Files.Count; //保存文件地址 var uploadPath = @"D:\D\TTest"; var fileNames = string.Empty; for (int i = 0; i < fileCount; i++) { var file = Request.Files[i]; if (file == null || string.IsNullOrEmpty(file.FileName)) { continue; } var fileName = file.FileName; var fileNewName = DateTime.Now.Ticks + fileName; file.SaveAs(Path.Combine(uploadPath, fileNewName)); fileNames += fileNewName + "|"; } if (!string.IsNullOrEmpty(fileNames)) { response.Status = 1; response.Data = fileNames.TrimEnd('|'); } } catch (Exception ex) { response.Data = ex.Message; } return Content(JsonConvert.SerializeObject(response)); }
好了插件需要讲解的就这么多,不知不觉有只剩我一个人了,该回家了,下面给出整体的代码,插件代码请使用连接获取:
插件下载地址:shenniu.upfile-0.0.1.js
Controller代码:
public class ErrorController : Controller { // // GET: /Error/ public ActionResult Index() { return View(); } [HttpPost] public ActionResult A() { var sbLog = new StringBuilder(string.Empty); var fileCount = Request.Files.Count; //访问上传文件地址 var path = @"http://localhost:1010/{0}"; //保存文件地址 var uploadPath = @"D:\D\TTest"; sbLog.AppendFormat("上传文件目录:{0}<br/>", uploadPath); sbLog.AppendFormat("上传文件量:{0}<br/>", fileCount); for (int i = 0; i < fileCount; i++) { var file = Request.Files[i]; if (file == null || string.IsNullOrEmpty(file.FileName)) { continue; } var fileName = file.FileName; var fileNewName = DateTime.Now.Ticks + fileName; sbLog.AppendFormat("第:{0}个文件名称:{1}新名称:{2}下载地址:<a href='{3}' target='_blank'>{2}</a><br/>", i + 1, fileName, fileNewName, string.Format(path, fileNewName)); file.SaveAs(Path.Combine(uploadPath, fileNewName)); } return Content(sbLog.ToString()); } [HttpPost] public ActionResult B(IEnumerable<HttpPostedFileBase> files) { var sbLog = new StringBuilder(string.Empty); var fileCount = files == null "http://localhost:1010/{0}"; //保存文件地址 var uploadPath = @"D:\D\TTest"; sbLog.AppendFormat("上传文件目录:{0}<br/>", uploadPath); sbLog.AppendFormat("上传文件量:{0}<br/>", fileCount); var i = 0; foreach (var file in files) { if (file == null || string.IsNullOrEmpty(file.FileName)) { continue; } var fileName = file.FileName; var fileNewName = DateTime.Now.Ticks + fileName; sbLog.AppendFormat("第:{0}个文件名称:{1}新名称:{2}下载地址:<a href='{3}' target='_blank'>{2}</a><br/>", i + 1, fileName, fileNewName, string.Format(path, fileNewName)); file.SaveAs(Path.Combine(uploadPath, fileNewName)); } return Content(sbLog.ToString()); } [HttpPost] public JsonResult C() { Thread.Sleep(1000 * 5); var response = new MoResponse(); var sbLog = new StringBuilder("开始处理..."); try { sbLog.AppendFormat("账号:{0}<br/>", Request.Params["userName"]); sbLog.AppendFormat("密码:{0}<br/>", Request.Params["userPwd"]); var fileCount = Request.Files.Count; //访问上传文件地址 var path = @"http://localhost:1010/{0}"; //保存文件地址 var uploadPath = @"D:\D\TTest"; sbLog.AppendFormat("上传文件目录:{0}<br/>", uploadPath); sbLog.AppendFormat("上传文件量:{0}<br/>", fileCount); for (int i = 0; i < fileCount; i++) { var file = Request.Files[i]; if (file == null || string.IsNullOrEmpty(file.FileName)) { continue; } var fileName = file.FileName; var fileNewName = DateTime.Now.Ticks + fileName; sbLog.AppendFormat("第:{0}个文件名称:{1}新名称:{2}下载地址:<a href='{3}' target='_blank'>{2}</a><br/>", i + 1, fileName, fileNewName, string.Format(path, fileNewName)); file.SaveAs(Path.Combine(uploadPath, fileNewName)); response.Status = 1; } } catch (Exception ex) { sbLog.AppendFormat("异常信息:{0}", ex.Message); } finally { response.Data = sbLog.ToString(); } return Json(response); } /// <summary> /// 保存form提交的表单数据 /// </summary> /// <returns></returns> [HttpPost] public JsonResult D() { var response = new MoResponse(); var sbLog = new StringBuilder(string.Empty); try { //访问上传文件地址 var path = @"http://localhost:1010/{0}"; sbLog.AppendFormat("账号:{0}<br/>", Request.Params["userName"]); sbLog.AppendFormat("密码:{0}<br/>", Request.Params["userPwd"]); foreach (var item in Request.Params["hidFileNames"].Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries)) { sbLog.AppendFormat("文件新名称:{0};下载地址:<a href='{1}' target='_blank'>{0}</a><br/>", item, string.Format(path, item)); } response.Status = 1; } catch (Exception ex) { sbLog.AppendFormat("异常信息:{0}", ex.Message); } finally { response.Data = sbLog.ToString(); } return Json(response); } /// <summary> /// 获取上传文件信息 /// </summary> /// <returns></returns> [HttpPost] public ContentResult D_A() { var response = new MoResponse(); response.Data = "上传失败"; try { Thread.Sleep(1000 * 3); var fileCount = Request.Files.Count; //保存文件地址 var uploadPath = @"D:\D\TTest"; var fileNames = string.Empty; for (int i = 0; i < fileCount; i++) { var file = Request.Files[i]; if (file == null || string.IsNullOrEmpty(file.FileName)) { continue; } var fileName = file.FileName; var fileNewName = DateTime.Now.Ticks + fileName; file.SaveAs(Path.Combine(uploadPath, fileNewName)); fileNames += fileNewName + "|"; } if (!string.IsNullOrEmpty(fileNames)) { response.Status = 1; response.Data = fileNames.TrimEnd('|'); } } catch (Exception ex) { response.Data = ex.Message; } return Content(JsonConvert.SerializeObject(response)); } } public class MoResponse { public object Data { get; set; } /// <summary> /// 0:失败 1:成功 /// </summary> public int Status { get; set; } }
View代码:
@{ ViewBag.Title = "上传 - 示例"; } <!DOCTYPE html> <html> <head> <title></title> <meta /> </head> <body> <div class=" error-container"> <fieldset> <legend>示例A - 普通表单上传文件,Request.Files获取上传文件</legend> <form action="/Error/A" method="post" enctype="multipart/form-data"> <input type="file" name="fileA" multiple /> <button type="submit">提交</button> </form> </fieldset> <fieldset> <legend>示例B - 普通表单上传文件,HttpPostedFileBase获取上传文件</legend> @using (Html.BeginForm("B", "Error", FormMethod.Post, new { enctype = "multipart/form-data" })) { <input type="file" name="files" multiple /> <button>提交</button> } </fieldset> <fieldset> <legend>示例C - ajax+FormData上传文件,Request.Files获取上传文件</legend> @using (Html.BeginForm("C", "Error", FormMethod.Post, new { enctype = "multipart/form-data", @id = "form_c" })) { <span>账号:</span><input type="text" name="userName" value="神牛步行3" /><br /> <span>密码:</span><input type="password" name="userPwd" value="神牛" /><br /> <span>文件:</span><input type="file" name="filesc" multiple /><br /> <button type="button">提交</button><br /> <span id="msg_c"></span> } </fieldset> <fieldset> <legend>示例D - 自定义上传插件,Request.Files获取上传文件并提交表单内容</legend> @using (Html.BeginForm("D", "Error", FormMethod.Post, new { enctype = "multipart/form-data", @id = "form_d" })) { <span>账号:</span><input type="text" name="userName" value="神牛步行3" /><br /> <span>密码:</span><input type="password" name="userPwd" value="神牛" /><br /> <span>文件:</span><input type="file" name="filesd" multiple /><br /> <button type="button">提交</button><br /> <span id="msg_d" style="color:red"></span> } </fieldset> </div> </body> </html> <script src="/UploadFiles/2021-04-02/jquery-1.10.2.min.js">以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!
文件上传
RTX 5090要首发 性能要翻倍!三星展示GDDR7显存
三星在GTC上展示了专为下一代游戏GPU设计的GDDR7内存。
首次推出的GDDR7内存模块密度为16GB,每个模块容量为2GB。其速度预设为32 Gbps(PAM3),但也可以降至28 Gbps,以提高产量和初始阶段的整体性能和成本效益。
据三星表示,GDDR7内存的能效将提高20%,同时工作电压仅为1.1V,低于标准的1.2V。通过采用更新的封装材料和优化的电路设计,使得在高速运行时的发热量降低,GDDR7的热阻比GDDR6降低了70%。
更新动态
- 第五街的士高《印度激情版》3CD [WAV+CUE][2.4G]
- 三国志8重制版哪个武将智力高 三国志8重制版智力武将排行一览
- 三国志8重制版哪个武将好 三国志8重制版武将排行一览
- 三国志8重制版武将图像怎么保存 三国志8重制版武将图像设置方法
- 何方.1990-我不是那种人【林杰唱片】【WAV+CUE】
- 张惠妹.1999-妹力新世纪2CD【丰华】【WAV+CUE】
- 邓丽欣.2006-FANTASY【金牌大风】【WAV+CUE】
- 饭制《黑神话》蜘蛛四妹手办
- 《燕云十六声》回应跑路:年内公测版本完成95%
- 网友发现国内版《双城之战》第二季有删减:亲亲环节没了!
- 邓丽君2024-《漫步人生路》头版限量编号MQA-UHQCD[WAV+CUE]
- SergeProkofievplaysProkofiev[Dutton][FLAC+CUE]
- 永恒英文金曲精选4《TheBestOfEverlastingFavouritesVol.4》[WAV+CUE]
- 群星《国风超有戏 第9期》[320K/MP3][13.63MB]
- 群星《国风超有戏 第9期》[FLAC/分轨][72.56MB]