【SSM】Kisso實用教程

鏈接:Kisso實例項目

版本:1.4

官方文檔:kisso 幫助文檔

Maven依賴項

/pom.xml

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>1.2.12</version>
</dependency>       
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>kisso</artifactId>
    <version>3.6.6</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.9</version>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.54</version>
</dependency>

Spring MVC設(shè)置

/resource/spring-mvc.xml

<!--  kisso 注入初始化惑申,也支持使用 web.xml 初始化 -->
<bean id="kissoInit" class="com.baomidou.kisso.web.WebKissoConfigurer" init-method="initKisso">
    <property name="ssoPropPath" value="sso.properties" />
    <!-- 測試模式 衩椒,不同環(huán)境配置選擇設(shè)置 -->
    <property name="runMode" value="test_mode" />
    <!-- 此處可以注入 SSOConfig 配置屬性优幸,也可以定義自己的 kisso 插件茵烈,基礎(chǔ) SSOPlugin 抽象類。
    <property name="pluginList">
        <list>
            <bean name="com.xxxx.MyPlugin">
        </list>
        </property>
    -->
    </bean>
    <mvc:interceptors>
    <!-- SSO 攔截器 -->
    <!-- path 對所有的請求攔截使用/**绑警,對某個模塊下的請求攔截使用:/myPath/* -->
    <mvc:interceptor>
        <mvc:mapping path="/user/*" />
        <mvc:mapping path="/permission/*" />
        <bean class="com.baomidou.kisso.web.interceptor.SSOSpringInterceptor" />
    </mvc:interceptor>      
</mvc:interceptors>

Kisso設(shè)置

/resource/sso.properties

################ SSOConfig file #################
sso.encoding=utf-8
sso.secretkey=30eb4892122c45fd0f
sso.cookie.name=uid
sso.cookie.domain=.vcap.me
sso.login.url=http://ssm.vcap.me:8080/ssm/user/tologin

或者

################ SSOConfig file #################
sso.encoding=utf-8
sso.secretkey=30eb4892122c45fd0f
sso.cookie.name=uid
sso.cookie.domain=127.0.0.1
sso.login.url=http://127.0.0.1:8080/ssm/user/tologin

domain不能為localhost旬迹,可修改hosts使用自定義域名弄贿。

User映射設(shè)置

/mapper/userMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.UserMapper">
    <!-- 解決表名與字段不匹配 -->
    <resultMap type="User" id="userResultMap">
        <result property="userid" column="userid" />
        <result property="username" column="username" />
        <result property="password" column="password" />
    </resultMap>

    <!-- 查詢用戶是否存在 -->
    <select id="checkUserByUsername" resultType="int" parameterType="java.lang.String">
        select count(1) from user WHERE username=#{username}
    </select>

    <!-- 添加用戶 -->
    <insert id="addUser" parameterType="User">
        insert into user(username,
        password) values(#{username}, #{password})
    </insert>
    
    <!-- 獲取用戶信息 -->
    <select id="getUserInfoByName" resultType="User" parameterType="User">
        select * from user WHERE username=#{username}
    </select>
    
    <!-- 查詢所有用戶-->
    <select id="findAllUser" resultType="User">
        select * from user
    </select>
</mapper>

登錄時沟蔑,根據(jù)username湿诊,獲取User類狱杰。

加鹽密碼=MD5(用戶名+原密碼)

User映射接口

/mapper/UserMapper.java

public interface UserMapper {
    
    /**
     * 添加用戶
     * @param user 用戶
     * @return 修改的行數(shù)
     */
    int addUser(User user);
    
    /**
     * 查詢用戶是否存在
     * @param username 用戶名
     * @return 
     */
    int checkUserByUsername(String username);
    
    /**
     * 根據(jù)用戶名返回用戶信息
     * @param user 用戶名
     * @return 用戶信息
     */
    List<User> getUserInfoByName(User user);
    
    /**
     * 查詢所有用戶的信息
     * @return 用戶信息的表
     */
    List<User> findAllUser();
}

Java Bean

/model/User.java

帶有mybatis-plus.jar提供的注解瘦材,用于導(dǎo)出SQL語句。

import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
public class User {

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    /** 主鍵ID */
    @TableId
    private Long userid;

    private String username;
    private String password;;

    public User() {
        super();
    }

    public Long getId() {
        return userid;
    }

