Asp.net_Study学习笔记

2023-11-30 18:58:58 浏览数 (2)

Asp.net_Study

web基本原理

浏览器向服务器发送请求,服务器响应.

报错

HTTP Error 403.14 - Forbidden Web 服务器被配置为不列出此目录的内容。

解决:

  1. 打开控制面板里的程序,点击启用或关闭Windows功能,选择IIS,同时勾选web管理工具及其下面的全部子选项。
  2. 打开IIS服务管理,选择目录浏览,开启应用。
  3. 检查c盘的文档下的IISexpess文件夹,查看applicationhost.config里的< directoryBrowse enabled=“true” />,确保是true。
  4. 在自己的asp.net web应用程序中的webconfig文件中添加 <system.webServer> ​ < directoryBrowse enabled=“true” /> </system.webServer>
HttpHandler

每当用户请求访问ashx页面的时候,ProcessRequest方 法就会被调用,在这里通过访问context.Request获得访问者的请求参数等。然后在ProcessRequest中通过context.Response向浏览器发回数据给浏览器。

调试形式启动项目(默认请求报错的问题),修改地址栏访问ashx。选择浏览器:选择浏览器安装目录;更方便的改代码测试:哪怕停止调试,只要web服务器还在运行,那么修改CS代码之后只要点击“生成”

表单提交

Html表单可以自动给服务器提交参数(get是 通过url,post是通过报文体,后面会讲区别),不用用户自己拼url。action指定把表单内容提交给谁。

浏览器向服务器端提交数据,被提交数据的表单(input. selecttextarea等)放到form中,form中 通过action属性设定表单被提交给哪个页面,为了在服务端取出表单项的值,需要在HTML中为表单元素设定name属性

注意id是给JS操作Dom用的,name才是提交给服务器用的。id不能重复,name可以重复,重复的name的值都会被提交给服务器。

服务器端用context. Request[“username”]来根据表单项的name 来获得提交的属性值。

checkbox没选中为null,选中为"on"。

补充

当我们点击[登录]按钮以后是浏览器将用户填写的文本框等控件中的值“提取”出来发送给服务器,而不是服务器来读取用户填写的这个页面。

哪些标签的哪些值会被提交给服务器呢?将用户填写的内容提交到服务器有如下几个条件[使用浏览器监视网络请求验证) :

只能为input. textarea. select三种类型的标签。

只有三种标签的value属性的值才会提交给服务器。标签必须设定name属性。如果要将标签的value属性值提交到服务器,则必须为标签设定name属性,提交到服务器的时候将会以“name= =value"的键值对的方式提交给服务器。name是给服务器用的,id是给Dom用的。

对于RadioButton, 同name的为一组,违中的RadioButton的value被提交到服务器

当input= submit的时候,只有被点击的按钮(要有name)的value才会被提交。

放到form标签内。只有放到form标签内的标签才可能会被提交到服务器,form之外的input等标签被 忽略。

HTTP协议报文

请求

GET / HTTP/1. 1表示向服务器用GET方式请求首页,使用HIIP/1. 1协议

User- Agent (简称UA)为浏览器的版本信息。通过这个信息可以读取浏览器是IE还是FireFox.支持的插件、. Net版本等。看看IE和Chrome的UserAgent不-样

Referer:来源页面、所属页面

Accept- Encoding:服务器支持什么压缩算法。Accept-Language: 浏览器支持什么语言。

请求是可以伪造的。

响应:

响应码:“200” : OK:“302” : Found 暂时转移,用于重定向,Response. Redirect0会让浏览器再请求一次重定向的地址,重定向的请求是Get方式:;

"404” : Not Found未找到。

500 服务器错误(一般服务器出现异常),通过调试方式自动跳转到出异常的点。304(ctrl f5)

Content-Type: texthtml; charset=utf-8表示返回数据的类型中服务器通过Content-Type告诉客户端响应的数据的类型,这样浏览器就根据返回数据的类型来进行不同的处理,如果是图片类型就显示,如果是文本类型就直接显示内容,如果用html类型就用浏览器显示内容.常用Content-Type: texthtml. image/gif、image/jpeg. text/plain. textjavascript。 这是为什么要的ashx中设置contenttype的原因,试着改成text/plain

HttpRequest类

context. Request (HttpRequest类型),请求相关信息。

