开发过程中疑难杂症解决方案总结一

一、批次加载列表数据
问题场景:机票列表页面一直处于loading状态,服务器日志显示已经返回了数据,但是ajax请求一直处于挂起状态,导致页面也一直loading中。
分析原因:通过排查,发现服务器返回的数据量过大(大约2M)导致ajax假死,网上有人说ajax请求的响应时间为10秒,超过了就会假死。
解决方案:
1、把原先的ajax请求分成多批次,每次请求让服务器返回5条数据,这样就避免了一次性响应数据量过大的问题;
2、服务端把数据做缓存,分批次从缓存读取数据给客户端;
3、客户端ajax每次请求返回后根据服务端返回的完成标识判断是否再次发送请求;
相关代码:
客户端:

function getCalendarPriceListAndFlightList() {
    var info = {
        "depCity": $("#hiddepCity").val(),
        "arrCity": $("#hidarrCity").val(),
        "depDate": $("#hiddepDate").val(),
        "queryModule": "1", //查询类型  1-单程;2-往返去程;3-往返回程
        "lineType": "OW", //查询类型  OW-单程; RT-往返
        "uniqueKey": "", //携程舱位标识,返程查询需要
        "queryuuid": "", //查询的唯一标识
        "retType": "1", //数据返回模式 0:多批次返回,覆盖刷新界面展示  1:一次性全部返回(默认)
        "enterpriseId": $("#hidEnterpriseid").val(),
        "isLoaded": -1,
        "guidStr": "",
        "userName": $("#hidUserName").val(),
        "action": "getCalendarPriceListAndFlightList"
    };
    $.ajax({
        url: "../FlightHandler/JDFlightHandler.ashx",
        type: "post",
        data: info,
        dataType: "text",
        beforeSend: function (XMLHttpRequest) {
            $('.page-list-con').html('');
            createLoading();
        },
        success: function (data) {
            var dataArr = data.split('|');
            if (dataArr[2] != "") {
                if (dataArr[0] == "-1")
                    $("#topPrice").text("¥");
                else
                    $("#topPrice").text("¥" + dataArr[0]);
                if (dataArr[1] == "-1")
                    $("#EndPrice").text("¥");
                else
                    $("#EndPrice").text("¥" + dataArr[1]);
                $('.page-list-con').html(dataArr[2]);
                if (dataArr[5] == "-1")
                    $("#dqPrice").text("¥");
                else
                    $("#dqPrice").text("¥" + dataArr[5]);
                if (dataArr[6] == "-1")
                    $('#time_sel_airway').html();
                else
                    $('#time_sel_airway').html(dataArr[6]);

                //保存点击的出发城市、到达城市、开始时间、结束时间
                $("#hidTempdepCity").val($("#hiddepCity").val());
                $("#hidTemparrCity").val($("#hidarrCity").val());
                $("#hidTempdepDate").val($("#hiddepDate").val());
                $("#hidTemparrDate").val($("#hidarrDate").val());
                if (dataArr[3] == "0") {
                    reloadFlightList(dataArr[3], dataArr[4]);
                }
            } else {
                $('.page-list-con').html(SetErroHtml());
                alert("读取数据超时,请尝试重新查询!");
            }
        },
        complete: function (XMLHttpRequest, textStatus) {
            //removeLoading();
        },
        error: function () {
        }
    });
}

function reloadFlightList(isLoaded, guidStr) {
    var info = {
        "depCity": $("#hiddepCity").val(),
        "arrCity": $("#hidarrCity").val(),
        "depDate": $("#hiddepDate").val(),
        "arrDate": $("#hidarrDate").val(),
        "enterpriseId": $("#hidEnterpriseid").val(),
        "isLoaded": isLoaded,
        "guidStr": guidStr,
        "userName": $("#hidUserName").val(),
        "action": "getCalendarPriceListAndFlightList"
    };
    $.ajax({
        url: "../FlightHandler/JDFlightHandler.ashx",
        type: "post",
        data: info,
        dataType: "text",
        success: function (data) {
            var dataArr = data.split('|');
            if (dataArr[3] == "0") {
                if (dataArr[2] != "") {
                    $("#tbFlightContainer").append(dataArr[2]);
                }
                reloadFlightList(isLoaded, guidStr);
            }
            else {
                removeLoading();
            }
        },
        error: function (XMLHttpRequest, textStatus, errorThrown) {
            removeLoading();
            return;
        }
    });
}

 
服务端:

