springboot-整合mybatis,security

最近做项目用到springboot整合mybatis,security。将其中遇到的问题做一个总结

注:本项目全程无xml文件,使用注解来添加配置。

注:本项目使用Gradle进行构建。

build.gradle

buildscript {

ext {

springBootVersion ='1.5.6.RELEASE'

   }

repositories {

mavenCentral()

    }

dependencies {

classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")

      }

  }

applyplugin:'java'

applyplugin:'eclipse-wtp'

applyplugin:'org.springframework.boot'

applyplugin:'war'

version='0.0.1-SNAPSHOT'

sourceCompatibility =1.8

repositories {

mavenCentral()

     }

configurations {

providedRuntime

   }

dependencies {

compile('org.springframework.boot:spring-boot-starter-data-redis')

compile('org.springframework.boot:spring-boot-starter-freemarker')

compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1')

compile('org.springframework.boot:spring-boot-starter-security')

compile('org.springframework.boot:spring-boot-starter-thymeleaf')

compile('org.springframework.boot:spring-boot-starter-actuator')

compilegroup:'org.springframework.boot',name:'spring-boot-starter-mobile',version:'1.5.4.RELEASE'

compile("org.springframework.boot:spring-boot-devtools")

compile('org.apache.commons:commons-io:1.3.2')

compilegroup:'io.jsonwebtoken',name:'jjwt',version:'0.7.0'

compilegroup:'org.apache.commons',name:'commons-lang3',version:'3.6'

compilegroup:'commons-beanutils',name:'commons-beanutils',version:'1.9.3'

compilegroup:'org.apache.poi',name:'poi',version:'3.10.1'

compileOnly"org.projectlombok:lombok:1.16.16"

runtime('mysql:mysql-connector-java')

providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')

testCompile('org.springframework.boot:spring-boot-starter-test')

testCompile('org.springframework.security:spring-security-test')

}

本项目使用JWT实现security验证

application.yml如下

server:

     port:8080

     session:

            timeout:1800

     max-http-header-size:20971520

spring:

      jackson:

            date-format:yyyy-MM-dd HH:mm:ss

             time-zone:GMT+8

      datasource:

             url:jdbc:mysql://127.0.0.1:3306/captable

             username:dev

             password:GoAhead-1985

             driver-class-name:com.mysql.jdbc.Driver

jwt:

       header:Authorization

        secret:mySecret

        expiration:604800

        route:

              authentication:

                      path:auth

                      refresh:refresh

那么肉戏来了:怎么使用无xml实现mybatis

springboot已经整合mybatis,我们不用自己整合mybatis,只需编写Mapper了就好;

在interface  Mapper中使用注解

l 映射语句

@Insert,@Update,@Delete,@SeelctStatements

l 结果映射

           一对一映射

          一对多映射

l 动态SQL

         @SelectProvider

          @InsertProvider

         @UpdateProvider

          @DeleteProvider

 映射语句

MyBatis提供了多种注解来支持不同类型的语句(statement)如SELECT,INSERT,UPDATE,DELETE。让我们看一下具体怎样配置映射语句。

 1.@Insert

我们可以使用@Insert注解来定义一个INSERT映射语句:

[java]

packagecom.mybatis3.mappers;

public interface StudentMapper

{

@Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)

VALUES(#{studId},#{name},#{email},#{address.addrId},#{phone})")

int insertStudent(Student student);

}

使用了@Insert注解的insertMethod()方法将返回insert语句执行后影响的行数

自动生成主键

在上一章中我们讨论过主键列值可以自动生成。我们可以使用@Options注解的userGeneratedKeys 和keyProperty属性让数据库产生auto_increment(自增长)列的值,然后将生成的值设置到输入参数对象的属性中。

[java]

@Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)

VALUES(#{name},#{email},#{address.addrId},#{phone})")

@Options(useGeneratedKeys =true, keyProperty ="studId")

int insertStudent(Student student);

这里STUD_ID列值将会通过MySQL数据库自动生成。并且生成的值将会被设置到student对象的studId属性中。

[java]

StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

mapper.insertStudent(student);

int studentId = student.getStudId();

有一些数据库如Orcal,并不支持AUTO_INCREMENT列属性,它使用序列(SEQUENCE)来产生主键的值。

我们可以使用@SelectKey注解来为任意SQL语句来指定主键值,作为主键列的值。

假设我们有一个名为STUD_ID_SEQ的序列来生成STUD_ID主键值。

该项目使用mysql数据库,所以在该处使用的是uuid()函数.

[java]

@Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)

