利用Spring_Boot WebSocKet實現(xiàn)一個推送的小Demo

利用Spring_Boot WebSocKet實現(xiàn)一個推送的小Demo

簡介:

文章共分為兩個模塊旦部,包括全局推送功能的實現(xiàn),和點對點的推送功能實現(xiàn)忌堂,源代碼在文末給出連接

模塊一:全局推送

1. 新建項目喘漏,利用引導創(chuàng)建

新建時采用引導的方式,下面給出的是幾張重點截圖白嘁,其他的過程截圖省略,

選擇項目類型

選擇Web 下的 WebSocket

選擇Template… 下的 Thymeleaf

以上是根據(jù)引導來創(chuàng)建Spring_Boot WebSocket項目過程中的幾個主要界面,其余界面較為普遍膘流,不加以介紹絮缅,詳細的pom.xml文件內(nèi)容此處不提供鲁沥,在點對點模塊有整體的pom.xml文件。

2. 配置WebSocketConfig

具體內(nèi)容看代碼注釋盟蚣,
注:測試全局推送的時候可將代碼中的“/socket”節(jié)點相關(guān)信息注釋掉黍析,也可去一對一模塊尋找對應(yīng)的攔截器代碼

//WebSocketConfig配置文件
@Configuration
@EnableWebSocketMessageBroker  //開啟使用STOMP協(xié)議來傳輸基于代理的消息
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
    //注冊STOMP協(xié)議的節(jié)點,并指定映射的URL
    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        /**
         * 注冊STOMP協(xié)議節(jié)點屎开,同時指定使用SockJS協(xié)議阐枣,在前端界面中會使用該處的節(jié)點進行SocKet連接
         * 根據(jù)需要注冊即可,這里因為只是測試奄抽,注冊了3個節(jié)點
         * 另外蔼两,在第三個節(jié)點中,可以看到比前兩個節(jié)點多了一些內(nèi)容逞度,制定了一個攔截器及其他信息
         * 這個根據(jù)需要來擴展额划,攔截器是定義的一個class文件,該攔截器在demo中主要用在點對點模塊档泽,因本人也是初次接觸這部分內(nèi)容俊戳,不太了解。
         */
        stompEndpointRegistry.addEndpoint("/endpointSang").withSockJS();
        stompEndpointRegistry.addEndpoint("/endpointChat").withSockJS();
        stompEndpointRegistry.addEndpoint("/socket").withSockJS()
                .setInterceptors( new ChatHandlerShareInterceptor())
                .setStreamBytesLimit(512 * 1024)
                .setHttpMessageCacheSize(1000)
                .setDisconnectDelay(30 * 1000);
    }
    //配置消息代理
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        /**
         * 配置消息代理馆匿,前端通過這個代理來進行消息訂閱,
         * 消息代理可以有一個抑胎,也可以有多個,用 “渐北,” 號分隔
         * 這里配置了兩個阿逃,"/topic"用作全局推送,"/queue"用做點對點使用
         */
        registry.enableSimpleBroker("/queue","/topic");
        /**
         * 配置接收前端信息的消息代理赃蛛,前端通過這個代理來向后臺傳遞消息
         * 簡單來說恃锉,前端通過這個消息代理訪問對應(yīng)controller中的MessageMapping(value)
         */
        registry.setApplicationDestinationPrefixes("/app");
        //registry.setPathMatcher(new AntPathMatcher("."));
    }

    @Override
    public void configureWebSocketTransport(final WebSocketTransportRegistration registration) {
        registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
            @Override
            public WebSocketHandler decorate(final WebSocketHandler handler) {
                return new WebSocketHandlerDecorator(handler) {
                    @Override
                    public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
                        // 客戶端與服務(wù)器端建立連接后,此處記錄誰上線了
                        String username = session.getPrincipal().getName();
                        //log.info("online: " + username);
                        System.out.println("online: " + username);
                        super.afterConnectionEstablished(session);
                    }

                    @Override
                    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
                        // 客戶端與服務(wù)器端斷開連接后呕臂,此處記錄誰下線了
                        String username = session.getPrincipal().getName();
                       // log.info("offline: " + username);
                        System.out.println("offline: " + username);
                        super.afterConnectionClosed(session, closeStatus);
                    }
                };
            }
        });
        super.configureWebSocketTransport(registration);
    }
}
3. 建立實體類