public string getFlightList(HttpRequest Request, ref string interval, ref int isFinished, out decimal minPrice, string AccountName)
{
    LogHelper.WriteLog("进入getFlightList方法");
    string retStr = string.Empty;
    string guidStr = "";
    int iswc = 1;
    minPrice = 0;
    try
    {
        string depCity = string.Empty;
        string arrCity = string.Empty;
        string depDate = string.Empty;
        string queryModule = string.Empty;
        string lineType = string.Empty;
        string uniqueKey = string.Empty;
        string queryuuid = string.Empty;
        string retType = string.Empty;
        if (!string.IsNullOrEmpty(Request.Form["depCity"]))
        {
            depCity = Request.Form["depCity"].ToString();
        }
        if (!string.IsNullOrEmpty(Request.Form["arrCity"]))
        {
            arrCity = Request.Form["arrCity"].ToString();
        }
        if (!string.IsNullOrEmpty(Request.Form["depDate"]))
        {
            depDate = Request.Form["depDate"].ToString();
        }
        if (!string.IsNullOrEmpty(Request.Form["queryModule"]))
        {
            queryModule = Request.Form["queryModule"].ToString();
        }
        if (!string.IsNullOrEmpty(Request.Form["lineType"]))
        {
            lineType = Request.Form["lineType"].ToString();
        }
        if (!string.IsNullOrEmpty(Request.Form["uniqueKey"]))
        {
            uniqueKey = Request.Form["uniqueKey"].ToString();
        }
        if (!string.IsNullOrEmpty(Request.Form["queryuuid"]))
        {
            queryuuid = Request.Form["queryuuid"].ToString();
        }
        if (!string.IsNullOrEmpty(Request.Form["retType"]))
        {
            retType = Request.Form["retType"].ToString();
        }
        string enterpriseId = Request.Form["enterpriseId"].ToString();
        string isLoaded = Request.Form["isLoaded"].ToString();
        string RguidStr = Request.Form["guidStr"].ToString();
        string userName = Request.Form["userName"].ToString();

        List<Common.JsonFlightInfo.FlightInfo> lstFlightInfo = null;
        List<Common.JsonFlightInfo.FlightInfo> lstFlightInfoEx = null;
        List<Common.JsonFlightInfo.FlightInfo> lstFlightInfoExFilter = null;
        List<JsonFlightInfo.FlightInfo> lst = null;
        //首次加载航班信息
        if (string.IsNullOrEmpty(RguidStr))
        {
            lst = PublicListDataBind(depCity, arrCity, depDate, queryModule, lineType, uniqueKey, queryuuid, retType, ref interval, ref isFinished);//获取机票数据
            if (lst!=null && lst.Count > 0)
            {
                lstFlightInfo = lst;
                lstFlightInfoEx = lst;
                lstFlightInfoExFilter = lst;
                //生成GUID缓存键
                guidStr = System.Guid.NewGuid().ToString();
                CacheHelper.SetCache("cache" + userName, lstFlightInfoExFilter, new TimeSpan(0, 10, 0));
            }
            else
            {
                return "|1|";
            }
        }
        else
        {
            //非首次加载航班信息,读缓存
            lstFlightInfo = CacheHelper.GetCache(RguidStr) as List<Common.JsonFlightInfo.FlightInfo>;
            lstFlightInfoEx = CacheHelper.GetCache(RguidStr) as List<Common.JsonFlightInfo.FlightInfo>;
            //用回传过来的GUID键,继续读取缓存
            guidStr = RguidStr;
        }
        //集合里航班信息大于5条,每次就读取前5条,然后把其余数据放到缓存下次读取用。
        if (lstFlightInfoEx.Count > 5)
        {
            lstFlightInfo = lstFlightInfoEx.Skip(0).Take(5).ToList();
            CacheHelper.SetCache(guidStr, lstFlightInfoEx.Skip(5).ToList()); //航班信息保存到缓存

            if (lstFlightInfoEx.Skip(5).Count() < 1)
                iswc = 1; //已完成
            else
                iswc = 0; //未完成
        }
        else
        {
            //集合里航班信息小于5条时,就直接全部输出给页面,清除缓存
            if (!string.IsNullOrEmpty(RguidStr))
            {
                CacheHelper.RemoveAllCache(RguidStr);
            }
            iswc = 1; //已完成
        }
        if (isLoaded == "-1")
        {
            List<string> listString = new List<string>();
            if (lst != null)
            {
                foreach (Common.JsonFlightInfo.FlightInfo flightInfo in lst)
                {
                    flightInfo.minPrice = Convert.ToDecimal(Common.Application.GetLeastPrice(flightInfo.flightNo, Request.Form["classFC"] == null ? "" : Request.Form["classFC"].ToString(),userName));
                    if (!listString.Contains(flightInfo.airwaysCn))
                    {
                        listString.Add(flightInfo.airwaysCn);
                        sbairway.Append("<li  name=\"airway\" hkdm=\"" + flightInfo.airways + "\">" + flightInfo.airwaysCn + "</li>");
                    }
                }
            }
        }
        retStr = Common.HtmlFlightList.getHtmlFlightListWAP(lstFlightInfo, depCity, arrCity, "1", out minPrice, enterpriseId, userName);
        LogHelper.WriteLog("返回航班信息。");
    }
    catch (Exception ex)
    {
        LogHelper.WriteLog("getFlightList异常!异常信息:" + ex.Message + ",堆栈:" + ex.StackTrace);
        throw;
    }

    if (!string.IsNullOrEmpty(guidStr))
        return retStr + "|" + iswc + "|" + guidStr;
    else
        return retStr + "|" + iswc + "|";
}

 
问题总结:
1、ajax请求响应时间为10秒,超过后请求就挂起,浏览器进入假死状态;
2、jQuery ajax实现分批次请求数据;
3、从Cache中利用linq分批次读取数据;