context.Request.Form[“username”]获取Post请求中的值

context.Request.QueryString[“username”]获取Get请求中的值。

context. Request[" username "]顺序从QueryString .Form. Cookies. ServerVariables中 找,第一个找到的就是(反编译验证).

'name=rupeng&age=8” 叫做QueryString请求参数获取的都是String类型数据,因为Http协议就是文本。

UrlReferrer; UserAgent; UserHostAddress客 户端IP地址

代码语言:javascript复制
			// text/plain 指示响应的内容是纯文本数据,text/html 指的是html代码
            context.Response.ContentType = "text/plain";
            context.Response.Write("Hello World");
            // context. Request [" name"]无论是Get还是Post都能获得
            // string name = context. Request [" name"]://[""]索引器
            // string age = context. Request[" age"]:	
			// 浏览器类型
            context.Response.Write(""   context.Request.Browser.Browser   "n");
			// 操作系统类型
            context.Response.Write(""   context.Request.Browser.Platform   "n");
			// 浏览器版本
            context.Response.Write(""   context.Request.Browser.Version   "n");
            context.Response.Write("------------------n");
			// 浏览器发送的请求头的信息的种类
            for (int i = 0; i < context.Request.Headers.AllKeys.Length; i  )
            {
                String key = context.Request.Headers.AllKeys[i];
                String value = context.Request.Headers[key];
                context.Response.Write(key   " n");
            }
			// 请求方法
            context.Response.Write(context.Request.HttpMethod   "n");
            // context.Response.Write(context.Request.InputStream)//请求报文体的流
			// 请求路径
            context.Response.Write(context.Request.Path   "n");
			// get请求时表单提交信息信息将拼接到url上
            context.Response.Write(context.Request.QueryString   "n"); 
			//被请求的文件的服务器上的物理路径
            context.Response.Write(context.Request.PhysicalPath   "n"); 
			// 浏览器,操作系统的详细信息
            context.Response.Write(context.Request.UserAgent   "n");       
			//客户端的IP地址
            context.Response.Write(context.Request.UserHostAddress   "n");
			// refer 请求的refer
            context.Response.Write(context.Request.UrlReferrer   "n");     
			//浏览器支持什么语言
            context.Response.Write(String.Join(", ", context.Request.UserLanguages)   "n");
HttpResponse类

响应浏览器的请求信息。提供最基本的write等方法,将字符串信息返回给浏览器。