分別建立兩個實體類破托,一個用于封裝向瀏覽器發(fā)送的消息,一個用于封裝接受來自瀏覽器的消息

/**
 * 接收來自瀏覽器的消息
 */
public class  RequestMessage {
    private String name;
    public String getName() {
        return name;
    }
}
/**
 * 保存服務(wù)器發(fā)給瀏覽器的信息
 */
public class ResponseMessage {
    private String responseMessage;
    public ResponseMessage(String responseMessage) {
        this.responseMessage = responseMessage;
    }
    public String getResponseMessage() {
        return responseMessage;
    }
}
4. Controller代碼

控制器歧蒋,用來控制頁面的跳轉(zhuǎn)和推送服務(wù)的使用

/**
 * 控制器
 */
@Controller
public class WsController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;   //實現(xiàn)向瀏覽器發(fā)送信息的功能
    
    //轉(zhuǎn)到ws.html界面,ws是一個測試界面土砂,既可以發(fā)布消息也可以接受推送
@RequestMapping(value = "/ws")
    public String tows(){
        return "/ws";
    }

    /**
     * 進行公告推送,此處只用注解疏尿,不使用注解請參考點對點中的@MessageMapping("/chat1")對應(yīng)具體方法
     * @MessageMapping("/welcome") 對應(yīng)ws.html中的stompClient.send("/app/welcome")
     * 多出來的“/app"是WebSocKetConfig中定義的,如不定義瘟芝,則HTML中對應(yīng)改為stompClient.send("/welcome")
     * @SendTo("/topic/getResponse") 指定訂閱路徑易桃,對應(yīng)HTML中的stompClient.subscribe('/topic/getResponse', ……)
     * 意味將信息推送給所有訂閱了"/topic/getResponse"的用戶
     */
    @MessageMapping("/welcome")
    @SendTo("/topic/getResponse")       //指明發(fā)布路徑
    public ResponseMessage say(RequestMessage message) {
        System.out.println(message.getName());
        return new ResponseMessage("welcome," + message.getName() + " !");
    }
}
5. 前端ws.html

前提:添加腳本褥琐,將stomp.js、sockjs.min.js 以及jqury.js 腳本放在src/main/resources/static下晤郑〉谐剩可在這里下載贸宏。
代碼:

<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>廣播式WebSocket</title>
    <script th:src="@{js/sockjs.min.js}"></script>
    <script th:src="@{js/stomp.js}"></script>
    <script th:src="@{js/jquery-3.1.1.js}"></script>
</head>
<body onload="disconnect()">
<noscript><h2 style="color: #e80b0a;">Sorry,瀏覽器不支持WebSocket</h2></noscript>
<div>
    <div>
        <button id="connect" onclick="connect();">連接</button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">斷開連接</button>
    </div>
    <div id="conversationDiv">
        <label>輸入你的名字</label><input type="text" id="name"/>
        <button id="sendName" onclick="sendName();">發(fā)送</button>
        <p id="response"></p>
    </div>