注:
用ajax设置请求头部超时时间,如:
ajax.setRequestHeader("connectionTimeout","5000");


二、APP自动登录实现和登录跳转问题
需求场景:公司要做一个APP,套个壳,把现有的几个H5站点加进去。这样APP和H5端用户的登录信息需共享,并且当H5站点重启时不再跳转原H5的登录页面,而是跳转到APP的登录界面。
解决方案:
1、APP端通过把用户登录信息写入H5的cookie中来传递,当用户退出APP再次登录时cookie会更新。
2、H5端站点通过HttpModule拦截HTTP请求,如果H5站点登录失效(比如MemCache或Session过期)就调用自动登录API来更新H5端的登录状态;
3、如果H5站点重启(比如重新发布站点或修改web.config),则跳转到APP登录界面;
相关代码:

HttpModule实现:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.SessionState;
using XFK.AppLYTravel.Common.CommonModel;
using XFK.AppLYTravel.Common.Configuration;
using XFK.AppLYTravel.Common.helper;
using XFK.AppLYTravel.Common.Logs;
using XFK.AppLYTravel.Common.Util;
using XinfuMall.XinfuWeb.PublicClass;

namespace XFK.AppLYTravel.Common
{
    /// <summary>
    /// AutoLoginModule 的摘要说明
    /// </summary>
    public class AutoLoginModule : IHttpModule, IRequiresSessionState
    {
        public static readonly object syncObject = new object();
        public void Init(HttpApplication context)
        {
            LogHelper.WriteLog("进入HttpModule_Init方法。");
            context.EndRequest+=context_EndRequest;
        }

