.NET CORE 基础

  • .NET CORE 基础已关闭评论
  • 243 次浏览
  • A+
所属分类:.NET技术
摘要

1.Microsoft Azure 微软拥抱云计算 2..net core 是为云所生的技术 3.Net Framework缺点: 系统级别的安装,互相影响


.NET CORE

1.Microsoft Azure 微软拥抱云计算

2..net core 是为云所生的技术

3.Net Framework缺点:

  • 系统级别的安装,互相影响

  • 无法独立部署

  • SAP.NET和IIS深度耦合

  • 非云原生

4.NET Framework历史包袱

  • 基于拖控件之上的MVC

  • ASP.NET 底层不支持很好的单元测试

5.NET Core的有点

  • 支持独立部署不相互影响

  • 彻底模块化

  • 运行效率高

  • 跨平台

  • 符合现代开发理念,依赖注入,单元测试等

.NET Core .NET Framework 两者根据标准实现 .NET Standard 规定的类 规定的方法 只有定义没有实现 反编译软件ILSpy

验证:.NET Standard只是标准,不是实现1)建.NET Standard类库项目,确认版本是 2.0,建一个类,方法中打印 typeof (FileStream).Assembly.Location。2分别建NET Framework和.NET Core的控制台项目,添加对类库项目引用,并且调用。3用反编译工具ILSpy尽管开源)分别反编译VS中FileStream、.NET Framework和. NETCore运行中的。BeginRead方法实现以及定义有 不同。

.NET5开发工具:1).NET CLI:命令行 2)yisual Studio: Windows-Only(推荐)3) Visual Studio for Mac 4)Jetbrains Rider:收费 5)vS Code (Visual Studio, Code):跨平台

.NET SDK、运行时、文档----https:// dotnet.mi crosoft.com/ 可能VS自带,但是在服务器上需要单独安装

dotnet —-version查看版本 dotnet new console当前文件夹下创建控制台项目 dotnet run构建并运行 详细见官方文档“.NET CLI”部分。

程序的发布。 1、部署模式:依赖框架;独立(推荐)﹔ 2、目标运行时。 3、生成单个文件。 4、ReadyToRun: AOT (ahead of-time)、JIT。缺点看文档。 5、裁剪未使用的程序集。缺点看文档。 自学就要养成把相关文档“翻一翻”的意识。

WSL:Windows subsystem for linux SandBox

NugGet

注意:【默认项目】为目标项目。 1)安装:Install-Package XXX.yersion指定版本。 可以看到把依赖组件都下载了。 版本检测:尝试把项目改为.NET core 1.0,然后再安装Zack. EFCore.Batch试试。 2)卸载:Uninstall-Package XXX 3)更新到最新版:Update-Package XXX

1、NuGet你也可以页献,就三步。 2、和.NET Framework不同,.NET core绝大部分官方程序集也要到NuGet下载。模块化! 3、少部分是收费的,如搜索“word file” 4、参差不齐,如何分辨质量。 5、内部部署NuGet服务mTo

异步编程1

不等 异步编程不能加快单个请求的响应速度,只是能处理更多的请求

C#关键字:async await 不能与多线程

“异步方法”:async关键字修饰的方法 1))异步方法的返回值一般是Task<T>,T是真正的返回值类型,Task<int>。惯例:异步方法名字以Async结尾。 2)即使方法没有返回值,也最好把返回值声明为非泛型的Task。 3)调用异步方法时,一般在方法前加上await,这样拿到的返回值就是泛型指定的T类型;4)异步方法的“传染性”:一个方法中如果有await调用,则这个方法也必须修饰为async

static async Task Main(string[] args)
{
  string fileName = "d:/1.txt" ;File. Delete(fileName) ;
  File.WriteA11TextAsync(fileName,"hello async");string s= await File.ReadAllTextAsync(fileName) ; Console.WriteLine(s):
}

如果同样的功能,既有同步方法,又有异步方法,那么首先使用异步方法。.NE5中,很多框架的方法也都支持异步:Main、WinForm事件处理函数。 对于不支持的异步方法怎么办? Wait()(无返回值);Result(有返回值)。风险:死锁。尽不用

异步委托

 //线程池  在子线程中执行的方法放到线程池
