Spring MVC 控制器初識

在 Spring MVC 中控制器的主要作用就是綁定請求參數處理業(yè)務邏輯逞度、返回模型數據和視圖悍手,要定義一個控制器也很簡單,使用@Controller注解標注一個類即可悠轩,但這還不夠间狂,還需要結合@RequestMapping注解,它可以在類火架、方法上使用鉴象,用來指定請求 URL 可以由控制類中的那個方法來處理,這里我們主要學習以下兩方面的內容:

  • 綁定請求參數
  • 模型數據和視圖
  • 重定向和轉發(fā)

一何鸡、綁定請求參數

1纺弊、普通請求參數

在 Spring MVC 中,如果網絡請求時傳遞的參數名稱和控制器方法的形參名稱一致骡男,則對應的參數值會自動綁定到控制器方法的形參上淆游。

先準備一個表單 RoleForm.jsp 來提交參數:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>RoleForm</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/role/commonParams" id="form" method="post">
    <table>
        <tr>
            <td>名稱</td>
            <td><input id="roleName" name="roleName" value=""/></td>
        </tr>
        <tr>
            <td>備注</td>
            <td><input id="note" name="note" value=""/></td>
        </tr>
        <tr>
            <td></td>
            <td align="right"><input id="commit" type="submit" value="提交"/></td>
        </tr>
    </table>
</form>
</body>
</html>

要提交的兩個參數名稱分別為roleNamename隔盛,地址是http://localhost:8080/role/commonParams犹菱,再看對應的控制器類:

@Controller
@RequestMapping("/role")
public class RoleController {
 
    @RequestMapping("/roleForm")
    public String roleForm() {
        return "roleForm";
    }

    @RequestMapping("/commonParams")
    public ModelAndView commonParams(String roleName, String note) {
        ModelAndView mv = new ModelAndView();
        Role role = new Role();
        role.setRoleName(roleName);
        role.setNote(note);
        mv.addObject("role", role);
        mv.setViewName("result");
        return mv;
    }
}

形參名和表單提交的參數名一致,這樣當提交表單后吮炕,就可以直接通過形參拿到參數值腊脱,同時將請求轉發(fā)到result.jsp來展示提交的數據,結果如下:

1

2

如果提交的參數名稱和控制器方法的形參不一致龙亲,則是無法將參數值綁定到形參上的陕凹,同時又無法修改提交的參數名稱悍抑。例如將 RoleForm.jspname="roleName"改為name="role_name",上邊的控制器方法自然是無法正常獲取參數值的捆姜,要解決這個問題可以使用@RequestParam注解传趾,它的作用是將請求參數的值賦給形參:修改后的控制器方法如下:

@RequestMapping("/commonParams2")
public ModelAndView commonParams2(@RequestParam("role_name") String roleName, String note) {
    return commonParams(roleName, note);
}

這樣問題就解決了,要注意的是使用了@RequestParam("role_name")role_name參數的值默認不能為空泥技,否則會有異常浆兰,如果不能保證role_name有值,可以配置required屬性:

@RequestMapping("/commonParams2")
public ModelAndView commonParams2(@RequestParam(value = "role_name", required = false) String roleName, String note) {
    return commonParams(roleName, note);
}

如果表單提交的參數過多珊豹,那么控制器方法的形參也會很多簸呈,這樣大大降到底了代碼的可讀性、增加了后期的維護成本店茶,當然 Spring MVC 早已想到了這一點蜕便,我們可以將控制器的方法參數聲明為一個對象,只要其包含的屬性名稱和請求參數的名稱一致即可贩幻,就會得到一個屬性值為提交參數的對像轿腺。

修改 RoleForm.jspaction屬性為${pageContext.request.contextPath}/role/commonParams3,對應的控制方法如下:

@RequestMapping("/commonParams3")
public ModelAndView commonParams3(Role role) {
    ModelAndView mv = new ModelAndView();
    mv.addObject("role", role);
    mv.setViewName("result");
    return mv;
}

2丛楚、URL 模板參數

RESTful API 的設計規(guī)范中族壳,可以通過 URL 來傳遞參數,例如要通過 id 獲得某個角色的信息趣些,并用 JSON 格式返回仿荆,我們希望 URL 是這樣的:http://localhost:8080/role/getRole/1,其中 1 是角色 id坏平,動態(tài)變化的拢操。當然 Spring MVC 也支持這樣的情況,通過@PathVariable注解可以方便的獲取請求 URL 的動態(tài)參數舶替,對應的控制器方法如下:

@RequestMapping("/getRole/{id}")
@ResponseBody
public Role getRole(@PathVariable("id") long id) {
    Role role = roleService.getRole(id);
    return role;
}

@ResponseBody注解可以將返回的對象轉換成 JSON 格式令境,在瀏覽器測試效果如下:

3

3、JSON 格式參數

如果參數結構比較復雜坎穿,為了方便而使用 JSON 格式傳遞數據也是比較常見的展父,接下來實現 JSON 數據的提交和解析。首先準備 RoleForm2.jsp玲昧,主要的作用是將表單數據已 JSON 格式提交:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>RoleForm2</title>
    <script src="${pageContext.request.contextPath}/js/jquery-3.4.1.min.js"></script>
    <script>
        $(function () {
            $("#commit").click(function () {
                var data = {
                    id: 1,
                    roleName: $("#roleName").val(),
                    note: $("#note").val()
                }
                
                $.post({
                    url: "./jsonParams",
                    contentType: "application/json",
                    data: JSON.stringify(data),
                    success: function (result) {
                        alert("名稱:" + result.roleName + "\n" + "備注:" + result.note)
                    }
                });
            });
        });
    </script>
</head>
<body>
<form id="form">
    <table>
        <tr>
            <td>名稱</td>
            <td><input id="roleName" name="roleName" value=""/></td>
        </tr>
        <tr>
            <td>備注</td>
            <td><input id="note" name="note" value=""/></td>
        </tr>
        <tr>
            <td></td>
            <td align="right"><input id="commit" type="button" value="提交"/></td>
        </tr>
    </table>
</form>
</body>
</html>

這里通過 jQuery 以 JSON 格式來提交數據到http://localhost:8080/role/jsonParams令漂,并在 success回調中用一個彈窗回顯提交的數據乱灵,控制器方法如下:

@Controller
@RequestMapping("/role")
public class RoleController {
    @RequestMapping("/roleForm2")
    public String roleForm2() {
        return "roleForm2";
    }

    @RequestMapping("/jsonParams")
    @ResponseBody
    public Role jsonParams(@RequestBody Role role) {
        return role;
    }
}

這里的重點是jsonParams方法中的@RequestBody注解孝常,它會將 JSON 串自動映射為對象的状共,當然 JSON 串中的屬性名要和對象中屬性名保持一致。同時jsonParams方法將提交的數據以 JSON 格式返回尘应,以便在彈窗中獲取其屬性值惶凝。測試效果如下:

4

同樣的原理吼虎,我們可以將列表數據以 JSON 的格式提交,具體代碼可參考 demo苍鲜。

4思灰、序列化參數

如果表單數據比較復雜,我們自己收集數據的話會比較麻煩混滔,一種簡便的方式就是將表單序列化洒疚。我們修改 RoleForm2.jsp 提交按鈕的事件:

$("#commit").click(function () {
    //提交表單
    var data = $("form").serialize();
    $.post({
        url: "./serializeParams",
        data: data,
        success: function (result) {
              var s =  $.parseJSON(result);
              alert("名稱:" + s.roleName + "\n" + "備注:" + s.note)
        }
    });
});

其中$("form").serialize()就是將表單的數據序列化坯屿,返回一個字符串油湖,得到的參數如下:roleName=管理員&note=666

對應的控制器方法為:

@RequestMapping("/serializeParams")
@ResponseBody
public Role serializeParams(Role role) {
    return role;
}

執(zhí)行效果和圖4類似领跛。

同樣可以將表單數據序列化成 JSON 數組格式乏德,使用如下 jQuery 方法即可:$("form").serializeArray(),得到的 JSON 數據格式參數如下:

[{
    "name": "roleName",
    "value": "管理員"
}, {
    "name": "note",
    "value": "666"
}]

二吠昭、模型數據和視圖

其實我們在綁定請求參數部分內容中喊括,已經接觸到了模型數據和視圖,在 Spring MVC 中兩者一般是搭配使用的矢棚,模型數據一般保存控制器方法中執(zhí)行業(yè)務后需要展示或返回的數據瘾晃,然后將模型數據渲染到視圖中。

1幻妓、模型數據