context. Response响应相关信息. ContentType; OutputStream输出流; End()将当前所有缓冲的输出发送到客户端,停止该页的执行。通过对End(进行try,发现是是抛出了异常。所以 End()之后的代码就不会执行了。 Redirect()重定向;

context.Server

Server是一个HttpServerUtility类型的对象,不是一个类名

MapPath: MapPath("~/a.htm")将 虚拟路径(~代表项目根目录)转换为磁盘上的绝对路径,操作项目中的文件使 用。

HtmlEncode. HtmlDecode: HTML编码解码。Encode 为的是把特殊字符转义显示,如<>等

UrlEncode、UrlDecode: url编码解码。汉字、特殊字符(空格、尖括号)等通过Url传递的时候要编码,举例子“1 '<如鹏 网rupeng.com>

Transfer()

代码语言:javascript复制
context.Response.ContentType = "text/html";
//得到文件在服务器磁盘上的绝对路径。~表示网站根目录
string filepath = context.Server.MapPath(" ~/t1.html");
FileInfo fi = new FileInfo (filepath);
context.Response.Write(fi. Length);
// 注意规范,对于响应的html代码,应构成完整的html结构
context.Response.Write("<html><head></head><body>");
/*
string cscode = "hello List<T> list = new List<T>();";
//HtmlEncode:把<>等特殊字符转换为htm1中的转义字符
string enCodeCSCode = context.Server.HtmlEncode(cscode);
// context.Response.Write(cscode);
context.Response.Write(enCodeCSCode);
context.Response.Write("</body></html>")*/
/*
string s = "hello List&lt:T&gt: list = new List&lt:T&gt:();";
string s1 = context.Server.HtmlDecode(s);//HtmlEncode的反过程
context.Response.Write(s1);*/
string s = "hello 如.鹏.网<>
// 对url进行编码解码,对其中的汉字,特殊符号等进行编码解码
string s1 = context.Server.Ur1Encode(s);
context.Response.Write(s1);
动态生成图片
代码语言:javascript复制
            // 需要注意contenttype的类型
			context.Response.ContentType = "image/jpeg";
            //String name = context.Request["name"];
            String name = "sb";
            String imgfullpath = context.Server.MapPath("~/test.jpg");
            using (Image bmp = Image.FromFile(imgfullpath))
            using (Graphics g = Graphics.FromImage(bmp))
            using (Font f1 = new Font(FontFamily.GenericSansSerif, 12))
            {
                g.DrawString(name, f1, Brushes.Red, 0, 0);
                bmp.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            }
下载文件

ashx的代码

代码语言:javascript复制
            context.Response.ContentType = "text/plain";
            // 告诉浏览器,返回的内容是以附件形式需要用户保存
            context.Response.AddHeader("Content-Disposition", "attachment;filename="   context.Server.UrlEncode("test.txt"));
            DataTable dt = SqlHelper.ExecuteQuery("select * from work_guangzhou limit 10");
            foreach (DataRow item in dt.Rows)
            {
                context.Response.Write(item["number"] " "  item["jobname"] " " item["salary"]   "n");
            }

在html中有一个a链接,链接到ashx,点击即可下载。

文件的上传

html中需要注意必须要post提交,一般文件数据较大,不适合报文头提交数据。enctype必须要设置multipart/form-data

代码语言:javascript复制
    <form action="handle_upload_file.ashx" method="post" enctype="multipart/form-data">
        <input type="file"  name="file1" value="选择文件" />
        <input type="text" name="text1" value="" />
        <input type="submit" name="sub1" value="上传" />
    </form>

c#代码中使用HttpPostedFile类获取对应文件控件的相关内容,并且保存。

代码语言:javascript复制
            context.Response.ContentType = "text/plain";
            // context.Response.Write("Hello World");
            HttpPostedFile file1 =  context.Request.Files["file1"];
            file1.SaveAs(context.Server.MapPath("~\upload"   "\"   file1.FileName)) ;
js代码运行在浏览器端,c#代码运行在服务器

对于一般的js代码,服务器而言只是一串字符串,对于客户端而言是需要执行js代码,需要按照语言规范来执行。所以服务器而言,只会对服务器的代码严格的执行。js的代码只会执行在浏览器。

网站安全

服务器端的数据检查是必不可少的,客户端的数据不能保证真实性!!!

客户端提交的数据都可以进行造假。

HTTP协议

协议是无状态的,浏览器每一次请求服务器,对于服务器而言根本不会认识这个浏览器,权当一样的请求处理。

代码中,浏览器的每一次请求但会生成一个新的IhttpHandle对象解析请求。

cookie

因为HTTP协议是无状态的,所以为了记录用户登录状态等目的,cookie就因此诞生。

Cookie是和站点相关的,并且每次向服务器请求的时候除了发送表单参数外,还会将和站点相关的所有Cookie都提交给服务器。Cookie也是保存在浏览器端的,而且浏览器会在每次请求的时候都会把和这个站点的相关的Cookie提交到服务器,并且将服务端返回的Cookie更新回数据库,因此可以将信息保存在Cookie中,然后在服务器端读取、修改(看报文)。

在服务器端控制Cookie案例,实现记住用户名的功能,设置值的页面: Responsea SetCookie(new HttpCookie(’ UserName’,username)); 读取值的页面: username= Reauest. Cookies[’ UserName’ ]. Value;

如果不设定Expires那么生命周期则是关闭浏览器则终止,否则“最多”到Expires的时候终止。保存7天”。 Cookie的缺点:还不能存储过多信息,机密信息不能存。Cookie:是可以被清除不能把不能丢的数据存到Cookie中; Cookie尺寸有限制,一般就是几K,几百K,Cookie无法跨不同的浏览器;浏览器的“隐私模式/小号模式

cookie不能跨浏览器,同时浏览器的隐藏模式,无痕模式,都会又不同的cookie

当一个新的浏览器请求服务器,服务器会返回一个cookie信息,浏览器接受到将保存至本地,在之后的每次请求服务器都会将cookie信息携带上发送给服务器。

cookkie注意事项
代码语言:javascript复制
            HttpCookie cookie = new HttpCookie("test");
            cookie.Value = "zhushaonb";
            context.Response.SetCookie(cookie);

以这种形式赋值cookie,cookie的生存期只会保持到浏览器关闭前。

cookie.Expires = DateTime.Now.AddSeconds(10);

设置expires属性,自定义cookie的生存时间。

可以通过设设置cookie的path属性,指定某个页面可以读取这个cookie

以及指定domain属性,控制可以读取cookie信息的域名范围。

Session

session 可以近似得看成是服务器端的cookie,因为对与浏览器端提交的cookie信息,本质上可以通过造假来欺骗服务器,对此只能将信息存储在cookie并且保存在浏览器中就存在极大的弊端,为此,服务器需要有自己得独立得一套记录浏览器情况的 “cookie”,这就可以看成是session。

cookie和session的区别

session的类型基本上可以是任意类型的值,cookie只能赋值字符串。

代码中对session的处理,只有当httphandle实现IRequiresSessionState接口,这是标记接口,asp.net引擎才会处理session。session具有自动销毁机制,如果在一段时间内浏览器没有和服务器发生交互,服务器则会销毁session,对于此时的浏览器而言则会需要进行重新登录等操作。在web.config文件中,在system.web节点下配置sessionState节点的timeout属性,单位是分钟,默认是20,可以手工设置。

使用Abandon()方法可以直接销毁服务器的session

生成简单的验证码
代码语言:javascript复制
// 生成随机数
Random rand = new Random();
int num = rand.Next(1000,10000);// 左闭右开的区间
String code = num.ToString();
// 存储在session中,用于服务器判断浏览器输入的验证码是否正确
content.Session[""] = code;
// 70X25大小的画板
using(Bitmap bmp = new Bitmap(70, 25))
{
	// 创建画笔
	using(Graphics g = Graphics.FromImage(bmp))
	// 创建字体样式
	using(Font font = new Font(FontFamily.GenericSerif, 15))
	{		
		g.DrawString(code, font, Brushes.Red, new PointF(0, 0));
	}
	// 保存在输出流中
	bmp.Save(content.Response.OutputStream, ImageFormat.Jpeg);
}
Session原理

session和cookie的关系,cookie中存放了一个sessionid,服务器中保存sessionid和数据的对应关系.

简单的借助Guid算法生成session

代码语言:javascript复制
    public class TestSesssion
    {
        private const String TestSessionId = "test";
        private String sessionid;
        private HttpContext context;
        public TestSesssion(HttpContext context) 
        {
            this.context = context;
            HttpCookie cookie = this.context.Request.Cookies[TestSessionId];
            if (cookie == null)
            {
                CreateGuidSession();
            }
            else
            {
                this.sessionid = cookie.Value;
            }
        }
        public void CreateGuidSession()
        {
            Guid guidt = Guid.NewGuid();
            this.sessionid = guidt.ToString();
            HttpCookie cookie = new HttpCookie(TestSessionId);
            cookie.Value = this.sessionid;
            this.context.Response.SetCookie(cookie);
        }

        public bool HasSessionId()
        {
            HttpCookie cookie = this.context.Request.Cookies[TestSessionId];
            return cookie != null;
        }
        public void SetValue(String value) 
        {
            String fullpath = this.context.Server.MapPath("~/MySession/"   TestSessionId);
            File.WriteAllText(fullpath, value);
        }
        public String GetValue() 
        {
            String fullpath = this.context.Server.MapPath("~/MySession/"   TestSessionId);            
            return File.ReadAllText(fullpath);
        }
    }
进程外session

因为,一般情况下session默认保存在iis服务器的内存中,所以在iis重启后session信息会丢失,所以将session存储在数据库中,这样在web服务器重启后依然能保持session信息.

session保存在数据库中的方法

1、Session保存在SQLServer中配置方法 1)运行.NetFramework安装目录下对应版本的aspnet_regsql.exe 来创建相关的数据库、表和存储过程等,比如: C:WindowsMicrosoft.NETFrameworkv4.0.30319> aspnet_regsql.exe -ssadd -sstype p -S 127.0.0.1 -U sa -P 123456

其中-sstype p表示数据库名固定为ASPState,-S(大写)为数据库服务器地址,-U和-P分别为数据库的用户名和密码,参数详细解释见 http://blog.csdn.net/yuanzhuohang/article/details/6758304 2)修改web.config中sessionState节点的配置:

代码语言:javascript复制
<sessionState mode="SQLServer" timeout="20" sqlConnectionString="server=.;uid=sa;password=123456;" ></sessionState>
Asp.net Web Form

服务器端常见基本控件:

  1. Button控件。On.ClientClick属性, 当用户点击按钮的时候在浏览器瑞执行的代码,注意Qn.ClientLlick是字符串属性,写的代码是JavaScript代码,渲染成onlick,运行在浏览器端。<asp:Button ID=“btnDel.” runat=“server” andlientclick=“return confirm(真的要删除吗? ')” Text=“删除”/>服务器端的OnClick。
  2. linkButton, 用法和Button差不多,区 别就是Button控件渲染为按钮,而LinkButton.渣染为超链接。不要用LinkButton来实现普通的超链接:QnClick→Redirect,作死的节奏。LinkButton真 没啥用,除了长得像Button一样。不利于SEO;而且效率低。
  3. FileUpload:一个对HttpPostedFile类实现封装的控件。
  4. DropDownList绑定数据源:使用DataTextFeild属性设置绑定数据的内容,DataValueFeild属性设置绑定数据的值。

5.viewstate;

ASPX

aspx文件(新建项→Web窗体)。在aspx中可以使用<%=表达式%>的方式在页面的当前位置输出表达式(表达式至少要用protected级别,后面会讲为什么,如果自动提示出不来,则重新生成项目)或者局部变量,表达式也可以是一个方法、属性、字段。不要忘了写=。

其实=就是相当于R.esponse.Write(),直接写Response.Write()也可以。

语法:

  • <% %>中写c#代码
  • <% =value%>相当于调用Respond.Write(value)方法

基本原理:本质上就是占位符替换内容。

aspx.和ashx.关系: aspx就是一个特殊的lHttpHandler.,aspx.对应的类是Page,它是实现了IHttpHandler接口,所以可以说aspx是高级的HttpHandler。

ashx(handler), aspx(page)

runat=server

aspx标签runat属性设置为server,可以实现后台控制该标签。对于没有设置runat=“server”属性的标签aspx引擎会将该标签当成普通字符串处理返回给浏览器。

代码语言:javascript复制
<input type="text" id="txt1" runat=server/>
<!-- 加完runat=server后可以,在c#里面操作这些标签 -->

Button控件。OnClientClick属性,设置成onclientclick=“return confirm(‘真的要删除吗’)”后,可以弹窗询问

PostBack

现在在A.aspx,这个页面上,点击页面上的按钮把数据提交到A.aspx,处理,这个过程可以看做是“从客户端浏览器把之前的状态数据提交回来(Post Back)”

**IsPostBack:**是否是第一次加载

aspx中<% %>可以编写c#;webstorm做了解就行,尽量不使用,最多轻量版。

Repeater

Repeater相当于一个高级的foreach,每一项的显示用ItemTemplate格式去显示。

代码语言:javascript复制
if (!IsPostBack)
{
    Repeater1.DataSource = SqlHelper.ExecuteQuery("select * from T_Persons");
    Repeater1.DataBind();
}
代码语言:javascript复制
<asp:Repeater ID="Repeater1" runat="server">
    <ItemTemplate>
        <%#Eval("Name") %><br />
    </ItemTemplate>
</asp:Repeater>
ViewState

ViewState就是一个隐藏字段,服务器把需要浏览器去记忆的值,放到<input type=“hidden” name=”__VIEWSTATE“ 每次表单提交的时候都把__VIEWSTATE提交服务器,服务器再根据__VIEWSTATE还原ViewrState

通过代码进行赋值的属性的值都会放到ViewState中。而aspx中的初始值则不会

禁用ViewState(设置:EnableViewState=“false”),客户端只会保留基本的__VIEWSTATE

AJAX

异步的js和XML。用于网页的局部刷新。前端通过Ajax请求后台数据,刷新局部页面。

jQuery对Ajax的封装,并使用

代码语言:javascript复制
<script type="text/javascript">
    $(function () {

        //$.ajax()方法中传入字典,键值对之间用:冒号分隔...
        $("#imgValidCode").click(function () {
            $("#imgValidCode").attr("src", "validCode.ashx?"   new Date());
        })
		// 在自己需要触发的事件上设置Ajax请求,定义需要完成的动作(函数)
        $("#loginBtn").click(function () {
            $.ajax({
                // 定义什么方式去请求url
                type: "post",
                // 需要去请求的url
                url: "Login.ashx",
                // 是否将返回的数据转换成json
                dataType:"json",//返回类型序列化成json格式,如果返回对象不能转换成json格式,则会执行error中的方法.
                // 需要将什么数据提交给url
                data: { userId: $("#userId").val(), password: $("#password").val(), inputValidCode: $("#inputValidCode").val() },
                // 请求成功,并且返回数据正确
                success: function (response) { alert(response.Msg) },
                error: function () { alert("error...") }
            })
        })

    })
</script>
Json数据

使用JavaScriptSerializer对.net对象序列化或反序列化

代码语言:javascript复制
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            String[] strs = { "t1", "t2", "t3"};
            JavaScriptSerializer jss = new JavaScriptSerializer();
            // Serialize 将对象转换成json字符串            
            String json = jss.Serialize(strs);
            // Deserialize 可以将json字符串反序列化字符串
            String[] s = jss.Deserialize<String[]>(json);
            context.Response.Write(json);
            context.Response.Write(s[0]);
            context.Response.Write(s[1]);
            context.Response.Write(s[2]);
        }
jQuery表单序列化(了解)
Ajax全局事件

应用场景:显示隐藏加载进度条…

body标签下的任何元素发送ajax请求时都会触发该事件。

Asp.net other
serverpush

因为HTTP协议的特性,使用HTTP协议传输的浏览器和服务器之间只能先由浏览器端先发起请求,服务器只能被动接受请求。响应浏览器的请求。然后关闭连接。一般而言,http协议有一次性连接的特点,每次连接只能处理一个请求,下一个请求需要再次建立连接才能处理。同时http协议没有记忆性,不会记录上一次与该客户端连接时状态,这些都保证了服务器能购高效率的运行。

因此,如果需要服务器先向浏览器发送数据,从http协议的层面上讲是不能实现的。

在视频的案例中,服务器的代码中对每次读取数据的请求进行while无限循环,直至读取到所需的数据,同时使用sleep来减轻服务求压力,这样不会立即响应请求并关闭连接。一定意义上,浏览器没有再次主动请求服务器,但服务器主动向浏览器返回数据。所以从http的一次连接只能处理一次请求来讲serverpush就是保持这一次的连接(即长连接),对此时的浏览器而言完全可以去请求其他的web服务器,就好像在被动等待服务器先发送数据。

global

全局应用程序类,在这个类中可以定义当一些事件触发时需要执行的方法。同时这是全局应用。

Application_Start:网站第一次被访问时执行 Session_Start:Session启动时 (*)Session_End:Seesion过期(只有进程内的Session,也就是InProc过期的时候才会调用Session_End) Application_BeginRequest:当一个请求过来的时候html等静态文件是iis直接把文件给到浏览器,不经过asp.net引擎的处理。所以不会调用Application_BeginRequest方法;即使访问不存在的页面也会被调用(用法:可以判断客户端输入的网址,然后转到另一个网址) Application_Error:程序中发生未处理异常

application

Application是应用全局对象,被全体共享(任何电脑,任何浏览器),操作之前先Application.Lock(),操作完成后Application.Unblock()

Asp.net缓存

HttpRuntime.Cache.Insert(CacheKey, objObject.null, absoluteExpiration, slidingExpiration),

代码语言:javascript复制
DataTable dt = (DataTable)HttpRuntime.Cache["person"]; // 从缓存中读取
if(dt == null)  // 缓存中没有则从数据库中查询
{
    dt = SqlHelper.ExecuteQuery("select * from T_Persons");
    // 放入缓存,这里是30s过期
    HttpRuntime.Cache.Insert("person", dt, null, DateTime.Now.AddSeconds(30), TimeSpan.Zero);
}
shtml

三个文件:1.shtml,head.html,foot.html

最后效果是三个文件拼接成一个页面显示

代码语言:javascript复制
1.shtml的内容示例
<!-- #include file="head.html" -->
2222中间部分
<!-- #include file="foot.html" -->

0 人点赞