使用RESTful風格開發(fā)Java Web

什么是RESTful風格陷猫?

REST是REpresentational State Transfer的縮寫(一般中文翻譯為表述性狀態(tài)轉(zhuǎn)移)踱蛀,REST 是一種體系結(jié)構(gòu),而 HTTP 是一種包含了 REST 架構(gòu)屬性的協(xié)議,為了便于理解组橄,我們把它的首字母拆分成不同的幾個部分:

  • 表述性(REpresentational): REST 資源實際上可以用各種形式來進行表述,包括 XML罚随、JSON 甚至 HTML——最適合資源使用者的任意形式玉工;
  • 狀態(tài)(State): 當使用 REST 的時候,我們更關(guān)注資源的狀態(tài)而不是對資源采取的行為淘菩;
  • 轉(zhuǎn)義(Transfer): REST 涉及到轉(zhuǎn)移資源數(shù)據(jù)遵班,它以某種表述性形式從一個應(yīng)用轉(zhuǎn)移到另一個應(yīng)用。

簡單地說潮改,REST 就是將資源的狀態(tài)以適合客戶端或服務(wù)端的形式從服務(wù)端轉(zhuǎn)移到客戶端(或者反過來)狭郑。在 REST 中,資源通過 URL 進行識別和定位汇在,然后通過行為(即 HTTP 方法)來定義 REST 來完成怎樣的功能翰萨。

實例說明:

在平時的 Web 開發(fā)中,method 常用的值是 GET 和 POST糕殉,但是實際上亩鬼,HTTP 方法還有 PATCH、DELETE阿蝶、PUT 等其他值雳锋,這些方法又通常會匹配為如下的 CRUD 動作:

CRUD 動作 HTTP 方法
Create POST
Read GET
Update PUT 或 PATCH
Delete DELETE

盡管通常來講,HTTP 方法會映射為 CRUD 動作羡洁,但這并不是嚴格的限制玷过,有時候 PUT 也可以用來創(chuàng)建新的資源,POST 也可以用來更新資源。實際上冶匹,POST 請求非冪等的特性(即同一個 URL 可以得到不同的結(jié)果)使其成一個非常靈活地方法习劫,對于無法適應(yīng)其他 HTTP 方法語義的操作,它都能夠勝任嚼隘。

在使用 RESTful 風格之前诽里,我們?nèi)绻胍?strong>增加一條商品數(shù)據(jù)通常是這樣的:

/addCategory?name=xxx

但是使用了 RESTful 風格之后就會變成:

/category

這就變成了使用同一個 URL ,通過約定不同的 HTTP 方法來實施不同的業(yè)務(wù)飞蛹,這就是 RESTful 風格所做的事情了谤狡,為了有一個更加直觀的理解,引用一下來自how2j.cn的圖:

SpringBoot 中使用 RESTful

下面我使用 SpringBoot 結(jié)合文章:http://blog.didispace.com/springbootrestfulapi/ 來實例演示如何在 SpringBoot 中使用 RESTful 風格的編程并如何做單元測試

RESTful API 具體設(shè)計如下:

User實體定義:

public class User { 
 
    private Long id; 
    private String name; 
    private Integer age; 
 
    // 省略setter和getter 
     
}

實現(xiàn)對User對象的操作接口

@RestController 
@RequestMapping(value="/users")     // 通過這里配置使下面的映射都在/users下 
public class UserController { 
 
    // 創(chuàng)建線程安全的Map 
    static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); 
 
    @RequestMapping(value="/", method=RequestMethod.GET) 
    public List<User> getUserList() { 
        // 處理"/users/"的GET請求卧檐,用來獲取用戶列表 
        // 還可以通過@RequestParam從頁面中傳遞參數(shù)來進行查詢條件或者翻頁信息的傳遞 
        List<User> r = new ArrayList<User>(users.values()); 
        return r; 
    } 
 
    @RequestMapping(value="/", method=RequestMethod.POST) 
    public String postUser(@ModelAttribute User user) { 
        // 處理"/users/"的POST請求墓懂,用來創(chuàng)建User 
        // 除了@ModelAttribute綁定參數(shù)之外,還可以通過@RequestParam從頁面中傳遞參數(shù) 
        users.put(user.getId(), user); 
        return "success"; 
    } 
 
    @RequestMapping(value="/{id}", method=RequestMethod.GET) 
    public User getUser(@PathVariable Long id) { 
        // 處理"/users/{id}"的GET請求霉囚,用來獲取url中id值的User信息 
        // url中的id可通過@PathVariable綁定到函數(shù)的參數(shù)中 
        return users.get(id); 
    } 
 
    @RequestMapping(value="/{id}", method=RequestMethod.PUT) 
    public String putUser(@PathVariable Long id, @ModelAttribute User user) { 
        // 處理"/users/{id}"的PUT請求捕仔,用來更新User信息 
        User u = users.get(id); 
        u.setName(user.getName()); 
        u.setAge(user.getAge()); 
        users.put(id, u); 
        return "success"; 
    } 
 
    @RequestMapping(value="/{id}", method=RequestMethod.DELETE) 
    public String deleteUser(@PathVariable Long id) { 
        // 處理"/users/{id}"的DELETE請求,用來刪除User 
        users.remove(id); 
        return "success"; 
    } 
 
}
編寫測試單元

