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 成人综合婷婷国产精品久久免费,日本在线中文,国产伦久视频免费观看视频

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          一個(gè)易學(xué)易用高效便捷的MVC和ORM框架

          發(fā)目的

          @copyright 楊同峰 保留所有權(quán)利

          本文可以轉(zhuǎn)載,但請(qǐng)保留版權(quán)信息。

          SSH框架配置復(fù)雜、難用。個(gè)人認(rèn)為這不是一個(gè)框架應(yīng)該有的樣子。框架應(yīng)該使用簡(jiǎn)單、配置簡(jiǎn)單、代碼簡(jiǎn)潔。于是參照Django的一些特性,編寫了這個(gè)MVC+ORM框架。

          特性

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

          配置

          1. 新建一個(gè)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標(biāo)簽內(nèi))加入
           <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> 
          

          所有需要配置的都在這里了。這里做個(gè)簡(jiǎn)要說(shuō)明

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

          它有controller和template兩個(gè)參數(shù)。

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

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

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

          dbhost dbname dbusr dbpwd 是數(shù)據(jù)庫(kù)的 地址、數(shù)據(jù)庫(kù)名、用戶名和密碼。目前這個(gè)MVC框架只支持MySQL,后續(xù)會(huì)添加其他數(shù)據(jù)庫(kù)的支持。

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

          YangMVC的第零個(gè)例子-HelloWorld程序

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

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

          第零個(gè)例子的顯示

          IndexController來(lái)處理應(yīng)用的根目錄下的請(qǐng)求。 index方法來(lái)處理這個(gè)目錄下的默認(rèn)請(qǐng)求。

          YangMVC第一個(gè)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/下建立一個(gè)index.jsp

          其中核心的代碼為

          <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
          (此處省略一堆無(wú)關(guān)的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>
          

          一個(gè)顯示列表的網(wǎng)頁(yè)就此搞定。訪問(wèn)應(yīng)用目錄下的book/目錄即可顯示出結(jié)果

          這里寫圖片描述

          你作出的結(jié)果可能沒(méi)那么好看,這完全取決于css。

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

          你可以通過(guò)類似下面的語(yǔ)句來(lái)加入到網(wǎng)頁(yè)中

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

          注意路徑要對(duì)。

          說(shuō)明:

          這個(gè)BookController是一個(gè)控制器,它的每一個(gè)公共方法都對(duì)應(yīng)一個(gè)網(wǎng)頁(yè)(如果不想對(duì)應(yīng),你需要將其設(shè)為私有的)

          Model和DBTool是整個(gè)ORM框架的核心。Model表示模型,它用來(lái)與數(shù)據(jù)庫(kù)表相對(duì)應(yīng)。在創(chuàng)建一個(gè)Model時(shí),會(huì)指定對(duì)應(yīng)的表名。

          這里和Hibernate不同,Hibernate需要預(yù)先生成所有數(shù)據(jù)庫(kù)表的對(duì)應(yīng)類, 而這個(gè)Model可以與任何表格關(guān)聯(lián),而不需要預(yù)先生成任何一個(gè)類。 這正是YangMVC中的ORM的優(yōu)勢(shì)所在。

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

          程序中使用Model的靜態(tài)方法tool獲取一個(gè)DBTool對(duì)象,tool傳入的參數(shù)book是數(shù)據(jù)庫(kù)的表名。

          這樣DBTool就和book表建立了關(guān)聯(lián)。

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

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

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

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

          這個(gè)例子相當(dāng)于執(zhí)行了如下SQL語(yǔ)句:

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

          在上面的例子中, all()返回的LasyList又調(diào)用了它的limit方法,這一步仍然沒(méi)有真正訪問(wèn)數(shù)據(jù)庫(kù)。

          那么訪問(wèn)數(shù)據(jù)庫(kù)從哪里開(kāi)始呢? 從你獲取這個(gè)列表的一項(xiàng)時(shí)。

          一個(gè)List,可以使用枚舉的方法來(lái)訪問(wèn)

          for(Model m : list){
           
          }
          

          也可以使用get方法來(lái)訪問(wèn)。如

          Model m = list.get(12)
          

          在你訪問(wèn)具體它的一個(gè)元素(Model)時(shí),數(shù)據(jù)庫(kù)查詢才會(huì)啟動(dòng)。而且也不是將所有數(shù)據(jù)放到內(nèi)存中。比如你通過(guò)上面for的方法枚舉時(shí),其實(shí)它是通過(guò)ResultSet的next游標(biāo)在移動(dòng),所以它很高效!也避免了無(wú)用的數(shù)據(jù)庫(kù)操作。

          put("book",list)
          

          該方法將查詢得到的book塞入request中,在jsp網(wǎng)頁(yè)中就可以使用JSTL來(lái)使用它。因?yàn)樗且粋€(gè)List,所以用forEach去訪問(wèn)他。

          Model 的一個(gè)對(duì)象對(duì)應(yīng)于數(shù)據(jù)庫(kù)表的一行(一條記錄),Model是一個(gè)Map的子類!!!,所以在JSTL中,你可以使用

          ${ b.name } 的方式來(lái)訪問(wèn)名為b的Model 的name項(xiàng)。 它相當(dāng)于

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

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

          第二個(gè)Demo

          添加書籍頁(yè)面

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

          對(duì)應(yīng)的/view/book/add.jsp (這是默認(rèn)對(duì)應(yīng)的模板地址)的核心內(nèi)容

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

          這里寫圖片描述

          上面的例子控制器其實(shí)是對(duì)應(yīng)兩個(gè)頁(yè)面。 在收到Get請(qǐng)求的時(shí)候顯示表單,在用戶提交數(shù)據(jù)時(shí),做插入操作,并顯示表單。(我們當(dāng)然可以把這兩個(gè)頁(yè)面寫到兩個(gè)不同的方法中)

          我們還是使用Model.tool獲取一個(gè)DBTool。

          先來(lái)看顯示表單,就一句話

           renderForm(tool.create());
          

          tool的create方法會(huì)返回一個(gè)Model對(duì)象,這個(gè)對(duì)象和book表相關(guān)聯(lián)(因?yàn)閠ool和book表關(guān)聯(lián))。

          并將這個(gè)Model傳遞給renderForm方法。這個(gè)方法會(huì)根據(jù)book表格的元數(shù)據(jù)自動(dòng)創(chuàng)建一個(gè)表格。

          哇偶!

          那么這個(gè)Form插入到網(wǎng)頁(yè)的什么位置呢? 將 ${book_form } 放入網(wǎng)頁(yè)中 即可。

          如果來(lái)的是POST請(qǐng)求(使用isPost()方法來(lái)判斷)

          使用tool的create方法創(chuàng)建一個(gè)新的Model, 盡快還有其他創(chuàng)建Model對(duì)象的方式,但如果你希望插入,請(qǐng)盡量使用這種方式。

          paramToModel(m) ,這個(gè)方法會(huì)自動(dòng)查找表單中,名字與數(shù)據(jù)庫(kù)字段名匹配的項(xiàng),并自動(dòng)賦值給Model的相應(yīng)項(xiàng)。是不是很方便。。。

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

          隨后直接調(diào)用tool的save方法將其保存到數(shù)據(jù)庫(kù)中!OK了!萬(wàn)事大吉!

          細(xì)心的小朋友會(huì)問(wèn): 數(shù)據(jù)庫(kù)中的字段名都是英文的如name,為什么在網(wǎng)頁(yè)上顯示的是中文???

          看看我的數(shù)據(jù)庫(kù)表格定義

          CREATE TABLE `book` (
           `id` int(11) NOT NULL auto_increment COMMENT '編號(hào)',
           `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 '簡(jiǎn)介',
           `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;
          

          真相大白與天下,我是通過(guò)給字段加注釋實(shí)現(xiàn)的這一點(diǎn)。只要你將數(shù)據(jù)庫(kù)表格加上注釋,它就會(huì)自動(dòng)獲取注釋并顯示,對(duì)于沒(méi)有注釋的字段,則會(huì)顯示字段名。如那個(gè)扎眼的file_name

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

          第三個(gè)demo-編輯(自動(dòng)創(chuàng)建的修改表單)

          細(xì)心的朋友發(fā)現(xiàn),我們是按照CRUD的邏輯來(lái)將的。下面是編輯網(wǎng)頁(yè)。

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

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

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

          這個(gè)代碼長(zhǎng)了一點(diǎn), 有17行。對(duì)于用YangMVC的,已經(jīng)算夠長(zhǎng)的了。它仍然是兩個(gè)網(wǎng)頁(yè)!!!

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

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

          調(diào)用checkNull來(lái)檢查一下。 在我的開(kāi)發(fā)生涯中,遇到各種參數(shù)檢查,所以這個(gè)功能是必須有的,如果checkNull不過(guò),就會(huì)拋出一個(gè)異常。 這樣做的目的是不要讓這種參數(shù)檢查干擾我們正常的邏輯。這不就是異常之所以存在的意義么?

          如果缺少這個(gè)參數(shù),頁(yè)面會(huì)提示說(shuō)缺少這個(gè)參數(shù)。

          下面使用tool.get(id)方法來(lái)獲取一個(gè)Model(一條記錄)。這個(gè)方法是根據(jù)表格的主鍵進(jìn)行查詢,返回的不是列表而是一個(gè)具體的Model對(duì)象。在這里我建議主鍵應(yīng)當(dāng)是整數(shù)、且是數(shù)據(jù)庫(kù)自增的。

          renderForm傳入一個(gè)model,這個(gè)model中有數(shù)據(jù),就會(huì)被顯示出來(lái)。

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

          有的朋友問(wèn),如果不想用默認(rèn)的表單怎么辦? 那你自己寫一個(gè)表單在你的模板里就是了。只不過(guò),你可以先用這個(gè)方法吧表單生成出來(lái),然后按你的意圖修改就成了。這也節(jié)省大量時(shí)間啊。做過(guò)Form的請(qǐng)舉手。

          第四個(gè)DEMO-刪除

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

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

          控制器創(chuàng)建

          控制器是一個(gè)Java類,類有若干方法。在YangMVC的設(shè)計(jì)中,控制器的每一個(gè)公共的方法都映射對(duì)應(yīng)一個(gè)網(wǎng)頁(yè)。這樣一個(gè)Java類可以寫很多的網(wǎng)頁(yè)。 方便管理。(當(dāng)然,你也可以在一個(gè)控制器中只寫一個(gè)方法來(lái)支持網(wǎng)頁(yè),這沒(méi)問(wèn)題(⊙﹏⊙)b)

          所有的控制器都要繼承 org.docshare.mvc.Controller 這個(gè)類。充當(dāng)控制器方法的方法應(yīng)當(dāng)是沒(méi)有參數(shù)沒(méi)有返回值的。如上面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>
          

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

          路徑映射

          所謂路徑映射就是要將 一個(gè)控制器(一個(gè)Java類)和一個(gè)網(wǎng)址建立關(guān)聯(lián)。 用戶訪問(wèn)某網(wǎng)址時(shí),框架自動(dòng)調(diào)用控制器的某個(gè)函數(shù)。

          因?yàn)楸究蚣茉O(shè)計(jì)思想希望配置盡可能少,所以這里的路徑映射是通過(guò)命名關(guān)系的。

          假設(shè)應(yīng)用的根目錄為

          http://localhost:8080/YangMVC/

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

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

          用戶訪問(wèn)這個(gè)路徑時(shí),框架會(huì)調(diào)用BookController 的index方法。如果沒(méi)有這個(gè)方法則會(huì)報(bào)錯(cuò)。

          index方法用以處理某個(gè)路徑下的默認(rèn)網(wǎng)頁(yè)(網(wǎng)站以斜杠結(jié)尾的都會(huì)調(diào)用某個(gè)類的index方法來(lái)處理)。

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

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

          這就是路徑和類名的默認(rèn)關(guān)聯(lián)。

          在這個(gè)網(wǎng)站后加入方法名可以訪問(wèn)BookController的 任何一個(gè)公共方法。

          如 http://localhost:8080/YangMVC/book/edit 與BookController的edit方法關(guān)聯(lián)。

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

          控制器方法

          獲取request中的參數(shù)

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

          輸出方法

          output方法

           output("Hello YangMVC");
          

          這個(gè)方法輸出一個(gè)文本到網(wǎng)頁(yè)上(輸出流中),并關(guān)閉輸出流。因?yàn)樗鼤?huì)關(guān)閉流,所以你不要調(diào)用它兩次。你如果需要輸出多次,以將內(nèi)容放到StringBuffer中,然后統(tǒng)一輸出。

          render方法

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

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

          這里的參數(shù)應(yīng)該是相對(duì)于模板目錄的相對(duì)路徑。

          render方法使用參數(shù)制定的網(wǎng)頁(yè)(一個(gè)包含JSTL的jsp文件),將其輸出。可以通過(guò)put來(lái)制定參數(shù)。下面會(huì)詳細(xì)講。
          

          render()方法

          這個(gè)render方法是沒(méi)有參數(shù)的,它會(huì)使用默認(rèn)模板,如果這個(gè)模板不存在,就會(huì)提示錯(cuò)誤。
           public void renderDemo(){
           request.setAttribute("a", "sss");
           render();
           
           }
          

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

          org.demo.IndexController的renderDemo方法會(huì)對(duì)應(yīng)/view/renderDemo.jsp

          之所以模板存在于模板根目錄下,是因?yàn)檫@個(gè)IndexController是處理應(yīng)用根目錄的。他們有對(duì)應(yīng)關(guān)系。

          如果是org.demo.BookController,它對(duì)應(yīng) app根目錄下的 /book/ 目錄。

          它的add方法對(duì)應(yīng)路徑 /book/add

          如果應(yīng)用名為hello,那么完成路徑應(yīng)該是 /hello/book/add

          outputJSON 方法

          該方法將參數(shù)轉(zhuǎn)化為JSON,并向網(wǎng)頁(yè)輸出。

           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);
           }
          這個(gè)代碼稍長(zhǎng),其實(shí)上面的所有都是生成一個(gè)Map,最后一句輸出。outputJSON可以輸出List,Map和任何Java對(duì)象。內(nèi)部轉(zhuǎn)換是使用fastjson實(shí)現(xiàn)的。
          

          自動(dòng)生成并輸出一個(gè)表單

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

          該函數(shù)會(huì)根據(jù)模型對(duì)應(yīng)的表結(jié)構(gòu),自動(dòng)生成一個(gè)表單,并將其內(nèi)容放入 表格名_form 中,如book表會(huì)輸出到 book_form 中。

          在網(wǎng)頁(yè)中,直接寫 ${book_form}就可以將表單放下去。

          template制定對(duì)應(yīng)的模板文件,可以省略,省略后按照默認(rèn)規(guī)則查找模板文件。

          postTo設(shè)定 表單提交的網(wǎng)頁(yè),可以省略,默認(rèn)是"",即當(dāng)前網(wǎng)頁(yè)(Controller)。

          獲取參數(shù)的方法

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

          檢查方法

          public void checkNull(String name,Object obj)

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

          ORM框架

          Model與DBTool

          Model 對(duì)象對(duì)應(yīng)數(shù)據(jù)庫(kù)的表格,它會(huì)與一個(gè)表格進(jìn)行綁定。DBTool相當(dāng)于是它的DAO類。

          YangMVC的ORM組件可以單獨(dú)使用。使用前需要先配置數(shù)據(jù)庫(kù):

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

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

          創(chuàng)建一個(gè)DBTool對(duì)象

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

          其中book是數(shù)據(jù)庫(kù)表的名字。

          創(chuàng)建一個(gè)空的Model

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

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

          根據(jù)主鍵讀取一個(gè)Model

           Model m = tool.get(12);
          

          查詢表中所有的行

           LasyList list = tool.all();
          all返回一個(gè)LasyList對(duì)象。這個(gè)對(duì)象在此事并沒(méi)有真正進(jìn)行數(shù)據(jù)庫(kù)查詢,只有在頁(yè)面真正讀取時(shí)才會(huì)讀取數(shù)據(jù)庫(kù)。這是它叫做Lasy的原因。此處借鑒了Django的實(shí)現(xiàn)機(jī)制。
          

          查詢的limit語(yǔ)句

           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","本草")
          查找所有名字中包含本草的書。返回一個(gè)LasyList
          

          排序

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

          級(jí)聯(lián)查詢

          因?yàn)檫@些上面的過(guò)濾器函數(shù)全部都會(huì)返回一個(gè)LasyList對(duì)象, 所以可以采用級(jí)聯(lián)的方式進(jìn)行復(fù)雜查詢。如:

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

          這個(gè)例子相當(dāng)于執(zhí)行了如下SQL語(yǔ)句:

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

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

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

          使用原始的sql獲取的List中的模型將和數(shù)據(jù)庫(kù)表沒(méi)有關(guān)聯(lián)。

          Model的相關(guān)功能

          model 是一個(gè)繼承自Map<String,Object> 的類,所以對(duì)于

          Model m;

          你可以在網(wǎng)頁(yè)中使用${m.name}的方式來(lái)訪問(wèn)它的name鍵對(duì)應(yīng)的值。相當(dāng)于m.get("name")

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

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

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

          訪問(wèn)所有的鍵值(即DAO對(duì)象的所有屬性)

           model.keySet();
          

          訪問(wèn)某一個(gè)屬性的值

           model.get(key)
          

          設(shè)置某一個(gè)屬性的值

          多 ASP.NET 開(kāi)發(fā)人員開(kāi)始接觸 MVC,都認(rèn)為 MVC 與 ASP.NET 完全沒(méi)有關(guān)系,是一個(gè)全新的 Web 開(kāi)發(fā)。

          事實(shí)上 ASP.NET 是創(chuàng)建 WEB 應(yīng)用的框架,而 MVC 是一種能夠用更好的方法來(lái)組織并管理代碼的體系,所以可以稱之為 ASP.NET MVC。

          因此,我們可以將原來(lái)的 ASP.NET 稱為 ASP.NET Webforms,新的 MVC 稱為 ASP.NET MVC

          ASP.NET Webforms

          ASP.NET 在過(guò)去的十幾年里,已經(jīng)服務(wù)并成功實(shí)現(xiàn)Web 應(yīng)用的開(kāi)發(fā)。那么,我們先了解一下為什么ASP.NET能夠如此流行,并成功應(yīng)用?

          微軟編程語(yǔ)言從 VB 開(kāi)始就能夠成為流行并廣泛應(yīng)用,都源于其提供的強(qiáng)大的 Visual studio 能夠進(jìn)行可視化的編程,實(shí)現(xiàn)快速開(kāi)發(fā)。

          使用 VS 時(shí),開(kāi)發(fā)人員能夠通過(guò)拖拽 UI 元素,并在后臺(tái)自動(dòng)生成這些界面的代碼,稱為后臺(tái)代碼。在后臺(tái)代碼中,開(kāi)發(fā)人員可以添加操作這些UI元素的邏輯代碼。

          因此微軟的可視化 RAD 架構(gòu)體系有兩方面組成,一方面是 UI,一方面是后臺(tái)代碼。

          ASP.NET WebForms 存在的問(wèn)題

          • 響應(yīng)時(shí)間

          如圖所示,每一次 WebForms 請(qǐng)求都有轉(zhuǎn)換邏輯,運(yùn)行并轉(zhuǎn)換服務(wù)器控件為 HTML 輸出。如果頁(yè)面使用表格,樹(shù)形控件等復(fù)雜控件,轉(zhuǎn)換就會(huì)變得很糟糕,HTML 輸出也是非常復(fù)雜的。由于這些不必要的轉(zhuǎn)換從而增加了響應(yīng)時(shí)間。上圖是 ASP.Net MVC 和 Webforms 的響應(yīng)時(shí)間對(duì)比,我們會(huì)發(fā)現(xiàn) Webforms 的響應(yīng)時(shí)間是 MVC 的兩倍左右。

          • 帶寬消耗

          ASP.NET 開(kāi)發(fā)人員都非常熟悉 Viewstates,因?yàn)樗軌蜃詣?dòng)保存 post 返回的狀態(tài),減少開(kāi)發(fā)時(shí)間。但是這種開(kāi)發(fā)時(shí)間的減少會(huì)帶來(lái)巨大的消耗,Viewstate增加了頁(yè)面的大小。

          從上圖中,我們可以看到與 MVC 對(duì)比,Viewstate 增加了兩倍的頁(yè)面存儲(chǔ)。

          MVC是怎么彌補(bǔ)這些問(wèn)題的?

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

          不使用服務(wù)器控件,直接編寫 HTML 代碼,并且將后臺(tái)代碼遷移到獨(dú)立的類庫(kù)中,是 MVC 解決 Webforms 問(wèn)題的方法。

          直接編寫HTML代碼的好處在于,web設(shè)計(jì)者可以與開(kāi)發(fā)人員緊密合作及時(shí)溝通,設(shè)計(jì)人員也可以使用他們喜愛(ài)的設(shè)計(jì)工具來(lái)設(shè)計(jì)HTML代碼。

          將后臺(tái)代碼遷移到獨(dú)立的簡(jiǎn)單的類庫(kù),執(zhí)行效率也會(huì)大大提高。

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

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

          敬請(qǐng)期待!

          相關(guān)開(kāi)發(fā)工具

          要進(jìn)行 ASP.ET MVC 的開(kāi)發(fā),不但需要具備 MVC 的知識(shí),還需要高效的工具來(lái)幫助開(kāi)發(fā)。

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

          快人一步,免費(fèi)試用

          如果您想試用 ComponentOne Studio ASP.NET MVC,請(qǐng)聯(lián)系我們:

          微信:GrapeCityDT

          郵件:marketing.xa@grapecity.com

          官網(wǎng):www.gcpowertools.com.cn

          關(guān)于葡萄城控件

          葡萄城是一家跨國(guó)軟件研發(fā)集團(tuán),專注控件領(lǐng)域近30年,是全球最大的控件提供商,也是微軟認(rèn)證的金牌合作伙伴

          下文章來(lái)源于非正式解決方案 ,作者winlion

          非正式解決方案

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

          MVC 應(yīng)用一般結(jié)構(gòu)

          目錄結(jié)構(gòu)說(shuō)明如下

          名稱內(nèi)容model模型層目錄,類比Java 中的entityview視圖層,存放所有templete模板ctrl控制器層, 存放全部控制器service服務(wù)層,類比Java里面的servicehtml一些靜態(tài)資源頁(yè)面util核心工具包,Md5加密,返回?cái)?shù)據(jù)封裝等asset靜態(tài)資源目錄,存放js/css/image等args封裝全部請(qǐng)求參數(shù)對(duì)象mnt上傳文件的存放目錄app.dev.conf開(kāi)發(fā)環(huán)境配置文件app.prod.conf生產(chǎn)環(huán)境配置文件start.sh/start.bat啟動(dòng)腳本build.sh/build.bat打包腳本main.go主應(yīng)用程序文件

          主程序結(jié)構(gòu)

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

          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)
          
              //初始化數(shù)據(jù)庫(kù)
              dbmap := util.GetSec("database")
              service.InitDb(dbmap)
          
              //注冊(cè)funcmap
              ctrl.RegisterFuncMap()
              //控制器
              ctrl.RegisterCtrl()
              //靜態(tài)資源文件
              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"])
              //打開(kāi)服務(wù)器監(jiān)聽(tīng)http
              err = http.ListenAndServe(appcfg["addr"], nil)
              if err!=nil{
                  fmt.Println(err.Error())
                  log.Println(err.Error())
              }
          }

          配置文件

          3.1 配置文件解析

          使用配置文件開(kāi)發(fā)包,如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)
          }

          調(diào)用案列

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

          3.2 監(jiān)聽(tīng)配置文件并自動(dòng)刷新配置

          使用github.com/fsnotify/fsnotify包,裝時(shí)候注意,一個(gè)函數(shù)里面如果有參數(shù)共享,應(yīng)該放到一個(gè)攜程里。

          //監(jiān)聽(tīng)文件
          func WatchConfig(filepath ...string) {
              //創(chuàng)建一個(gè)監(jiān)控對(duì)象
              go func() {
                  watch, err := fsnotify.NewWatcher()
                  if err != nil {
                      log.Fatal(err)
                  }
                  defer watch.Close()
                  //添加要監(jiān)控的對(duì)象,文件或文件夾
                  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 {
                                  //監(jiān)聽(tīng)到文件系統(tǒng)使用加載新東西
                                  ReloadAllCfg()
                              }
                              fmt.Println(ev.Op, ev.Name)
                          }
                      case err := <-watch.Errors:
                          {
                              log.Println("error : ", err)
                              return
                          }
                      }
                  }
              }()
          }

          fsnotify 支持很多種事件監(jiān)聽(tīng),一般在 Write 事件刷新配置文件

          //判斷事件發(fā)生的類型,如下5種
          // Create 創(chuàng)建
          // Write 寫入
          // Remove 刪除
          // Rename 重命名
          // Chmod 修改權(quán)限
          if ev.Op&fsnotify.Create == fsnotify.Create {
              log.Println("創(chuàng)建文件 : ", 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("修改權(quán)限 : ", ev.Name);
          }

          3.3 區(qū)分系統(tǒng)級(jí)配置和用戶級(jí)配置

          系統(tǒng)級(jí)配置參數(shù) 假設(shè)修改了會(huì)影響整個(gè)應(yīng)用,需要另起服務(wù)的我們稱之為系統(tǒng)級(jí)配置,修改了參數(shù),往需要進(jìn)行相應(yīng)的操作。如修改了數(shù)據(jù)庫(kù)連接地址,需要重置數(shù)據(jù)庫(kù)連接操作。修改了應(yīng)用服務(wù)器端口,則需要重啟應(yīng)用服務(wù)。

          用戶級(jí)配置參數(shù) 如微信公眾號(hào) appsecret,每次調(diào)用的時(shí)候會(huì)從配置中獲取,因此只需要重新加載數(shù)據(jù)即可。

          3.4 配置內(nèi)容緩存

          需要將配置文件內(nèi)容緩存到 map 中。 需要考慮到 map 的并發(fā)操作。

          實(shí)體層(model)寫法

          4.1 狀態(tài)變量定義在實(shí)體文件內(nèi)部

          //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 定義在同一個(gè)文件中,便于閱讀。

          4.2 為實(shí)體添加 tag

          實(shí)體和表結(jié)構(gòu)對(duì)應(yīng),一定要定義 Form 和 Json tag。這樣可以提高系統(tǒng)適用性,為什么呢?因?yàn)榭梢赃m配前端以各種 Content-Type 提交數(shù)據(jù)如。后端統(tǒng)一用該實(shí)體接收數(shù)據(jù)即可。

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

          4.3 統(tǒng)一 tag 參數(shù)命名方式

          約定統(tǒng)一使用駝峰式或者下劃線標(biāo)記。如下,建議使用駝峰式。

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

          模板(view)層配置

          如下幾點(diǎn)需要注意

          5.1 模板文件和模板名稱關(guān)聯(lián)

          關(guān)聯(lián)便于代碼管理和閱讀。模板位置 /view/demo/index.html,模板內(nèi)容如下。

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

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

          http://localhost/demo/index

          5.2 一個(gè)函數(shù)搞定全部 Html 請(qǐng)求類的頁(yè)面

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

          //ctrl/base.go
          func RegisterPage(isDev bool) {
              //初始化一個(gè)全局的模板變量
              GlobTemplete := template.New("root")
              //把一些函數(shù)添加進(jìn)去,這樣頁(yè)面里面就可以使用函數(shù)啦
              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  這個(gè)url 和對(duì)應(yīng)的處理函數(shù)之間的關(guān)系
                  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)
              //
          }

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

          http://localhost/demo/index

          5.3 添加調(diào)試模式支持

          為什么要添加調(diào)試模式支持?因?yàn)檎{(diào)試模式狀態(tài)下,我們修改來(lái)了頁(yè)面模板,需要立即看到頁(yè)面內(nèi)容,而不需要重啟應(yīng)用。核心代碼如下,即在調(diào)試模式狀態(tài)下,每次請(qǐng)求都重新解析模板。

          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
                              }
                  }
              }

          由上可見(jiàn),調(diào)試模式效率是非常低的,我們不應(yīng)該在生產(chǎn)環(huán)境采用調(diào)試模式。

          5.4 添加數(shù)據(jù)注入

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

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

          前端模板調(diào)用代碼如下

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

          返回結(jié)果

          Hello,Modal ,User id is xxx

          5.5 在頁(yè)面使用函數(shù)

          在 RegisterPage 方法內(nèi)定義一個(gè) 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()
              //
          }

          前端模板調(diào)用代碼如下

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

          返回結(jié)果

          Hello,Modal ,hello func retutn hello
          Hello,Modal ,hello func retutn hello2參數(shù)2

          5.6 多多使用 templete 方法

          主要使用場(chǎng)景是分角色菜單,用戶

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

          其他頁(yè)面統(tǒng)一調(diào)用,進(jìn)行角色菜單等控制。

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

          控制器層(ctrl)

          控制器層主要處理對(duì)外接口

          6.1 一個(gè)典型的控制器結(jié)構(gòu)**

          import (
              ///很多包  
              )
          //定義個(gè)一個(gè)控制器對(duì)象
          type UserCtrl struct {
          }
          //將url和處理函數(shù)綁定
          func  ( ctrl *UserCtrl)Router(){
              Router("/user/login",ctrl.authwithcode)
          }
          //定義用戶處理函數(shù)
          var userService service.UserService
          
          //用戶通過(guò)小程序登錄處理函數(shù),輸入code
          //通過(guò)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 數(shù)據(jù)和參數(shù)綁定

          所有參數(shù)都需要可預(yù)期在一個(gè)結(jié)構(gòu)體里面。這樣整個(gè)系統(tǒng)編程將變得非常簡(jiǎn)單。在上 面函數(shù)中,通過(guò)如下代碼實(shí)現(xiàn)參數(shù)綁定

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

          其中 args.AuthArg 對(duì)象定義如下

          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 作用是存放一切請(qǐng)求參數(shù)。每個(gè)業(yè)務(wù)都建議定義一個(gè) arg。每個(gè) arg 都有一個(gè)公共屬性 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"`
          }
          //獲得分頁(yè)大小
          func (p*PageArg) GetPageSize() int{
              if p.Pagesize==0{
                  return 100
              }else{
                  return p.Pagesize
              }
          
          }
          //獲得分頁(yè)當(dāng)前第幾頁(yè)
          func (p*PageArg) GetPageFrom() int{
              if p.Pagefrom<0{
                  return 0
              }else{
                  return p.Pagefrom
              }
          }
          //獲得排序 ID DESC ,前端傳遞參數(shù) 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 參數(shù)綁定核心函數(shù) util.Bind

          大體結(jié)構(gòu)如下

          func Bind(req *http.Request,obj interface{}) error{
              contentType := req.Header.Get("Content-Type")
              //如果是簡(jiǎn)單的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)
              }
              //可以自行擴(kuò)展xml
              if strings.Contains(strings.ToLower(contentType),"text/xml"){
                  return   BindXml(req,obj)
              }
              return errors.New("當(dāng)前方法暫不支持")
          }

          以 BindJson 為例子

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

          可能大家更關(guān)心 BindForm,篇幅太長(zhǎng),大家可以移步

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

          6.4 封裝返回函數(shù)

          一般封裝一個(gè)底層 JSON,然后根據(jù)返回成功或失敗響應(yīng)對(duì)應(yīng)的 code

          /util/resp.go
          package util
          
          import (
              "net/http"
          
              "encoding/json"
          
              "fmt")
          //定義個(gè)通用的結(jié)構(gòu)體用于裝載返回的數(shù)據(jù)
          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)
          }
          
          //當(dāng)操作成功返回Ok,
          func RespOk(w http.ResponseWriter,data interface{}){
          
              RespJson(w,H{
                  Code:http.StatusOK,
                  Data:data,
              })
          }
          
          //當(dāng)操作失敗返回Error,
          func RespFail(w http.ResponseWriter,msg string){
          
              RespJson(w,H{
                  Code:http.StatusNotFound,
                  Msg :msg,
              })
          }

          服務(wù)層(service)實(shí)現(xiàn)

          7.1 服務(wù)層一般結(jié)構(gòu)

          以訂單管理為例

          package service
          
          import (
          
          
              "../model"
          
              "../args"
          
              "github.com/go-xorm/xorm"
                  "log"
              "github.com/pkg/errors"
              "encoding/json"
              "time"
          )
          
          type OrderService struct {
          
          }
          //構(gòu)造條件
          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
          
          }
          //查詢某一個(gè)
          func (service *OrderService) Create(order model.Order) (model.Order){
          
              _,err := DBengin.InsertOne(&order)
              if err!=nil{
                  log.Println(err.Error())
              }
              return order
          
          }

          一般需要構(gòu)建如下幾類函數(shù),具體隨業(yè)務(wù)而定

          名稱內(nèi)容Create添加Update修改Search搜索,返回列表Find返回某一個(gè)對(duì)象Delete刪除buildCond構(gòu)建條件函數(shù)Count符合某一條件的記錄數(shù)目

          7.2 條件統(tǒng)一管理

          我們可以用類似于如下函數(shù)來(lái)統(tǒng)一管理查詢條件,該函數(shù)輸出參數(shù),輸出一個(gè) session。

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

          條件規(guī)范化可以讓應(yīng)用更靈活,讓業(yè)務(wù)更清晰。如果不規(guī)范,樓主曾經(jīng)經(jīng)歷的教訓(xùn)可能也會(huì)撞上你。

          7.3 連接數(shù)據(jù)庫(kù)

          數(shù)據(jù)庫(kù)建議使用 xorm。 在 server 包目錄下新建 init.go 在其中實(shí)現(xiàn)數(shù)據(jù)庫(kù)的初始化

          //定義全局變量DBengin 
          var DBengin *xorm.Engine
          //定義初始化函數(shù)InitDb,dbmap是數(shù)據(jù)庫(kù)配置參數(shù),=來(lái)自于外部參數(shù)
          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 中初始化數(shù)據(jù)庫(kù)

          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 路由功能怎實(shí)現(xiàn)

          在每一個(gè) ctrl 中都定義一個(gè) Router 函數(shù)

          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)
          }

          這些函數(shù)調(diào)用了 Router 方法,該方法本質(zhì)上是對(duì) 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)
              })
          }

          定義路由注冊(cè)函數(shù)

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

          注冊(cè)路由 在 main.go 中完成路由注冊(cè)

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

          8.2 支持 Post/Get/Any

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

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

          接著定義路由綁定函數(shù)

          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 支持正則

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

          //這一個(gè)專門存uri和處理函數(shù)之間關(guān)系的字典
          var RegExRouterMap map[string]func(w http.ResponseWriter, req *http.Request) = make(map[string]func(w http.ResponseWriter, req *http.Request), 0)
          
          //這是一個(gè)存儲(chǔ)Uri和對(duì)應(yīng)正則表達(dá)式的字典以后就不要編譯啦。
          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
              //形成映射關(guān)系
              RegexpMatchMap[pantern],_ = regexp.Compile(pantern)
          }
          //沒(méi)有找到需要一個(gè)默認(rèn)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)
              })
          }

          在路由注冊(cè)中初始化

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

          現(xiàn)在我們可以在控制器頁(yè)面通過(guò) 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)
          }

          客戶端請(qǐng)求

          http://localhost/d/12345678977

          響應(yīng)數(shù)據(jù)

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

          8.4 404 沒(méi)找到

          在如上所示中定義了 notfound 函數(shù),當(dāng)沒(méi)有任何一個(gè)匹配對(duì)象時(shí)候,進(jìn)入這個(gè)函數(shù)。

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

          8.5 實(shí)現(xiàn)攔截器功能

          我們可以在 Router 方法里面實(shí)現(xiàn)攔截器功能,主要用來(lái)做鑒權(quán),日志記錄等

          func Router(pantern string, fun func(w http.ResponseWriter, req *http.Request)) {
              http.HandleFunc(pantern, func(w http.ResponseWriter, req *http.Request) {
                  //包含某些關(guān)鍵字的不需要鑒權(quán)啦
                  if strings.Contains(req.RequestURI, "/test/") {
                      fun(w, req)
                  } else {
                      //否則判斷一下,如果從xxxplatform平臺(tái)來(lái)的不需要鑒權(quán),直接往下走
                      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
                      }
                      //否則這要鑒權(quán),通過(guò)就直接往下走
                      if userService.Authorization(ticket, clientid) {
                          fun(w, req)
                      } else {
                          //沒(méi)通過(guò)返回木有權(quán)限。
                          util.RespFail(w, "沒(méi)有權(quán)限")
                      }
          
                  }
          
              })
              fmt.Printf("register patern %s ==> %s\n", pantern, pantern)
          }

          8.6 提升路由性能

          我主要在 Router 函數(shù)上下功夫,一種可用的設(shè)計(jì)是利用攜程,如下

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

          需要注意的是要先把數(shù)據(jù) copy 出來(lái),然后才能利用攜程,否則 fun 函數(shù)里面取出的數(shù)據(jù)是空的。

          其他及源代碼獲取

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


          主站蜘蛛池模板: 无码少妇一区二区浪潮av| 成人无码AV一区二区| 国产一区二区电影| 亚洲午夜一区二区电影院| 视频一区在线免费观看| 伦精品一区二区三区视频| 国产精品区一区二区三在线播放| 中文激情在线一区二区| 久久久91精品国产一区二区三区| 日本丰满少妇一区二区三区| 亚洲Av永久无码精品一区二区| 熟女少妇精品一区二区| 亚洲国产综合无码一区二区二三区| 国产精品成人一区二区三区| 竹菊影视欧美日韩一区二区三区四区五区 | 中文字幕精品一区二区2021年| 伊人久久精品无码av一区| 亚洲国产精品一区二区九九| 国产一区二区在线观看视频| 日韩国产免费一区二区三区| 97精品国产一区二区三区| 亚洲va乱码一区二区三区| AA区一区二区三无码精片| 无码人妻精品一区二区三区在线 | 插我一区二区在线观看| 国产一区在线观看免费| 国产伦精品一区二区三区在线观看| 九九无码人妻一区二区三区| 一本一道波多野结衣一区| 国产午夜精品片一区二区三区| 3d动漫精品啪啪一区二区中| 亚洲综合一区二区| 日韩一区二区a片免费观看| 精品无码人妻一区二区三区18| 国产日韩一区二区三区在线播放 | 久久精品视频一区| 亚洲蜜芽在线精品一区| 日韩av片无码一区二区不卡电影| 久久一区二区三区精华液使用方法| 国模极品一区二区三区| 日本一区二区在线不卡|