</div>
<script type="text/javascript">
    var stompClient = null;
    function setConnected(connected) {
        document.getElementById("connect").disabled = connected;
        document.getElementById("disconnect").disabled = !connected;
        document.getElementById("conversationDiv").style.visibility = connected ? 'visible' : 'hidden';
//        $("#connect").disabled = connected;
//        $("#disconnect").disabled = !connected;
        $("#response").html();
    }
    //點擊連接按鈕后執(zhí)行
    function connect() {
        //指明連接的節(jié)點磕洪,節(jié)點在WebSocKetConfig中配置
        var socket = new SockJS('/endpointSang');
        //利用Stomp協(xié)議創(chuàng)建socket客戶端
        stompClient = Stomp.over(socket);
        /**
         * 調(diào)用stompClient中的connect方法來連接服務(wù)端吭练,
         * 連接成功之后調(diào)用setConnected方法,該隱藏的隱藏析显,該顯示的顯示
         */
        stompClient.connect({}, function (frame) {
            setConnected(true);
            console.log('Connected:' + frame);
            //調(diào)用stompClient中的subscribe方法來訂閱/topic/getResponse發(fā)送來的消息
            stompClient.subscribe('/topic/getResponse', function (response) {
                showResponse(JSON.parse(response.body).responseMessage);
            })
        });
    }
    function disconnect() {
        if (stompClient != null) {
            //斷開連接
            stompClient.disconnect();
        }
        setConnected(false);
        console.log('Disconnected');
    }
    function sendName() {
        var name = $('#name').val();
        console.log('name:' + name);
        /**
         * 發(fā)送一條消息到服務(wù)端鲫咽,"/app/welcome"由WebSocketConfig中配置的“/app"
         * 和控制器中對應(yīng)的MessageMapping("welcome")
         */
        stompClient.send("/app/welcome", {}, JSON.stringify({'name': name}));
    }
    //顯示消息
    function showResponse(message) {
        $("#response").html(message);
    }
</script>
</body>
</html>
6. 啟動入口DemoApplication.Java

自動生成的入口代碼。不做更改

@SpringBootApplication
public class DemoApplication {

   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
   }
}
7. 結(jié)果測試

啟動該工程谷异,在瀏覽器中訪問 http://localhost:8080/ws 打開兩個或多個分尸,連接后在其中一個界面輸入內(nèi)容,點擊“發(fā)送”后可在所有的界面看到相關(guān)信息歹嘹,證明全局推送成功箩绍。

效果圖如下:

全局推送測試

模塊二:點對點推送

在進行點對點推送的開發(fā)時,考慮到點對點里面的每個“點”都需要有一個唯一的標識符尺上,所以在項目中引入了pring-boot –security材蛛,用來對每個“點”進行唯一的標識。

1. pom.xml

本項目采用maven的構(gòu)建方式怎抛,下面是項目的pom.xml文件內(nèi)容

<?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>cn.tourage</groupId>
   <artifactId>demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>demo</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.3.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-websocket</artifactId>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>

      <!--增加的security-->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-security</artifactId>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>


</project>
2.創(chuàng)建WebSecurityConfig

配置WebSecurityConfig.java卑吭,設(shè)置攔截規(guī)則,初始化用戶及其角色

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //設(shè)置攔截規(guī)則
                .antMatchers("/","/login")   ////1根路徑和/login路徑不攔截
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                //開啟默認登錄頁面
                .formLogin()
                //默認登錄頁面
                .loginPage("/login")
                //默認登錄成功跳轉(zhuǎn)頁面
                .defaultSuccessUrl("/chat")
                .permitAll()
                .and()
                //設(shè)置注銷
                .logout()
                .permitAll();
    }
    //設(shè)置用戶
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("111").password("111").roles("USER")
                .and()
                .withUser("222").password("222").roles("USER")
                .and()
                .withUser("333").password("333").roles("USER")
                .and()
                .withUser("444").password("444").roles("USER");
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        //設(shè)置不攔截規(guī)則
        web.ignoring().antMatchers("/resources/static/**");
    }
} 
3. 創(chuàng)建WebSocketConfig

該類與上一模塊相同抽诉,其中陨簇,在設(shè)置STOMP節(jié)點時引入了一個攔截器功能,具體見“/socket”節(jié)點及相關(guān)注釋信息

