整合營銷服務商

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

          免費咨詢熱線:

          自定義代碼生成器(上)

          概述

          1.1 介紹

          在項目開發過程中,有很多業務模塊的代碼是具有一定規律性的,例如controller控制器、service接口、service實現類、mapper接口、model實體類等等,這部分代碼可以使用代碼生成器生成,我們就可以將更多的時間放在業務邏輯上。

          傳統的開發步驟:

          創建數據庫和表 根據表設計實體類 ? 編寫mapper接口 ? 編寫service接口和實現類 ? 編寫controller控制器 ? 編寫前端頁面 ? 前后端聯調

          基于代碼生成器開發步驟:

          創建數據庫和表 ? 使用代碼生成器生成實體類、mapper、service、controller、前端頁面 ? 將生成好的代碼拷貝到項目中并做調整 ? 前后端聯調

          我們只需要知道數據庫和表相關信息,就可以結合模版生成各個模塊的代碼,減少了很多重復工作,也減少出錯概率,提高效率。

          1.2 實現思路

          (1)需要對數據庫表解析獲取到元數據,包含表字段名稱、字段類型等等

          (2)將通用的代碼編寫成模版文件,部分數據需使用占位符替換

          (3)將元數據和模版文件結合,使用一些模版引擎工具(例如freemarker)即可生成源代碼文件

          2 Freemarker

          2.1 介紹

          FreeMarker 是一款 模板引擎: 即一種基于模板和要改變的數據, 并用來生成輸出文本(HTML網頁,電子郵件,配置文件,源代碼等)的通用工具。 它不是面向最終用戶的,而是一個Java類庫,是一款程序員可以嵌入他們所開發產品的組件。

          模板編寫為FreeMarker Template Language (FTL)。它是簡單的,專用的語言, 在模板中,你可以專注于如何展現數據, 而在模板之外可以專注于要展示什么數據。

          2.2 應用場景

          (1)動態頁面

          freemarker可以作為springmvc一種視圖格式,像jsp一樣被瀏覽器訪問。

          (2)頁面靜態化

          對于一些內容比較多,更新頻率很小,訪問又很頻繁的頁面,可以使用freemarker靜態化,減少DB的壓力,提高頁面打開速度。

          (3)代碼生成器

          根據配置生成頁面和代碼,減少重復工作,提高開發效率。

          2.3 快速入門

          (1)創建freemarker-demo模塊,并導入相關依賴

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
          
              <groupId>com.itheima</groupId>
              <artifactId>freemarker-demo</artifactId>
              <version>1.0-SNAPSHOT</version>
          
              <parent>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-parent</artifactId>
                  <version>2.3.1.RELEASE</version>
              </parent>
          
              <properties>
                  <maven.compiler.source>8</maven.compiler.source>
                  <maven.compiler.target>8</maven.compiler.target>
              </properties>
          
              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-test</artifactId>
                  </dependency>
                  <!-- freemarker -->
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-freemarker</artifactId>
                  </dependency>
                  <!-- lombok -->
                  <dependency>
                      <groupId>org.projectlombok</groupId>
                      <artifactId>lombok</artifactId>
                  </dependency>
              </dependencies>
          </project>

          (2)application.yml相關配置

          server:
            port: 8881 #服務端口
          spring:
            application:
              name: freemarker-demo #指定服務名
            freemarker:
              cache: false #關閉模板緩存,方便測試
              settings:
                template_update_delay: 0 #檢查模板更新延遲時間,設置為0表示立即檢查,如果時間大于0會有緩存不方便進行模板測試
              suffix: .ftl #指定Freemarker模板文件的后綴名

          (3)創建啟動類

          package com.heima.freemarker;
          
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          
          @SpringBootApplication
          public class FreemarkerDemotApplication {
              public static void main(String[] args) {
                  SpringApplication.run(FreemarkerDemotApplication.class,args);
              }
          }

          (4)創建Student模型類

          package com.itheima.freemarker.entity;
          
          import lombok.AllArgsConstructor;
          import lombok.Data;
          import lombok.NoArgsConstructor;
          
          import java.util.Date;
          
          @Data
          @NoArgsConstructor
          @AllArgsConstructor
          public class Student {
              private Integer id;
              private String name;//姓名
              private Integer age;//年齡
              private Date birthday;//生日
              private Float money;//錢包
          }

          (5)創建StudentController

          package com.itheima.freemarker.controller;
          
          import com.itheima.freemarker.entity.Student;
          import org.springframework.stereotype.Controller;
          import org.springframework.ui.Model;
          import org.springframework.web.bind.annotation.GetMapping;
          import org.springframework.web.bind.annotation.RequestMapping;
          
          import java.util.Date;
          
          @Controller
          @RequestMapping("student")
          public class StudentController {
          
              @GetMapping("index")
              public String index(Model model){
                  //1.純文本形式的參數
                  model.addAttribute("name", "Freemarker");
          
                  //2.實體類相關的參數
                  Student student = new Student();
                  student.setName("黑馬");
                  student.setAge(18);
                  model.addAttribute("stu", student);
          
                  return "01-index";
              }
          }

          (6)在resources/templates下創建01-index.ftl模版文件

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>首頁</title>
          </head>
          <body>
          <b>普通文本 String 展示:</b><br/>
          Hello ${name} <br>
          
          <hr>
          <b>對象Student中的數據展示:</b><br/>
          姓名:${stu.name}<br/>
          年齡:${stu.age}
          <hr>
          </body>
          </html>

          (7)測試

          瀏覽器訪問 http://localhost:8881/student/index

          效果如下

          2.4 模版

          2.4.1 基礎語法種類

          (1)注釋,即<#-- -->,介于其之間的內容會被freemarker忽略

          <#--我是一個freemarker注釋-->

          (2)插值(Interpolation):即 ${..} 部分,freemarker會用真實的值代替${..}

          Hello ${name}

          (3)FTL指令:和HTML標記類似,名字前加#予以區分,Freemarker會解析標簽中的表達式或邏輯。

          <# >FTL指令</#> 

          (4)文本,僅文本信息,這些不是freemarker的注釋、插值、FTL指令的內容會被freemarker忽略解析,直接輸出內容。

          <#--freemarker中的普通文本-->
          我是一個普通的文本

          2.4.2 if指令

          if 指令即判斷指令,是常用的FTL指令,freemarker在解析時遇到if會進行判斷,條件為真則輸出if中間的內容,否則跳過內容不再輸出。

          格式如下

          <#if condition>
            ....
          <#elseif condition2>
            ...
          <#elseif condition3>   
            ...
          <#else>
            ...
          </#if>

          需求:根據年齡輸出所處的年齡段

          童年:0歲—6歲(周歲,下同) 少年:7歲—17歲 青年:18歲—40歲 中年:41—65歲 老年:66歲以后

          實例代碼:

          (1)在01-index.ftl添加如下代碼

          <#if stu.age <= 6>
          童年
          <#elseif stu.age <= 17>
          少年
          <#elseif stu.age <= 40>   
          青年
          <#elseif stu.age <= 65>   
          中年
          <#else>
          老年
          </#if>

          (2)測試

          瀏覽器訪問http://localhost:8881/student/index

          效果如下

          2.4.3 list指令

          list指令時一個迭代輸出指令,用于迭代輸出數據模型中的集合

          格式如下

          <#list items as item>
              ${item_index + 1}------${item}-----<#if item_has_next>,</#if>
          </#list>

          迭代集合對象時,包括兩個特殊的循環變量: (1)item_index:當前變量的索引值。 (2)item_has_next:是否存在下一個對象

          item_index 和 item_has_nex 中的item為<#list items as item> 中as后面的臨時變量

          需求:遍歷學生集合,輸出序號,學生id,姓名,所處的年齡段,是否最后一條數據

          (1)在StudentController中增加方法

          @GetMapping("list")
          public String list(Model model) throws ParseException {
              List<Student> list = new ArrayList<>();
          
              list.add(new Student(1001,"張飛",15, null, 1000.11F));
              list.add(new Student(1002,"劉備",28, null, 5000.3F));
              list.add(new Student(1003,"關羽",45, null, 9000.63F));
              list.add(new Student(1004,"諸葛亮",62, null, 10000.99F));
              list.add(new Student(1005,"成吉思汗",75, null, 16000.66F));
              model.addAttribute("stus",list);
          
              return "02-list";
          }

          (2)在resources/templates目錄下創建02-list.ftl模版

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>列表頁面</title>
              <style>
                  table{
                      border-spacing: 0;/*把單元格間隙設置為0*/
                      border-collapse: collapse;/*設置單元格的邊框合并為1*/
                  }
                  td{
                      border:1px solid #ACBED1;
                      text-align: center;
                  }
              </style>
          </head>
          <body>
          
          <table>
              <tr>
                  <td>序號</td>
                  <td>id</td>
                  <td>姓名</td>
                  <td>所處的年齡段</td>
                  <td>生日</td>
                  <td>錢包</td>
                  <td>是否最后一條數據</td>
              </tr>
              <#list stus as stu >
                  <tr>
                      <td>${stu_index + 1}</td>
                      <td>${stu.id}</td>
                      <td>${stu.name}</td>
                      <td>
                          <#if stu.age <= 6>
                              童年
                          <#elseif stu.age <= 17>
                              少年
                          <#elseif stu.age <= 40>   
                              青年
                          <#elseif stu.age <= 65>   
                              中年
                          <#else>
                              老年
                          </#if>
                      </td>
                      <td></td>
                      <td>${stu.money}</td>
                      <td>
                          <#if stu_has_next>
                          否
                          <#else>
                          是
                          </#if>
                      </td>
                  </tr>
              </#list>
          </table>
          
          <hr>
          </body>
          </html>

          (2)測試

          瀏覽器訪問http://localhost:8881/student/list

          效果如下

          2.4.4 include指令

          include指令的作用類似于JSP的包含指令,用于包含指定頁,include指令的語法格式如下

          <#include filename [options]></#include>

          (1)filename:該參數指定被包含的模板文件 (2)options:該參數可以省略,指定包含時的選項,包含encoding和parse兩個選項,encoding 指定包含頁面時所使用的解碼集,而parse指定被包含是否作為FTL文件來解析。如果省略了parse選項值,則該選項值默認是true

          需求:"早上好,尊敬的 某某 用戶!" 這句話在很多頁面都有用到,請合理設計!

          (1)在resources/templates目錄下創建00-head.ftl模版,內容如下

          早上好,尊敬的 ${name} 用戶!

          (2)在resources/templates目錄下創建03-include.ftl模版,使用include引入00-head.ftl模版,內容如下

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>詳情頁</title>
          </head>
          <body>
          
          <#include "00-head.ftl" />
          <br>
          歡迎來到黑馬程序員。
          
          </body>
          </html>

          (3)在StudentController中增加方法

          @GetMapping("include")
          public String include(Model model) throws ParseException {
          model.addAttribute("name", "黑馬");
          return "03-include";
          }

          (4)測試

          瀏覽器訪問http://localhost:8881/student/include

          效果如下

          2.4.5 assign指令

          它用于為該模板頁面創建或替換一個頂層變量

          <#assign name = "zhangsan" />

          2.4.6 運算符

          (1)算數運算符

          FreeMarker表達式中完全支持算術運算,FreeMarker支持的算術運算符包括:

          • 加法: +
          • 減法: -
          • 乘法: *
          • 除法: /
          • 求模 (求余): %

          (2)比較運算符

          • =或者==:判斷兩個值是否相等.
          • !=:判斷兩個值是否不等.
          • >或者gt:判斷左邊值是否大于右邊值
          • >=或者gte:判斷左邊值是否大于等于右邊值
          • <或者lt:判斷左邊值是否小于右邊值
          • <=或者lte:判斷左邊值是否小于等于右邊值

          比較運算符注意

          • =!=可以用于字符串、數值和日期來比較是否相等
          • =!=兩邊必須是相同類型的值,否則會產生錯誤
          • 字符串 "x""x ""X"比較是不等的.因為FreeMarker是精確比較
          • 其它的運行符可以作用于數字和日期,但不能作用于字符串
          • 使用gt等字母運算符代替>會有更好的效果,因為 FreeMarker會把>解釋成FTL標簽的結束字符
          • 可以使用括號來避免這種情況,如:<#if (x>y)>

          (3)邏輯運算符

          • 邏輯與:&&
          • 邏輯或:||
          • 邏輯非:!

          邏輯運算符只能作用于布爾值,否則將產生錯誤 。

          2.4.7 空值處理

          (1)缺失變量默認值使用 “!”

          • 使用!要以指定一個默認值,當變量為空時顯示默認值
          • 例: ${name!''}表示如果name為空顯示空字符串。
          • 如果是嵌套對象則建議使用()括起來
          • 例: ${(stu.bestFriend.name)!''}表示,如果stu或bestFriend或name為空默認顯示空字符串。

          (2)判斷某變量是否存在使用 “??”

          用法為:variable??,如果該變量存在,返回true,否則返回false

          例:為防止stus為空報錯可以加上判斷如下:

              <#if stus??>
                  <#list stus as stu>
                      ......
                  </#list>
              </#if>

          2.4.8 內建函數

          內建函數語法格式: 變量+?+函數名稱

          (1)求集合的大小

          ${集合名?size}

          (2)日期格式化

          顯示年月日: ${today?date} 顯示時分秒:${today?time} 顯示日期+時間:${today?datetime} 自定義格式化: ${today?string("yyyy年MM月")}

          (3)內建函數c

          model.addAttribute("point", 102920122);

          point是數字型,使用${point}會顯示這個數字的值,每三位使用逗號分隔。

          如果不想顯示為每三位分隔的數字,可以使用c函數將數字型轉成字符串輸出

          ${point?c}

          (4)將json字符串轉成對象

          一個例子:

          其中用到了 assign標簽,assign的作用是定義一個變量。

          <#assign text="{'bank':'工商銀行','account':'10101920201920212'}" />
          <#assign data=text?eval />
          開戶行:${data.bank}  賬號:${data.account}

          (5)常見內建函數匯總

          ?html:html字符轉義
          ?cap_first: 字符串的第一個字母變為大寫形式
          ?lower_case :字符串的小寫形式
          ?upper_case :字符串的大寫形式
          ?trim:去掉字符串首尾的空格
          ?substring(from,to):截字符串  from是第一個字符的開始索引,to最后一個字符之后的位置索引,當to為空時,默認的是字符串的長度
          ?lenth: 取長度
          ?size: 序列中元素的個數
          ?int: 數字的整數部分(比如 -1.9?int 就是 -1)
          ?replace(param1,param2):字符串替換  param1是匹配的字符串 param2是將匹配的字符替換成指定字符

          內建函數測試demo1

          (1)在StudentController新增方法:

          @GetMapping("innerFunc")
          public String testInnerFunc(Model model) {
              //1.1 小強對象模型數據
              Student stu1 = new Student();
              stu1.setName("小強");
              stu1.setAge(18);
              stu1.setMoney(1000.86f);
              stu1.setBirthday(new Date());
              //1.2 小紅對象模型數據
              Student stu2 = new Student();
              stu2.setName("小紅");
              stu2.setMoney(200.1f);
              stu2.setAge(19);
              //1.3 將兩個對象模型數據存放到List集合中
              List<Student> stus = new ArrayList<>();
              stus.add(stu1);
              stus.add(stu2);
              model.addAttribute("stus", stus);
              // 2.1 添加日期
              Date date = new Date();
              model.addAttribute("today", date);
              // 3.1 添加數值
              model.addAttribute("point", 102920122);
              return "04-innerFunc";
          }

          (2)在resources/templates目錄下創建04-innerFunc.ftl模版頁面:

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>inner Function</title>
          </head>
          <body>
          
              <b>獲得集合大小</b><br>
          
              集合大小:${stus?size}
              <hr>
          
          
              <b>獲得日期</b><br>
          
              顯示年月日: ${today?date}       <br>
          
              顯示時分秒:${today?time}<br>
          
              顯示日期+時間:${today?datetime}<br>
          
              自定義格式化:  ${today?string("yyyy年MM月")}<br>
          
              <hr>
          
              <b>內建函數C</b><br>
              沒有C函數顯示的數值:${point} <br>
          
              有C函數顯示的數值:${point?c}
          
              <hr>
          
              <b>聲明變量assign</b><br>
              <#assign text="{'bank':'工商銀行','account':'10101920201920212'}" />
              <#assign data=text?eval />
              開戶行:${data.bank}  賬號:${data.account}
          
          <hr>
          </body>
          </html>

          (3)測試

          瀏覽器訪問http://localhost:8881/student/innerFunc

          效果如下

          內建函數測試demo2

          需求:遍歷學生集合,顯示集合總條數,id不要逗號隔開,顯示學生的生日(只顯示年月日),錢包顯示整數并顯示單位,用戶姓名做脫敏處理(如果是兩個字第二個字顯示為星號,例如張三顯示為張*,如果大于兩個字,中間字顯示為星號,例如成吉思汗顯示為成*汗,諸葛亮顯示為諸*亮

          (1)修改StudentController中的list方法,

          @GetMapping("list")
          public String list(Model model) throws ParseException {
              DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
              List<Student> list = new ArrayList<>();
          
              list.add(new Student(1001,"張三",15, dateFormat.parse("2007-10-01 10:00:00"), 1000.11F));
              list.add(new Student(1002,"李四",28, dateFormat.parse("1994-10-01 10:00:00"), 5000.3F));
              list.add(new Student(1003,"王五",45, dateFormat.parse("1977-10-01 10:00:00"), 9000.63F));
              list.add(new Student(1004,"趙六",62, dateFormat.parse("1960-10-01 10:00:00"), 10000.99F));
              list.add(new Student(1005,"孫七",75, dateFormat.parse("1947-10-01 10:00:00"), 16000.66F));
              model.addAttribute("stus",list);
          
              return "02-list";
          }

          (2)修改02-list.ftl模版

          共${stus?size}條數據:輸出總條數

          stu.id后面加?c:id不需要逗號分割

          stu.birthday后面加?date:生日只輸出年月日

          stu.money后面加?int:金額取整

          姓名需要使用replace和substring函數處理

          完整內容如下

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>列表頁面</title>
              <style>
                  table{
                      border-spacing: 0;/*把單元格間隙設置為0*/
                      border-collapse: collapse;/*設置單元格的邊框合并為1*/
                  }
                  td{
                      border:1px solid #ACBED1;
                      text-align: center;
                  }
              </style>
          </head>
          <body>
          共${stus?size}條數據
          <table>
              <tr>
                  <td>序號</td>
                  <td>id</td>
                  <td>姓名</td>
                  <td>所處的年齡段</td>
                  <td>生日</td>
                  <td>錢包</td>
                  <td>是否最后一條數據</td>
              </tr>
              <#list stus as stu >
                  <tr>
                      <td>${stu_index + 1}</td>
                      <td>${stu.id?c}</td>
                      <td>
                          <#if stu.name?length=2>
                              ${stu.name?replace(stu.name?substring(1), "*")}
                          <#else>
                              ${stu.name?replace(stu.name?substring(1, stu.name?length-1), "*")}
                          </#if>
                      </td>
                      <td>
                          <#if stu.age <= 6>
                              童年
                          <#elseif stu.age <= 17>
                              少年
                          <#elseif stu.age <= 40>   
                              青年
                          <#elseif stu.age <= 65>   
                              中年
                          <#else>
                              老年
                          </#if>
                      </td>
                      <td>${stu.birthday?date}</td>
                      <td>${stu.money?int}元</td>
                      <td>
                          <#if stu_has_next>
                          否
                          <#else>
                          是
                          </#if>
                      </td>
                  </tr>
              </#list>
          </table>
          
          <hr>
          </body>
          </html>

          (3)測試

          瀏覽器訪問http://localhost:8881/student/list

          效果如下

          2.4.9 靜態化

          (1)springboot整合freemarker靜態化文件用法

          編寫springboot測試用例

          package com.itheima.test;
          
          import com.itheima.freemarker.FreemarkerDemoApplication;
          import com.itheima.freemarker.entity.Student;
          import freemarker.template.Configuration;
          import freemarker.template.Template;
          import freemarker.template.TemplateException;
          import org.junit.Test;
          import org.junit.runner.RunWith;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.test.context.SpringBootTest;
          import org.springframework.test.context.junit4.SpringRunner;
          
          import java.io.FileWriter;
          import java.io.IOException;
          import java.util.*;
          
          @SpringBootTest(classes = FreemarkerDemoApplication.class)
          @RunWith(SpringRunner.class)
          public class FreemarkerTest {
          
              //注入freemarker配置類
              @Autowired
              private Configuration configuration;
          
              @Test
              public void test() throws IOException, TemplateException {
                  Template template = configuration.getTemplate("04-innerFunc.ftl");
                  /**
                   * 靜態化并輸出到文件中   參數1:數據模型     參數2:文件輸出流
                   */
                  template.process(getData(), new FileWriter("d:/list.html"));
                  /**
                   * 靜態化并輸出到字節輸出流中
                   */
                  //StringWriter out = new StringWriter();
                  //template.process(getData(), out);
                  //System.out.println(out.toString());
              }
          
          
              private Map getData(){
          
                  Map<String,Object> map = new HashMap<>();
          
                  Student stu1 = new Student();
                  stu1.setName("小強");
                  stu1.setAge(18);
                  stu1.setMoney(1000.86f);
                  stu1.setBirthday(new Date());
          
                  //小紅對象模型數據
                  Student stu2 = new Student();
                  stu2.setName("小紅");
                  stu2.setMoney(200.1f);
                  stu2.setAge(19);
          
                  //將兩個對象模型數據存放到List集合中
                  List<Student> stus = new ArrayList<>();
                  stus.add(stu1);
                  stus.add(stu2);
          
                  //向model中存放List集合數據
                  map.put("stus",stus);
          
          
                  //map數據
                  Map<String,Student> stuMap = new HashMap<>();
                  stuMap.put("stu1",stu1);
                  stuMap.put("stu2",stu2);
          
                  map.put("stuMap",stuMap);
                  //日期
                  map.put("today",new Date());
          
                  //長數值
                  map.put("point",38473897438743L);
          
                  return map;
          
              }
          }

          (2)freemarker原生靜態化用法

          package com.itheima.freemarker.test;
          
          import com.itheima.freemarker.entity.Student;
          import freemarker.cache.FileTemplateLoader;
          import freemarker.cache.NullCacheStorage;
          import freemarker.template.Configuration;
          import freemarker.template.Template;
          import freemarker.template.TemplateException;
          import freemarker.template.TemplateExceptionHandler;
          
          import java.io.File;
          import java.io.FileWriter;
          import java.io.IOException;
          import java.util.*;
          
          public class FreemarkerTest {
          
              public static void main(String[] args) throws IOException, TemplateException {
                  //創建配置類
                  Configuration CONFIGURATION = new Configuration(Configuration.VERSION_2_3_22);
                  //設置模版加載路徑
          
                  //ClassTemplateLoader方式:需要將模版放在FreemarkerTest類所在的包,加載模版時會從該包下加載
                  //CONFIGURATION.setTemplateLoader(new ClassTemplateLoader(FreemarkerTest.class,""));
          
                  String path = java.net.URLDecoder.decode(FreemarkerTest.class.getClassLoader().getResource("").getPath(),"utf-8");
                  //FileTemplateLoader方式:需要將模版放置在classpath目錄下 目錄有中文也可以
                  CONFIGURATION.setTemplateLoader(new FileTemplateLoader(new File(path)));
          
                  //設置編碼
                  CONFIGURATION.setDefaultEncoding("UTF-8");
                  //設置異常處理器
                  CONFIGURATION.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
                  //設置緩存方式
                  CONFIGURATION.setCacheStorage(NullCacheStorage.INSTANCE);
                  //加載模版
                  Template template = CONFIGURATION.getTemplate("templates/04-innerFunc.ftl");
                  /**
                   * 靜態化并輸出到文件中   參數1:數據模型     參數2:文件輸出流
                   */
                  template.process(getModel(), new FileWriter("d:/list.html"));
                  /**
                   * 靜態化并輸出到字節輸出流中
                   */
                  //StringWriter out = new StringWriter();
                  //template.process(getData(), out);
                  //System.out.println(out.toString());
              }
          
          
              public static Map getModel(){
                  Map map = new HashMap();
                  //1.1 小強對象模型數據
                  Student stu1 = new Student();
                  stu1.setName("小強");
                  stu1.setAge(18);
                  stu1.setMoney(1000.86f);
                  stu1.setBirthday(new Date());
                  //1.2 小紅對象模型數據
                  Student stu2 = new Student();
                  stu2.setName("小紅");
                  stu2.setMoney(200.1f);
                  stu2.setAge(19);
                  //1.3 將兩個對象模型數據存放到List集合中
                  List<Student> stus = new ArrayList<>();
                  stus.add(stu1);
                  stus.add(stu2);
                  map.put("stus", stus);
                  // 2.1 添加日期
                  Date date = new Date();
                  map.put("today", date);
                  // 3.1 添加數值
                  map.put("point", 102920122);
                  return map;
              }
          }

          3 數據庫元數據

          3.1 介紹

          元數據(Metadata)是描述數據的數據。

          數據庫元數據(DatabaseMetaData)就是指定義數據庫各類對象結構的數據。

          在mysql中可以通過show關鍵字獲取相關的元數據

          show status; 獲取數據庫的狀態
          show databases; 列出所有數據庫
          show tables; 列出所有表
          show create database [數據庫名]; 獲取數據庫的定義
          show create table [數據表名]; 獲取數據表的定義
          show columns from <table_name>; 顯示表的結構
          show index from <table_name>; 顯示表中有關索引和索引列的信息
          show character set; 顯示可用的字符集以及其默認整理
          show collation; 顯示每個字符集的整理
          show variables; 列出數據庫中的參數定義值

          也可以從 information_schema庫中獲取元數據,information_schema數據庫是MySQL自帶的信息數據庫,它提供了訪問數據庫元數據的方式。存著其他數據庫的信息。

          select schema_name from information_schema.schemata; 列出所有的庫
          select table_name FROM information_schema.tables; 列出所有的表

          在代碼中可以由JDBC的Connection對象通過getMetaData方法獲取而來,主要封裝了是對數據庫本身的一些整體綜合信息,例如數據庫的產品名稱,數據庫的版本號,數據庫的URL,是否支持事務等等。

          DatabaseMetaData的常用方法:

          getDatabaseProductName:獲取數據庫的產品名稱
          getDatabaseProductName:獲取數據庫的版本號
          getUserName:獲取數據庫的用戶名
          getURL:獲取數據庫連接的URL
          getDriverName:獲取數據庫的驅動名稱
          driverVersion:獲取數據庫的驅動版本號
          isReadOnly:查看數據庫是否只允許讀操作
          supportsTransactions:查看數據庫是否支持事務

          3.2 搭建環境

          (1)導入mysql依賴

          <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>5.1.47</version>
          </dependency>

          (2)創建測試用例

          package com.itheima.test;
          
          import org.junit.Before;
          import org.junit.Test;
          
          import java.sql.*;
          import java.util.Properties;
          
          public class DataBaseMetaDataTest {
              private Connection conn;
          
              @Before
              public void init() throws Exception {
                  Properties pro = new Properties();
                  pro.setProperty("user", "root");
                  pro.setProperty("password", "123456");
                  pro.put("useInformationSchema", "true");//獲取mysql表注釋
                  //pro.setProperty("remarksReporting","true");//獲取oracle表注釋
                  conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/?useUnicode=true&characterEncoding=UTF8", pro);
              }   
          }

          3.3 綜合信息元數據

          (1)獲取數據庫元信息綜合信息

          @Test
          public void testDatabaseMetaData() throws SQLException {
              //獲取數據庫元數據
              DatabaseMetaData dbMetaData = conn.getMetaData();
              //獲取數據庫產品名稱
              String productName = dbMetaData.getDatabaseProductName();
              System.out.println(productName);
              //獲取數據庫版本號
              String productVersion = dbMetaData.getDatabaseProductVersion();
              System.out.println(productVersion);
              //獲取數據庫用戶名
              String userName = dbMetaData.getUserName();
              System.out.println(userName);
              //獲取數據庫連接URL
              String userUrl = dbMetaData.getURL();
              System.out.println(userUrl);
              //獲取數據庫驅動
              String driverName = dbMetaData.getDriverName();
              System.out.println(driverName);
              //獲取數據庫驅動版本號
              String driverVersion = dbMetaData.getDriverVersion();
              System.out.println(driverVersion);
              //查看數據庫是否允許讀操作
              boolean isReadOnly = dbMetaData.isReadOnly();
              System.out.println(isReadOnly);
              //查看數據庫是否支持事務操作
              boolean supportsTransactions = dbMetaData.supportsTransactions();
              System.out.println(supportsTransactions);
          }

          (2)獲取數據庫列表

          @Test
          public void testFindAllCatalogs() throws Exception {
              //獲取元數據
              DatabaseMetaData metaData = conn.getMetaData();
              //獲取數據庫列表
              ResultSet rs = metaData.getCatalogs();
              //遍歷獲取所有數據庫表
              while (rs.next()) {
                  //打印數據庫名稱
                  System.out.println(rs.getString(1));
              }
              //釋放資源
              rs.close();
              conn.close();
          }

          (3)獲取某數據庫中的所有表信息

          @Test
          public void testFindAllTable() throws Exception {
              //獲取元數據
              DatabaseMetaData metaData = conn.getMetaData();
              //獲取所有的數據庫表信息
              ResultSet rs = metaData.getTables("庫名", "%", "%", new String[]{"TABLE"});
              //拼裝table
              while (rs.next()) {
                  //所屬數據庫
                  System.out.println(rs.getString(1));
                  //所屬schema
                  System.out.println(rs.getString(2));
                  //表名
                  System.out.println(rs.getString(3));
                  //數據庫表類型
                  System.out.println(rs.getString(4));
                  //數據庫表備注
                  System.out.println(rs.getString(5));
                  System.out.println("--------------");
              }
          }

          (4)獲取某張表所有的列信息

          @Test
          public void testFindAllColumns() throws Exception {
              //獲取元數據
              DatabaseMetaData metaData = conn.getMetaData();
              //獲取所有的數據庫某張表所有列信息
              ResultSet rs = metaData.getColumns("庫名", "%", "表名","%");
          
              while(rs.next()) {
                  //表名
                  System.out.println(rs.getString("TABLE_NAME"));
                  //列名
                  System.out.println(rs.getString("COLUMN_NAME"));
                  //類型碼值
                  System.out.println(rs.getString("DATA_TYPE"));
                  //類型名稱
                  System.out.println(rs.getString("TYPE_NAME"));
                  //列的大小
                  System.out.println(rs.getString("COLUMN_SIZE"));
                  //小數部分位數,不適用的類型會返回null
                  System.out.println(rs.getString("DECIMAL_DIGITS"));
                  //是否允許使用null
                  System.out.println(rs.getString("NULLABLE"));
                  //列的注釋信息
                  System.out.println(rs.getString("REMARKS"));
                  //默認值
                  System.out.println(rs.getString("COLUMN_DEF"));
                  //是否自增
                  System.out.println(rs.getString("IS_AUTOINCREMENT"));
                  //表中的列的索引(從 1 開始
                  System.out.println(rs.getString("ORDINAL_POSITION"));
                  System.out.println("--------------");
              }
          }

          3.4 參數元數據

          參數元數據(ParameterMetaData):是由PreparedStatement對象通過getParameterMetaData方法獲取而 來,主要是針對PreparedStatement對象和其預編譯的SQL命令語句提供一些信息,ParameterMetaData能提供占位符參數的個數,獲取指定位置占位符的SQL類型等等 以下有一些關于ParameterMetaData的常用方法:

          getParameterCount:獲取預編譯SQL語句中占位符參數的個數
          @Test
          public void testParameterMetaData() throws Exception {
              String sql = "select * from health.t_checkgroup where id=? and code=?";
              PreparedStatement pstmt = conn.prepareStatement(sql);
              pstmt.setString(1, "7");
              pstmt.setString(2, "0003");
              //獲取ParameterMetaData對象
              ParameterMetaData paramMetaData = pstmt.getParameterMetaData();
              //獲取參數個數
              int paramCount = paramMetaData.getParameterCount();
              System.out.println(paramCount);
          }

          3.5 結果集元數據

          結果集元數據(ResultSetMetaData):是由ResultSet對象通過getMetaData方法獲取而來,主要是針對由數據庫執行的SQL腳本命令獲取的結果集對象ResultSet中提供的一些信息,比如結果集中的列數、指定列的名稱、指 定列的SQL類型等等,可以說這個是對于框架來說非常重要的一個對象。 以下有一些關于ResultSetMetaData的常用方法:

          getColumnCount:獲取結果集中列項目的個數
          getColumnType:獲取指定列的SQL類型對應于Java中Types類的字段
          getColumnTypeName:獲取指定列的SQL類型
          getClassName:獲取指定列SQL類型對應于Java中的類型(包名加類名
          @Test
          public void testResultSetMetaData() throws Exception {
              String sql = "select * from health.t_checkgroup where id=?";
              PreparedStatement pstmt = conn.prepareStatement(sql);
              pstmt.setString(1, "7");
              //執行sql語句
              ResultSet rs = pstmt.executeQuery();
              //獲取ResultSetMetaData對象
              ResultSetMetaData metaData = rs.getMetaData();
              //獲取查詢字段數量
              int columnCount = metaData.getColumnCount();
              System.out.println("字段總數量:"+ columnCount);
              for (int i = 1; i <= columnCount; i++) {
                  //獲取表名稱
                  System.out.println(metaData.getColumnName(i));
                  //獲取java類型
                  System.out.println(metaData.getColumnClassName(i));
                  //獲取sql類型
                  System.out.println(metaData.getColumnTypeName(i));
                  System.out.println("----------");
              }
          }

          4 代碼生成器環境搭建

          4.1 創建maven工程

          創建maven工程并導入以下依賴

          <properties>
              <java.version>11</java.version>
              <!-- 項目源碼及編譯輸出的編碼 -->
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
              <!-- 項目編譯JDK版本 -->
              <maven.compiler.source>11</maven.compiler.source>
              <maven.compiler.target>11</maven.compiler.target>
          </properties>
          
          <dependencies>
              <dependency>
                  <groupId>org.freemarker</groupId>
                  <artifactId>freemarker</artifactId>
                  <version>2.3.23</version>
              </dependency>
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>5.1.47</version>
              </dependency>
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <version>1.18.8</version>
              </dependency>
              <dependency>
                  <groupId>org.apache.commons</groupId>
                  <artifactId>commons-lang3</artifactId>
                  <version>3.10</version>
              </dependency>
          </dependencies>

          目錄結構如下

          十分鐘學會 Less

          每一門技術的出現都是為了解決現存的問題,同樣的,Less 的出現是為了解決 CSS 中過于呆板的寫法。Less 官方文檔 中對 Less 的使用有詳細的介紹,總結一下為:Less = 變量 + 混合 + 函數。如果你對 js 和 css 有所了解,那么就可以很快掌握并在你的項目中使用 Less。

          一、Less 使用初體驗

          1. 使用 Less 寫樣式

          使用 Npm 全局安裝 Less

          $ npm install less -g

          創建一個空文件夾,這里命名為:learn-less

          在根目錄下創建 index.html 文件,復制內容如下:

          <!doctype html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>初識 Less</title>
              <link href="./main.css" rel="stylesheet">
          </head>
          <body>
              <div class="container">1</div>
              <div class="container2">2</div>
              <div class="container3">3</div>
          </body>
          </html>

          在根目錄下創建 main.less 文件,復制內容如下:

          // main.less
          @width: 100%;
          @height: 100px;
          @color: red;
          
          .container{
             width: @width;
             height: @height;
             background-color: @color;
             margin-bottom: 5px;
           }
          
          .container2{
            width: @width;
            height: @height;
            background-color: @color;
            margin-bottom: 5px;
          }
          
          .container3{
            width: @width;
            height: @height;
            background-color: @color;
            margin-bottom: 5px;
          }

          現在打開瀏覽器看一下,會發現并沒有加載樣式。這是因為 index.html 中引入的樣式文件是 main.css 而不是 main.less。所以接下來,我們需要將 main.less 轉換為 main.css,不用擔心,這一步驟并不需要你手動操作,僅僅是運行一條命令就會自動完成轉換。

          $ lessc main.less

          操作完以上步驟就會發現在根目錄下生成了一個 main.css 文件,此時再打開瀏覽器看看,樣式已經出現了。

          main.css 轉義內容為:

          .container {
            width: 100%;
            height: 100px;
            background-color: red;
            margin-bottom: 5px;
          }
          .container2 {
            width: 100%;
            height: 100px;
            background-color: red;
            margin-bottom: 5px;
          }
          .container3 {
            width: 100%;
            height: 100px;
            background-color: red;
            margin-bottom: 5px;
          }

          如果你使用了 Webstorm 作為開發工具,那么連手動輸入命令行這一步都可以跳過,因為 Webstorm 會在你的 .less 文件被修改后自動生成對應的 .css 文件,具體配置跳轉:Webstorm 配置 Less 自動轉譯成 css

          2. 感受 Less 的便利

          現在有一個新的需求,需要將三個 div 的背景顏色改成藍色(blue),如果是之前 css 的寫法需要依次找到 container、container2、container3,對應修改里面的 background-color 屬性,但是使用 less 我們僅僅修改前面定義過的變量值即可。

          // main.less
          @width: 100%;
          @height: 100px;
          @color: blue;
          
          ...

          使用 lessc main.less 進行轉譯后打開瀏覽器可以看到三個 div 的背景顏色已經被改變了。

          二、變量

          在前面介紹的案例中已經使用了“變量”的概念,是不是感覺和 js 很像,事實上 less 就是用 js 的寫法來寫 css。

          官網在介紹變量的時候會給出很多應用場景,總結一下就是使用 @ 符號定義變量,使用 @ 符號獲取變量,僅僅將 @變量名 看成是一個字符串。

          @classname: main;
          @color: red;
          
          .@classname{
              background-color: @color;
          }

          從上面例子中可以看到,變量不僅僅可以作為樣式屬性值:background-color: @color;,還可以作為類名:.@classname 表示的就是 .main。這也就是為什么說僅僅將 @變量名 看成是一個字符串。

          三、混合

          先看一個 example.css 文件:

          #menu a {
              color: #111;
              border-top: dotted 1px black;
              border-bottom: solid 2px black;
          }
          
          #menu span {
              height: 16px;
              border-top: dotted 1px black;
              border-bottom: solid 2px black;
          }
          
          #menu p {
              color: red;
              border-top: dotted 1px black;
              border-bottom: solid 2px black;
          }

          可以看到上面三個樣式中都有 border-topborder-bottom 兩個屬性,并且內容完全相同;在傳統 CSS 寫法中只能這樣一遍有一遍的去書寫重復的內容,在 Less 中通過將公共屬性抽取出來作為一個公共類的方式規避這一點。

          // example2.less
          .bordered {
              border-top: dotted 1px black;
              border-bottom: solid 2px black;
          }
          
          #menu a {
              color: #111;
              .bordered;
          }
          
          #menu span {
              height: 16px;
              .bordered;
          }
          
          #menu p {
              color: red;
              .bordered();
          }

          將以上 example2.less 進行轉譯成 example2.css 文件為:

          .bordered {
            border-top: dotted 1px black;
            border-bottom: solid 2px black;
          }
          #menu a {
            color: #111;
            border-top: dotted 1px black;
            border-bottom: solid 2px black;
          }
          #menu span {
            height: 16px;
            border-top: dotted 1px black;
            border-bottom: solid 2px black;
          }
          #menu p {
            color: red;
            border-top: dotted 1px black;
            border-bottom: solid 2px black;
          }

          可以看到 examle2.css 與 example.css 很相似,只是多了一個 .bordered 樣式。

          修改 example2.less,將 .bordered 寫成 .bordered(),此時在進行轉譯之后會看到 example2.css 和 example.css 文件就完全一樣了,使用 less 書寫更加簡單。

          // example2.less
          .bordered() {
              border-top: dotted 1px black;
              border-bottom: solid 2px black;
          }
          
          ...

          總結:

          • 混合也是減少代碼書寫量的一個方法;
          • 混合的類名在定義的時候加上小括弧 (),那么在轉譯成 css 文件時就不會出現;
          • 混合的類名在被調用的時候加上小括弧 ()和不加上小括弧 ()是一樣的效果,看個人習慣,如:第三行和第八行轉譯成 css 是一樣的。
          • 1 #menu span {
            2 height: 16px;
            3 .bordered;
            4 }
            5
            6 #menu p {
            7 color: red;
            8 .bordered();
            9 }

          四、函數

          曾幾何時,在書寫呆板的 css 時有沒有想過讓類名動態化,根據不同的參數生成不同的樣式。看下面的示例:

          // func.less
          .border-radius(@radius) {
            -webkit-border-radius: @radius;
               -moz-border-radius: @radius;
                    border-radius: @radius;
          }
          
          #header {
            .border-radius(4px);
          }
          .button {
            .border-radius(6px);
          }

          使用 $ lessc func.less 進行轉譯 func.css 文件內容如下:

          #header {
            -webkit-border-radius: 4px;
            -moz-border-radius: 4px;
            border-radius: 4px;
          }
          .button {
            -webkit-border-radius: 6px;
            -moz-border-radius: 6px;
            border-radius: 6px;
          }

          可以看到,這里就用到了函數的概念,在 #header.button 中分別傳入不同的參數,結果也就生成不同的代碼。

          關于 less 中函數的寫法還有以下幾種:

          // 函數的參數設置默認值:
          .border-radius(@radius: 5px) {
            -webkit-border-radius: @radius;
            -moz-border-radius: @radius;
            border-radius: @radius;
          }
          
          // 函數有多個參數時用分號隔開
          .mixin(@color; @padding:2) {
            color-2: @color;
            padding-2: @padding;
          }
          
          // 函數如果沒有參數,在轉譯成 css 時就不會被打印出來,詳見上面混合中的示例
          .wrap() {
            text-wrap: wrap;
          }
          
          // 函數參數如果有默認,調用時就是通過變量名稱,而不是位置
          .mixin(@color: black; @margin: 10px; @padding: 20px) {
            color: @color;
            margin: @margin;
            padding: @padding;
          }
          .class1 {
            .mixin(@margin: 20px; @color: #33acfe);
          }
          
          // 函數參數有個內置變量 @arguments,相當于 js 中的 arguments
          .box-shadow(@x: 0; @y: 0; @blur: 1px; @color: #000) {
            -webkit-box-shadow: @arguments;
               -moz-box-shadow: @arguments;
                    box-shadow: @arguments;
          }
          
          // 函數名允許相同,但參數不同,類似于 java 中多態的概念
          .mixin(@color: black) {      
          .mixin(@color: black; @margin: 10px) { 

          當然,上面是開發人員自定義的函數,Less 也為我們定義了很多好用的內置函數。關于內置函數,如果掌握,可以在開發過程中節約很多時間,由于內置函數數量很多,這里就不一一介紹,傳送門:Less 內置函數官方文檔[https://less.bootcss.com/functions/]。

          五、父子元素的寫法

          在 css 中父子元素的寫法通常如下:

          .container {
              padding: 0;
          }
          .container .article {
              background-color: red;
          }

          在 Less 寫法如下,父子嵌套關系一目了然。

          .container {
              padding: 0;
              .article {
                  background-color: red;
              }
          }

          當然,父子元素還要一種是偽類的寫法,在 css 中寫法如下:

          #header :after {
            content: " ";
            display: block;
            font-size: 0;
            height: 0;
            clear: both;
            visibility: hidden;
          }

          在 less 中寫法如下,可以看到引入了新的符號 &,以 & 來代替主類 #header

          #header {
            &:after {
              content: " ";
              display: block;
              font-size: 0;
              height: 0;
              clear: both;
              visibility: hidden;
            }
          }

          六、神奇 @import

          在傳統 css 文件中,每個文件都是獨立的。在 less 中可以像 js 的模塊那樣在一個 less 文件中引入另一個 less 文件。

          創建 one.less 文件:

          .container {
            width: 100px;
            height: 200px;
          }

          創建 two.less 文件:

          @import "one";

          使用 $ lessc two.less 轉譯成 two.css 文件,可以看到內容如下:

          .container {
            width: 100px;
            height: 200px;
          }

          @import 的作用可以看成是將 one.less 的內容復制一份到當前 .less 文件中。

          那么如果 two.less 中也有一個類名叫 container 的,使用 @import 之后會變成什么樣子呢?這個留給自行測試好啦。

          轉載自作者:dkvirus

          義符:一般都是在字符串中的字符才需要轉義

          1)JS中需要轉義符的情況

          1.1路徑中的反斜杠 比如 c:\b\a.txt;在JS中不能使用@符號進行轉義

          1.2常見轉義符比如 \t,\n,\’,\”,\

          1.3 在正則表達式中

          <!DOCTYPE html>
          <html xmlns="http://www.w3.org/1999/xhtml">
          <head>
              <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
              <title>JavaScript</title>
              <script type="text/javascript">
                  //轉義字符串中文件路徑中的\
                  var a1 ='c:\\b\\a.txt';
                  alert(a1);
                  //轉義字符串中的單引號
                  var a2 ="c'b'a"; //第一種方式
                  var a3='c\'b\'a'; //第二種方式
                  alert(a2);
                  alert(a3);
                  //轉義字符串中的雙引號
                  var a4='a"b';//第一種方式
                  var a5="a\"b";//第二種方式
                  alert(a4);
                  alert(a5);
                  //其他的不再舉例說明
              </script>
          </head>
          <body>
          </body>
          </html>

          2)JS中的等于(==)與全等于(===)

          JS中的等于只要變量值相同即可;全等于需要值與類型全部相同

          使用等于判斷兩個變量是否相同,忽略了數據類型(不嚴謹),推薦使用全等于

          3)JS中的選擇循環語句

          if-else,switch; for,while,do-while,continue,break的用法與C#中幾乎一樣

          for循環與C#中不同的點是:js中聲明變量使用的是var(let等暫時忽略);C#中一般使用int

          switch中的判斷條件使用的也是全等于

          switch語句


          主站蜘蛛池模板: 日韩人妻无码一区二区三区| 中文字幕日韩一区| 国产成人精品一区二三区在线观看| 欧美日本精品一区二区三区| 国产精品盗摄一区二区在线| 中文字幕无线码一区| 久久se精品动漫一区二区三区| 精品午夜福利无人区乱码一区| 视频一区视频二区制服丝袜| 久久久人妻精品无码一区| 秋霞午夜一区二区| 精品国产一区二区麻豆| 国产在线一区二区| 国产精品成人国产乱一区| 国产午夜精品一区二区三区漫画| 91精品一区二区三区久久久久| 亚洲制服丝袜一区二区三区| 日韩精品无码Av一区二区| 亚洲日本一区二区一本一道| 日韩一区二区超清视频| 国产一区二区三区夜色| 精品一区中文字幕| 亚洲欧洲专线一区| 精品黑人一区二区三区| 一区二区在线视频| 亚洲国产精品一区二区久| 久久4k岛国高清一区二区| 无码人妻一区二区三区免费看| 亚洲区精品久久一区二区三区| 中文乱码精品一区二区三区| 国产一区二区三区樱花动漫| 日韩精品国产一区| 国产精品一区二区久久| 一区二区三区四区视频| 日韩一区二区在线观看视频| 无码国产精品一区二区免费vr| 亚洲AV无码一区东京热久久| 亚洲av无码一区二区三区不卡| 国产免费一区二区三区免费视频 | 色视频综合无码一区二区三区| 日韩免费无码视频一区二区三区 |