一步一步asp.net_页面静态化管理
最近事情多,中間還生病了一次,糾結,最近一年來都沒有什么毛病,不知道咋了...頭痛.....
今天閑下來寫篇日志,頁面靜態化.
頁面靜態化是我們經常碰到的問題,在web中,要說速度,只有html靜態頁面最快,所以頁面靜態化對于web網站來說,是一個非常好的減少請求降低服務器壓力的方式.
而常規的html靜態頁面也有很多問題,比如不能像php,aspx,jsp頁面那樣輕松的和數據庫交互.
在以前,html靜態化都是,對于一些子頁面(比如新聞內容等),一旦發布基本上很少進行修改的頁面,就可以通過數據庫讀取,然后生成html,
這種方式性能最好,而且對SEO友好,但不靈活,只對于那些很少修改的頁面,如果頁面發生修改又需要重新生成,這對于硬盤也是一種傷害.
于是乎,動態語言橫行.
ajax技術的出現,再度讓頁面進入靜態化.
html頁面中大量的ajax,即有很高的靈活性,而且達到的降低服務器壓力減少請求的目的.
但是這樣也會造成SEO的問題,所以,對于一些需要SEO的頁面就不要用ajax實現了.
那么哪些適合頁面靜態化呢?
對于那些私人的空間
比如:會員空間,大師空間,企業空間,登錄,注冊等等,這些地方,就不需要.
?
但是還有一個問題,對于這些頁面靜態化,我們經常性都是先做aspx頁面(母板頁,用戶控件)等,最后做頁面靜態化操作,我們就需要重新把那些動態頁面的鏈接全部改成靜態,這就太痛苦了,這時候我們就需要事先寫一個URL頁面靜態化管理,所有的鏈接經過這個URLManage.GetURL處理.
1: /// <summary> 2: /// 獲得路徑(暫時只做靜態頁面管理)(/*在這里可以擴展出URL重寫*/) 3: /// </summary> 4: /// <param name="PageUrl">頁面的URL(不包括擴展名)</param> 5: /// <param name="QueryString">頁面參數</param> 6: /// <returns></returns> 7: public static string GetURL(string PageUrl,string QueryString) 8: { 9: //頁面路徑 10: string PagePath = ""; 11: 12: //如果當前的參數不為空,則加上? 13: if (QueryString != "") 14: QueryString = "?" + QueryString; 15: //如果是靜態頁面(從配置文件中讀取靜態頁面狀態(是否)) 16: if (ReadURLConfig(PageUrl) == true) 17: { 18: PagePath = PageUrl + ".htm"; 19: } 20: //如果是動態頁面 21: else 22: PagePath = PageUrl + ".aspx"; 23: //把相對路徑轉化為絕對路徑 24: return System.Web.VirtualPathUtility.ToAbsolute(PagePath)+QueryString ; 25: } 26: /// <summary> 27: /// 從配置文件中讀取是否生成靜態頁面 28: /// </summary> 29: /// <param name="PageName">頁面的名稱</param> 30: /// <returns></returns> 31: public static bool ReadURLConfig(string PageURL) 32: { 33: //讀取配置文件 34: string path = HttpContext.Current.Server.MapPath(@"~/Admin/ConfigManage/URLConfig.xml"); 35: //XmlHelper.Read(path, "/Node/Element[@Attribute='Name']", "Attribute") 36: //是否生成HTML 37: string IsHtml="false"; 38: IsHtml=XMlHelper.Read(path, "/PageSettings/Page[@PageURL='"+PageURL+"']", "IsHtml"); 39: if (IsHtml.ToLower() == "true") 40: { 41: return true; 42: } 43: else return false; 44: 45: } 46:這個類主要是幫助我們可以直接從配置文件中讀取關于文件頁面靜態化的信息
對于我們大量的ajax頁面(可能涉及到超鏈接),我們也需要通過這個URLManage來獲取他的路徑,由于js不能調用asp.net中的函數,所以我們需要在每個頁面的開頭定義,js中需要用到的超鏈接.
看看它的ajax文件中是怎么寫的.
這時候,我們的前臺的工作就完成了,主要是后臺的頁面靜態化管理的設計了,
后臺的頁面靜態化管理,主要實現,通過樹形菜單的形式,可以自由選擇,那些頁面生成靜態頁面,方便測試和維護.
不過這里,少處理了關于新聞內容的頁面靜態化,新聞內容的頁面靜態化則是采用的第一種方法,完全的頁面靜態化,從數據庫讀取出數據,全部生成的方式.
主要效果:
把需要頁面靜態化的頁面寫在配置文件中,然后通過這個頁面靜態化管理,進行頁面靜態化.
這樣做的好處就是方便開發和維護,可以很輕松的管理靜態頁面.
主要技術:
1.XML操作以及LINQ簡單應用,
2.頁面靜態化,只需要通過簡單的WebClient下載動態頁面就達到了頁面靜態化的目的.簡單方便.
?
?
WebClient下載文件類
1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Text; 5: using System.Net; 6: using System.Web; 7: using System.IO; 8: ? 9: namespace Common 10: { 11: /// <summary> 12: /// 用webClient下載文件 13: /// </summary> 14: public partial class DownFile 15: { 16: #region 生成靜態頁面 17: /// <summary> 18: /// 生成靜態頁面 19: /// 調用實例: 20: /// Common.DownFile webclient = new Common.DownFile(); 21: /// string RequestVirtualUrl= "/News/ViewNews.aspx?NewsId="+Info.Id; 22: /// string SaveVirtualPath = "~/News/" + Info.Id + ".htm"; 23: /// webclient.CreateStaticByWebClient(RequestVirtualUrl, SaveVirtualPath); 24: /// </summary> 25: /// <param name="VirtualRequestUrl">要請求的虛擬路徑,例如: "/News/ViewNews.aspx?NewsId="+Info.Id;</param> 26: /// <param name="SaveVirtualPath">要保存的虛擬路徑,例如:"~/News/" + Info.Id + ".htm";</param> 27: public static void CreateStaticByWebClient(string VirtualRequestUrl, string SaveVirtualPath) 28: { 29: WebClient wc = new WebClient(); 30: wc.Encoding = Encoding.UTF8; 31: //通過WebClient向服務器發Get請求,把服務器返回的html內容保存到磁盤上,以后用戶直接請html文件請求. 32: string AppVirtualPath = HttpContext.Current.Request.ApplicationPath; 33: //由于網站應用程序虛擬目錄是/czcraft,而傳遞過來/News是正確的, 34: //但是發布到iis上面,虛擬路徑就是/,而傳遞過來的確實/News,路徑就出錯了, 35: ? 36: if (AppVirtualPath == "/") 37: { 38: AppVirtualPath = ""; 39: } 40: string FilePath = HttpContext.Current.Request.Url.Scheme + "://" + HttpContext.Current.Request.Url.Authority + AppVirtualPath + VirtualRequestUrl; 41: ? 42: //保存路徑 43: string SaveFilePath = HttpContext.Current.Server.MapPath(SaveVirtualPath); 44: ? 45: //下載并保存文件 46: wc.DownloadFile(FilePath, SaveFilePath); 47: ? 48: } 49: #endregion 50: #region 文件刪除 51: /// <summary> 52: /// 文件刪除 53: /// </summary> 54: /// <param name="VirtualFilePath">文件虛擬路徑</param> 55: public void FileDelete(string VirtualFilePath) 56: { 57: //物理路徑 58: string RealFilePath = HttpContext.Current.Server.MapPath(VirtualFilePath); 59: ? 60: //如果文件存在則刪除 61: if (File.Exists(VirtualFilePath)) 62: { 63: File.Delete(VirtualFilePath); 64: } 65: } 66: #endregion 67: } 68: } 頁面靜態化管理類(URLXMLInfoManage): 1: using System; 2: using System.Data; 3: using System.Configuration; 4: using System.Linq; 5: using System.Web; 6: using System.Web.Security; 7: using System.Web.UI; 8: using System.Web.UI.HtmlControls; 9: using System.Web.UI.WebControls; 10: using System.Web.UI.WebControls.WebParts; 11: using System.Xml.Linq; 12: using System.Xml; 13: using System.Text; 14: using System.Collections; 15: using System.Collections.Generic; 16: using System.IO; 17: using Newtonsoft.Json; 18: ? 19: /// <summary> 20: ///URLXMLInfoManage 的摘要說明 21: /// </summary> 22: public class URLXMLInfoManage 23: { 24: #region 字段 25: //單例模式 26: public static readonly URLXMLInfoManage XMLInfo = new URLXMLInfoManage(); 27: /// <summary> 28: /// 路徑(XML) 29: /// </summary> 30: private static readonly string XMLPath = "~/Admin/ConfigManage/URLConfig.xml"; 31: #endregion 32: #region 實例化 33: /// <summary> 34: /// 私有實例化 35: /// </summary> 36: private URLXMLInfoManage() 37: { 38: } 39: /// <summary> 40: /// 實例化(靜態) 41: /// </summary> 42: /// <param name="path">xml的路徑</param> 43: /// <returns></returns> 44: public static URLXMLInfoManage Instance() 45: { 46: return XMLInfo; 47: } 48: #endregion 49: #region 通過頁面信息(返回json) 50: /// <summary> 51: /// 通過頁面信息(返回json) 52: /// </summary> 53: /// <returns></returns> 54: public string GetURLInfoForJson() 55: { 56: ? 57: //加載XML 58: XDocument xDoc = XDocument.Load(HttpContext.Current.Server.MapPath(XMLPath)); 59: ? 60: IEnumerable<XElement> PageList = xDoc.Root.Descendants("Page"); 61: //linq分組(根據xml中Page的Type的名稱分組 62: var group = PageList.GroupBy(page => page.Attribute("Type").Value); 63: //輸出json格式數據 64: StringBuilder json = new StringBuilder(); 65: StringWriter sw = new StringWriter(json); 66: using (JsonWriter jsonWriter = new JsonTextWriter(sw)) 67: { 68: jsonWriter.Formatting = Newtonsoft.Json.Formatting.Indented; 69: jsonWriter.WriteStartArray(); 70: foreach (IGrouping<string, XElement> item in group) 71: { 72: jsonWriter.WriteStartObject(); 73: //-1代表不存在的id 74: jsonWriter.WritePropertyName("id"); 75: jsonWriter.WriteValue(-1); 76: jsonWriter.WritePropertyName("text"); 77: jsonWriter.WriteValue(item.First().Attribute("TypeName").Value); 78: jsonWriter.WritePropertyName("expanded"); 79: jsonWriter.WriteValue(false); 80: jsonWriter.WritePropertyName("children"); 81: ? 82: jsonWriter.WriteStartArray(); 83: foreach (XElement XElem in item) 84: { 85: //頁面名稱 86: string PageName = XElem.Attribute("PageName").Value; 87: //頁面URL 88: string PageURL = XElem.Attribute("PageURL").Value; 89: //頁面標識 90: string PageId = XElem.Attribute("Id").Value; 91: //是否是html頁面 92: bool IsHtml = Convert.ToBoolean(XElem.Attribute("IsHtml").Value); 93: jsonWriter.WriteStartObject(); 94: jsonWriter.WritePropertyName("id"); 95: jsonWriter.WriteValue(PageId); 96: jsonWriter.WritePropertyName("text"); 97: jsonWriter.WriteValue(PageName); 98: jsonWriter.WritePropertyName("expanded"); 99: jsonWriter.WriteValue(IsHtml); 100: jsonWriter.WriteEndObject(); 101: } 102: jsonWriter.WriteEndArray(); 103: ? 104: jsonWriter.WriteEndObject(); 105: ? 106: } 107: jsonWriter.WriteEndArray(); 108: ? 109: ? 110: ? 111: ? 112: } 113: return json.ToString(); 114: ? 115: ? 116: } 117: ? 118: #endregion 119: #region 設置頁面靜態化信息 120: /// <summary> 121: /// 設置頁面靜態化信息 122: /// </summary> 123: /// <param name="Ids"></param> 124: /// <returns></returns> 125: public bool SetURLInfo(string Ids) 126: { 127: //獲取URL的Id 128: string[] IdList = Ids.Split(','); 129: //加載XML 130: XDocument xDoc = XDocument.Load(HttpContext.Current.Server.MapPath(XMLPath)); 131: ? 132: IEnumerable<XElement> PageList = xDoc.Root.Descendants("Page"); 133: foreach (XElement Page in PageList) 134: { 135: //默認不生成HTML頁面 136: Page.SetAttributeValue("IsHtml", false); 137: foreach (string Id in IdList) 138: { 139: 140: if (Id == Page.Attribute("Id").Value) 141: { 142: //頁面靜態化 143: CreateHTML(Page.Attribute("PageURL").Value); 144: //寫回XML中 145: Page.SetAttributeValue("IsHtml", true); 146: break; 147: } 148: } 149: } 150: xDoc.Save(HttpContext.Current.Server.MapPath(XMLPath)); 151: return true; 152: } 153: #endregion 154: #region 頁面靜態化(不適合文章等純HTML) 155: /// <summary> 156: /// 頁面靜態化(不適合文章等純HTML) 157: /// </summary> 158: /// <param name="PathURL"></param> 159: /// <returns></returns> 160: public bool CreateHTML(string PathURL) 161: { 162: ? 163: //保存路徑 164: string SavePath = PathURL + ".htm"; 165: //請求路徑(刪除前綴的~標識) 166: string RequestPath = PathURL.TrimStart('~')+".aspx"; 167: //下載文件(原路徑保存) 168: Common.DownFile.CreateStaticByWebClient(RequestPath, SavePath); 169: return true; 170: } 171: #endregion 172: }這里涉及到一個LINQ,以前沒怎么用過LINQ,僅僅是會點基本語法,這次,感覺真是挺強大的,簡潔清晰.
????? //查找所有的Page節點
IEnumerable<XElement> PageList = xDoc.Root.Descendants("Page");
?? ? //linq分組(根據xml中Page的Type的名稱分組)
?? ? var group = PageList.GroupBy(page => page.Attribute("Type").Value);
? 不過,groupby的接口是IGrouping<string, XElement>,group的key就是Type,value就是Type相同的Page節點
以前我們也寫過類似的分組的處理,可以對比一下:
下面這個是根據類別,輸出前8個產品,跟上面是類似的.
可以看到LINQ的簡潔,LINQ和Lambda表達式,更簡潔,更清晰也更容易理解.
1: #region 根據企業id查找企業的產品信息(每種分別顯示前8個) 2: /// <summary> 3: /// 根據企業id查找企業的產品信息(每種分別顯示前8個) 4: /// </summary> 5: /// <param name="CompanyId"></param> 6: /// <returns></returns> 7: public string GetCompanyWorkForJson(string CompanyId) 8: { 9: //查詢狀態 10: bool Status = false; 11: //獲取企業的產品信息(每種顯示前8個) 12: DataTable dtListProduct = new VProductCraftTypeDAL().ListAllByCompanyIdToDatable(CompanyId); 13: //轉化為json格式 14: StringBuilder json = new StringBuilder(); 15: StringWriter sw = new StringWriter(json); 16: ? 17: using (JsonWriter jsonWriter = new JsonTextWriter(sw)) 18: { 19: ? 20: jsonWriter.Formatting = Formatting.Indented; 21: //判斷數據讀取狀態 22: if (dtListProduct.Rows.Count > 0) 23: { 24: Status = true; 25: } 26: jsonWriter.WriteStartObject(); 27: jsonWriter.WritePropertyName("Status"); 28: jsonWriter.WriteValue(Status); 29: jsonWriter.WritePropertyName("Data"); 30: ? 31: jsonWriter.WriteStartArray(); 32: if (Status == true) 33: { 34: //先輸出第一個元素的類別信息 35: jsonWriter.WriteStartObject(); 36: jsonWriter.WritePropertyName("TypeId"); 37: jsonWriter.WriteValue(dtListProduct.Rows[0]["TypeId"].ToString()); 38: jsonWriter.WritePropertyName("TypeName"); 39: jsonWriter.WriteValue(dtListProduct.Rows[0]["TypeName"].ToString()); 40: //第一個元素的開始 41: jsonWriter.WritePropertyName("Product"); 42: jsonWriter.WriteStartArray(); 43: ? 44: //按照類別分組 45: //產品計數(一個分組下的產品,從1開始算起) 46: ? 47: for (int num = 0, numProduct = 1; num < dtListProduct.Rows.Count; num++, numProduct++) 48: { 49: ? 50: //獲取該類別下的分組總個數 51: int Total = Convert.ToInt32(dtListProduct.Rows[num]["total"]); 52: //如果該類別下還存在未輸出的產品 53: if (numProduct <= Total) 54: { 55: ? 56: ? 57: jsonWriter.WriteStartObject(); 58: jsonWriter.WritePropertyName("ProductId"); 59: jsonWriter.WriteValue(dtListProduct.Rows[num]["Id"].ToString()); 60: jsonWriter.WritePropertyName("Name"); 61: jsonWriter.WriteValue(dtListProduct.Rows[num]["Name"].ToString()); 62: jsonWriter.WritePropertyName("SimpleName"); 63: jsonWriter.WriteValue(dtListProduct.Rows[num]["SimpleName"].ToString()); 64: jsonWriter.WritePropertyName("Lsprice"); 65: jsonWriter.WriteValue(dtListProduct.Rows[num]["Lsprice"].ToString()); 66: jsonWriter.WritePropertyName("Picturepath"); 67: jsonWriter.WriteValue(dtListProduct.Rows[num]["Picturepath"].ToString()); 68: jsonWriter.WriteEndObject(); 69: ? 70: } 71: else 72: { 73: //將該類別的產品計數重置為1 74: numProduct = 1; 75: //這里給上一個類別的產品結束標記 76: ? 77: jsonWriter.WriteEndArray(); 78: jsonWriter.WriteEndObject(); 79: ? 80: jsonWriter.WriteStartObject(); 81: jsonWriter.WritePropertyName("TypeId"); 82: jsonWriter.WriteValue(dtListProduct.Rows[num]["TypeId"].ToString()); 83: jsonWriter.WritePropertyName("TypeName"); 84: jsonWriter.WriteValue(dtListProduct.Rows[num]["TypeName"].ToString()); 85: //如果還存在產品 86: if (num < dtListProduct.Rows.Count) 87: { 88: //下一個元素的開始 89: jsonWriter.WritePropertyName("Product"); 90: jsonWriter.WriteStartArray(); 91: ? 92: } 93: ? 94: } 95: } 96: } 97: ? 98: ? 99: jsonWriter.WriteEndArray(); 100: jsonWriter.WriteEndObject(); 101: ? 102: } 103: return json.ToString(); 104: } 105: #endregion接下來我們就可以寫,UI層和業務層了,
UI層仍然是用的MUNIUI框架(以后再也不用這個了,其實,不太好用,感覺,換個其他的JQuery框架),
1: ? 2: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3: <html xmlns="http://www.w3.org/1999/xhtml"> 4: <head> 5: <title>頁面管理(靜態頁面生成管理)</title> 6: <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> 7: <link href="../css/demo.css" rel="stylesheet" type="text/css" /> 8: ? 9: <script src="../scripts/jquery-1.6.2.min.js" type="text/javascript"></script> 10: ? 11: <script src="../scripts/miniui/miniui.js" type="text/javascript"></script> 12: ? 13: <link href="../scripts/miniui/themes/default/miniui.css" rel="stylesheet" type="text/css" /> 14: <link href="../scripts/miniui/themes/icons.css" rel="stylesheet" type="text/css" /> 15: </head> 16: <body> 17: <h1> 18: 頁面靜態化管理</h1> 19: <ul id="tree2" class="mini-tree" url="Data/UrlInfo.ashx?method=GetURLInfo" style="width: 300px;" 20: showtreeicon="true" textfield="text" idfield="id" showcheckbox="true" 21: checkrecursive="true"> 22: </ul> 23: <br /> 24: 25: <a class="mini-button" iconCls=" icon-new" href="javascript:ToHtml()">頁面靜態化</a> 26: <!-- <input type="button" value="setCheckedNodes" οnclick="setCheckedNodes()" /> 27: <input type="button" value="getCheckedNodes" οnclick="getCheckedNodes()" />--> 28: <br /> 29: ? 30: <script type="text/javascript"> 31: 32: 33: function getCheckedNodes() { 34: var tree = mini.get("tree2"); 35: var value = tree.getValue(); 36: alert(value); 37: ? 38: } 39: //頁面靜態化 40: function ToHtml(){ 41: var tree = mini.get("tree2"); 42: var value = tree.getValue(); 43: if(value==null){ 44: alert("請選擇要生成的靜態頁面的頁面名稱"); 45: return; 46: } 47: else{ 48: //alert(value); 49: $.ajax({ 50: url: "Data/UrlInfo.ashx?method=ToHtml", 51: data:{Id:value}, 52: cache: false, 53: success: function (text){ 54: if(text) 55: { 56: alert("頁面HTML生成成功!"); 57: } 58: } 59: }); 60: 61: } 62: 63: } 64: //-------------------------------- 65: function onBeforeCheckNode(e) { 66: var tree = e.sender; 67: var node = e.node; 68: if (tree.hasChildren(node)) { 69: //e.cancel = true; 70: } 71: } 72: 73: </script> 74: ? 75: 76: </body> 77: </html> 78: ?后臺Ashx頁面:
1: <%@ WebHandler Language="C#" Class="UrlInfo" %> 2: ? 3: using System; 4: using System.Web; 5: using czcraft.BLL; 6: using czcraft.Model; 7: using Common; 8: using System.Collections.Generic; 9: using Newtonsoft.Json.Linq; 10: public class UrlInfo : IHttpHandler { 11: ? 12: ? 13: public void ProcessRequest(HttpContext context) 14: { 15: String methodName = context.Request["method"]; 16: if (!string.IsNullOrEmpty(methodName)) 17: CallMethod(methodName, context); 18: } 19: /// <summary> 20: /// 根據業務需求調用不同的方法 21: /// </summary> 22: /// <param name="Method">方法</param> 23: /// <param name="context">上下文</param> 24: public void CallMethod(string Method, HttpContext context) 25: { 26: switch (Method) 27: { 28: case "GetURLInfo": 29: GetURLInfo(context); 30: break; 31: case "ToHtml": 32: ToHtml(context); 33: break; 34: default: 35: return; 36: ? 37: ? 38: } 39: } 40: /// <summary> 41: /// 頁面html 42: /// </summary> 43: /// <param name="context"></param> 44: public void ToHtml(HttpContext context) 45: { 46: string Ids= context.Request["Id"]; 47: 48: URLXMLInfoManage xmlManage = URLXMLInfoManage.Instance(); 49: //寫回XML中 50: context.Response.Write(xmlManage.SetURLInfo(Ids)); 51: } 52: /// <summary> 53: /// 獲取URL信息(生成html的) 54: /// </summary> 55: /// <param name="context"></param> 56: public void GetURLInfo(HttpContext context) 57: { 58: URLXMLInfoManage xmlManage = URLXMLInfoManage.Instance(); 59: ? 60: context.Response.Write(xmlManage.GetURLInfoForJson()); 61: } 62: public bool IsReusable { 63: get { 64: return false; 65: } 66: } 67: ? 68: }頁面靜態化效果:
可以看到首頁和右下角的超鏈接,后綴都變成了htm,而且,項目中可以看到都生成了html頁面
?
最近再看3本好書,一本是.net設計規范
極品,看了之后才知道代碼命名等等問題.
還有一本是博客園的. 小洋(燕洋天)寫的,蠻不錯的,叫做.net應用架構設計原則,模式與實踐
比較適合我這種,web對于架構方面有點感覺,但是不知道怎么樣設計的好,擴展性好的孩紙.
還有一本就是C# in Depth
買了本英文版,慢慢品讀,學英語,英語太差真吃虧呀!
接下來就是寫一個緩存管理,
以前設計的緩存,只適合asp.net的Cache,但是asp.net的緩存,不適合大型構架,大型構架都會把緩存單獨放在緩存服務器中,經常性,別人擴展到大型應用就麻煩了,我們需要考慮周全就需要寫一個可擴展性的緩存結構,
MemCache是一個很強力的高性能分布式緩存系統.
接下來就準備設計一個緩存管理組件,用來管理緩存,如果網站擴大可以輕松的把緩存往MemCache上移植.
轉載于:https://www.cnblogs.com/mysweet/archive/2012/05/26/2519686.html
總結
以上是生活随笔為你收集整理的一步一步asp.net_页面静态化管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 也许你不知道的c#基本数据类型及其默认值
- 下一篇: 通过飞行CALL找到BT飞行偏移 和飞行