Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 一级特黄aa大片试看二分钟,国产成人福利视频,91成人在线

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          一個易學易用高效便捷的MVC和ORM框架

          發目的

          @copyright 楊同峰 保留所有權利

          本文可以轉載,但請保留版權信息。

          SSH框架配置復雜、難用。個人認為這不是一個框架應該有的樣子。框架應該使用簡單、配置簡單、代碼簡潔。于是參照Django的一些特性,編寫了這個MVC+ORM框架。

          特性

          1. 大量的默認約定,避免了大量的配置
          2. 配置方便、使用便捷、易于上手
          3. 支持延遲加載技術的List
          4. 和JSTL無縫兼容

          配置

          1. 新建一個Web Project(MyEclipse為例)
          2. 將以下jar放到WebRoot/Web-INF下面
          3. yangmvc-1.6-all-in-one.jar
          4. 下載地址
          5. http://git.oschina.net/yangtf/YangMVC/attach_files
          6. 在web.xml中(web-app標簽內)加入
           <filter>
           <filter-name>yangmvc</filter-name>
           <filter-class>org.docshare.mvc.MVCFilter</filter-class>
           <init-param>
           <param-name>controller</param-name>
           <param-value>org.demo</param-value>
           </init-param>
           <init-param>
           <param-name>template</param-name>
           <param-value>/view</param-value>
           </init-param>
           </filter>
           
           <filter-mapping>
           <filter-name>yangmvc</filter-name>
           <url-pattern>/*</url-pattern>
           </filter-mapping>
           <context-param>
           <param-name>dbhost</param-name>
           <param-value>localhost</param-value>
           </context-param>
           <context-param>
           <param-name>dbusr</param-name>
           <param-value>root</param-value>
           </context-param>
           <context-param>
           <param-name>dbpwd</param-name>
           <param-value>123456</param-value>
           </context-param>
           <context-param>
           <param-name>dbname</param-name>
           <param-value>mvc_demo</param-value>
           </context-param>
           <context-param>
           <param-name>dbport</param-name>
           <param-value>3306</param-value>
           </context-param> 
          

          所有需要配置的都在這里了。這里做個簡要說明

          MVCFilter是我們MVC框架的入口。(不管是啥MVC框架都免不了這個)

          它有controller和template兩個參數。

          controller 是你控制器存放位置的包名。 比如這里是org.demo 你建立的控制器都必須寫在這個包中

          template是你存放模板(視圖)的地方。這個路徑是相對于WebRoot即網站根目錄的。

          比如這里的配置(/view)是WebRoot下的view目錄。

          dbhost dbname dbusr dbpwd 是數據庫的 地址、數據庫名、用戶名和密碼。目前這個MVC框架只支持MySQL,后續會添加其他數據庫的支持。

          注意,模板目錄(template參數所配置的值)以/開頭,如/view。

          YangMVC的第零個例子-HelloWorld程序

          public class IndexController extends Controller {
           public void index(){
           output("Hello YangMVC");
           }
          }
          

          他的作用就是顯示一句話。如圖

          第零個例子的顯示

          IndexController來處理應用的根目錄下的請求。 index方法來處理這個目錄下的默認請求。

          YangMVC第一個Demo

          在org.demo包下建立此類:

          public class BookController extends Controller {
           public void index(){
           DBTool tool = Model.tool("book");
           LasyList list = tool.all().limit(0, 30);
           put("books", list);
           render();
           }
          }
          

          在WebRoot/view/book/下建立一個index.jsp

          其中核心的代碼為

          <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
          (此處省略一堆無關的HTML代碼)
          <table class="table table-bordered">
           <c:forEach var="b" items="${books }">
           <tr>
           <td>${b.id }</td>
           <td>${b.name }</td>
           <td>${b.author }</td>
           <td>${b.chaodai }</td>
           <td>${b.tm_year }</td>
           <td>
           <a href='book/edit?id=${b.id}'>編輯</a>
           <a href='book/del?id=${b.id}'>刪除</a>
           
           </td>
           </tr>
           </c:forEach>
          </table>
          

          一個顯示列表的網頁就此搞定。訪問應用目錄下的book/目錄即可顯示出結果

          這里寫圖片描述

          你作出的結果可能沒那么好看,這完全取決于css。

          在YangMVCDemo / WebRoot / view / book / mvc.css 中有一個漂亮的表格定義。

          你可以通過類似下面的語句來加入到網頁中

          <link href="view/book/mvc.css" rel="stylesheet">
          

          注意路徑要對。

          說明:

          這個BookController是一個控制器,它的每一個公共方法都對應一個網頁(如果不想對應,你需要將其設為私有的)

          Model和DBTool是整個ORM框架的核心。Model表示模型,它用來與數據庫表相對應。在創建一個Model時,會指定對應的表名。

          這里和Hibernate不同,Hibernate需要預先生成所有數據庫表的對應類, 而這個Model可以與任何表格關聯,而不需要預先生成任何一個類。 這正是YangMVC中的ORM的優勢所在。

          DBTool tool = Model.tool("book");

          程序中使用Model的靜態方法tool獲取一個DBTool對象,tool傳入的參數book是數據庫的表名。

          這樣DBTool就和book表建立了關聯。

          LasyList list = tool.all().limit(0, 30);

          伙計們快看,這是個LasyList,一個支持懶惰加載機制的列表。它是List類的子類,這也就是它為什么能在JSTL中使用foreach變量的原因。

          首先我們調用了tool的all()方法,天哪,難道要加載book表的所有數據,兄弟不用害怕,在這個時候,它并沒有進行任何數據的讀寫,指示記錄了現在要訪問book表的所有數據這一信息。 all()方法會返回一個LasyList對象。這么設計的原因是我們后面可以跟一連串的過濾方法。方便我們編程。我們可以寫出這樣的東西:

          list = tool.all().gt("id", 12).lt("id", 33).eq("name","haha").like("author","王");
          

          這個例子相當于執行了如下SQL語句:

           select * from book where id>12 and id<33 and name='haha' and author like '%王%'
          

          在上面的例子中, all()返回的LasyList又調用了它的limit方法,這一步仍然沒有真正訪問數據庫。

          那么訪問數據庫從哪里開始呢? 從你獲取這個列表的一項時。

          一個List,可以使用枚舉的方法來訪問

          for(Model m : list){
           
          }
          

          也可以使用get方法來訪問。如

          Model m = list.get(12)
          

          在你訪問具體它的一個元素(Model)時,數據庫查詢才會啟動。而且也不是將所有數據放到內存中。比如你通過上面for的方法枚舉時,其實它是通過ResultSet的next游標在移動,所以它很高效!也避免了無用的數據庫操作。

          put("book",list)
          

          該方法將查詢得到的book塞入request中,在jsp網頁中就可以使用JSTL來使用它。因為它是一個List,所以用forEach去訪問他。

          Model 的一個對象對應于數據庫表的一行(一條記錄),Model是一個Map的子類!!!,所以在JSTL中,你可以使用

          ${ b.name } 的方式來訪問名為b的Model 的name項。 它相當于

           Model m = ....
           m.get("name")
          

          是不是很方便??? 真的是非常方便的。。

          第二個Demo

          添加書籍頁面

           public void add(){
           DBTool tool = Model.tool("book");
           //處理提交數據
           if(isPost()){ //isPost
           Model m = tool.create(); //創建新的
           Log.d(m);
           paramToModel(m);
           tool.save(m);
           put("msg","添加成功");
           }
           //顯示數據
           renderForm(tool.create());
           }
          

          對應的/view/book/add.jsp (這是默認對應的模板地址)的核心內容

           <div style="margin-left:100px">
           <h1>添加書籍 ${msg }</h1>
           ${book_form }
           </div>
          

          這里寫圖片描述

          上面的例子控制器其實是對應兩個頁面。 在收到Get請求的時候顯示表單,在用戶提交數據時,做插入操作,并顯示表單。(我們當然可以把這兩個頁面寫到兩個不同的方法中)

          我們還是使用Model.tool獲取一個DBTool。

          先來看顯示表單,就一句話

           renderForm(tool.create());
          

          tool的create方法會返回一個Model對象,這個對象和book表相關聯(因為tool和book表關聯)。

          并將這個Model傳遞給renderForm方法。這個方法會根據book表格的元數據自動創建一個表格。

          哇偶!

          那么這個Form插入到網頁的什么位置呢? 將 ${book_form } 放入網頁中 即可。

          如果來的是POST請求(使用isPost()方法來判斷)

          使用tool的create方法創建一個新的Model, 盡快還有其他創建Model對象的方式,但如果你希望插入,請盡量使用這種方式。

          paramToModel(m) ,這個方法會自動查找表單中,名字與數據庫字段名匹配的項,并自動賦值給Model的相應項。是不是很方便。。。

          想起了Struts那悲催的功能定義。 淚奔。。。。

          隨后直接調用tool的save方法將其保存到數據庫中!OK了!萬事大吉!

          細心的小朋友會問: 數據庫中的字段名都是英文的如name,為什么在網頁上顯示的是中文???

          看看我的數據庫表格定義

          CREATE TABLE `book` (
           `id` int(11) NOT NULL auto_increment COMMENT '編號',
           `file_name` varchar(50) default NULL,
           `name` varchar(50) default NULL COMMENT '名稱',
           `author` varchar(50) default NULL COMMENT '作者',
           `chaodai` varchar(50) default NULL COMMENT '朝代',
           `tm_year` varchar(50) default NULL COMMENT '年代',
           `about` longtext COMMENT '簡介',
           `type` varchar(50) default NULL COMMENT '類型',
           `catalog_id` int(11) default NULL COMMENT '分類',
           PRIMARY KEY (`id`),
           KEY `catalog` USING BTREE (`catalog_id`)
          ) ENGINE=InnoDB AUTO_INCREMENT=912 DEFAULT CHARSET=utf8;
          

          真相大白與天下,我是通過給字段加注釋實現的這一點。只要你將數據庫表格加上注釋,它就會自動獲取注釋并顯示,對于沒有注釋的字段,則會顯示字段名。如那個扎眼的file_name

          好了,這幾行代碼就搞定了輸入表單和表單的處理。

          第三個demo-編輯(自動創建的修改表單)

          細心的朋友發現,我們是按照CRUD的邏輯來將的。下面是編輯網頁。

           public void edit() throws NullParamException{
           DBTool tool = Model.tool("book");
           //處理提交數據
           if(isPost()){ //isPost
           Model m = tool.get(paramInt("id"));
           Log.d(m);
           paramToModel(m);
           tool.save(m);
           put("msg","修改成功");
           }
           //顯示數據
           Integer id = paramInt("id");
           checkNull("id", id);
           renderForm(tool.get(id));
           }
          

          HTML頁面放在/view/book/edit.jsp中,核心代碼只是將add.jsp中的添加二字改為了"編輯“二字。

           <div style="margin-left:100px">
           <h1>編輯書籍 ${msg }</h1>
           ${book_form }
           </div>
          

          這個代碼長了一點, 有17行。對于用YangMVC的,已經算夠長的了。它仍然是兩個網頁!!!

          你可以吧顯示表單的代碼和處理表單的分到兩個方法中寫。

          先看顯示數據。 首先使用paramInt方法獲取URL參數id,我們就是要編輯id指定的書籍。

          調用checkNull來檢查一下。 在我的開發生涯中,遇到各種參數檢查,所以這個功能是必須有的,如果checkNull不過,就會拋出一個異常。 這樣做的目的是不要讓這種參數檢查干擾我們正常的邏輯。這不就是異常之所以存在的意義么?

          如果缺少這個參數,頁面會提示說缺少這個參數。

          下面使用tool.get(id)方法來獲取一個Model(一條記錄)。這個方法是根據表格的主鍵進行查詢,返回的不是列表而是一個具體的Model對象。在這里我建議主鍵應當是整數、且是數據庫自增的。

          renderForm傳入一個model,這個model中有數據,就會被顯示出來。

          就這樣。編輯功能寫好了。

          有的朋友問,如果不想用默認的表單怎么辦? 那你自己寫一個表單在你的模板里就是了。只不過,你可以先用這個方法吧表單生成出來,然后按你的意圖修改就成了。這也節省大量時間啊。做過Form的請舉手。

          第四個DEMO-刪除

           public void del(){
           Integer id = paramInt("id");
           Model.tool("book").del(id);
           jump("index");
           
           
           }
          

          瞧瞧就這點代碼了, 獲取參數id,并調用tool的del方法刪除。最后一句我們第一次見,就是跳轉。跳轉到同目錄下的index這個默認頁(顯示的是書籍列表)

          控制器創建

          控制器是一個Java類,類有若干方法。在YangMVC的設計中,控制器的每一個公共的方法都映射對應一個網頁。這樣一個Java類可以寫很多的網頁。 方便管理。(當然,你也可以在一個控制器中只寫一個方法來支持網頁,這沒問題(⊙﹏⊙)b)

          所有的控制器都要繼承 org.docshare.mvc.Controller 這個類。充當控制器方法的方法應當是沒有參數沒有返回值的。如上面demo所示。

          public class IndexController extends Controller {
           public void index(){
           output("Hello YangMVC");
           }
          }
          

          這些控制器都要寫在配置所制定的package中,或者子package中。如在上面的配置中

           <init-param>
           <param-name>controller</param-name>
           <param-value>org.demo</param-value>
           </init-param>
          

          這個包為org.demo所有的控制器都要卸載這個包內。(你可以寫到外面,但它不會管用O(∩_∩)O~)

          路徑映射

          所謂路徑映射就是要將 一個控制器(一個Java類)和一個網址建立關聯。 用戶訪問某網址時,框架自動調用控制器的某個函數。

          因為本框架設計思想希望配置盡可能少,所以這里的路徑映射是通過命名關系的。

          假設應用的根目錄為

          http://localhost:8080/YangMVC/

          如在org.demo下(這個目錄可以在web.xml中配置,可見上一節)有一個BookController。

          那么這個類的路徑是 http://localhost:8080/YangMVC/book/

          用戶訪問這個路徑時,框架會調用BookController 的index方法。如果沒有這個方法則會報錯。

          index方法用以處理某個路徑下的默認網頁(網站以斜杠結尾的都會調用某個類的index方法來處理)。

          book這個地址,將第一個字母大寫,后面追加Controller。于是

          book (路徑名)-> Book -> BookController(類名)

          這就是路徑和類名的默認關聯。

          在這個網站后加入方法名可以訪問BookController的 任何一個公共方法。

          如 http://localhost:8080/YangMVC/book/edit 與BookController的edit方法關聯。

          需要注意的是,如果你寫的是 http://localhost:8080/YangMVC/book/edit/ (比上一個網站多了一個斜杠), 則它對應的是 book.EditController下的index方法 而不是BookController下的edit方法。

          控制器方法

          獲取request中的參數

          String s = param("name");
          Integer id = paramInt("id");
          

          輸出方法

          output方法

           output("Hello YangMVC");
          

          這個方法輸出一個文本到網頁上(輸出流中),并關閉輸出流。因為它會關閉流,所以你不要調用它兩次。你如果需要輸出多次,以將內容放到StringBuffer中,然后統一輸出。

          render方法

           public void paramDemo(){
           put("a", "sss");
           render("/testrd.jsp");
           
           }
          

          這里的testrd.jsp是模板目錄(/view)目錄下的。 /view/testrd.jsp

          這里的參數應該是相對于模板目錄的相對路徑。

          render方法使用參數制定的網頁(一個包含JSTL的jsp文件),將其輸出。可以通過put來制定參數。下面會詳細講。
          

          render()方法

          這個render方法是沒有參數的,它會使用默認模板,如果這個模板不存在,就會提示錯誤。
           public void renderDemo(){
           request.setAttribute("a", "sss");
           render();
           
           }
          

          在配置 controller 為org.demo , template為/view 這種情況下。

          org.demo.IndexController的renderDemo方法會對應/view/renderDemo.jsp

          之所以模板存在于模板根目錄下,是因為這個IndexController是處理應用根目錄的。他們有對應關系。

          如果是org.demo.BookController,它對應 app根目錄下的 /book/ 目錄。

          它的add方法對應路徑 /book/add

          如果應用名為hello,那么完成路徑應該是 /hello/book/add

          outputJSON 方法

          該方法將參數轉化為JSON,并向網頁輸出。

           public void jsonDemo(){
           Map<String, Object> map = new HashMap<String, Object>();
           map.put("id", 12);
           map.put("name", "Yang MVC");
           map.put("addtm",new Date());
           
           outputJSON(map);
           }
          這個代碼稍長,其實上面的所有都是生成一個Map,最后一句輸出。outputJSON可以輸出List,Map和任何Java對象。內部轉換是使用fastjson實現的。
          

          自動生成并輸出一個表單

          public void renderForm(Model m,String template,String postTo)
          

          該函數會根據模型對應的表結構,自動生成一個表單,并將其內容放入 表格名_form 中,如book表會輸出到 book_form 中。

          在網頁中,直接寫 ${book_form}就可以將表單放下去。

          template制定對應的模板文件,可以省略,省略后按照默認規則查找模板文件。

          postTo設定 表單提交的網頁,可以省略,默認是"",即當前網頁(Controller)。

          獲取參數的方法

          1. param(String p) 獲取參數p的值,以String類型返回
          2. paramInt(String p) 獲取參數p的值,以Int類型返回,如果不是整數,則會出現異常
          3. public Model paramToModel(Model m)
          4. 根據名稱匹配的原則,將與模型中參數名相同的參數的值放入模型中。并返回該模型。
          5. 是收集表單數據到模型中的神器,手機后就可以直接進行數據庫操作了。
          6. paramWithDefault 獲取參數,但同時帶上默認值,如果沒這個參數則返回默認值。

          檢查方法

          public void checkNull(String name,Object obj)

          檢查obj是否為null,如果是拋出NullParamException異常。

          ORM框架

          Model與DBTool

          Model 對象對應數據庫的表格,它會與一個表格進行綁定。DBTool相當于是它的DAO類。

          YangMVC的ORM組件可以單獨使用。使用前需要先配置數據庫:

           Config.dbhost = "localhost";
           Config.dbname = "dc2";
           Config.dbpwd = "123456";
           Config.dbusr ="root";
           Config.dbport="3306";
          

          也可以和MVC框架一起使用。配置時在web.xml中配置

          創建一個DBTool對象

           DBTool tool = Model.tool("book");
          

          其中book是數據庫表的名字。

          創建一個空的Model

          DBTool tool = Model.tool("book");

          Model m = tool.create(); //創建新的

          根據主鍵讀取一個Model

           Model m = tool.get(12);
          

          查詢表中所有的行

           LasyList list = tool.all();
          all返回一個LasyList對象。這個對象在此事并沒有真正進行數據庫查詢,只有在頁面真正讀取時才會讀取數據庫。這是它叫做Lasy的原因。此處借鑒了Django的實現機制。
          

          查詢的limit語句

           LasyList list = tool.all().limit(30);
           list = tool.all().limit(10,30);
           
          

          查詢的等式約束

           tool.all().eq("name","本草綱目")
          

          查詢的不等式約束

           tool.all().gt("id",12) //id < 12
           tool.all().lt("id",33) //id <33
           tool.all().gte("id",12) //id>=12
           tool.all().lte("id",33) //id<=33
           tool.all().ne("id",33) //不相等
           
          

          模糊查詢

           tool.all().like("name","本草")
          查找所有名字中包含本草的書。返回一個LasyList
          

          排序

           tool.all().orderby("id",true);
          按照id的增序排列。 如果是false,則是降序。
          

          級聯查詢

          因為這些上面的過濾器函數全部都會返回一個LasyList對象, 所以可以采用級聯的方式進行復雜查詢。如:

          list = tool.all().gt("id", 12).lt("id", 33).eq("name","haha").like("author","王");
          

          這個例子相當于執行了如下SQL語句:

           select * from book where id>12 and id<33 and name='haha' and author like '%王%'
          

          根據原始sql獲取(version >=1.5.4)

           LasyList list = LasyList.fromRawSql("select name from book");
          

          使用原始的sql獲取的List中的模型將和數據庫表沒有關聯。

          Model的相關功能

          model 是一個繼承自Map<String,Object> 的類,所以對于

          Model m;

          你可以在網頁中使用${m.name}的方式來訪問它的name鍵對應的值。相當于m.get("name")

          這種寫法在JSTL中非常有用。讓Model繼承Map的初衷就在于此:方便在JSTL中使用。

          大家也許注意到了LasyList是一個繼承自List<Model> 的類.

          這就使得不管是LasyList還是Model在JSTL中訪問都極為的便利。

          訪問所有的鍵值(即DAO對象的所有屬性)

           model.keySet();
          

          訪問某一個屬性的值

           model.get(key)
          

          設置某一個屬性的值

          多 ASP.NET 開發人員開始接觸 MVC,都認為 MVC 與 ASP.NET 完全沒有關系,是一個全新的 Web 開發。

          事實上 ASP.NET 是創建 WEB 應用的框架,而 MVC 是一種能夠用更好的方法來組織并管理代碼的體系,所以可以稱之為 ASP.NET MVC。

          因此,我們可以將原來的 ASP.NET 稱為 ASP.NET Webforms,新的 MVC 稱為 ASP.NET MVC

          ASP.NET Webforms

          ASP.NET 在過去的十幾年里,已經服務并成功實現Web 應用的開發。那么,我們先了解一下為什么ASP.NET能夠如此流行,并成功應用?

          微軟編程語言從 VB 開始就能夠成為流行并廣泛應用,都源于其提供的強大的 Visual studio 能夠進行可視化的編程,實現快速開發。

          使用 VS 時,開發人員能夠通過拖拽 UI 元素,并在后臺自動生成這些界面的代碼,稱為后臺代碼。在后臺代碼中,開發人員可以添加操作這些UI元素的邏輯代碼。

          因此微軟的可視化 RAD 架構體系有兩方面組成,一方面是 UI,一方面是后臺代碼。

          ASP.NET WebForms 存在的問題

          • 響應時間

          如圖所示,每一次 WebForms 請求都有轉換邏輯,運行并轉換服務器控件為 HTML 輸出。如果頁面使用表格,樹形控件等復雜控件,轉換就會變得很糟糕,HTML 輸出也是非常復雜的。由于這些不必要的轉換從而增加了響應時間。上圖是 ASP.Net MVC 和 Webforms 的響應時間對比,我們會發現 Webforms 的響應時間是 MVC 的兩倍左右。

          • 帶寬消耗

          ASP.NET 開發人員都非常熟悉 Viewstates,因為它能夠自動保存 post 返回的狀態,減少開發時間。但是這種開發時間的減少會帶來巨大的消耗,Viewstate增加了頁面的大小。

          從上圖中,我們可以看到與 MVC 對比,Viewstate 增加了兩倍的頁面存儲。

          MVC是怎么彌補這些問題的?

          Asp.Net MVC 由 Model,View,Controller 三部分組成。Controller 中包含后臺代碼邏輯,View 是ASPX,如純 HTML 代碼,Model 是中間層。

          不使用服務器控件,直接編寫 HTML 代碼,并且將后臺代碼遷移到獨立的類庫中,是 MVC 解決 Webforms 問題的方法。

          直接編寫HTML代碼的好處在于,web設計者可以與開發人員緊密合作及時溝通,設計人員也可以使用他們喜愛的設計工具來設計HTML代碼。

          將后臺代碼遷移到獨立的簡單的類庫,執行效率也會大大提高。

          ASP.NET Webform 和 MVC 比較,如上圖所示。

          深入理解 ASP.NET MVC 今天就講到這里,后續還會更新 “七天學會 ASP.NET MVC” 的其它篇章。

          敬請期待!

          相關開發工具

          要進行 ASP.ET MVC 的開發,不但需要具備 MVC 的知識,還需要高效的工具來幫助開發。

          使用 ComponentOne Studio Enterprise 中提供的 ComponentOne Studio ASP.NET MVC,您能獲取快速的輕量級控件來滿足用戶所有需求,大大減輕工作量。

          快人一步,免費試用

          如果您想試用 ComponentOne Studio ASP.NET MVC,請聯系我們:

          微信:GrapeCityDT

          郵件:marketing.xa@grapecity.com

          官網:www.gcpowertools.com.cn

          關于葡萄城控件

          葡萄城是一家跨國軟件研發集團,專注控件領域近30年,是全球最大的控件提供商,也是微軟認證的金牌合作伙伴

          下文章來源于非正式解決方案 ,作者winlion

          非正式解決方案

          思考鏈接價值,非正式解決方案,既扯高大上如人工智能、大數據,也關注碼農日常如分布式、java和golang,每天分享瞎想的東西。

          MVC 應用一般結構

          目錄結構說明如下

          名稱內容model模型層目錄,類比Java 中的entityview視圖層,存放所有templete模板ctrl控制器層, 存放全部控制器service服務層,類比Java里面的servicehtml一些靜態資源頁面util核心工具包,Md5加密,返回數據封裝等asset靜態資源目錄,存放js/css/image等args封裝全部請求參數對象mnt上傳文件的存放目錄app.dev.conf開發環境配置文件app.prod.conf生產環境配置文件start.sh/start.bat啟動腳本build.sh/build.bat打包腳本main.go主應用程序文件

          主程序結構

          主程序主要做各種初始化工作

          func main() {
              //解析配置文件
              fpath  := flag.String("c","app.dev.conf","config file path")
              flag.Parse()
              _,err:=util.Parse(*fpath)
              if err!=nil{
                  fmt.Sprintf("error when %s",err.Error())
                  return
              }
              //配置日志
              logmap := util.GetSec("log")
              service.InitLog(logmap)
          
              //初始化數據庫
              dbmap := util.GetSec("database")
              service.InitDb(dbmap)
          
              //注冊funcmap
              ctrl.RegisterFuncMap()
              //控制器
              ctrl.RegisterCtrl()
              //靜態資源文件
              fileservermap := util.GetSec("fileserver")
              ctrl.InitFileServer(fileservermap)
          
          
              //初始化session
              sessioncfg:=util.GetSec("session")
          
              util.StartSession(sessioncfg)
              appcfg := util.GetSec("app")
          
              //視圖控制器
              ctrl.RegisterView(appcfg)
              fmt.Println("http ListenAndServe " + appcfg["addr"])
              //打開服務器監聽http
              err = http.ListenAndServe(appcfg["addr"], nil)
              if err!=nil{
                  fmt.Println(err.Error())
                  log.Println(err.Error())
              }
          }

          配置文件

          3.1 配置文件解析

          使用配置文件開發包,如github.com/Unknwon/goconfig 包。

           //util/config.go
          var cfg *goconfig.ConfigFile
          var cfgmap map[string]map[string]string = make(map[string]map[string]string)
          var filepath string
          //解析peiz
          func Parse(fpath string)(c map[string]map[string]string ,err error){
              cfg, err := goconfig.LoadConfigFile(fpath)
              filepath = fpath
              sec :=cfg.GetSectionList()
              for _,v :=range sec{
                  cfgmap[v]=make(map[string]string,0)
                  keys := cfg.GetKeyList(v)
                  for _,b:= range keys{
                      cfgmap[v][b],_ = cfg.GetValue(v,b)
                  }
              }
              return cfgmap,err
          }
          //全部都存放在存放
          func GetAllCfg()(c map[string]map[string]string){
              return cfgmap
          }
          //重新刷新配置文件
          func ReloadAllCfg()(c map[string]map[string]string){
              return return Parse(filepath)
          }

          調用案列

          util.GetAllCfg()["app"]["port"]

          3.2 監聽配置文件并自動刷新配置

          使用github.com/fsnotify/fsnotify包,裝時候注意,一個函數里面如果有參數共享,應該放到一個攜程里。

          //監聽文件
          func WatchConfig(filepath ...string) {
              //創建一個監控對象
              go func() {
                  watch, err := fsnotify.NewWatcher()
                  if err != nil {
                      log.Fatal(err)
                  }
                  defer watch.Close()
                  //添加要監控的對象,文件或文件夾
                  for _, fpath := range filepath {
                      err = watch.Add(fpath)
                      if err != nil {
                          log.Fatal(err)
                      }
                      fmt.Println("WatchConfig " + fpath)
                  }
          
                  for {
                      select {
                      case ev := <-watch.Events:
                          {
                              if ev.Op&fsnotify.Write == fsnotify.Write {
                                  //監聽到文件系統使用加載新東西
                                  ReloadAllCfg()
                              }
                              fmt.Println(ev.Op, ev.Name)
                          }
                      case err := <-watch.Errors:
                          {
                              log.Println("error : ", err)
                              return
                          }
                      }
                  }
              }()
          }

          fsnotify 支持很多種事件監聽,一般在 Write 事件刷新配置文件

          //判斷事件發生的類型,如下5種
          // Create 創建
          // Write 寫入
          // Remove 刪除
          // Rename 重命名
          // Chmod 修改權限
          if ev.Op&fsnotify.Create == fsnotify.Create {
              log.Println("創建文件 : ", ev.Name);
          }
          if ev.Op&fsnotify.Write == fsnotify.Write {
              log.Println("寫入文件 : ", ev.Name);
          }
          if ev.Op&fsnotify.Remove == fsnotify.Remove {
              log.Println("刪除文件 : ", ev.Name);
          }
          if ev.Op&fsnotify.Rename == fsnotify.Rename {
              log.Println("重命名文件 : ", ev.Name);
          }
          if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
              log.Println("修改權限 : ", ev.Name);
          }

          3.3 區分系統級配置和用戶級配置

          系統級配置參數 假設修改了會影響整個應用,需要另起服務的我們稱之為系統級配置,修改了參數,往需要進行相應的操作。如修改了數據庫連接地址,需要重置數據庫連接操作。修改了應用服務器端口,則需要重啟應用服務。

          用戶級配置參數 如微信公眾號 appsecret,每次調用的時候會從配置中獲取,因此只需要重新加載數據即可。

          3.4 配置內容緩存

          需要將配置文件內容緩存到 map 中。 需要考慮到 map 的并發操作。

          實體層(model)寫法

          4.1 狀態變量定義在實體文件內部

          //model/user.go
          //用戶性別和角色
          const (
              WOMEN=2
              MAN=1
              Unknow=0
              ROLE_ADMIN =1
              ROLE_USER=0
          )
          type User struct {
          Id   int64  `xorm:"pk autoincr BIGINT(20)" form:"id" json:"id"`
          NickName  string `xorm:"VARCHAR(40)" form:"nickName" json:"nickName"`
          Openid     string  `xorm:"VARCHAR(40)" form:"openid" json:"openid"`
          Mobile     string `xorm:"VARCHAR(15)" form:"mobile" json:"mobile"`
          Passwd     string  `xorm:"VARCHAR(40)" form:"passwd" json:"-"`
          Role       int      `xorm:"int(11)" form:"role" json:"role"`
          Enable     int     `xorm:"int(11)" form:"enable" json:"enable"`
          Gender     int     `xorm:"int(11)" form:"gender" json:"gender"`
          }

          在如上代碼中,常用角色變量 ROLE_USER和ROLE_ADMIN 定義在同一個文件中,便于閱讀。

          4.2 為實體添加 tag

          實體和表結構對應,一定要定義 Form 和 Json tag。這樣可以提高系統適用性,為什么呢?因為可以適配前端以各種 Content-Type 提交數據如。后端統一用該實體接收數據即可。

          //urlencode類
          application/x-www-form-urlencoded格式
          mobile=18273252300&passwd=123456
          
          //json類
          application/x-www-form-urlencoded格式
          {"mobile":"18273252315","passwd":"123456"}

          4.3 統一 tag 參數命名方式

          約定統一使用駝峰式或者下劃線標記。如下,建議使用駝峰式。

          #駝峰
          NickName  string `xorm:"VARCHAR(40)" form:"nickName" json:"nickName"`
          #下劃線
          NickName  string `xorm:"VARCHAR(40)" form:"nick_name" json:"nick_name"`

          模板(view)層配置

          如下幾點需要注意

          5.1 模板文件和模板名稱關聯

          關聯便于代碼管理和閱讀。模板位置 /view/demo/index.html,模板內容如下。

          {{define "demo/index"}}
          <div>
          Hello,Modal
          </div>
          {{end}}

          外部調用方法如下,大家能很自然知道模板文件位置。

          http://localhost/demo/index

          5.2 一個函數搞定全部 Html 請求類的頁面

          主要是為了程序員生活更美好(早點下班+偷懶)。

          //ctrl/base.go
          func RegisterPage(isDev bool) {
              //初始化一個全局的模板變量
              GlobTemplete := template.New("root")
              //把一些函數添加進去,這樣頁面里面就可以使用函數啦
              GlobTemplete.Funcs(GetFuncMap())
              //解析模板 ,demo/index => 模板
              GlobTemplete, err := GlobTemplete.ParseGlob("view/**/*")
              for _, templete := range GlobTemplete.Templates() {
                  tplname := templete.Name()
                  patern := "/" + tplname
                  fmt.Printf("register templete  %s ==> %s\n", patern, tplname)
                  //這里就是 /demo/index  這個url 和對應的處理函數之間的關系
                  http.HandleFunc(patern, func(w http.ResponseWriter, req *http.Request) {
                      fmt.Println(patern + "=>" + tplname)
                      if isDev {
                          GlobTemplete := template.New("root")
                          GlobTemplete.Funcs(GetFuncMap())
                          GlobTemplete, err = GlobTemplete.ParseGlob("view/**/*")
                          for _, v := range GlobTemplete.Templates() {
                              if v.Name() == tplname {
                                  templete = v
                              }
                          }
          
                      }
                      err = templete.ExecuteTemplate(w, tplname, nil)
                      if err != nil {
                          fmt.Println(err.Error())
                      }
          
                  })
              }
          }
          
          //在main.go中初始化
          func main(){
              ///
            ctrl.RegisterPage(true)
              //
          }

          外部調用方法如下,大家能很自然知道模板文件位置。

          http://localhost/demo/index

          5.3 添加調試模式支持

          為什么要添加調試模式支持?因為調試模式狀態下,我們修改來了頁面模板,需要立即看到頁面內容,而不需要重啟應用。核心代碼如下,即在調試模式狀態下,每次請求都重新解析模板。

          if isDev {
              GlobTemplete := template.New("root")
              GlobTemplete.Funcs(GetFuncMap())
              GlobTemplete, err = GlobTemplete.ParseGlob("view/**/*")
              for _, v := range GlobTemplete.Templates() {
                              if v.Name() == tplname {
                                  templete = v
                              }
                  }
              }

          由上可見,調試模式效率是非常低的,我們不應該在生產環境采用調試模式。

          5.4 添加數據注入

          應用場景是在每個頁面中都需要使用 session 中的用戶 ID 數據。方法是在 RegisterPage 函數內部模板templete.ExecuteTemplate(w, tplname, nil)處秀修改成如下代碼

          //從session中獲取用戶信息
          user := loadDataFromSession(req)
          err = templete.ExecuteTemplate(w, tplname, user)

          前端模板調用代碼如下

          {{define "demo/index"}}
          <div>
          Hello,Modal ,User id is {{.Id}}
          </div>
          {{end}}

          返回結果

          Hello,Modal ,User id is xxx

          5.5 在頁面使用函數

          在 RegisterPage 方法內定義一個 funMap

          //ctrl/funcmap.go
          var resFuncMap template.FuncMap = make(template.FuncMap)
          func hello (){
              return "hello"
          }
          func hello2 (test string){
              return "hello" + test
          }
          //初始化方法
          func RegisterFuncMap(){
              resFuncMap ["hello"]=hello
          }

          main.go 中初始化

          //在main.go中初始化
          func main(){
              ///
            ctrl.RegisterFuncMap()
              //
          }

          前端模板調用代碼如下

          {{define "demo/index"}}
          <div>
          Hello,Modal ,hello func retutn  {{hello}}
          Hello,Modal ,hello2 func retutn  {{hello2 "參數2"}}
          </div>
          {{end}}

          返回結果

          Hello,Modal ,hello func retutn hello
          Hello,Modal ,hello func retutn hello2參數2

          5.6 多多使用 templete 方法

          主要使用場景是分角色菜單,用戶

          {{define "demo/memo"}}
          {{if eq .Role 1}}
          菜單內容1
          {{else if eq .Role 2}}
          菜單內容2
          {{end}}
          <script>
          GLOB={"ROLE":.Role}
          </script>
          {{end}}

          其他頁面統一調用,進行角色菜單等控制。

          {{define "demo/index"}}
          <div>
          {{templete "demo/menu"}}
          Hello,Modal ,hello func retutn  {{hello}}
          Hello,Modal ,hello2 func retutn  {{hello2 "參數2"}}
          </div>
          {{end}}

          控制器層(ctrl)

          控制器層主要處理對外接口

          6.1 一個典型的控制器結構**

          import (
              ///很多包  
              )
          //定義個一個控制器對象
          type UserCtrl struct {
          }
          //將url和處理函數綁定
          func  ( ctrl *UserCtrl)Router(){
              Router("/user/login",ctrl.authwithcode)
          }
          //定義用戶處理函數
          var userService service.UserService
          
          //用戶通過小程序登錄處理函數,輸入code
          //通過util.RespOk 或者util.RespFail輸出
          func( ctrl * UserCtrl)authwithcode(w http.ResponseWriter, req *http.Request) {
          
              var requestdata args.AuthArg
              util.Bind(req,&requestdata)
          
              cfgminapp := util.GetSec("miniapp")
              resp,err := util.Code2Session(cfgminapp["appid"],cfgminapp["secret"],requestdata.Code)
              if err!=nil{
                  util.RespFail(w,err.Error())
                  return
              }
              requestdata.User.Openid = resp.Openid
                  requestdata.User.SessionKey = resp.SessionKey
              u,err:= userService.LoginWithOpenId(requestdata.User)
              if err!=nil{
                  util.RespFail(w,err.Error())
              }else{
                  util.RespOk(w,model.User{
                      Ticket:u.Ticket,
                      ClientId:u.ClientId,
                      Role:u.Role,
                  })
              }
          }

          6.2 數據和參數綁定

          所有參數都需要可預期在一個結構體里面。這樣整個系統編程將變得非常簡單。在上 面函數中,通過如下代碼實現參數綁定

          var requestdata args.AuthArg
          util.Bind(req,&requestdata)

          其中 args.AuthArg 對象定義如下

          package args
          
          import "../model"
          type AuthArg struct {
              PageArg
              model.User
              Code string `json:"code" form:"code"`
              Kword string `json:"kword" form:"kword"`
              Passwd string `json:"passwd" form:"passwd"`
          }

          args 作用是存放一切請求參數。每個業務都建議定義一個 arg。每個 arg 都有一個公共屬性 PageArg。PageArg 定義如下

          import (
              "fmt"
              "time"
          )
          //常用的搜索,大家可以自行添加
          type PageArg struct {
              Pagefrom int  `json:"pagefrom" form:"pagefrom"`
              Pagesize int  `json:"pagesize" form:"pagesize"`
              Kword string  `json:"kword" form:"kword"`
              Asc string  `json:"asc" form:"asc"`
              Desc string     `json:"desc" form:"desc"`
              Stat int    `json:"stat" form:"stat"`
              Datefrom time.Time  `json:"datafrom" form:"datafrom"`
              Dateto time.Time    `json:"dateto" form:"dateto"`
              Total  int64        `json:"total" form:"total"`
          }
          //獲得分頁大小
          func (p*PageArg) GetPageSize() int{
              if p.Pagesize==0{
                  return 100
              }else{
                  return p.Pagesize
              }
          
          }
          //獲得分頁當前第幾頁
          func (p*PageArg) GetPageFrom() int{
              if p.Pagefrom<0{
                  return 0
              }else{
                  return p.Pagefrom
              }
          }
          //獲得排序 ID DESC ,前端傳遞參數 desc=排序字段 或者asc=排序字段
          func (p*PageArg) GetOrderBy() string{
              if len(p.Asc)>0{
                  return fmt.Sprintf(" %s asc",p.Asc)
              } else if len(p.Desc)>0{
                  return fmt.Sprintf(" %s desc",p.Desc)
              }else{
                  return ""
              }
          }

          6.3 參數綁定核心函數 util.Bind

          大體結構如下

          func Bind(req *http.Request,obj interface{}) error{
              contentType := req.Header.Get("Content-Type")
              //如果是簡單的json,那么直接用JSON解碼
              if strings.Contains(strings.ToLower(contentType),"application/json"){
                  return  BindJson(req,obj)
              }
              //如果是其他的urlencode那么就用BindForm去處理
              if strings.Contains(strings.ToLower(contentType),"application/x-www-form-urlencoded"){
                  return   BindForm(req,obj)
              }
              //可以自行擴展xml
              if strings.Contains(strings.ToLower(contentType),"text/xml"){
                  return   BindXml(req,obj)
              }
              return errors.New("當前方法暫不支持")
          }

          以 BindJson 為例子

          func BindJson(req *http.Request,obj interface{}) error{
              s, err := ioutil.ReadAll(req.Body) //把  body 內容讀入字符串
              if err!=nil{
                  return err
              }
              err = json.Unmarshal(s,obj)
              return err
          }

          可能大家更關心 BindForm,篇幅太長,大家可以移步

          https://www.github/winlion/restgo-admin

          6.4 封裝返回函數

          一般封裝一個底層 JSON,然后根據返回成功或失敗響應對應的 code

          /util/resp.go
          package util
          
          import (
              "net/http"
          
              "encoding/json"
          
              "fmt")
          //定義個通用的結構體用于裝載返回的數據
          type H struct {
              Code int    `json:"code"`
              Rows interface{} `json:"rows,omitempty"`
              Data interface{} `json:"data,omitempty"`
              Msg string `json:"msg,omitempty"`
              Total interface{} `json:"total,omitempty"`
          
          }
          //返回Json的底層方法
          func RespJson(w http.ResponseWriter,data interface{}){
                  header :=w.Header()
                  header.Set("Content-Type","application/json;charset=utf-8")
                  w.WriteHeader(http.StatusOK)
                  ret,err :=json.Marshal(data)
                  if err!=nil{
                      fmt.Println(err.Error())
                  }
                  w.Write(ret)
          }
          
          //當操作成功返回Ok,
          func RespOk(w http.ResponseWriter,data interface{}){
          
              RespJson(w,H{
                  Code:http.StatusOK,
                  Data:data,
              })
          }
          
          //當操作失敗返回Error,
          func RespFail(w http.ResponseWriter,msg string){
          
              RespJson(w,H{
                  Code:http.StatusNotFound,
                  Msg :msg,
              })
          }

          服務層(service)實現

          7.1 服務層一般結構

          以訂單管理為例

          package service
          
          import (
          
          
              "../model"
          
              "../args"
          
              "github.com/go-xorm/xorm"
                  "log"
              "github.com/pkg/errors"
              "encoding/json"
              "time"
          )
          
          type OrderService struct {
          
          }
          //構造條件
          func (service *OrderService)buildCond(arg args.PageArg)(*xorm.Session){
              orm := DBengin.Where("id > ?",0)
              if(!arg.Datefrom.IsZero()){
                  orm = orm.And("createat >= ?",arg.Datefrom.String())
              }
              if(!arg.Dateto.IsZero()){
                  orm = orm.And("createat <to ?",arg.Datefrom.String())
              }
              if (arg.Seller>0){
                  orm = orm.And("seller = ?",arg.Seller)
              }
              if (arg.Buyer>0){
                  orm = orm.And("buyer = ?",arg.Buyer)
              }
              if (arg.Stat>0){
                  orm = orm.And("stat = ?",arg.Stat)
              }
              return orm
          }
          //增加
          func (service *OrderService) Create(order model.Order) (model.Order,err){
          
              _,err = DBengin.InsertOne(&order)
              if err!=nil{
                  log.Println(err.Error())
              }
              return order
          
          }
          //刪除
          func (service *OrderService) Delete(order model.Order) (error){
          
              return nil
          
          }
          //修改
          func (service *OrderService) Create(order model.Order) (model.Order,err){
          
              _,err = DBengin.InsertOne(&order)
              if err!=nil{
                  log.Println(err.Error())
              }
              return order
          
          }
          //搜索
          func (service *OrderService) Search(orderArg args.OrderArg) ([]model.Order, error){
          var ret []model.Order = make([]model.Order,0)
          return  ret,nil
          
          }
          //查詢某一個
          func (service *OrderService) Create(order model.Order) (model.Order){
          
              _,err := DBengin.InsertOne(&order)
              if err!=nil{
                  log.Println(err.Error())
              }
              return order
          
          }

          一般需要構建如下幾類函數,具體隨業務而定

          名稱內容Create添加Update修改Search搜索,返回列表Find返回某一個對象Delete刪除buildCond構建條件函數Count符合某一條件的記錄數目

          7.2 條件統一管理

          我們可以用類似于如下函數來統一管理查詢條件,該函數輸出參數,輸出一個 session。

          func (service *OrderService)buildCond(arg args.PageArg)(*xorm.Session)

          條件規范化可以讓應用更靈活,讓業務更清晰。如果不規范,樓主曾經經歷的教訓可能也會撞上你。

          7.3 連接數據庫

          數據庫建議使用 xorm。 在 server 包目錄下新建 init.go 在其中實現數據庫的初始化

          //定義全局變量DBengin 
          var DBengin *xorm.Engine
          //定義初始化函數InitDb,dbmap是數據庫配置參數,=來自于外部參數
          func InitDb(dbmap map[string]string){
          
              driverName     := dbmap["driveName"]
              dataSourceName := dbmap["dataSourceName"]
              showsql :=       dbmap["showSql"]!="false"
              maxIdle,_ :=        strconv.Atoi(dbmap["maxIdle"])
              maxOpen,_ :=       strconv.Atoi(dbmap["maxOpen"])
              sync     :=      dbmap["sync"]=="true"
              dbengin , err := xorm.NewEngine(driverName, dataSourceName)
          
              if err != nil {
                  panic("data source init error ==>"+err.Error())
              }
              if sync{
                  dbengin.Sync2(new(model.User),
                      new(model.Item),
                      new(model.Order),
                      new(model.User),
                      )
              }
              dbengin.ShowSQL(showsql)
              dbengin.SetMaxIdleConns(maxIdle)
              dbengin.SetMaxOpenConns(maxOpen)
          
              dbengin.SetConnMaxLifetime(5*time.Second)
              DBengin = dbengin
          }

          main.go 中初始化數據庫

          func main(){
              //
              dbmap = util.GetSec("database") 
              server.InitDb(dbmap)
              //
          }

          具體使用可以參考 Xorm

          func (service *OrderService) Create(order model.Order) (model.Order){
              //就是這么用的
              _,err := DBengin.InsertOne(&order)
              if err!=nil{
                  log.Println(err.Error())
              }
              return order
          
          }

          路由

          8.1 路由功能怎實現

          在每一個 ctrl 中都定義一個 Router 函數

          func  ( ctrl *UserCtrl)Router(){
              Router("/open/register",ctrl.Register)
              Router("/open/authwithpwd",ctrl.authwithpwd)
              Router("/user/find",ctrl.Find)
              Router("/user/quit",ctrl.quit)
              Router("/open/authwithcode",ctrl.authwithcode)
          }

          這些函數調用了 Router 方法,該方法本質上是對 http.HanderFunc 的封裝

          //ctrl/base.go
          func Router(pantern string, fun func(w http.ResponseWriter, req *http.Request)) {
              http.HandleFunc(pantern, func(w http.ResponseWriter, req *http.Request) {
                fun(w, req)
              })
          }

          定義路由注冊函數

          //注冊控制器
          func RegisterCtrl() {
              new(UserCtrl).Router()
              new(OpenCtrl).Router()
              new(AttachCtrl).Router()
          }

          注冊路由 在 main.go 中完成路由注冊

          func main(){
              //  
              ctrl.RegisterCtrl()
              //
          }

          8.2 支持 Post/Get/Any

          解決思路如下 首先在 ctrl/base.go 里面定義一個 map

          PostRouterMap := make(map[string]HandFunc)
          GetRouterMap := make(map[string]HandFunc)

          接著定義路由綁定函數

          type Handlefunc func(w http.ResponseWriter,req *http.Request)
          func Post(formate string,handlefunc func(w http.ResponseWriter,req *http.Request)){
              http.HandleFunc(formate,func(w http.ResponseWriter,req *http.Request){
                      if req.Method==http.MethodPost {
                          handlefunc(w,req)
                      }else{
                          //not sourport 處理
                      }
          
              })
          }
          
          func Get(formate string,
          handlefunc func(w http.ResponseWriter,req *http.Request)){
          http.HandleFunc(formate,
          func(w http.ResponseWriter,req *http.Request){
                  if req.Method==http.MethodGet {
                      handlefunc(w,req)
                  }else{
                      //not sourport 處理
                  }
              })
          }
          
          //支持任意方式
          func Any(formate string,
          handlefunc func(w http.ResponseWriter,req *http.Request)){
          http.HandleFunc(formate,
          func(w http.ResponseWriter,req *http.Request){
          
                  handlefunc(w,req)
              })
          }

          8.3 支持正則

          首先需要定義默認路由。RegisterRegExRouter() 中定義了默認路由 http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request)。任何找不到的路由都會進入這個。

          //這一個專門存uri和處理函數之間關系的字典
          var RegExRouterMap map[string]func(w http.ResponseWriter, req *http.Request) = make(map[string]func(w http.ResponseWriter, req *http.Request), 0)
          
          //這是一個存儲Uri和對應正則表達式的字典以后就不要編譯啦。
          var RegexpMatchMap map[string]*regexp.Regexp = make(map[string]*regexp.Regexp, 0)
          func RegExRouter(pantern string, fun func(w http.ResponseWriter, req *http.Request)) {
              RegExRouterMap[pantern] = fun
              //形成映射關系
              RegexpMatchMap[pantern],_ = regexp.Compile(pantern)
          }
          //沒有找到需要一個默認404
          func notfound(w http.ResponseWriter, req *http.Request){
              w.Write([]byte("404 NOT FOUNT"))
          }
          
          func RegisterRegExRouter(){
              http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
                  uris :=  strings.Split(req.RequestURI,"?")
                  uri := uris[0]
                  handlefunc :=  notfound
                  for p,regm := range  RegexpMatchMap{
                      if regm.MatchString(uri){
                          handlefunc = RegExRouterMap[p]
                          break
                      }
                  }
                  handlefunc(w,req)
              })
          }

          在路由注冊中初始化

          //注冊控制器
          func RegisterCtrl() {
              //new(AttachCtrl).Router()
              RegisterRegExRouter()
          }

          現在我們可以在控制器頁面通過 RegExRouter 添加正則路由啦

          //ctrl/user.go
          
          func (ctrl *UserCtrl) Router() {
              Router("/open/authwithcode", ctrl.authwithcode)
              RegExRouter("/d/.*", ctrl.regtext)
          }
          func (ctrl *UserCtrl) regtext(w http.ResponseWriter, req *http.Request) {
              util.RespOk(w, req.RequestURI)
          }

          客戶端請求

          http://localhost/d/12345678977

          響應數據

          {"code":200,"data":"/d/12345678977"}

          8.4 404 沒找到

          在如上所示中定義了 notfound 函數,當沒有任何一個匹配對象時候,進入這個函數。

          //沒有找到需要一個默認404
          func notfound(w http.ResponseWriter, req *http.Request){
              w.Write([]byte("404 NOT FOUNT"))
          }

          8.5 實現攔截器功能

          我們可以在 Router 方法里面實現攔截器功能,主要用來做鑒權,日志記錄等

          func Router(pantern string, fun func(w http.ResponseWriter, req *http.Request)) {
              http.HandleFunc(pantern, func(w http.ResponseWriter, req *http.Request) {
                  //包含某些關鍵字的不需要鑒權啦
                  if strings.Contains(req.RequestURI, "/test/") {
                      fun(w, req)
                  } else {
                      //否則判斷一下,如果從xxxplatform平臺來的不需要鑒權,直接往下走
                      ticket := req.Header.Get("request-ticket")
                      clientid := req.Header.Get("request-clientid")
                      platform := req.Header.Get("request-platform")
                      if platform != "xxxplatform" {
                          fun(w, req)
                          return
                      }
                      //否則這要鑒權,通過就直接往下走
                      if userService.Authorization(ticket, clientid) {
                          fun(w, req)
                      } else {
                          //沒通過返回木有權限。
                          util.RespFail(w, "沒有權限")
                      }
          
                  }
          
              })
              fmt.Printf("register patern %s ==> %s\n", pantern, pantern)
          }

          8.6 提升路由性能

          我主要在 Router 函數上下功夫,一種可用的設計是利用攜程,如下

          func Router(pantern string, fun func(w http.ResponseWriter, req *http.Request)) {
              http.HandleFunc(pantern, func(w http.ResponseWriter, req *http.Request) {
              //先copy出來
              var bodyBytes []byte
              if c.Request.Body != nil {
                  bodyBytes, _ = ioutil.ReadAll(req.Body)
              }
              // 把剛剛讀出來的再寫進去
              req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
              //這樣就可以利用攜程干事情啦
              go fun(w, req)    
          
              })
          
          }

          需要注意的是要先把數據 copy 出來,然后才能利用攜程,否則 fun 函數里面取出的數據是空的。

          其他及源代碼獲取

          session、日志,可以引用第三方包。 鑒權可以參考攔截器。 安全,防 xss 攻擊可以參考攔截器。 代碼獲取在公眾號回復:golang框架


          主站蜘蛛池模板: 亚洲丰满熟女一区二区v| 精品国产一区二区三区久| 亚洲日韩中文字幕一区| 国产精品视频一区国模私拍| 久久91精品国产一区二区| 最美女人体内射精一区二区| tom影院亚洲国产一区二区| 波多野结衣中文字幕一区| 久久99精品波多结衣一区| 区三区激情福利综合中文字幕在线一区亚洲视频1 | 精品女同一区二区三区免费站| 欧美日韩精品一区二区在线视频| 99国产精品一区二区| 国模视频一区二区| ...91久久精品一区二区三区 | 亚洲熟女一区二区三区| 另类ts人妖一区二区三区| 久久久91精品国产一区二区| 亚洲免费一区二区| 视频一区视频二区日韩专区| 无码国产精品一区二区免费式影视| 一区二区免费在线观看| 国产伦精品一区二区三区精品 | 精品欧洲AV无码一区二区男男| 国产精品小黄鸭一区二区三区| 国产精品无码一区二区在线观| 亚洲蜜芽在线精品一区| 国产亚洲一区二区三区在线观看 | 国产婷婷色一区二区三区| 男人免费视频一区二区在线观看| 精品无码人妻一区二区三区品| 在线观看国产一区二区三区| 亚洲av无码一区二区三区天堂古代| 亚洲制服中文字幕第一区| 亚洲色偷精品一区二区三区| 国产日韩视频一区| 韩日午夜在线资源一区二区| 香蕉久久av一区二区三区| 综合无码一区二区三区四区五区| 成人丝袜激情一区二区| 狠狠做深爱婷婷综合一区 |