@Configuration
@EnableWebSocketMessageBroker  //開啟使用STOMP協(xié)議來傳輸基于代理的消息
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    //注冊STOMP協(xié)議的節(jié)點迹淌,并指定映射的URL
    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        /**
         * 注冊STOMP協(xié)議節(jié)點河绽,同時指定使用SockJS協(xié)議,在前端界面中會使用該處的節(jié)點進行SocKet連接
         * 根據(jù)需要注冊即可唉窃,這里因為只是測試耙饰,注冊了3個節(jié)點
         * 另外,在第三個節(jié)點中纹份,可以看到比前兩個節(jié)點多了一些內(nèi)容苟跪,制定了一個攔截器及其他信息,該攔截器在demo中主要用在點對點模塊
         * 這個根據(jù)需要來擴展蔓涧,攔截器是定義的一個class文件件已,因本人也是初次接觸這部分內(nèi)容,不太了解元暴。
         */
        stompEndpointRegistry.addEndpoint("/endpointSang").withSockJS();
        stompEndpointRegistry.addEndpoint("/endpointChat").withSockJS();
        stompEndpointRegistry.addEndpoint("/socket").withSockJS()
                .setInterceptors( new ChatHandlerShareInterceptor())
                .setStreamBytesLimit(512 * 1024)
                .setHttpMessageCacheSize(1000)
                .setDisconnectDelay(30 * 1000);
    }

    //配置消息代理
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        /**
         * 配置消息代理篷扩,前端通過這個代理來進行消息訂閱,
         * 消息代理可以有一個,也可以有多個茉盏,用 “鉴未,” 號分隔
         * 這里配置了兩個枢冤,"/topic"用作全局推送,"/queue"用做點對點使用
         */
        registry.enableSimpleBroker("/queue","/topic");
        /**
         * 配置接收前端信息的消息代理铜秆,前端通過這個代理來向后臺傳遞消息
         * 簡單來說淹真,前端通過這個消息代理訪問對應(yīng)controller中的MessageMapping(value)
         */
        registry.setApplicationDestinationPrefixes("/app");
        //registry.setPathMatcher(new AntPathMatcher("."));
    }

    @Override
    public void configureWebSocketTransport(final WebSocketTransportRegistration registration) {
        registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
            @Override
            public WebSocketHandler decorate(final WebSocketHandler handler) {
                return new WebSocketHandlerDecorator(handler) {
                    @Override
                    public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
                        // 客戶端與服務(wù)器端建立連接后,此處記錄誰上線了
                        String username = session.getPrincipal().getName();
                        //log.info("online: " + username);
                        System.out.println("online: " + username);
                        super.afterConnectionEstablished(session);
                    }

                    @Override
                    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
                        // 客戶端與服務(wù)器端斷開連接后连茧,此處記錄誰下線了
                        String username = session.getPrincipal().getName();
                       // log.info("offline: " + username);
                        System.out.println("offline: " + username);
                        super.afterConnectionClosed(session, closeStatus);
                    }
                };
            }
        });
        super.configureWebSocketTransport(registration);
    }
}
4.配置攔截器

攔截器功能可通過自己定義來實現(xiàn)具體的功能核蘸,此處的攔截器并未在測試項目中有太多價值的用法,增加該功能只是為了在具體使用“點對點推送”功能時進行擴展啸驯。

/**
 * websocket攔截器配置值纱,讀取session.
 */
public class ChatHandlerShareInterceptor extends HttpSessionHandshakeInterceptor {

    private static Logger logger = LoggerFactory.getLogger(ChatHandlerShareInterceptor.class);

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                               Exception ex) {
        // TODO Auto-generated method stub
        super.afterHandshake(request, response, wsHandler, ex);
    }

    @Override
    public boolean beforeHandshake(ServerHttpRequest arg0, ServerHttpResponse arg1, WebSocketHandler arg2,
                                   Map<String, Object> arg3) throws Exception {

        if(arg0 instanceof ServletServerHttpRequest){
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) arg0;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            if (session != null) {
                //使用userName區(qū)分WebSocketHandler,以便定向發(fā)送消息
                String httpSessionId = session.getId();
                logger.info(httpSessionId);
                arg3.put("HTTP_SESSION_ID",httpSessionId);
            }else{
            }
        }
        return true;
    }
}
5. Controller控制器
/**
 * 控制器
 */