    public void setId(Long id) {
        this.userid = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

User服務(wù)接口

/service/UserService.java

注意:Controller中帶有Autowired注解的字段必須為接口仿畸。

public interface UserService {
    
    /**
     * 添加用戶
     * @param user 用戶
     * @return 修改的行數(shù)
     */
    int addUser(User user);
    
    /**
     * 查詢用戶是否存在
     * @param username 用戶名
     * @return 
     */
    boolean checkUserByUsername(String username);
    
    /**
     * 檢查用戶名和密碼是否合法
     * @param user 登錄信息
     * @return 成功則返回id食棕,失敗返回-1
     */
    long validUserAndPassword(User user);
    
    /**
     * 查詢所有用戶的信息
     * @return 用戶信息的表
     */
    List<User> findAllUser();
}

User服務(wù)實現(xiàn)

/service/impl/UserServiceImpl.java

@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Resource
    public UserMapper userMapper;

    @Override
    public int addUser(User user) {
        int userid = userMapper.addUser(user);
        return userid;
    }

    @Override
    public boolean checkUserByUsername(String username) {
        return userMapper.checkUserByUsername(username) == 1;
    }
    
    @Override
    public long validUserAndPassword(User user) {
        List<User> users = userMapper.getUserInfoByName(user);
        if (users.isEmpty()) {
            return -1;// 不存在
        }
        User info = users.get(0);
        if (SaltEncoder.md5SaltValid(user.getUsername(), info.getPassword(), user.getPassword())) {
            return info.getId();
        } else {
            return -1;// 不存在
        }
    }

    @Override
    public List<User> findAllUser() {
        return userMapper.findAllUser();
    }
}

用戶注冊

/controller/UserController.java

@Login(action = Action.Skip)
@RequestMapping(value = "/reg", method = RequestMethod.POST)
public @ResponseBody Map<String, Object> addUser(
    @RequestParam(value = "username") String username,
    @RequestParam(value = "password") String password) {
    Map<String, Object> map = new HashMap<String, Object>();
        if (userService.checkUserByUsername(username)) {
            User user = new User();
            user.setUsername(username);
            user.setPassword(SaltEncoder.md5SaltEncode(username, password));
            int id = userService.addUser(user);
            logger.debug(String.format("add user: id=%d name=%s", id, username));
            map.put("code", "200");
            map.put("msg", "注冊成功朗和!");
        } else {
            logger.warn(String.format("conflict user: name=%s", username));
            map.put("code", "400");
            map.put("msg", "用戶已存在!");
        }
        return map;
    }

SaltEncoder.md5SaltEncode(登錄名簿晓,原密碼)=> 返回哈希密碼

用戶登錄

/controller/UserController.java

@Login(action = Action.Skip)
@RequestMapping(value = "/login", method = RequestMethod.POST)
public @ResponseBody Map<String, Object> login(
    @RequestParam(value = "username") String username,
    @RequestParam(value = "password") String password,
    @RequestParam(value = "verify") String verify) {
        Map<String, Object> map = new HashMap<String, Object>();
        String verifyCode = String.valueOf(request.getSession().getAttribute("verify"));
        if (!verifyCode.equalsIgnoreCase(verify)) {
            map.put("code", "400");
            map.put("msg", "驗證碼錯誤");
            return map;
        }
        request.getSession().removeAttribute("verify");
        /**
         * 生產(chǎn)環(huán)境需要過濾sql注入
         */
        WafRequestWrapper req = new WafRequestWrapper(request);
        String username_ = req.getParameter("username");
        String password_ = req.getParameter("password");
        User user = new User();
        user.setUsername(username_);
        user.setPassword(password_);
        long userid = userService.validUserAndPassword(user);
        if (userid != -1) {
            logger.debug(String.format("login success: name=%s password=%s", username_, password_));
            map.put("code", "200");
            map.put("msg", "登錄成功眶拉!");

            /*
             * authSSOCookie 設(shè)置 cookie 同時改變 jsessionId
             */
            SSOToken st = new SSOToken(request);
            st.setId(userid);
            st.setUid(username_);
            st.setType(1);

            // 記住密碼,設(shè)置 cookie 時長 1 天 = 86400 秒 【動態(tài)設(shè)置 maxAge 實現(xiàn)記住密碼功能】
            /*
             * String rememberMe = req.getParameter("rememberMe"); if
             * ("on".equals(rememberMe)) {
             * request.setAttribute(SSOConfig.SSO_COOKIE_MAXAGE, 86400); }
             */
            request.setAttribute(SSOConfig.SSO_COOKIE_MAXAGE, -1);//瀏覽器關(guān)閉自動刪除cookie
            SSOHelper.setSSOCookie(request, response, st, true);
        } else {
            logger.warn(String.format("wrong login: name=%s password=%s", username_, password_));
            map.put("code", "400");
            map.put("msg", "您輸入的帳號或密碼有誤");
        }
        return map;
    }

登錄的邏輯:

  1. @RequestParam憔儿,規(guī)范參數(shù)格式
  2. 判斷驗證碼忆植,從Session中取
  3. WafRequestWrapper過濾SQL注入
  4. 將用戶名和密碼放入Java Bean,即User
  5. 調(diào)用UserService的驗證機制
  6. 查看結(jié)果
  7. 如果驗證失敗谒臼,則返回失敗
  8. 如果成功朝刊,則新建SSOToken,放入useridusername
  9. SSOHelper.setSSOCookie(request, response, st, true)完成SSO注冊
  10. 最后蜈缤,用戶的Cookie中拾氓,有一項uid是加密的,保存了用戶的useridusername

注意點:

  • 為什么是uid底哥? sso.properties中的sso.cookie.name
  • 怎么加密咙鞍?密鑰在sso.properties中的sso.secretkey

驗證機制

控制器

/controller/UserController.java

/**
 * 驗證碼 (注解跳過權(quán)限驗證)
 */
@Login(action = Action.Skip)
@ResponseBody
@RequestMapping("/verify")
public void verify() {
    try {
        String verifyCode = CaptchaUtil.outputImage(response.getOutputStream());
        request.getSession().setAttribute("verify", verifyCode);//把驗證碼存入session
        logger.debug(String.format("verify code: %s", verifyCode));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

注意點:

  • 地址是 #{controller}/verify,設(shè)為Action.Skip趾徽,因為任何人都可以獲取驗證碼续滋,不寫則默認為Action.Normal啟用認證。

  • 將驗證碼的明文存入Session中孵奶,待驗證登錄時取出吃粒。

繪圖

引用自SpringWind
來自CaptchaHelper.java中的/com/utils/CaptchaUtil.java拒课。

public class CaptchaUtil {
    public static String outputImage(OutputStream out) throws IOException {
            ConfigurableCaptchaService cs = new ConfigurableCaptchaService();
            //驗證碼寬高
            cs.setWidth(85);
            cs.setHeight(35);
            
            //設(shè)置 6 位自適應(yīng)驗證碼
    //      AdaptiveRandomWordFactory arw = new AdaptiveRandomWordFactory();
    //      arw.setMinLength(6);
    //      arw.setMaxLength(6);
    //      cs.setWordFactory(arw);
            
            //字符大小設(shè)置
            RandomFontFactory rf = new RandomFontFactory();
            rf.setMinSize(25);
            rf.setMaxSize(28);
            cs.setFontFactory(rf);
            
            //文本渲染
    //      cs.setTextRenderer(new RandomYBestFitTextRenderer());
            
            //設(shè)置一個單一顏色字體
            cs.setColorFactory(new SingleColorFactory(new Color(59, 162, 9)));
    //      cs.setFilterFactory(new CurvesRippleFilterFactory(cs.getColorFactory()));
    
            
            //圖片濾鏡設(shè)置
            ConfigurableFilterFactory filterFactory = new ConfigurableFilterFactory();
            List<BufferedImageOp> filters = new ArrayList<BufferedImageOp>();
            
            //擺動干擾
            WobbleImageOp wio = new WobbleImageOp();
            wio.setEdgeMode(AbstractImageOp.EDGE_CLAMP);
            wio.setxAmplitude(2.0);
            wio.setyAmplitude(1.0);
            filters.add(wio);
    
            //曲線干擾
    //      CurvesImageOp cio = new CurvesImageOp();
    //      cio.setColorFactory(new SingleColorFactory(new Color(59, 162, 9)));
    //      cio.setEdgeMode(AbstractImageOp.EDGE_ZERO);
    //      cio.setStrokeMax(0.3f);
    //      cio.setStrokeMin(0.1f);
    //      filters.add(cio);
            
            filterFactory.setFilters(filters);
            cs.setFilterFactory(filterFactory);
            
            //橢圓形干擾背景
    //      cs.setBackgroundFactory(new OvalNoiseBackgroundFactory(7));
            
            //線形干擾背景
            cs.setBackgroundFactory(new LineNoiseBackgroundFactory(37));
            
            //輸出驗證圖片
            return EncoderHelper.getChallangeAndWriteImage(cs, "png", out);
        }
}

HTML

/WebContent/jsp/login.jsp

引入js / html

<script src="${js_root}/js/jquery-1.11.1.js"></script>
<script src="${js_root}/js/jquery.validate.min.js"></script>
<script src="${js_root}/js/messages_zh.js"></script>

gup取參函數(shù) / js

function gup(name) {
        name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
        var regexS = "[\\?&]" + name + "=([^&#]*)";
        var regex = new RegExp(regexS);
        var results = regex.exec(location.pathname);
        if (results == null) {
            return location.pathname;
        } else {
            return results[1];
        }
    }

初始化驗證 / js

$(document).ready(function() {
        // validate the comment form when it is submitted
        $("#signupForm").validate({
            rules : {
                username : {
                    required : true,
                    minlength : 2,
                },
                password : {
                    required : true,
                    minlength : 6
                },
                verify : {
                    required : true,
                    minlength : 4
                }
            },
            messages : {
                username : {
                    required : "請輸入用戶名",
                    minlength : "用戶名至少由兩個字符組成"
                },
                password : {
                    required : "請輸入密碼",
                    minlength : "密碼長度不能小于 6 個字符"
                },
                verify : {
                    required : "請輸入驗證碼",
                    minlength : "驗證碼長度為4個字符"
                }
            }
        });
    });

設(shè)置提交方式 / js

$.validator.setDefaults({
        submitHandler : function() {
            $.post(
            // 接收數(shù)據(jù)的頁面
            'login',
            // 傳給后臺的數(shù)據(jù)徐勃,多個參數(shù)用&連接或者使用json格式數(shù)據(jù):{a:'value1',b:'value2'}
            {
                username : $("#username").val(),
                password : $("#password").val(),
                verify : $("#verify").val()
            }, function(data) {
                if (data.code == '200') {
                    alert("msg: " + data.msg + "\n" + "即將跳轉(zhuǎn)疼蛾。");
                    location.href = gup("ReturnURL");
                } else if (data.code == '400') {
                    alert(data.msg);
                    location.reload();
                }
            },
            // 默認返回字符串症杏,設(shè)置值等于json則返回json數(shù)據(jù)
            'json').error(function() {
                alert("登錄失敗,請稍后再試缅叠。");
            });
        }
    });

設(shè)置表單 / html

<form class="cmxform" id="signupForm" method="post" action="login">
    <fieldset>
        <legend>請輸入你的用戶名和密碼</legend>
        <p>
            <label for="cusername">用戶名</label> <input id="username"
                name="username" type="text">
        </p>
        <p>
            <label for="cpassword">密碼</label> <input id="password"
                name="password" type="password">
        </p>
        <p>
            <label for="cverify">驗證碼</label> <input id="verify" name="verify"
                type="text"> <img id="verifyImg"
                onclick="javascript:this.src=('verify?reload='+(new Date()).getTime())"
                src="verify" width="85" height="35" alt="點擊查看驗證碼">
        </p>
        <p>
            <input class="reset" type="reset" value="重置"> <input
                class="submit" type="submit" value="登錄">
        </p>
    </fieldset>
</form>

代碼驗證 / java

/controller/UserController.java

@Login(action = Action.Skip)
@RequestMapping(value = "/login", method = RequestMethod.POST)
    public @ResponseBody Map<String, Object> login(
    @RequestParam(value = "username") String username,
    @RequestParam(value = "password") String password,
    @RequestParam(value = "verify") String verify) {
        Map<String, Object> map = new HashMap<String, Object>();
        String verifyCode = String.valueOf(request.getSession().getAttribute("verify"));
        if (!verifyCode.equalsIgnoreCase(verify)) {
            map.put("code", "400");
            map.put("msg", "驗證碼錯誤");
            return map;
        }
        request.getSession().removeAttribute("verify");
        // 其他登錄認證機制...
    }

登出

/controller/UserController.java

@RequestMapping(value = "/logout")
public String logout() {
    /**
     * <p>
     * SSO 退出卢鹦,清空退出狀態(tài)即可
     * </p>
     * 
     * <p>
     * 子系統(tǒng)退出 SSOHelper.logout(request, response); 注意 sso.properties 包含 退出到
     * SSO 的地址 臀脏, 屬性 sso.logout.url 的配置
     * </p>
     */
    SSOToken st = SSOHelper.getToken(request);
    if (st != null) {
        logger.debug(String.format("logout: id=%d, uid=%s", st.getId(), st.getUid()));
    }
    SSOHelper.clearLogin(request, response);
    return "redirect:/";
}

觸發(fā)登出事件:利用<a href='logout'></a>即可。

注意點:

  • 使用SSOHelper.clearLogin(request, response)

重定向

/WebContent/jsp/login.jsp

添加地址取參函數(shù)

function gup(name) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regexS = "[\\?&]" + name + "=([^&#]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(location.pathname);
    if (results == null) {
        return location.pathname;
    } else {
        return results[1];
    }
}

跳轉(zhuǎn)回登錄前的頁面

Kisso攔截器將未授權(quán)訪問重定向至登錄頁冀自,帶ReturnURL參數(shù)揉稚,存放跳轉(zhuǎn)前地址,登錄成功后熬粗,自動跳回搀玖。

if (data.code == '200') {
    alert("msg: " + data.msg + "\n" + "即將跳轉(zhuǎn)。");
    location.href = gup("ReturnURL");
} else if (data.code == '400') {
    alert(data.msg);
    location.reload();
}

注意點:

  • 返回200時驻呐,為成功灌诅,跳轉(zhuǎn)
  • 返回400時芳来,為失敗,刷新頁面

登出的重定向

點擊鏈接登出時猜拾,服務(wù)器返回302重定向即舌。

HTML

<p><a href="tologout">登出</a></p>

JAVA

@RequestMapping(value = "/logout")
public String logout() {
    // SSO清理工作
    // ...
    return "redirect:/";
}

注意點:

  • 適用ajax。瀏覽器中的js跳轉(zhuǎn)挎袜,地址可以從服務(wù)器寫顽聂,如/ssm
  • 適用a href。服務(wù)器的302盯仪、301跳轉(zhuǎn)芜飘,Controller方法返回String,值為"redirect:path/to/redirect"

顯示用戶名

/WebContent/jsp/index.jsp

/WebContent/jsp/permission.jsp

HTML

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- other -->
<p>${ userid }磨总,歡迎光臨嗦明!</p>

JAVA

SSOToken st = SSOHelper.getToken(request);
if (st != null) {
    request.setAttribute("userid", st.getUid());
}
return "/index";

注意點:

  • 模版引擎除了JSTL外還有Velocity等。Velocity充分體現(xiàn)了的MVC思想蚪燕。
  • 顯示用戶名的流程娶牌。

MVC簡易流程:

  1. 控制層:利用Kisso獲取用戶信息,放入模型馆纳。
  2. 模型層:存放诗良、傳遞數(shù)據(jù)。
  3. 視圖層:根據(jù)模型鲁驶,解析數(shù)據(jù)鉴裹,渲染頁面。

常見問題

ContextLoader類不存在

項目 -> 屬性 -> Web Deployment Assembly
Add => Java Build Path Entries => Maven Dependencies

缺少類

修改pom.xml钥弯,然后Update Project径荔。

常用解決辦法

  • 清理Tomcat目錄
  • 重啟Tomcat
  • Classpath路徑問題,增加JRE脆霎、Tomcat总处、Maven、Web App Lib
  • Web Module問題睛蛛,在項目屬性中的Project Facets
  • 修改容器名稱鹦马,即localhost:8080/????,項目屬性中的Web Project Settings
  • Java文件錯誤忆肾,修改Java Compiler荸频,即編譯器版本
  • 注意文件名和路徑的大小寫
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市客冈,隨后出現(xiàn)的幾起案子旭从,更是在濱河造成了極大的恐慌,老刑警劉巖郊酒,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遇绞,死亡現(xiàn)場離奇詭異,居然都是意外死亡燎窘,警方通過查閱死者的電腦和手機摹闽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褐健,“玉大人付鹿,你說我怎么就攤上這事⊙裂福” “怎么了舵匾?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谁不。 經(jīng)常有香客問我坐梯,道長,這世上最難降的妖魔是什么刹帕? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任吵血,我火速辦了婚禮,結(jié)果婚禮上偷溺,老公的妹妹穿的比我還像新娘蹋辅。我一直安慰自己,他們只是感情好挫掏,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布侦另。 她就那樣靜靜地躺著,像睡著了一般尉共。 火紅的嫁衣襯著肌膚如雪褒傅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天袄友,我揣著相機與錄音樊卓,去河邊找鬼。 笑死杠河,一個胖子當(dāng)著我的面吹牛碌尔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播券敌,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼唾戚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了待诅?” 一聲冷哼從身側(cè)響起叹坦,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卑雁,沒想到半個月后募书,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绪囱,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年莹捡,在試婚紗的時候發(fā)現(xiàn)自己被綠了鬼吵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡篮赢,死狀恐怖齿椅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情启泣,我是刑警寧澤涣脚,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站寥茫,受9級特大地震影響遣蚀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纱耻,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一妙同、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膝迎,春花似錦粥帚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至卖漫,卻和暖如春费尽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背羊始。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工旱幼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人突委。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓柏卤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匀油。 傳聞我的和親對象是個殘疾皇子缘缚,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

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