        void context_EndRequest(object sender, EventArgs e)
        {
            lock (syncObject)
            {
                HttpApplication app = (HttpApplication)sender;
                HttpContext _context = app.Context;
                string originReq = app.Request.Headers["User-Agent"];
                LogHelper.WriteLog("HttpModule获取请求头user_agent:" + originReq);
                try
                {
                    if (!string.IsNullOrEmpty(originReq))
                    {
                        //如果是来自APP的请求
                        if (originReq.ToLower().IndexOf("xinfukaapp") > -1)
                        {

                            string userNo = string.Empty;
                            string xf_sid = string.Empty;
                            if (_context.Request.Cookies["userNo"] != null)
                            {
                                userNo = _context.Request.Cookies["userNo"].Value;
                            }
                            if (_context.Request.Cookies["xf_sid"] != null)
                            {
                                xf_sid = _context.Request.Cookies["xf_sid"].Value;
                            }

                            LogHelper.WriteLog(string.Format("HttpModule获取到的cookie数据:userNo={0},xf_sid={1}", userNo, xf_sid));
                            bool isGoLogin = false;
                            if (!string.IsNullOrEmpty(userNo) && !string.IsNullOrEmpty(xf_sid))
                            {

                                HttpContext.Current.Items["xf_sid"] = xf_sid;
                                if (SessionManager.Read(UserUtil.user_sessionStr) != null)
                                {
                                    LogHelper.WriteLog(string.Format("用户{0}处于已登录状态。", userNo));
                                }
                                else
                                {
                                    LogHelper.WriteLog(string.Format("用户{0}登录已过期,需要自动登录。", userNo));
                                    string pstr = string.Format("userNo={0}&sessionID={1}", userNo, xf_sid);
                                    var url = ConfigurationManager.AppSettings["LoginAppAutoApi"];
                                    LogHelper.WriteLog(string.Format("调用APP自动登录接口:{0},入参:{1}", url, pstr));
                                    string strResult = JDAPI.PostWebRequest(url.ToString(), pstr);
                                    LogHelper.WriteLog("返回数据:" + strResult);
                                    JavaScriptSerializer js = new JavaScriptSerializer();
                                    GeneralResult loginResult = js.Deserialize<GeneralResult>(strResult);
                                    if (loginResult.Ret == "200" && loginResult.data != null)
                                    {
                                        LogHelper.WriteLog("用户" + userNo + "自动登录成功!");
                                    }
                                    else
                                    {
                                        isGoLogin = true;
                                    }
                                }
                            }
                            else
                            {
                                isGoLogin = true;
                            }
                            if (isGoLogin)
                            {
                                LogHelper.WriteLog("用户" + userNo + "自动登录失败!需重新手工登录。");
                                string mimeStr = _context.Response.ContentType;
                                LogHelper.WriteLog("获取到的请求中mime类型:" + mimeStr);
                                if (!string.IsNullOrEmpty(mimeStr))
                                {
                                    //当请求的是页面
                                    if (mimeStr.IndexOf(@"text/html") > -1)
                                    {

                                            if (originReq.Equals("XinfukaAppAndroid"))
                                            {
                                                LogHelper.WriteLog("检测到客户端是安卓系统,跳转APP登录。");
                                                _context.Response.Write("<script type=\"text/javascript\">window.AndroidInterface.tokenTimeOut();</script>");
                                                _context.Response.End();
                                            }
                                            else if (originReq.Equals("XinfukaAppIos"))
                                            {
                                                LogHelper.WriteLog("检测到客户端是IOS系统,跳转APP登录。");
                                                StringBuilder sb = new StringBuilder();
                                                sb.Append("<script type=\"text/javascript\"> function loadURL(url) {");
                                                sb.Append("var iFrame;");
                                                sb.Append("iFrame = document.createElement(\"iframe\");");
                                                sb.Append("iFrame.setAttribute(\"src\", url);");
                                                sb.Append("iFrame.setAttribute(\"style\", \"display:none;\");");
                                                sb.Append("iFrame.setAttribute(\"height\", \"0px\");");
                                                sb.Append("iFrame.setAttribute(\"width\", \"0px\");");
                                                sb.Append("iFrame.setAttribute(\"frameborder\", \"0\");");
                                                sb.Append("document.body.appendChild(iFrame);");
                                                sb.Append("iFrame.parentNode.removeChild(iFrame);");
                                                sb.Append("iFrame = null;");
                                                sb.Append("}");
                                                sb.Append("loadURL(\"haleyAction://tokenTimeOut\");</script>");
                                                _context.Response.Write(sb.ToString());
                                                _context.Response.End();
                                            }
                                            else
                                            {
                                                LogHelper.WriteLog("无效识别。");
                                                _context.Response.Write("无效识别。");
                                                _context.Response.End();
                                            }
                                    }
                                }
                            }
                        }
                    }
                }
                catch (System.Threading.ThreadAbortException) { }
                catch (Exception ex)
                {
                    LogHelper.WriteLog("用户自动登录异常,信息:" + ex.Message + ",堆栈:" + ex.StackTrace);
                }
            }
        }