@Controller
public class WsController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;   //實現(xiàn)向瀏覽器發(fā)送信息的功能

    //  轉(zhuǎn)到相關(guān)的HTML界面
    @RequestMapping(value = "/login")
    public String toLogin() {
        return "/login";
    }

@RequestMapping(value = "/chat")
public String toChat() {
    return "/chat";
}

    /**
     * 轉(zhuǎn)到消息推送坯汤,對應(yīng)前端chat.html中的stomp.send("/chat1",{},text)
     * 此處未使用注解虐唠,而是使用代碼編程來完成推送,目的是為了方便對批量(可理解為分組)推送進行擴展
     * 編程實現(xiàn)消息推送,點對點使用convertAndSendToUser惰聂,廣播使用convertAndSendTo
     * 使用注解可參考全局推送里面的具體實現(xiàn)疆偿,注:點對點用SendToUser()來代替SendTo()
     */
    @MessageMapping("/chat1")
//    可以直接在參數(shù)中獲取Principal,Principal中包含有當前用戶的用戶名搓幌。
    public void handleChat(Principal principal, String msg) {
        List<String> list = new ArrayList<String>();
        list.add("222");
        list.add("333");
        list.add("444");
        if (principal.getName().equals("111")) {
            for (String s : list)
            /**
             * 第一個參數(shù):用戶名杆故;第二個參數(shù):訂閱路徑;第三個參數(shù):要推送的消息文本
             * 注:全局推送對應(yīng)方法有兩個參數(shù)溉愁,第一個參數(shù):訂閱路徑处铛;第二個參數(shù):要推送的消息文本
             */
                messagingTemplate.convertAndSendToUser(s, "/queue/notifications", principal.getName() + "給您發(fā)來了消息:" + msg);
        } else {
            messagingTemplate.convertAndSendToUser("111", "/queue/notifications", principal.getName() + "給您發(fā)來了消息:" + msg);
        }
    }
}
6. 前端界面