ThreadPool.QueueUserWorkItem(async(obj) =>
{
    while (true)
    {
    await File.WriteAllTextAsync(@"D:text1.txt","aaaaaaaaaaa");
    Console.WriteLine("------------------");
    }
});
Console.Read();

·用ILSpy反编译dll(.exe只是 windows下的启动器)成C#4.0版本,就能看到容易理解的底层lL代码。await、async是“语法糖”,最终编译成“状态机调用”.

总结: async的方法会被C#编译器编译成一个类,会主要根据await调用进行切分为多个状态,对async方法的调用会被拆分为对MoveNext的调用。 用await看似是“等待”,经过编译后,其实没有“wait”。

            //使用异步编程获取百度网址页面的代码并且写道文件中  然后读出来写入到控制器
using (HttpClient httpContent = new HttpClient())
          {
              string html = await httpContent.GetStringAsync("https://www.baidu.com");
              Console.WriteLine(html);
          }
          string txt = "hellow zyb";
          string filename = @"D:text1.txt";
          await File.WriteAllTextAsync(filename, txt);
          Console.WriteLine("写入成功");
          string s = await File.ReadAllTextAsync(filename);
          Console.WriteLine("文件内容"+s);

await调用的等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后续的代码。

Thread.CurrentThread.ManagedThreadId 获得当前线程Id

验证:在耗时异步(写入大字符串)操作前分别打印线程Id

 Console.WriteLine(Thread.CurrentThread.ManagedThreadId);  //托管线程id
StringBuilder sb = new StringBuilder(); //大文件
for (int i = 0; i < 2000; i++)  
{
sb.Append("xxxxxxxxxxxxx");   //等待的过程中把当前的线程放到线程池,然后从线程池取出随机线程执行别的方法
}
await File.WriteAllTextAsync(@"D:text1.txt",sb.ToString());
          Console.WriteLine(Thread.CurrentThread.ManagedThreadId); //托管线程id   两次线程的id不一样

异步编程不等于线程

异步方法的代码并不会自动在新线程中执行,除非把代码放到新的线程中执行

用Task.Run改造之前的例子,再看看线程变化

 
  Console.WriteLine("之前", +Thread.CurrentThread.ManagedThreadId);
          double r = await CalcAsync(5000);
          Console.WriteLine($"r={r}");
          Console.WriteLine("之前", +Thread.CurrentThread.ManagedThreadId);
public static async Task<double> CalcAsync(int n)
      {//自动根据返回值进行类型推断   将下面代码放到新的线程执行
          return await Task.Run(() =>
          {
              Console.WriteLine("CalcAsync", +Thread.CurrentThread.ManagedThreadId);
              double result = 0;
              Random radn = new Random();
              for (int i = 0; i < n * n; i++)
              {
                  result += radn.NextDouble();
              }
              return result;
          });
         

      }

anync方法缺点

1.异步方法会生成一个类,运行效率没有普通方法高,

2.可能会占用非常多的线程

只甩手Task,不拆完了再装,反编译上面的代码,知识普通方法的调用

有点:运行效率高,不会造成线程浪费

返回值为Task的不一定都要标注async,标注async知识让我们可以更方便的await而已

如果一个异步的方法只是对别的方法调用的转发,并没有太多的复杂逻辑,(比如等待a的结果,再调用b,把a调用的返回值拿到内部做一些处理再返回),那么就可以去掉async关键字


//内部只是对方法的调用
static Task<string> ReadAsync(int num)
      {
          if (num == 1)
          {
            return File.ReadAllTextAsync(@"D:text1.txt");
           
          }
          else if (num == 2)
          {
            return   File.ReadAllTextAsync(@"D:text2.txt");
          }
          else
          {
              throw new ArgumentException();
          }

      }

异步编程 不要用sleep

如果想在异步方法中暂停一段时间,不要用thread.sleep(),因为他会阻塞调用线程,而要用await task.Delay()

在控制台中没看到区别,但是放到winform程序中就能看到区别了,asp.net core中也看不到区别但是sleep()会降低并发