參考文章:http://tengj.top/2017/12/28/springboot12/#Controller單元測試
看過這幾篇文章之后覺得好棒盈罐,還有這么方便的測試方法榜跌,這些以前都沒有接觸過...

下面針對該Controller編寫測試用例驗證正確性,具體如下盅粪。當然也可以通過瀏覽器插件等進行請求提交驗證钓葫,因為涉及一些包的導入,這里給出全部代碼:

package cn.wmyskxz.springboot;

import cn.wmyskxz.springboot.controller.UserController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


/**
 * @author: @我沒有三顆心臟
 * @create: 2018-05-29-上午 8:39
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MockServletContext.class)
@WebAppConfiguration
public class ApplicationTests {

    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
    }

    @Test
    public void testUserController() throws Exception {
        // 測試UserController
        RequestBuilder request = null;

        // 1票顾、get查一下user列表础浮,應(yīng)該為空
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[]")));

        // 2、post提交一個user
        request = post("/users/")
                .param("id", "1")
                .param("name", "測試大師")
                .param("age", "20");
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));

        // 3奠骄、get獲取user列表豆同,應(yīng)該有剛才插入的數(shù)據(jù)
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"測試大師\",\"age\":20}]")));

        // 4、put修改id為1的user
        request = put("/users/1")
                .param("name", "測試終極大師")
                .param("age", "30");
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));

        // 5含鳞、get一個id為1的user
        request = get("/users/1");
        mvc.perform(request)
                .andExpect(content().string(equalTo("{\"id\":1,\"name\":\"測試終極大師\",\"age\":30}")));

        // 6诱告、del刪除id為1的user
        request = delete("/users/1");
        mvc.perform(request)
                .andExpect(content().string(equalTo("success")));

        // 7、get查一下user列表民晒,應(yīng)該為空
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[]")));

    }

}

MockMvc實現(xiàn)了對HTTP請求的模擬精居,從示例的代碼就能夠看出MockMvc的簡單用法,它能夠直接使用網(wǎng)絡(luò)的形式潜必,轉(zhuǎn)換到Controller的調(diào)用靴姿,這樣使得測試速度快、不依賴網(wǎng)絡(luò)環(huán)境磁滚,而且提供了一套驗證的工具佛吓,這樣可以使得請求的驗證統(tǒng)一而且很方便宵晚。

需要注意的就是在MockMvc使用之前需要先用MockMvcBuilders構(gòu)建MockMvc對象,如果對單元測試感興趣的童鞋請戳上面的鏈接哦维雇,這里就不細說了

測試信息

運行測試類淤刃,控制臺返回的信息如下:

 __      __                               __
/\ \  __/\ \                             /\ \
\ \ \/\ \ \ \    ___ ___   __  __    ____\ \ \/'\    __  _  ____
 \ \ \ \ \ \ \ /' __` __`\/\ \/\ \  /',__\\ \ , <   /\ \/'\/\_ ,`\
  \ \ \_/ \_\ \/\ \/\ \/\ \ \ \_\ \/\__, `\\ \ \ \`\\/>  </\/_/  /_
   \ `\___x___/\ \_\ \_\ \_\/`____ \/\____/ \ \_\ \_\/\_/\_\ /\____\
    '\/__//__/  \/_/\/_/\/_/`/___/> \/___/   \/_/\/_/\//\/_/ \/____/
                               /\___/
                               \/__/
2018-05-29 09:28:18.730  INFO 5884 --- [           main] cn.wmyskxz.springboot.ApplicationTests   : Starting ApplicationTests on SC-201803262103 with PID 5884 (started by Administrator in E:\Java Projects\springboot)
2018-05-29 09:28:18.735  INFO 5884 --- [           main] cn.wmyskxz.springboot.ApplicationTests   : No active profile set, falling back to default profiles: default
2018-05-29 09:28:18.831  INFO 5884 --- [           main] o.s.w.c.s.GenericWebApplicationContext   : Refreshing org.springframework.web.context.support.GenericWebApplicationContext@7c37508a: startup date [Tue May 29 09:28:18 CST 2018]; root of context hierarchy
2018-05-29 09:28:19.200  INFO 5884 --- [           main] cn.wmyskxz.springboot.ApplicationTests   : Started ApplicationTests in 1.184 seconds (JVM running for 2.413)
2018-05-29 09:28:19.798  INFO 5884 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[PUT]}" onto public java.lang.String cn.wmyskxz.springboot.controller.UserController.putUser(java.lang.Long,cn.wmyskxz.springboot.pojo.User)
2018-05-29 09:28:19.800  INFO 5884 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/],methods=[GET]}" onto public java.util.List<cn.wmyskxz.springboot.pojo.User> cn.wmyskxz.springboot.controller.UserController.getUserList()
2018-05-29 09:28:19.800  INFO 5884 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/],methods=[POST]}" onto public java.lang.String cn.wmyskxz.springboot.controller.UserController.postUser(cn.wmyskxz.springboot.pojo.User)
2018-05-29 09:28:19.801  INFO 5884 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[DELETE]}" onto public java.lang.String cn.wmyskxz.springboot.controller.UserController.deleteUser(java.lang.Long)
2018-05-29 09:28:19.801  INFO 5884 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/users/{id}],methods=[GET]}" onto public cn.wmyskxz.springboot.pojo.User cn.wmyskxz.springboot.controller.UserController.getUser(java.lang.Long)
2018-05-29 09:28:19.850  INFO 5884 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.test.web.servlet.setup.StubWebApplicationContext@42f8285e
2018-05-29 09:28:19.924  INFO 5884 --- [           main] o.s.mock.web.MockServletContext          : Initializing Spring FrameworkServlet ''
2018-05-29 09:28:19.925  INFO 5884 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : FrameworkServlet '': initialization started
2018-05-29 09:28:19.926  INFO 5884 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : FrameworkServlet '': initialization completed in 1 ms

通過控制臺信息,我們得知通過 RESTful 風格能成功調(diào)用到正確的方法并且能獲取到或者返回正確的參數(shù)吱型,沒有任何錯誤逸贾,則說明成功!

如果你想要看到更多的細節(jié)信息津滞,可以在每次調(diào)用 perform() 方法后再跟上一句 .andDo(MockMvcResultHandlers.print()) 铝侵,例如:

        // 1、get查一下user列表触徐,應(yīng)該為空
        request = get("/users/");
        mvc.perform(request)
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("[]")))
                .andDo(MockMvcResultHandlers.print());

就能看到詳細的信息咪鲜,就像下面這樣:

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /users/
       Parameters = {}
          Headers = {}
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = cn.wmyskxz.springboot.controller.UserController
           Method = public java.util.List<cn.wmyskxz.springboot.pojo.User> cn.wmyskxz.springboot.controller.UserController.getUserList()

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = {Content-Type=[application/json;charset=UTF-8]}
     Content type = application/json;charset=UTF-8
             Body = []
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

總結(jié)

我們?nèi)匀皇褂?@RequestMapping 注解,但不同的是撞鹉,我們指定 method 屬性來處理不同的 HTTP 方法疟丙,并且通過 @PathVariable 注解來將 HTTP 請求中的屬性綁定到我們指定的形參上。

事實上鸟雏,Spring 4.3 之后享郊,為了更好的支持 RESTful 風格,增加了幾個注解:@PutMapping崔慧、@GetMapping拂蝎、@DeleteMapping穴墅、@PostMapping惶室,從名字也能大概的看出,其實也就是將 method 屬性的值與 @RequestMapping 進行了綁定而已玄货,例如皇钞,我們對UserController中的deleteUser方法進行改造:

-----------改造前-----------
@RequestMapping(value="/{id}", method=RequestMethod.DELETE)
public String deleteUser(@PathVariable Long id) {
    // 處理"/users/{id}"的DELETE請求,用來刪除User
    users.remove(id);
    return "success";
}

-----------改造后-----------
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
    // 處理"/users/{id}"的DELETE請求松捉,用來刪除User
    users.remove(id);
    return "success";
}

使用Swagger2構(gòu)造RESTful API文檔

參考文章:http://blog.didispace.com/springbootswagger2/

RESTful 風格為后臺與前臺的交互提供了簡潔的接口API夹界,并且有利于減少與其他團隊的溝通成本,通常情況下隘世,我們會創(chuàng)建一份RESTful API文檔來記錄所有的接口細節(jié)可柿,但是這樣做有以下的幾個問題:

  1. 由于接口眾多,并且細節(jié)復雜(需要考慮不同的HTTP請求類型丙者、HTTP頭部信息复斥、HTTP請求內(nèi)容等),高質(zhì)量地創(chuàng)建這份文檔本身就是件非常吃力的事械媒,下游的抱怨聲不絕于耳目锭。
  2. 隨著時間推移评汰,不斷修改接口實現(xiàn)的時候都必須同步修改接口文檔,而文檔與代碼又處于兩個不同的媒介痢虹,除非有嚴格的管理機制被去,不然很容易導致不一致現(xiàn)象。

Swagger2的出現(xiàn)就是為了解決上述的這些問題奖唯,并且能夠輕松的整合到我們的SpringBoot中去惨缆,它既可以減少我們創(chuàng)建文檔的工作量,同時說明內(nèi)容又可以整合到代碼之中去臭埋,讓維護文檔和修改代碼整合為一體踪央,可以讓我們在修改代碼邏輯的同時方便的修改文檔說明,這太酷了瓢阴,另外Swagger2頁提供了強大的頁面測試功能來調(diào)試每個RESTful API畅蹂,具體效果如下:

讓我們趕緊來看看吧:

第一步:添加Swagger2依賴:

pom.xml 中加入Swagger2的依賴:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.2.2</version>
</dependency>

第二步:創(chuàng)建Swagger2配置類

在SpringBoot啟動類的同級目錄下創(chuàng)建Swagger2的配置類 Swagger2

@Configuration
@EnableSwagger2
public class Swagger2 {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.wmyskxz.springboot"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot中使用Swagger2構(gòu)建RESTful APIs")
                .description("原文地址鏈接:http://blog.didispace.com/springbootswagger2/")
                .termsOfServiceUrl("http://blog.didispace.com/")
                .contact("@我沒有三顆心臟")
                .version("1.0")
                .build();
    }

}

如上面的代碼所示,通過 @Configuration 注解讓Spring來加載該配置類荣恐,再通過 @EnableSwagger2 注解來啟動Swagger2液斜;

再通過 createRestApi 函數(shù)創(chuàng)建 Docket 的Bean之后,apiInfo() 用來創(chuàng)建該API的基本信息(這些基本信息會展現(xiàn)在文檔頁面中)叠穆,select() 函數(shù)返回一個 ApiSelectorBuilder 實例用來控制哪些接口暴露給Swagger來展現(xiàn)少漆,本例采用指定掃描的包路徑來定義,Swagger會掃描該包下所有的Controller定義的API硼被,并產(chǎn)生文檔內(nèi)容(除了被 @ApiIgnore 指定的請求)

第三步:添加文檔內(nèi)容

在完成了上述配置后示损,其實已經(jīng)可以生產(chǎn)文檔內(nèi)容,但是這樣的文檔主要針對請求本身嚷硫,而描述主要來源于函數(shù)等命名產(chǎn)生检访,對用戶并不友好,我們通常需要自己增加一些說明來豐富文檔內(nèi)容仔掸。如下所示脆贵,我們通過@ApiOperation注解來給API增加說明、通過@ApiImplicitParams起暮、@ApiImplicitParam注解來給參數(shù)增加說明卖氨。

@RestController
@RequestMapping(value="/users")     // 通過這里配置使下面的映射都在/users下,可去除
public class UserController {

    static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());

    @ApiOperation(value="獲取用戶列表", notes="")
    @RequestMapping(value={""}, method=RequestMethod.GET)
    public List<User> getUserList() {
        List<User> r = new ArrayList<User>(users.values());
        return r;
    }

    @ApiOperation(value="創(chuàng)建用戶", notes="根據(jù)User對象創(chuàng)建用戶")
    @ApiImplicitParam(name = "user", value = "用戶詳細實體user", required = true, dataType = "User")
    @RequestMapping(value="", method=RequestMethod.POST)
    public String postUser(@RequestBody User user) {
        users.put(user.getId(), user);
        return "success";
    }

    @ApiOperation(value="獲取用戶詳細信息", notes="根據(jù)url的id來獲取用戶詳細信息")
    @ApiImplicitParam(name = "id", value = "用戶ID", required = true, dataType = "Long")
    @RequestMapping(value="/{id}", method=RequestMethod.GET)
    public User getUser(@PathVariable Long id) {
        return users.get(id);
    }

    @ApiOperation(value="更新用戶詳細信息", notes="根據(jù)url的id來指定更新對象负懦,并根據(jù)傳過來的user信息來更新用戶詳細信息")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "id", value = "用戶ID", required = true, dataType = "Long"),
            @ApiImplicitParam(name = "user", value = "用戶詳細實體user", required = true, dataType = "User")
    })
    @RequestMapping(value="/{id}", method=RequestMethod.PUT)
    public String putUser(@PathVariable Long id, @RequestBody User user) {
        User u = users.get(id);
        u.setName(user.getName());
        u.setAge(user.getAge());
        users.put(id, u);
        return "success";
    }

    @ApiOperation(value="刪除用戶", notes="根據(jù)url的id來指定刪除對象")
    @ApiImplicitParam(name = "id", value = "用戶ID", required = true, dataType = "Long")
    @RequestMapping(value="/{id}", method=RequestMethod.DELETE)
    public String deleteUser(@PathVariable Long id) {
        users.remove(id);
        return "success";
    }

}

完成上述代碼添加之后筒捺,啟動Spring Boot程序,訪問:http://localhost:8080/swagger-ui.html纸厉,就能看到前文展示的RESTful API的頁面系吭,我們可以點開具體的API請求,POST類型的/users請求為例残腌,可找到上述代碼中我們配置的Notes信息以及參數(shù)user的描述信息村斟,如下圖所示:

API文檔訪問與調(diào)試

在上圖請求的頁面中贫导,我們可以看到一個Value的輸入框,并且在右邊的Model Schema中有示例的User對象模板蟆盹,我們點擊右邊黃色的區(qū)域Value框中就會自動填好示例的模板數(shù)據(jù)孩灯,我們可以稍微修改修改,然后點擊下方的 “Try it out!” 按鈕逾滥,即可完成一次請求調(diào)用峰档,這太酷了。

總結(jié)

對比之前用文檔來記錄RESTful API的方式寨昙,我們通過增加少量的配置內(nèi)容讥巡,在原有代碼的基礎(chǔ)上侵入了忍受范圍內(nèi)的代碼,就可以達到如此方便舔哪、直觀的效果欢顷,可以說是使用Swagger2來對API文檔進行管理,是個很不錯的選擇捉蚤!

歡迎轉(zhuǎn)載抬驴,轉(zhuǎn)載請注明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關(guān)注公眾微信號:wmyskxz_javaweb
分享自己的Java Web學習之路以及各種Java學習資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缆巧,一起剝皮案震驚了整個濱河市布持,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌陕悬,老刑警劉巖题暖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捉超,居然都是意外死亡胧卤,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門狂秦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灌侣,“玉大人推捐,你說我怎么就攤上這事裂问。” “怎么了牛柒?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵堪簿,是天一觀的道長。 經(jīng)常有香客問我皮壁,道長椭更,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任蛾魄,我火速辦了婚禮虑瀑,結(jié)果婚禮上湿滓,老公的妹妹穿的比我還像新娘。我一直安慰自己舌狗,他們只是感情好叽奥,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痛侍,像睡著了一般朝氓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上主届,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天赵哲,我揣著相機與錄音,去河邊找鬼君丁。 笑死枫夺,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的绘闷。 我是一名探鬼主播筷屡,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼簸喂!你這毒婦竟也來了毙死?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤喻鳄,失蹤者是張志新(化名)和其女友劉穎扼倘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體除呵,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡再菊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了颜曾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纠拔。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泛豪,靈堂內(nèi)的尸體忽然破棺而出稠诲,到底是詐尸還是另有隱情,我是刑警寧澤诡曙,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布臀叙,位于F島的核電站,受9級特大地震影響价卤,放射性物質(zhì)發(fā)生泄漏劝萤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一慎璧、第九天 我趴在偏房一處隱蔽的房頂上張望床嫌。 院中可真熱鬧跨释,春花似錦、人聲如沸厌处。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘱蛋。三九已至蚯姆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洒敏,已是汗流浹背龄恋。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凶伙,地道東北人郭毕。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像函荣,于是被迫代替她去往敵國和親显押。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理傻挂,服務(wù)發(fā)現(xiàn)乘碑,斷路器,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,804評論 6 342
  • 一列布局金拒!設(shè)寬度兽肤!用margin: 0 auto; 兩列布局float:一個left 一個right 再清除浮動 ...
    加加大叔閱讀 249評論 0 0
  • 風擁抱了大海蔚藍 浪席卷千愁百緒 我緊握山的回聲 想抓住你的翅膀 在糾纏中 我突悟了蝶的夢想 千百年的掙扎 只為一...
    若魚_吻風閱讀 269評論 17 13
  • 看了一則廣告绪抛,說的是:8月6日“山水武隆”恩施出發(fā)武隆直通車二日游资铡,報名就送滑雪,體驗冰火兩重天的快感幢码,享受山...
    精進的醫(yī)生閱讀 408評論 0 1