        public void Dispose()
        {
        }
    }
}

 H5站点引入:
<system.webServer>
    <modules>
      <add name="AutoLoginModuleNew" type="XFK.AppLYTravel.Common.AutoLoginModule,XFK.AppLYTravel.Common" />
    </modules>
</system.webServer>

H5跳转方法

public static void AppRedirect(HttpContext context, Action cusAction)
{
    LogHelper.WriteLog("进入AppRedirect方法。");
    HttpContext _context = context;
    string originReq = _context.Request.Headers["User-Agent"];
    LogHelper.WriteLog("AppRedirect获取请求头user_agent:" + originReq);
    try
    {
        if (!string.IsNullOrEmpty(originReq))
        {
            //如果是来自APP的请求
            if (originReq.ToLower().IndexOf("xinfukaapp") > -1)
            {
                string mimeStr = _context.Response.ContentType;
                LogHelper.WriteLog("AppRedirect获取到的请求中mime类型:" + mimeStr);
                if (!string.IsNullOrEmpty(mimeStr))
                {
                    //当请求的是页面
                    if (mimeStr.IndexOf(@"text/html") > -1)
                    {
                        LoginInfo sessionInfo = _context.Session["UserLoginInfo"] as LoginInfo;
                        if (sessionInfo == null)
                        {
                            LogHelper.WriteLog("session信息为空!");
                            if (originReq.Equals("XinfukaAppAndroid"))
                            {
                                LogHelper.WriteLog("AppRedirect检测到客户端是安卓系统,跳转APP登录。");
                                _context.Response.Write("<script type=\"text/javascript\">window.AndroidInterface.tokenTimeOut();</script>");
                                _context.Response.End();
                            }
                            else if (originReq.Equals("XinfukaAppIos"))
                            {
                                LogHelper.WriteLog("AppRedirect检测到客户端是IOS系统,跳转APP登录。");
                                StringBuilder sb = new StringBuilder();
                                sb.Append("<script type=\"text/javascript\"> function loadURL(url) {");
                                sb.Append("var iFrame;");
                                sb.Append("iFrame = document.createElement(\"iframe\");");
                                sb.Append("iFrame.setAttribute(\"src\", url);");
                                sb.Append("iFrame.setAttribute(\"style\", \"display:none;\");");
                                sb.Append("iFrame.setAttribute(\"height\", \"0px\");");
                                sb.Append("iFrame.setAttribute(\"width\", \"0px\");");
                                sb.Append("iFrame.setAttribute(\"frameborder\", \"0\");");
                                sb.Append("document.body.appendChild(iFrame);");
                                sb.Append("iFrame.parentNode.removeChild(iFrame);");
                                sb.Append("iFrame = null;");
                                sb.Append("}");
                                sb.Append("loadURL(\"haleyAction://tokenTimeOut\");</script>");
                                _context.Response.Write(sb.ToString());
                                _context.Response.End();
                            }
                            else
                            {
                                LogHelper.WriteLog("AppRedirect无效识别。");
                                _context.Response.Write("AppRedirect无效识别。");
                                _context.Response.End();
                            }
                        }
                    }
                }
            }
            else
            {
                cusAction.Invoke();
            }
        }
    }
    catch (System.Threading.ThreadAbortException) { }
    catch (Exception ex)
    {
        LogHelper.WriteLog("AppRedirect异常,信息:" + ex.Message + ",堆栈:" + ex.StackTrace);
    }
}

H5端在BasePage中调用,如:

using Common;
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Script.Serialization;
using System.Web.SessionState;
using XinfuMall.XinfuWeb.PublicClass;

/// <summary>
/// PageBase 的摘要说明
/// </summary>
public class PageBase : System.Web.UI.Page
{
    public PageBase()
    {
    }