常用的模型數據有ModelModelMap劫拢、ModelAndView肉津。Spring MVC 在控制器方法被調用前會創(chuàng)建一個隱含的BindingAwareModelMap類型的模型數據,這樣如果在控制器方法中聲明了Model舱沧、ModelMap妹沙、ModelAndView其中某個類型的形參,則 Spring MVC 會將隱含的模型數據轉換為聲明的模型數據類型熟吏,即自動創(chuàng)建了形參的實例距糖,當然你也可以選擇手動創(chuàng)建。有了模型數據的實例牵寺,接下來就可以添加數據了悍引,之后可以在視圖頁面得到添加的數據。模型數據以鍵值對的形式來添加數據帽氓。

2趣斤、視圖

視圖分為邏輯視圖非邏輯視圖,怎么理解呢黎休?

  • 邏輯視圖浓领,需要通過視圖解析器解析邏輯視圖名玉凯,得到真實的視圖,然后渲染模型數據联贩,例如InternalResourceView漫仆,視圖解析器以及邏輯視圖的文件目錄在dispatcher_servlet.xml中已經配置過了。
  • 非邏輯視圖泪幌,無需視圖解析器盲厌,直接渲染模型數據,例如MappingJackson2JsonView
2.1

使用邏輯視圖時座菠,控制器方法的返回值可以是String類型狸眼,即邏輯視圖名,這樣 Spring MVC 會根據邏輯視圖名以及配置的視圖解析器的視圖文件目錄和文件后綴名找到對應的視圖文件浴滴,得到真實的視圖拓萌,如果有模型數據則渲染數據,將最終的視圖呈現給用戶升略∥⑼酰看如下的例子:

@Controller
@RequestMapping("/role")
public class RoleController {
    @Autowired
    private RoleService roleService;

    @RequestMapping("/getRoles2")
    public String getRoles2(Model model) {
        List<Role> roles = roleService.getAllRole();
        model.addAttribute("roles", roles);
        return "role";
    }
}

控制器方法getRoles2返回邏輯視圖名role,所以需要提前在/WEB-INF/JSP/目錄下定義role.jsp文件品嚣。模型數據model中保存了角色的集合數據炕倘。所以在jsp文件中根據角色數據渲染一個表格:

<c:forEach items="${roles}" var="role">
        <tr>
            <td><c:out value="${role.id}"/></td>
            <td><c:out value="${role.roleName}"/></td>
            <td><c:out value="${role.note}"/></td>
        </tr>
    </c:forEach>

運行后可以看到如下效果:


5
2.2

控制器方法除了返回String,還可以返回ModelAndView翰撑,ModelAndView既可以添加數據也可以設置邏輯視圖名罩旋,對應的控制器方法如下:

@RequestMapping("/getRoles")
public ModelAndView getRoles(ModelAndView mv) {
    List<Role> roles = roleService.getAllRole();
    mv.setViewName("role");
    mv.addObject("roles", roles);
    return mv;
}

運行效果和圖5一致。

2.3

這里補充一點眶诈,如果在控制器方法中聲明了Model或者ModelMap參數涨醋,同時聲明了一個類的對象參數,例如Role來接收請求參數逝撬,那么Model或者ModelMap參數會自動添加聲明的Role對象浴骂,添加數據時使用的默認 key 就是類首字母小寫的字符串,即role宪潮,是由框架自動推斷出的溯警。

2.4

使用非邏輯視圖時,例如返回 JSON 格式的數據狡相,由于 Spring MVC 默認使用 Jackson 處理 JSON 數據梯轻,所以可以先用MappingJackson2JsonView視圖來完成數據格式的轉換:

@RequestMapping("/getRoles3")
public ModelAndView getRoles3() {
    ModelAndView mv = new ModelAndView();
    List<Role> roles = roleService.getAllRole();
    mv.addObject("roles", roles);
    mv.setView(new MappingJackson2JsonView());
    return mv;
}

當然,要返回 JSON 格式數據尽棕,更簡單的方式是使用@ResponseBody檩淋,但本質上還是采用 Jackson 處理 JSON 數據,當然也可以整合其它的 JSON 處理框架,例如Fastjson蟀悦,這樣當使用@ResponseBody媚朦、@RequestBody注解時會使用 Fastjson 來處理 JSON 數據。整合的方式也很簡單日戈,使用 Fastjson 提供的FastJsonHttpMessageConverter來替換 Spring MVC 默認的 HttpMessageConverter即可询张,在dispatcher_servlet.xml添加如下配置

<mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>

使用@ResponseBody返回 JSON 的控制器如下:

@RequestMapping("/getRoles4")
@ResponseBody
public List<Role> getRoles4() {
    List<Role> roles = roleService.getAllRole();
    return roles;
}