//这是winform的代码块
private async void button1_Click(object sender, EventArgs e)
      {
          using (HttpClient httpClient = new HttpClient())
          {
              string sl = await
              httpClient.GetStringAsync("https://www.youzack.com");
              textBox1.Text = sl.Substring(0,20);
              //Thread.Sleep(3000);
              await Task.Delay(3000);
              string s2 = await
              httpClient.GetStringAsync("https://www.baidu.com");
              textBox1.Text = sl.Substring(0, 20);
          }
      }

异步编程CancellationToken

有时需要提前终止任务,比如:请求超时,用户取消请求,跟多异步方法都有CancellationToken参数 用于获得提前终止的信号

CancellationToken结构体None:空 bool lsCancellationRequested是否取消(*)Register(Action callback)注册取消监听ThrowlfCancellationRequested()如果任务被取消,执行到这句话就抛异常。

CancellationTokenSource CancelAfter()超时后发出取消信号Cancel()发出取消信号 CancellationToken Token

为“下载一个网址N次”的方法增加取消功能。 分别用GetStringAsync + IsCancellationRequested、 GetStringAsync+ ThrowlfCancellationRequested()、带CancellationToken的GetAsync()分别实现。取消分别用超时、用户敲按键(不能await)实现。

ASP.NET Core开发中,一般不需要自己处理CancellationToken、 CancellationTokenSource这些,只要做到“能转发cancellationToken就转发”即可。ASP.NET Core会对于用户请求中断进行处理。(*)演示一下ASP.NETCore中的使用:写一个方法,Delay1000次,用 Debug.WriteLine(输出,访问中间跳到放到其他网站。

CancellationTokenSource cts = new CancellationTokenSource();
          cts.CancelAfter(2000);
          CancellationToken token = cts.Token;
          await DownloadlAsync("https://www.baidu.com",1000,token);
static async Task DownloadlAsync(string url, int n,CancellationToken cancellationToken)
      {
          using (HttpClient client = new HttpClient())
          {
              for (int i = 0; i < n; i++)
              {
                  var resp = await client.GetAsync(url,cancellationToken);  
                  string html = await resp.Content.ReadAsStringAsync();
                  File.WriteAllTextAsync(@"D:text1.txt", html,cancellationToken);
                  Console.WriteLine($"{DateTime.Now}:{html}");
                  //if (cancellationToken.IsCancellationRequested)
                  //{
                  //   Console.WriteLine("请求被取消了");
                  //   break;
                  //}
                  //cancellationToken.ThrowIfCancellationRequested();  
              }
          }
      }
  //CancellationToken cancellationToken  这个参数是为了防止用户访问别的网站或者关闭浏览器服务端还在执行
static async Task DownloadlAsync(string url, int n, CancellationToken cancellationToken)
{
using (HttpClient client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
var resp = await client.GetAsync(url, cancellationToken);
string html = await resp.Content.ReadAsStringAsync();
Debug.WriteLine(html);
}
}
}

public async Task<IActionResult> Index(CancellationToken cancellationToken)
{
await DownloadlAsync("https://www.baidu.com", 10000, cancellationToken);
return View();
}

异步编程WhenAll

Task类的重要方法: 1.Task<Task> WhenAny(lEnumerable<Task>tasks)等,任何一个Task完成,Task就完成2.Task<TResult[]> WhenAll<TResult>(paramsTask<TResult>[] tasks)等,所有Task完成,Task才完成。用于等待多个任务执行结束,但是不在乎它们的执行顺序。 3.FromResult()创建普通数值的Task对象。

 string[] file = Directory.GetFiles(@"D:text1.txt");
Task<int>[] counts = new Task<int>[file.Length];
for (int i = 0; i < file.Length; i++)
{
string fileName = file[i];
Task<int> t = ReadCharCount(fileName);
counts[i] = t;
}
int[] countTask = await Task.WhenAll(counts);
Console.WriteLine(countTask);
static async Task<int> ReadCharCount(string fileName)
{
string a = await File.ReadAllTextAsync(fileName);
return a.Length;

}

异步编程其他问题

接口中的异步方法: async是提示编译器为异步方法中的await代码进行分段处理的,而一个异步方法是否修饰了async对于方法的调用者来讲没区别的,因此对于接口中的方法或者抽象方法不能修饰为async。


interface IText
{

Task<int> GetCharCount(string file);
}

class Text : IText
{
public async Task<int> GetCharCount(string file)
{
string a = await File.ReadAllTextAsync(file);
return a.Length;
}
}