VALUES(#{studId},#{name},#{email},#{address.addrId},#{phone})")

@SelectKey(statement="SELECT STUD_ID_SEQ.NEXTVAL FROM DUAL",

keyProperty="studId", resultType=int.class, before=true)

int insertStudent(Student student);

这里我们使用了@SelectKey来生成主键值,并且存储到了student对象的studId属性上。由于我们设置了before=true,该语句将会在执行INSERT语句之前执行。

如果你使用序列作为触发器来设置主键值,我们可以在INSERT语句执行后,从sequence_name.currval获取数据库产生的主键值。

[java]

@Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)

VALUES(#{name},#{email},#{address.addrId},#{phone})")

@SelectKey(statement="SELECT STUD_ID_SEQ.CURRVAL FROM DUAL",

keyProperty="studId", resultType=int.class, before=false)

intinsertStudent(Student student);

2.@Update

我们可以使用@Update注解来定义一个UPDATE映射语句,如下所示:

[java]

@Update("UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email},

PHONE=#{phone} WHERE STUD_ID=#{studId}")

int updateStudent(Student student);

使用了@Update的updateStudent()方法将会返回执行了update语句后影响的行数。

[java]

StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

int noOfRowsUpdated = mapper.updateStudent(student);

3.@Delete

我们可以使用@Delete  注解来定义一个DELETE映射语句,如下所示:

[java]

@Delete("DELETE FROM STUDENTS WHERE STUD_ID=#{studId}")

int deleteStudent(int studId);

使用了@Delete的deleteStudent()方法将会返回执行了update语句后影响的行数。

4.@Select

我们可以使用@ Select注解来定义一个SELECT映射语句。

让我们看一下怎样使用注解配置一个简单的select查询。

[java]

packagecom.mybatis3.mappers;

publicinterfaceStudentMapper

{

@Select("SELECT STUD_ID AS STUDID, NAME, EMAIL, PHONE FROM

STUDENTS WHERE STUD_ID=#{studId}")

Student findStudentById(Integer studId);

}

为了将列名和Studentbean属性名匹配,我们为stud_id起了一个studId的别名。如果返回了多行结果,将抛出 TooManyResultsException异常。

结果映射

我们可以将查询结果通过别名或者是@Results注解与JavaBean属性映射起来。

现在让我们看看怎样使用@Results注解将指定列于指定JavaBean属性映射器来,执行SELECT查询的:

[java]

packagecom.mybatis3.mappers;

public interface StudentMapper

{

@Select("SELECT * FROM STUDENTS")

@Results(

{

@Result(id =true, column ="stud_id", property ="studId"),

@Result(column ="name", property ="name"),

@Result(column ="email", property ="email"),

@Result(column ="addr_id", property ="address.addrId")

})

List findAllStudents();

}

例如,看下面的findStudentById()和findAllStudents()方法:

[java]

@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")

@Results(

{

@Result(id =true, column ="stud_id", property ="studId"),

@Result(column ="name", property ="name"),

@Result(column ="email", property ="email"),

@Result(column ="addr_id", property ="address.addrId")

     })

Student findStudentById(int studId);

@Select("SELECT * FROM STUDENTS")

@Results(

{

@Result(id =true, column ="stud_id", property ="studId"),

@Result(column ="name", property ="name"),

@Result(column ="email", property ="email"),

@Result(column ="addr_id", property ="address.addrId")

        })

List findAllStudents();

这里两个语句的@Results配置完全相同,但是我必须得重复它。这里有一个解决方法。我们可以创建一个映射器Mapper配置文件, 然后配置元素,然后使用@ResultMap注解引用此。

在StudentMapper.xml中定义一个ID为StudentResult的。

在StudentMapper.java中,使用@ResultMap引用名为StudentResult的resultMap。

[java]

public interface StudentMapper

{

@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")

@ResultMap("com.mybatis3.mappers.StudentMapper.StudentResult")

Student findStudentById(intstudId);

@Select("SELECT * FROM STUDENTS")

@ResultMap("com.mybatis3.mappers.StudentMapper.StudentResult")

List findAllStudents();

 }

1 一对一映射

MyBatis提供了@One注解来使用嵌套select语句(Nested-Select)加载一对一关联查询数据。让我们看看怎样使用@One注解获取学生及其地址信息。

[java]

public interface StudentMapper

{

@Select("SELECT ADDR_ID AS ADDRID, STREET, CITY, STATE, ZIP, COUNTRY

FROM ADDRESSES WHERE ADDR_ID=#{id}")

Address findAddressById(intid);

@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId} ")

@Results(

{

@Result(id =true, column ="stud_id", property ="studId"),

@Result(column ="name", property ="name"),

@Result(column ="email", property ="email"),

@Result(property ="address", column ="addr_id",

one =@One(select = "com.mybatis3.mappers.StudentMapper.

findAddressById"))

  })

Student selectStudentWithAddress(intstudId);

}

这里我们使用了@One注解的select属性来指定一个使用了完全限定名的方法上,该方法会返回一个Address对象。使用column=”addr_id”,则STUEDNTS表中列addr_id的值将会作为输入参数传递给findAddressById()方法。如果@OneSELECT查询返回了多行结果,则会抛出TooManyResultsException异常。

[java]

intstudId =1;

StudentMapper studentMapper =

sqlSession.getMapper(StudentMapper.class);

Student student = studentMapper.selectStudentWithAddress(studId);

System.out.println("Student :"+student);

System.out.println("Address :"+student.getAddress());

我们可以通过基于XML的映射器配置,使用嵌套结果ResultMap来加载一对一关联的查询。而MyBatis3.2.2版本,并没有对应的注解支持。但是我们可以在映射器Mapper配置文件中配置并且使用@ResultMap注解来引用它。

在StudentMapper.xml中配置,如下所示:

[java]

public interface StudentMapper

{

@Select("select stud_id, name, email, a.addr_id, street, city, state, zip, country" + "FROM students s left outer join addresses a

on s.addr_id=a.addr_id" + "where stud_id=#{studId} ")

@ResultMap("com.mybatis3.mappers.StudentMapper.

StudentWithAddressResult")

Student selectStudentWithAddress(intid);

}

2 一对多映射

MyBatis提供了@Many注解,用来使用嵌套Select语句加载一对多关联查询。

现在让我们看一下如何使用@Many注解获取一个讲师及其教授课程列表信息:

[java]

public interface TutorMapper

{

@Select("select addr_id as addrId, street, city, state, zip,

country from addresses where addr_id=#{id}")

Address findAddressById(int id);

@Select("select * from courses where tutor_id=#{tutorId}")

@Results(

{

@Result(id =true, column ="course_id", property ="courseId"),

@Result(column ="name", property ="name"),

@Result(column ="description", property ="description"),

@Result(column ="start_date"property ="startDate"),

@Result(column ="end_date"property ="endDate")

    })

List findCoursesByTutorId(int tutorId);

@Select("SELECT tutor_id, name as tutor_name, email, addr_id

FROM tutors where tutor_id=#{tutorId}")

@Results(

{

@Result(id =true, column ="tutor_id", property ="tutorId"),

@Result(column ="tutor_name", property ="name"),

@Result(column ="email", property ="email"),

@Result(property ="address", column ="addr_id",

one =@One(select = " com.mybatis3.

mappers.TutorMapper.findAddressById")),

@Result(property ="courses", column ="tutor_id",

many =@Many(select = "com.mybatis3.mappers.TutorMapper.

findCoursesByTutorId"))

   })

Tutor findTutorById(inttutorId);

}

这里我们使用了@Many注解的select属性来指向一个完全限定名称的方法,该方法将返回一个List对象。使用column=”tutor_id”,TUTORS表中的tutor_id列值将会作为输入参数传递给findCoursesByTutorId()方法。

我们可以通过基于XML的映射器配置,使用嵌套结果ResultMap来加载一对多关联的查询。而MyBatis3.2.2版本,并没有对应的注解支持。但是我们可以在映射器Mapper配置文件中配置并且使用@ResultMap注解来引用它。

在TutorMapper.xml中配置,如下所示:

[java]

public interface TutorMapper

{

@Select("SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL,

A.ADDR_ID, STREET, CITY, STATE, ZIP, COUNTRY, COURSE_ID, C.NAME,

DESCRIPTION, START_DATE, END_DATE  FROM TUTORS T LEFT OUTER

JOIN ADDRESSES A ON T.ADDR_ID=A.ADDR_ID LEFT OUTER JOIN COURSES

C ON T.TUTOR_ID=C.TUTOR_ID WHERE T.TUTOR_ID=#{tutorId}")

@ResultMap("com.mybatis3.mappers.TutorMapper.TutorResult")

Tutor selectTutorById(int tutorId);

}

动态SQL

有时候我们需要根据输入条件动态地构建SQL语句。MyBatis提供了各种注解如@InsertProvider,@UpdateProvider,@DeleteProvider和@SelectProvider,来帮助构建动态SQL语句,然后让MyBatis执行这些SQL语句。

1 @SelectProvider

现在让我们来看一个使用@SelectProvider注解来创建一个简单的SELECT映射语句的例子。

创建一个TutorDynaSqlProvider.java类,以及findTutorByIdSql()方法,如下所示:

[java]

packagecom.mybatis3.sqlproviders;

importorg.apache.ibatis.jdbc.SQL;

publicclassTutorDynaSqlProvider

{

publicString findTutorByIdSql(inttutorId)

{

return"SELECT TUTOR_ID AS tutorId, NAME, EMAIL FROM TUTORS

WHERE TUTOR_ID=" + tutorId;

  }

}

在TutorMapper.java接口中创建一个映射语句,如下:

[java]

@SelectProvider(type=TutorDynaSqlProvider.class, method="findTutorByIdSql")

Tutor findTutorById(int tutorId);

这里我们使用了@SelectProvider来指定了一个类,及其内部的方法,用来提供需要执行的SQL语句。

但是使用字符串拼接的方法构建SQL语句是非常困难的,并且容易出错。所以MyBaits提供了一个SQL工具类不使用字符串拼接的方式,简化构造动态SQL语句。

现在,让我们看看如何使用org.apache.ibatis.jdbc.SQL工具类来准备相同的SQL语句。

[java] 

packagecom.mybatis3.sqlproviders;

import org.apache.ibatis.jdbc.SQL;

public class TutorDynaSqlProvider

{

publicString findTutorByIdSql(final int tutorId)

{

return new SQL(){

{

SELECT("tutor_id as tutorId, name, email");

FROM("tutors");

WHERE("tutor_id="+ tutorId);

    } } .toString();

 }

}

SQL工具类会处理以合适的空格前缀和后缀来构造SQL语句。

动态SQL provider方法可以接收以下其中一种参数:

1.无参数

2.映射器Mapper接口的方法同类型的参数

3. java.util.Map

如果SQL语句的准备不取决于输入参数,你可以使用不带参数的SQL Provider方法。

例如:

[java]

publicString findTutorByIdSql()

{

returnnewSQL()

{

{

SELECT("tutor_id as tutorId, name, email");

FROM("tutors");

WHERE("tutor_id = #{tutorId}");

   }

  } .toString();

}

这里我们没有使用输入参数构造SQL语句,所以它可以是一个无参方法。

如果映射器Mapper接口方法只有一个参数,那么可以定义SQLProvider方法,它接受一个与Mapper接口方法相同类型的参数。

例如映射器Mapper接口有如下定义:

[java]

Tutor findTutorById(int tutorId);

这里findTutorById(int)方法只有一个int类型的参数。我们可以定义findTutorByIdSql(int)方法作为SQL provider方法。

[java]

publicString findTutorByIdSql(final int tutorId)

{

returnnewSQL()

{

{

SELECT("tutor_id as tutorId, name, email");

FROM("tutors");

WHERE("tutor_id="+ tutorId);

   }

   } .toString();

}

如果映射器Mapper接口有多个输入参数,我们可以使用参数类型为java.util.Map的方法作为SQLprovider方法。然后映射器Mapper接口方法所有的输入参数将会被放到map中,以param1,param2等等作为key,将输入参数按序作为value。你也可以使用0,1,2等作为key值来取的输入参数。

[java]

@SelectProvider(type = TutorDynaSqlProvider.class,

method ="findTutorByNameAndEmailSql")

Tutor findTutorByNameAndEmail(String name, String email);

publicString findTutorByNameAndEmailSql(Map map)

{

String name = (String) map.get("param1");

String email = (String) map.get("param2");

//you can also get those values using 0,1 keys

//String name = (String) map.get("0");

//String email = (String) map.get("1");

returnnewSQL()

{

{

SELECT("tutor_id as tutorId, name, email");

FROM("tutors");

WHERE("name=#{name} AND email=#{email}");

  }

   } .toString();

}

SQL工具类也提供了其他的方法来表示JOINS,ORDER_BY,GROUP_BY等等。

让我们看一个使用LEFT_OUTER_JOIN的例子:

[java]

public class TutorDynaSqlProvider

{

public String selectTutorById()

{

returnnewSQL()

{

{

SELECT("t.tutor_id, t.name as tutor_name, email");

SELECT("a.addr_id, street, city, state, zip, country");

SELECT("course_id, c.name as course_name, description,

start_date, end_date");

FROM("TUTORS t");

LEFT_OUTER_JOIN("addresses a on t.addr_id=a.addr_id");

LEFT_OUTER_JOIN("courses c on t.tutor_id=c.tutor_id");

WHERE("t.TUTOR_ID = #{id}");

    }

    } .toString();

     }

   }

public interface TutorMapper

{

@SelectProvider(type = TutorDynaSqlProvider.class,

method ="selectTutorById")

@ResultMap("com.mybatis3.mappers.TutorMapper.TutorResult")

Tutor selectTutorById(int tutorId);

}

由于没有支持使用内嵌结果ResultMap的一对多关联映射的注解支持,我们可以使用基于XML的配置,然后与@ResultMap映射。

使用了动态的SQL provider,我们可以取得讲师及其地址和课程明细。

2 @InsertProvider

我们可以使用@InsertProvider注解创建动态的INSERT语句,如下所示:

[java]

public class TutorDynaSqlProvider

{

public String  insertTutor(finalTutor tutor)

{

return new SQL()

{

{

INSERT_INTO("TUTORS");

if(tutor.getName() !=null)

{

VALUES("NAME","#{name}");

     }

if(tutor.getEmail() !=null)

{

VALUES("EMAIL","#{email}");

   }

   }

   } .toString();

    }

    }

public interface TutorMapper

{

@InsertProvider(type = TutorDynaSqlProvider.class,

method ="insertTutor")

@Options(useGeneratedKeys =true, keyProperty ="tutorId")

int insertTutor(Tutor tutor);

}

3 @UpdateProvider

我们可以通过@UpdateProvider注解创建UPDATE语句,如下所示:

[java]

publicclassTutorDynaSqlProvider

{

publicString updateTutor(finalTutor tutor)

{

returnnewSQL()

{

{

UPDATE("TUTORS");

if(tutor.getName() !=null)

{

SET("NAME = #{name}");

   }

if(tutor.getEmail() !=null)

{

SET("EMAIL = #{email}");

   }

WHERE("TUTOR_ID = #{tutorId}");

    }

    } .toString();

    }

   }

public interface TutorMapper

{

@UpdateProvider(type = TutorDynaSqlProvider.class,

method ="updateTutor")

int updateTutor(Tutor tutor);

}

4 @DeleteProvider

我们可以使用@DeleteProvider注解创建动态地DELETE语句,如下所示:

[java]

publicclassTutorDynaSqlProvider

{

publicString deleteTutor(inttutorId)

{

returnnewSQL()

{

{

DELETE_FROM("TUTORS");

WHERE("TUTOR_ID = #{tutorId}");

    }

     } .toString();

     }

    }

publicinterfaceTutorMapper

{

@DeleteProvider(type = TutorDynaSqlProvider.class,

method ="deleteTutor")

intdeleteTutor(inttutorId);

}

作者:谁在烽烟彼岸

链接:https://www.jianshu.com/p/3e5e1f5a85d1

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 没有人生来完美,尽管世界在误解,也会有太阳每天照向你的脸庞,没有余晖将尽也无晨曦微露,我是一片云,只有遮住你的一片清凉
    一秋而冬阅读 189评论 0 0
  • 年后回来,感觉自己变了一些。之前看待一些问题不太成熟,自己有时容易被自己感动。或许,有些急功近利了。 闲了半个月,...
    蒋帅创客总部阅读 528评论 0 0
  • 寒风被醒雪一夜, 遥望远方传苦歌。 天公止住伤心泪, 换得人间春风乐。 —— 丁酉年癸丑月癸卯日
    愚仙阅读 171评论 0 0
  • 樓下附近的老人院,院邊有顆快要老死的老桑樹。聽說是活了好些年,至於多少年,沒人記得。真要往細了說年份,估摸...
    小半仙阅读 323评论 2 3