    private string accountName;
    private string enterpriseid;
    public string AccountName
    {
        get { return this.accountName; }
    }
    public string Enterpriseid
    {
        get { return this.enterpriseid; }
    }
    protected override void OnInit(EventArgs e)
    {
        if (HttpContext.Current.Request.Cookies["xf_sid"] == null && XFK.Infrastructure.Util.SessionManager.Read(UserUtil.user_sessionStr) == null)
        {
            Response.Redirect(System.Configuration.ConfigurationManager.AppSettings["xinfuUrl"] + "mall/Login?originUrl=" + HttpUtility.UrlEncode(Request.Url.AbsoluteUri, System.Text.Encoding.UTF8));
        }
        else
        {
            LoginInfo loginfo = Session["UserLoginInfo"] as LoginInfo;
            if (loginfo == null)
            {
               XFK.AppLYTravel.Common.CommUtil.AppRedirect(HttpContext.Current,WebRedirect);
            }
            this.accountName = loginfo.AccountName;
            this.enterpriseid = loginfo.Enterpriseid;

            var limitEnterprise = String.IsNullOrEmpty(System.Configuration.ConfigurationManager.AppSettings["limitEnterprise"]) ? "" : System.Configuration.ConfigurationManager.AppSettings["limitEnterprise"];
            if (limitEnterprise.Split(',').Contains(loginfo.Enterpriseid.ToString()))
            {
                System.Web.HttpContext.Current.Response.Write("对不起,所属的企业没有订购此产品的权限!");
                System.Web.HttpContext.Current.Response.End();
            }
        }
    }

    void WebRedirect()
    {
        Response.Redirect(System.Configuration.ConfigurationManager.AppSettings["xinfuUrl"] + "mall/Login?originUrl=" + HttpUtility.UrlEncode(Request.Url.AbsoluteUri, System.Text.Encoding.UTF8));
    }
}

 
遇到的问题:
1、H5如何判断请求来自APP;
2、HttpModule获取请求资源mime类型的时机;
3、Http请求管道事件(如BeginRequest、EndRequest)对于js脚本执行的影响;
例如IOS端跳转APP登录页的方法是这样的:

<script>
    function loadURL(url) {
        var iFrame;
        iFrame = document.createElement("iframe");
        iFrame.setAttribute("src", url);
        iFrame.setAttribute("style", "display:none;");
        iFrame.setAttribute("height", "0px");
        iFrame.setAttribute("width", "0px");
        iFrame.setAttribute("frameborder", "0");
        document.body.appendChild(iFrame);
        iFrame.parentNode.removeChild(iFrame);
        iFrame = null;
    }
    loadURL("haleyAction://tokenTimeOut");
</script>

这段js代码在EndRequest事件中可以输出到</html>标签外,页面最底部,如下图:

这时候所有的DOM元素都已加载完毕,所以执行没问题;
但是放在BeginRequest里,js代码被输出到了页面最顶部,如:

这时候因为DOM元素还未加载好,会有如下错误:


解决方案:
1、APP端通过修改http请求头User-Agent来标识;
2、精确获取mime类型要在EndRequest事件里,如:string mimeStr = _context.Response.ContentType;
在BeginRequest里不管请求资源是页面、脚本、图片或是其他,获取的都是"text/html"

3、这种情况,用window.onload包装一下就可以正常执行了,如:

<script>
    window.onload=function(){
        function loadURL(url) {
            var iFrame;
            iFrame = document.createElement("iframe");
            iFrame.setAttribute("src", url);
            iFrame.setAttribute("style", "display:none;");
            iFrame.setAttribute("height", "0px");
            iFrame.setAttribute("width", "0px");
            iFrame.setAttribute("frameborder", "0");
            document.body.appendChild(iFrame);
            iFrame.parentNode.removeChild(iFrame);
            iFrame = null;
        }
        loadURL("haleyAction://tokenTimeOut");
    }
</script>

 
总结:
1、HttpModule的BeginRequest、EndRequest事件对于页面元素(HTML和js)加载顺序的影响;
2、获取mime类型一定要在EndRequest事件里,并且用Response.ContentType方法;
3、session依赖于cookie,当cookie在页面中失效后session也会失效;

三、用到的或了解到的跨域知识总结
1、WEB站点调用远程API,通过Ajax请求本地Handler或Controller,而本地Handler或Controller利用HttpWebRequest和HttpWebResponse与远程API交互;
2、WEB站点在调用远程登录接口时,HTTP请求中带上cookie,实现统一登录;
3、实现跨域的技术如JSONP、CORS、postMessage等作为技术储备;

posted @ 2018-04-28 15:36  skybirdzw  阅读(813)  评论(0编辑  收藏  举报