异步与yield: 复习: yield return不仅能够简化数据的返回,而且可以让数据处理“流水线化”,提升性能。

  foreach (var s in Test2())
{
Console.WriteLine(s);
}
static IEnumerable<string> Test2()
{
yield return "hellow"; //这里直接返回一条进行foreach循环 取一条执行一条 方法内部拆分搞成状态机
yield return "yzk";
yield return "youzack";

}

在旧版C#中,async方法中不能用yield。从C#8.0开始,把返回值声明为IAsyncEnumerable(不要带Task),然后遍历的时候用await foreach()即可。

  await foreach (var s in Test3())
{
Console.WriteLine(s);
}
static async IAsyncEnumerable<string> Test3()
{
yield return "hellow";
yield return "yzk";
yield return "youzack";
}

为什么要学LING

为什么要学LINQ?让数据处理变得简单: 让数据处理傻瓜化 统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出出现频率高于2次的单词和其出现的频率。

(复习)委托 1、委托是可以指向方法的类型,调用委托变量时执行的就是变量指向的方法。举例。 2、.NET中定义了泛型委托Action(无返回值)和Func(有返回值),所以一般不用自定义委托类型。举例。

 static void Main(string[] args)
{
D1 d = F1;
d();
d = F2;
d();
D2 d2 = Add;
Console.WriteLine(d2(1,2));
Action a1 = F2;
a1();
Func<int, int, int> f = Add;
f(1,2);
Func<int, int, string> a = F33; //有返回值委托
a(1,2);
Action<int, string> c = F44; //无返回值委托
c(1,"zha");
}
static void F1()
{
Console.WriteLine("我是F1");
}
static void F2()
{
Console.WriteLine("我是F2");
}
static int Add(int a,int b)
{
return a + b;
}
static string F33(int i,int a)
{
return "hellow";
}
static void F44(int a,string b)
{

}

}
delegate void D1();
delegate int D2(int a,int b);

Lambda表达式

可以省略参数数据类型,因为编译能根据委托类型推断出参数的类型,用=>引出来方法体。

一步一步简化

Action f1 = delegate ()
{
Console.WriteLine("我是方法");
};

Action f11 = () => Console.WriteLine("我是方法");

f1 (); //匿名方法无参无返回值
Action<string, int> f2 = (string a, int b) => Console.WriteLine($"a={a},b={b}"); ;
f2("zyb",1); //匿名方法 两个参数
Func<int, int, int> f3 = (int a, int b) => { return a + b; };

f3(1,2); //有参有返回值 匿名方法
Func<int, int, int> f4 = (int i, int j) => i + j;
f4(1,2);

Func<int, int, int> f5 = ( i, j) =>
{
return i + j;
};
f5(1, 2);
Action<int> a1 = i => Console.WriteLine(i);
Func<int, bool> f66 = delegate (int i)
{
return i > 0;
};
Func<int, bool> f67 = i => i > 0;
Func<int, bool> f68 = delegate (int i)
{
return i > 5;
};
}

LING方法的背后

LING中提供了很多集合的扩展方法 配合lambda能简化数据处理

 static void Main(string[] args)
{
int[] nums = new int[] { 20,55,51,55,58,99,22};
//where方法会遍历集合中每个元素 对于每个元素
//都调用a=>a>10 这个表达式判断一下是否为true
//如果为true 则把这个放到返回的集合中
//IEnumerable<int> result = nums.Where(a=>a>10);
var result = MyWhere2(nums,a => a > 10);
foreach (int item in result)
{
Console.WriteLine(item);
}
}

static IEnumerable<int> MyWhere(IEnumerable<int> items,Func<int,bool> f)
{
List<int> result = new List<int>();
foreach (int item in result)
{
if (f(item)==true)
{
result.Add(item);
}
}
return result;
}

static IEnumerable<int> MyWhere2(IEnumerable<int> items, Func<int, bool> f)
{
foreach (int item in items)
{
if (f(item) == true)
{
yield return item; //流水线处理 满足条件直接返回
}
}
}
}

LING常用扩展方法 IEnumberble<T> 扩展方法