這部分使用兩個前端界面,一個簡單的登錄界面login.html拐揭,還有一個聊天界面chat.html撤蟆,在chat.html中同時可以使用發(fā)布信息和接受推送的功能,可按照實際需求進行自我定制
注:在前端編寫的時候一定要記得添加相關(guān)的js文件堂污,下載地址:(http://pan.baidu.com/s/1gfKJCAJ)

Login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<meta charset="UTF-8" />
<head>
    <title>登陸頁面</title>
</head>
<body>
<div th:if="${param.error}">
    無效的賬號和密碼
</div>
<div th:if="${param.logout}">
    你已注銷
</div>
<form th:action="@{/login}" method="post">
    <div><label> 賬號 : <input type="text" name="username"/> </label></div>
    <div><label> 密碼: <input type="password" name="password"/> </label></div>
    <div><input type="submit" value="登陸"/></div>
</form>
</body>
</html>

Chat.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>聊天室</title>
    <script th:src="@{js/sockjs.min.js}"></script>
    <script th:src="@{js/stomp.js}"></script>
    <script th:src="@{js/jquery-3.1.1.js}"></script>
</head>
<body>
<p>聊天室</p>
<form id="sangForm">
    <textarea rows="4" cols="60" name="text"></textarea>
    <input type="submit" value="發(fā)送"/>
</form>
<script th:inline="javascript">
    $("#sangForm").submit(function (e) {
        e.preventDefault();
        var textArea = $("#sangForm").find('textarea[name="text"]');
        var text = textArea.val();
        sendSpittle(text);
        textArea.val('');
    });
    var sock = new SockJS("/endpointChat");
    var stomp = Stomp.over(sock);
    /**
     * 連接服務(wù)端家肯,成功后注冊監(jiān)聽,注冊地址為/user/queue/notifications
     * 監(jiān)聽地址比WebSocket配置文件中的多了一個/user盟猖,這個/user是必不可少的讨衣,使用了它消息才會點對點傳送。
     */
    stomp.connect('guest','guest',function (frame) {
        stomp.subscribe("/user/queue/notifications", handleNotification);
    });
    function handleNotification(message) {
        $("#output").append("<b>Received: "+message.body+"</b><br/>")
    }
    /**
     * 消息轉(zhuǎn)發(fā)式镐,對應(yīng)相關(guān)controller中的MessageMapping("/chat1")
     * 增加的“/app”源于WebSocketConfig中配置的結(jié)果
     * @param text
     */
    function sendSpittle(text) {
        stomp.send("/app/chat1", {}, text);
    }
//  關(guān)閉連接
    $("#stop").click(function () {
        sock.close();
    });
</script>
<div id="output"></div>
</body>
</html>
7. 啟動入口:

略反镇,項目默認的啟動界面

8. 結(jié)果測試

采用兩個瀏覽器分別登入兩個用戶,輸入測試可看到如下結(jié)果
注意:一定要使用兩個瀏覽器登錄娘汞,不然會默認為同一個Session

Paste_Image.png
項目地址:

https://github.com/AnRic/WebSocket_demo
GitHub新人歹茶,求關(guān)注

寫在最后

本文是我在參考網(wǎng)上資源并親身實踐后編寫的一篇小文章,做一個簡單的學習記錄,也希望能幫助到大家辆亏。在其中可能也會有一些的不足之處,請包容鳖目。如有疑問扮叨,請發(fā)送私信聯(lián)系我,謝謝领迈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末彻磁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子狸捅,更是在濱河造成了極大的恐慌衷蜓,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尘喝,死亡現(xiàn)場離奇詭異磁浇,居然都是意外死亡,警方通過查閱死者的電腦和手機朽褪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門置吓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缔赠,你說我怎么就攤上這事衍锚。” “怎么了嗤堰?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵戴质,是天一觀的道長。 經(jīng)常有香客問我踢匣,道長告匠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任离唬,我火速辦了婚禮凫海,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘男娄。我一直安慰自己行贪,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布模闲。 她就那樣靜靜地躺著建瘫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尸折。 梳的紋絲不亂的頭發(fā)上啰脚,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音,去河邊找鬼橄浓。 笑死粒梦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的荸实。 我是一名探鬼主播匀们,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼准给!你這毒婦竟也來了泄朴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤露氮,失蹤者是張志新(化名)和其女友劉穎祖灰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畔规,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡局扶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了叁扫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片详民。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖陌兑,靈堂內(nèi)的尸體忽然破棺而出沈跨,到底是詐尸還是另有隱情,我是刑警寧澤兔综,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布饿凛,位于F島的核電站,受9級特大地震影響软驰,放射性物質(zhì)發(fā)生泄漏涧窒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一锭亏、第九天 我趴在偏房一處隱蔽的房頂上張望纠吴。 院中可真熱鬧,春花似錦慧瘤、人聲如沸戴已。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糖儡。三九已至,卻和暖如春怔匣,著一層夾襖步出監(jiān)牢的瞬間握联,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留金闽,地道東北人纯露。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像代芜,于是被迫代替她去往敵國和親埠褪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,108評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理蜒犯,服務(wù)發(fā)現(xiàn),斷路器荞膘,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫罚随、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,102評論 4 62
  • 一羽资、核心驅(qū)動力是銷售與其他職業(yè)最重要的不同點淘菩。 銷售工作的業(yè)績考核都是以簽約為衡量指標,沒有簽約屠升, 銷售工作就沒有...
    陽光_sunshine閱讀 569評論 0 2
  • 一潮改、緣由 過年期間參加了老師舉辦的經(jīng)典共讀活動,由易仁永澄老師帶著我們一起閱讀《如何閱讀一本書》這本書閱讀屆的圣經(jīng)...
    紫微閱讀 339評論 0 0