瀏覽器輸入http://localhost:8080/role/getRoles4,效果如下:

6

三浙炼、重定向和轉發(fā)

首先通過一個表格來了解下重定向和轉發(fā)之間的差別:

重定向(redirect) 轉發(fā)(forward)
執(zhí)行重定向會重新發(fā)起新請求 執(zhí)行轉發(fā)依然是上次的請求
地址欄的請求URL會變?yōu)樾抡埱蟮腢RL 地址欄的請求URL不會改變
由于重新發(fā)起請求份氧,原請求的請求參數、request范圍內的屬性全部丟失 原請求的請求參數弯屈、request范圍內的屬性依然存在

1蜗帜、重定向(redirect)

在 Spring MVC 中,當控制器方法返回的字符串帶有redirect:前綴時资厉,那么該字符串不是用來查找視圖的邏輯視圖名厅缺,而是要執(zhí)行重定向,我們這里所說的重定向僅代表重定向到控制器的請求處理方法宴偿。例如"redirect:./result"湘捎,代表重定向到./result路徑對應的控制器方法。其實要實現重定向很簡單窄刘,關鍵是如何傳遞參數窥妇。

1.1

如果要傳遞參數到目標控制器方法,可以采用model.addAttribute方式添加參數:

@RequestMapping("/addRole")
public String addRole(Model model, Role role) {
    model.addAttribute("roleName", role.getRoleName());
    model.addAttribute("note", role.getNote());
    return "redirect:./result";
}

目標控制器方法很簡單娩践,僅僅是展示傳遞的參數:

@RequestMapping("/result")
public String result(Model model, Role role) {
    return "result";
}

大致的流程是活翩,通過表單將參數提交到addRole控制器方法,然后傳遞參數重定向到result控制器方法來展示提交的參數翻伺。在瀏覽器執(zhí)行后發(fā)現纱新,如果按照上述方式添加重定向參數,則在重定向時參數會以查詢參數的形式拼接到 URL 上穆趴,例如:http://localhost:8080/role/result?roleName=%E7%AE%A1%E7%90%86%E5%91%98&note=666

1.2

由于通過model.addAttribute添加重定向參數時,參數會拼接在 URL 上遇汞,這樣存在安全性問題未妹。我們可以考慮另外一種方式,就是我們之前學習的通過 URL 模板傳遞參數空入,代碼如下:

@RequestMapping("/addRole2")
public String addRole2(Model model, Role role) {
    model.addAttribute("roleName", role.getRoleName());
    model.addAttribute("note", role.getNote());
    return "redirect:./result2/{roleName}";
}

model 中的roleName會作為參數填充到 URL 中络它,note由于和 URL 模板參數不匹配則會以查詢參數的形式拼接到 URL 上。

要重定向到的控制器方法如下歪赢,用來接收 URL 模板參數和查詢參數:

@RequestMapping("/result2/{roleName}")
public String result2(Model model, @PathVariable("roleName") String roleName, String note) {
    model.addAttribute("roleName", roleName);
    model.addAttribute("note", note);
    return "result2";
}

重定向時的 URL 如下:http://localhost:8080/role/result2/%E7%AE%A1%E7%90%86%E5%91%98?note=666化戳,可以和第一種方式對比下。

1.3

上邊兩種方式只能添加簡單的基本類型參數,例如字符串点楼、數字等扫尖,如要傳遞對象就無能為力了,當然 Spring MVC 自然有解決方案掠廓,我們可以采用另外一個模型數據RedirectAttributes來添加 flash 類型的參數换怖,在執(zhí)行重定向前,會將參數復制到會話(Session)中蟀瞧,當重定向后沉颂,從會話中取出參數。

添加參數的方式如下:

@RequestMapping("/addRole3")
public String addRole3(RedirectAttributes ra, Role role) {
    ra.addFlashAttribute("role", role);
    return "redirect:./result3";
}

重定向后悦污,參數值會自動定轉存到 model 中铸屉,當然也可以定義 Role 對象來接收參數值。

@RequestMapping("/result3")
public String result3(Model model) {
    return "result";
}

重定向的 URL 如下:http://localhost:8080/role/result3切端,由于是通過會話保存參數的彻坛,并不會將參數值暴露,所以也更加的安全帆赢。

1.4

上邊的例子中小压,我們的重定向是在同一個控制器中進行的,更多的時候可能需要重定向到另一個控制器的請求處理方法椰于,我們來修改1.3的例子怠益,要重定向到的控制器類的方法如下:

@Controller
@RequestMapping("/result")
public class ResultController {
    @RequestMapping("/success")
    public String result(Role role) {
        return "result";
    }
}

編寫addRole4方法來重定向到上邊的控制器方法:

@RequestMapping("/addRole4")
public String addRole4(RedirectAttributes ra, Role role) {
    ra.addFlashAttribute("role", role);
    return "redirect:/result/success";
}

注意返回值,即多了一個控制器類的根路徑/result瘾婿,少了.符號蜻牢。

1.5

上邊的例子重定向時都是返回以redirect:開頭的字符串,當然也可以使用ModelAndView偏陪,例如:mv.setViewName("redirect:/result/success")

2抢呆、轉發(fā)(forward)

2.1

和重定向類似, 在 Spring MVC 中笛谦,當控制器方法返回的字符串帶有forward:前綴時抱虐,那么就是要執(zhí)行請求轉發(fā)操作,我們之前接觸到的返回一個邏輯視圖名饥脑,來展示相應的jsp頁面恳邀,就是一種轉發(fā),只是默認省略了forward:灶轰。這里我們要討論的控制器方法之間的轉發(fā)谣沸。先看個例子:

@RequestMapping("/addRole5")
public String addRole5(Model model, Role role) {
    model.addAttribute("roleName", role.getRoleName());
    return "forward:./fail";
}

addRole5控制器方法會直接將請求轉發(fā)到當前控制下可以相應fail請求的控制器方法,提示當前添加的角色已存在:

@RequestMapping("/fail")
public String fail() {
    return "fail";
}
2.2

如果控制器方法返回ModelAndView笋颤,也是可以進行請求轉發(fā)的:

@RequestMapping("/addRole6")
public ModelAndView addRole6(ModelAndView mv, Role role) {
    mv.addObject("roleName", role.getRoleName());
    mv.setViewName("forward:./fail");
    return mv;
}
2.3

當然也可以轉發(fā)到另一個控制器方法:

@RequestMapping("/addRole7")
public ModelAndView addRole7(ModelAndView mv, Role role) {
    mv.addObject("roleName", role.getRoleName());
    mv.setViewName("forward:/result/fail");
    return mv;
}

關鍵的代碼就是mv.setViewName("forward:/result/fail")乳附,要轉發(fā)的目標控制器方法如下:

@Controller
@RequestMapping("/result")
public class ResultController {
    @RequestMapping("/fail")
    public String fail() {
        return "fail";
    }
}

文中demo地址:https://github.com/SheHuan/MyJavaEE

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子赋除,更是在濱河造成了極大的恐慌阱缓,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贤重,死亡現場離奇詭異茬祷,居然都是意外死亡,警方通過查閱死者的電腦和手機并蝗,發(fā)現死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進店門祭犯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人滚停,你說我怎么就攤上這事沃粗。” “怎么了键畴?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵最盅,是天一觀的道長。 經常有香客問我起惕,道長涡贱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任惹想,我火速辦了婚禮问词,結果婚禮上,老公的妹妹穿的比我還像新娘嘀粱。我一直安慰自己激挪,他們只是感情好,可當我...
    茶點故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布锋叨。 她就那樣靜靜地躺著垄分,像睡著了一般。 火紅的嫁衣襯著肌膚如雪娃磺。 梳的紋絲不亂的頭發(fā)上薄湿,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天,我揣著相機與錄音偷卧,去河邊找鬼豺瘤。 笑死,一個胖子當著我的面吹牛涯冠,可吹牛的內容都是我干的。 我是一名探鬼主播逼庞,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼蛇更,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起派任,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤砸逊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后掌逛,有當地人在樹林里發(fā)現了一具尸體师逸,經...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年豆混,在試婚紗的時候發(fā)現自己被綠了篓像。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡皿伺,死狀恐怖员辩,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情鸵鸥,我是刑警寧澤奠滑,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站妒穴,受9級特大地震影響宋税,放射性物質發(fā)生泄漏。R本人自食惡果不足惜讼油,卻給世界環(huán)境...
    茶點故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一杰赛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汁讼,春花似錦淆攻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至耸彪,卻和暖如春伞芹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蝉娜。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工唱较, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人召川。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓南缓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親荧呐。 傳聞我的和親對象是個殘疾皇子汉形,可洞房花燭夜當晚...
    茶點故事閱讀 43,587評論 2 350