获取一条数据(是否带参数的两种写法):Single:有且只有一条满足要求的数据;SingleOrDefault:最多只有一条满足要求的数据; First :至少有一条,返回第一条; FirstOrDefault:返回第一条或者默认值; 选择合适的方法,“防御性编程”

LING解决面试

性能与面试 LINQ大部分时间不会影响性能,不过我曾经遇到过,讲讲。 面试时候的算法题一般尽量避免使用正则表达式、LINQ等这些高级的类库。大饭店面试大厨的故事。

案例1 有一个用逗号分隔的表示成绩的字符串,如 "61,90,100,99,18,22,38,66,80,93,55,50,89",计算这些成绩的平均值。

 string s = "33,55,66,99";
double avg2 = s.Split(',').Select(e => Convert.ToInt32(e)).Average();
Console.WriteLine(avg);

案例2 统计一个字符串中每个字母出现的频率(忽略大小写),然后按照从高到低的顺序输出出现频率高于2次的单词和其出现的频率。

  string s = "fjdssl fjsdj ejejejej dfaSFSDd";
var item = s.Where(c => char.IsLetter(c)).Select(c => char.ToLower(c)) //过滤掉空格逗号
.GroupBy(c => c).Select(g => new { g.Key, Count = g.Count() })
.OrderByDescending(g => g.Count).Where(g => g.Count > 2);
foreach (var c in item)
{
Console.WriteLine(c);
}

依赖注入 控制反转

概念 生活中的“控制反转”:自己发电和用电网的电。 依赖注入(Dependency Injection,Dl)是控制反转(lnversion of Control,IOC)思想的实现方式。 依赖注入简化模块的组装过程,降低模块之间的耦合度

自己发电的代码
var connSettings =
ConfigurationManager.ConnectionStrings["connStr1"];
string connStr = connSettings.ConnectionString;SqIConnection conn = new
SqlConnection(connStr);缺点是?


//自己从配置文件取到连接字符串 然后实例一个连接数据库的连接把字符串放进去

代码控制反转的目的 “怎样创建XX对象"→“我要XX对象” 两种实现方式: 1)服务定位器(ServiceLocator); 2)依赖注入(Dependency Injection,Dl);

DI几个概念 服务(service):对象;注册服务; 服务容器:负责管理注册的服务;查询服务:创建对象及关联对象; 对象生命周期:Transient(瞬态);Scoped(范围) ; Singleton(单例);

.NET中使用DI 2、根据类型来获取和注册服务。 可以分别指定服务类型(service type) 和实现类 型(implementation type)。这两者可能相同,也可能不同。服务类型可以是类,也可以是接口,建议面向接口编程,更灵活。 3、.NET控制反转组件取名为Dependencylnjection,但它包含ServiceLocator的功能。

生命周期

生命周期 1、给类构造函数中打印,看看不同生命周期的对象创建,使用serviceProvider.CreateScope()创建Scope。 2、如果一个类实现了IDisposable接口,则离开作用域之后容器会自动调用对象的Dispose方法。 3、不要在长生命周期的对象中引用比它短的生命周期的对象。在ASP.NET Core中,这样做默认会抛异常。 4、生命周期的选择:如果类无状态,建议为Singleton;如果类有状态,且有Scope控制,建议为Scoped,因为通常这种Scope控制下的代码都是运行在同一个线程中的,没有并发修改的问题;在使用Transient的时候要谨慎。 5、.NET注册服务的重载方法很多,看着文档琢磨吧。

//使用ioc容器
ServiceCollection services = new ServiceCollection();
services.AddTransient<TestSrvicelmpl>(); // 调用一次实例新的对象 瞬态
//services.AddSingleton<TestSrvicelmpl>(); //相同的对象 单例
//services.AddScoped<TestSrvicelmpl>(); //在范围内拿到的是同一个对象
using (ServiceProvider sp = services.BuildServiceProvider()) //ServiceProvider相当于服务定位器
{
//TestSrvicelmpl T = sp.GetService<TestSrvicelmpl>();
//T.Name = "ZHAGNSNA";
//T.SayHi();

//TestSrvicelmpl T1 = sp.GetService<TestSrvicelmpl>();
//T1.Name = "放假啊可大幅度";
//T1.SayHi();
////比较两个是否同一个对象 结果false
//Console.WriteLine(object.ReferenceEquals(T, T1));
//T.SayHi();
using (IServiceScope scope1 = sp.CreateScope())
{
TestSrvicelmpl T = scope1.ServiceProvider.GetService<TestSrvicelmpl>();
T.Name = "ZHAGNSNA";
T.SayHi();
TestSrvicelmpl T1 = scope1.ServiceProvider.GetService<TestSrvicelmpl>();
Console.WriteLine(object.ReferenceEquals(T, T1));
}

using (IServiceScope scope2 = sp.CreateScope())
{
TestSrvicelmpl T = scope2.ServiceProvider.GetService<TestSrvicelmpl>();
T.Name = "ZHAGNSNA";
T.SayHi();
TestSrvicelmpl T1 = scope2.ServiceProvider.GetService<TestSrvicelmpl>();
Console.WriteLine(object.ReferenceEquals(T, T1));
}
}

 

IServiceProvider的服务定位器方法:

T GetService<T>()如果获取不到对象,则返回null。object GetService(Type serviceType) T GetRequiredService<T>()如果获取不到对象,则抛异常 object GetRequiredService(Type serviceType) lEnumerable<T> GetServices<T>()适用于可能有很多满足条件的服务 lEnumerable<object> GetServices(Type serviceType)

 ServiceCollection services = new ServiceCollection();
services.AddScoped<ITestServie, TestSrvicelmpl>();
services.AddScoped<ITestServie, TestSrvicelmpl2>();
services.AddSingleton(typeof(ITestServie),new TestSrvicelmpl());
using (ServiceProvider sp = services.BuildServiceProvider())
{
//GetService找不到服务返回null
//ITestServie ts1 = sp.GetService<ITestServie>();
//ts1.Name = "tom";
//ts1.SayHi();
//Console.WriteLine(ts1.GetType());

//GetRequiredService 必须的如果找不到 直接抛异常
//ITestServie ts1 = sp.GetRequiredService<ITestServie>();
//ts1.Name = "tom";
//ts1.SayHi();
//Console.WriteLine(ts1.GetType());

//注册多个服务
//IEnumerable<ITestServie> tets = sp.GetServices<ITestServie>();
// foreach (ITestServie item in tets)
// {
// Console.WriteLine(item.GetType());
// }

ITestServie test1 = sp.GetService<ITestServie>();
Console.WriteLine(test1.GetType());


}

DI魅力渐显:依赖注入

1、依赖注入是有传染性”的,如果一个类的对象是通过DI创建的,那么这个奚的构造函数中声明的所有服务类型的参数都会被DI赋值;但是如果一个对象是程序员手动创建的,那么这个对象就和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值。 2、.NET的DI默认是构造函数注入。 3、举例:编写一个类,连接数据库做插入操作,并且记录日志(模拟的输出),把Dao、日志都放入单独的服务类。connstr见备注。

static void Main(string[] args)
{
//降低模块之间的耦合
ServiceCollection services = new ServiceCollection();
services.AddScoped<Controller>();
services.AddScoped<ILog,LogImp1>();
services.AddScoped<IStorage,StorageImp1>();
services.AddScoped<IConfig,ConfiImp1>();
using (var sp = services.BuildServiceProvider())
{
var c = sp.GetService<Controller>();
c.Test();
}
Console.ReadKey();
}

class Controller
{
private readonly ILog log;
private readonly IStorage storage;
public Controller(ILog log, IStorage storage)
{
this.log = log;
this.storage = storage;
}
public void Test()
{
log.Log("开始上传");
storage.Save("fddfs","1.txt");
log.Log("上传完毕");

}
}

interface ILog
{
public void Log(string msg);
}

public class LogImp1 : ILog
{
public void Log(string msg)
{
Console.WriteLine($"日志:{msg}");
}
}

interface IConfig
{
public string GetValue(string name);
}

class ConfiImp1 : IConfig
{
public string GetValue(string name)
{
return "config";
}
}

interface IStorage
{
public void Save(string content,string name);
}

class StorageImp1 : IStorage
{
private readonly IConfig config;
public StorageImp1(IConfig config)
{
this.config = config;
}

public void Save(string content, string name)
{
string server = config.GetValue("server");
Console.WriteLine($"向服务器{server}的文件名为{name}上传{content}");
}
}