Kotlin+SpringBoot+MyBatisPlus完美搭建最簡潔最酷的前后端分離框架

一、為什么學(xué)習(xí)(初衷):

1.1 初衷

  • 1、經(jīng)過調(diào)研很多培訓(xùn)機(jī)構(gòu)大量投入課程的產(chǎn)出,說明在新一代的編程領(lǐng)域有一定的地位芳绩,它前程應(yīng)用一定會(huì)有更好的廣泛的使用。

  • 2昆箕、阿里p3c掃描代碼ReView插件底層大量使用了Kotlin進(jìn)行實(shí)際開發(fā)

  • 3鸦列、經(jīng)過調(diào)研SpringBoot2.以上的全家桶很多組件底層框架和Spring5.x版本用到kotlin支持開發(fā)(擁抱Kotlin)。

  • 4鹏倘、在2017年在朋友圈偶遇它的影子薯嗤,2018年準(zhǔn)備學(xué)習(xí)kotlin相關(guān)的熱身工作,在學(xué)習(xí)過程中纤泵,把犧牲個(gè)人周末休息時(shí)間骆姐,嘗試把公司的Spring Cloud工程改寫成Kotlin框架镜粤。

  • 5、當(dāng)然了現(xiàn)在學(xué)習(xí)和了解它玻褪,不一定現(xiàn)在就要用它肉渴,主要目的是為以后做準(zhǔn)備。

  • 6带射、作為技術(shù)人員同规,希望為kotlin的生態(tài)作出一份渺小的貢獻(xiàn)。

  • 7窟社、華為鴻蒙操作系統(tǒng)發(fā)布會(huì)都提到kotlin!

1.2 應(yīng)用場(chǎng)景

  • Spring5.x暗示什么呢
  • 阿里p3c掃描代碼ReView插件
  • Kotlin&Spring5.x
  • 鴻蒙發(fā)布會(huì)
Spring5.x暗示什么呢券勺?
阿里p3c掃描代碼ReView插件
Kotlin&Spring5.x
鴻蒙發(fā)布會(huì)

二、技術(shù)選型

Kotlin框架.png
  • 解析器:JSOUP灿里、FastJSON
  • 開發(fā)工具:JDK1.8 关炼、Maven 、Eclipse
  • 技術(shù)框架:SpringBoot
  • ORM技術(shù):MyBatisPlus
  • 數(shù)據(jù)庫:MySQL
  • Apache 工具:HttpClient匣吊、Lang3
  • Git代碼版本控制

三儒拂、kotlin背景簡要描述

  • Kotlin 是一種在 Java 虛擬機(jī)上運(yùn)行的靜態(tài)類型編程語言,被稱之為 Android 世界的Swift缀去,由 JetBrains 設(shè)計(jì)開發(fā)并開源侣灶。Kotlin 可以編譯成Java字節(jié)碼,也可以編譯成 JavaScript缕碎,方便在沒有 JVM 的設(shè)備上運(yùn)行褥影。
  • 立太子:在Google I/O 2017中,Google 宣布 Kotlin 成為 Android 官方開發(fā)語言
  • Kotlin支持表達(dá)式語法編程非常友好,以簡潔代碼等眾多方面強(qiáng)大功能咏雌,吸引很多開發(fā)者的青睞凡怎。

官方網(wǎng)站
kotlin手冊(cè)

四、Spring Boot 發(fā)展路線簡要描述

  • 隨著動(dòng)態(tài)語言的流行 (Ruby赊抖、Groovy统倒、Scala、Node.js)氛雪,Java 的開發(fā)顯得格外的笨重:繁多的配置房匆、低下的開發(fā)效率、復(fù)雜的部署流程以及第三方技術(shù)集成難度大报亩。
  • 在上述環(huán)境下浴鸿,Spring Boot 應(yīng)運(yùn)而生。它使用“習(xí)慣優(yōu)于配置”(項(xiàng)目中存在大量的配置弦追,此外還內(nèi)置了一個(gè)習(xí)慣性的配置岳链,讓你無需手動(dòng)進(jìn)行配置)的理念讓你的項(xiàng)目快速的運(yùn)行起來。使用 Spring Boot 很容易創(chuàng)建一個(gè)獨(dú)立運(yùn)行(運(yùn)行 Jar劲件,內(nèi)嵌 Servlet 容器)準(zhǔn)生產(chǎn)級(jí)別的基于 Spring框架的項(xiàng)目掸哑,使用 Spring Boot 你可以不用或者只需很少的 Spring 配置约急。

4.1 SpringBoot插件使用

  • spring-boot-starter-actuator actuator是監(jiān)控系統(tǒng)健康情況的工具
  • spring-boot-devtools 實(shí)現(xiàn)熱部署,實(shí)際開發(fā)過程中,修改應(yīng)用的業(yè)務(wù)邏輯時(shí)常常需要重啟應(yīng)用苗分,這顯得非常繁瑣厌蔽,降低了開發(fā)效率,所以熱部署對(duì)于開發(fā)來說顯得十分必要了
  • spring-boot-starter-aop 此插件沒什么好說的了,aop是spring的兩大功能模塊之一俭嘁,功能非常強(qiáng)大躺枕,為解耦提供了非常優(yōu)秀的解決方案。如:面向方面編程
  • spring-boot-starter-tomcat spring boot 內(nèi)置Tomcat插件
  • spring-boot-starter-test 測(cè)試工具
  • mybatis-spring-boot-starter spring boot整合MyBatis的jar
  • spring-boot-maven-plugin Spring Boot Maven plugin能夠?qū)pring Boot應(yīng)用打包為可執(zhí)行的jar或war文件供填,然后以通常的方式運(yùn)行Spring Boot應(yīng)用拐云。

五、Kotlin插件

  • kotlin-stdlib-jdk8 這個(gè)是kotlin的標(biāo)準(zhǔn)jar近她,也就是最基礎(chǔ)的一個(gè)工程叉瘩,底層封裝了大量kotlin的語法和實(shí)現(xiàn)調(diào)用邏輯,也是最復(fù)雜的一個(gè)工程‘引入工程會(huì)出現(xiàn)這三個(gè)jar工程
    kotlin-stdlib-jdk8-1.2.20.jar
    kotlin-stdlib-1.2.20.jar
    kotlin-stdlib-jdk7-1.2.20.jar

  • kotlin-reflect reflect 顧名思義就是反射工程了

六粘捎、jsoup簡要

  • jsoup 是一款Java 的HTML解析器薇缅,可直接解析某個(gè)URL地址、HTML文本內(nèi)容攒磨。它提供了一套非常省力的API泳桦,可通過DOM,CSS以及類似于jQuery的操作方法來取出和操作數(shù)據(jù)娩缰。
  • 個(gè)人認(rèn)為jsoup是最好的解析器灸撰,在很多場(chǎng)景都能見到他的影子,不單只可以解析HTML所有結(jié)構(gòu),還可以解析XML拼坎,在做爬蟲器最為廣泛浮毯。

官方網(wǎng)站 https://jsoup.org/
深入理解Jsoup解析器API與實(shí)際運(yùn)用

七、fastJson

  • 阿里JSON解析器

詳細(xì)文檔請(qǐng)看官方 https://github.com/alibaba/fastjson

八泰鸡、HttpClient

  • HttpClient 是Apache Jakarta Common 下的子項(xiàng)目债蓝,可以用來提供高效的、最新的盛龄、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包饰迹,并且它支持 HTTP 協(xié)議最新的版本。

官方網(wǎng)站

九余舶、Maven

  • Apache Maven是一個(gè)軟件項(xiàng)目管理和理解工具蹦锋。 基于項(xiàng)目對(duì)象模型(POM)的概念,Maven可以從中央信息管理項(xiàng)目的構(gòu)建欧芽,報(bào)告和文檔。

  • 官方網(wǎng):http://maven.apache.org/

  • 學(xué)習(xí)博客: https://yq.aliyun.com/articles/28591

十葛圃、MyBatis-Plus

十一憎妙、工程準(zhǔn)備

  • 方法一、打開Eclipse Marketplace-->>輸入kotlin在線安裝即可


    Kotlin在線安裝
  • 方法二曲楚、下載離線安裝包(不提供教程厘唾,說明有這種方式)


    工程選擇插件安裝
  • 方法三、可以使用優(yōu)秀的kotlin編輯器eg: IntelliJ IDEA

十二龙誊、工程結(jié)構(gòu)

工程結(jié)構(gòu)1
工程結(jié)構(gòu)2
工程結(jié)構(gòu)3

十三抚垃、工程代碼結(jié)構(gòu)

  • SpringBoot入口類
@EnableAsync
@Configuration
@EnableScheduling
@EnableAutoConfiguration  //啟用讀取配置
@ComponentScan("com.flong.kotlin")  //掃描com.flong文件目錄下
@SpringBootApplication(scanBasePackages = ["com.flong.kotlin"] )
//@SpringBootApplication(scanBasePackages = arrayOf("com.flong.kotlin")) 這種寫法也OK
open class Application {
   @Bean
   open fun jspViewResolver(): InternalResourceViewResolver {
       var resolver = InternalResourceViewResolver();
       resolver.setPrefix("/WEB-INF/views/");
       resolver.setSuffix(".jsp");
       return resolver;
   }
   //靜態(tài)類
   companion object {

       /**啟動(dòng)SpringBoot的主入口.*/
       @JvmStatic fun main(args: Array<String>) {
           //*args的星號(hào)表示引用相同類型的變量
           SpringApplication.run(Application::class.java, *args)
       }
   }
}

  • Maven 的pom依賴的jar工程配置

Maven 的pom依賴的jar工程配置

  • WebCofig工具類統(tǒng)一處理配置

  • 消息轉(zhuǎn)換器,中文亂碼,Long的精度長度問題趟大,時(shí)間格式等問題
  • cors 跨域支持 可以用@CrossOrigin在controller上單獨(dú)設(shè)置
  • 統(tǒng)一處理請(qǐng)求URL攔截器
@Configuration
@ConditionalOnClass(WebMvcConfigurer::class)
@Order(Ordered.HIGHEST_PRECEDENCE)
open class WebConfig : WebMvcConfigurer{

    constructor() : super()

    @Bean
    open fun customConverters(): HttpMessageConverters {
        //創(chuàng)建fastJson消息轉(zhuǎn)換器
        var fastJsonConverter = FastJsonHttpMessageConverter()
        //創(chuàng)建配置類
        var fastJsonConfig = FastJsonConfig()
        //修改配置返回內(nèi)容的過濾
        fastJsonConfig.setSerializerFeatures(
                // 格式化
                SerializerFeature.PrettyFormat,
                // 可解決long精度丟失 但會(huì)有帶來相應(yīng)的中文問題
                //SerializerFeature.BrowserCompatible,
                // 消除對(duì)同一對(duì)象循環(huán)引用的問題鹤树,默認(rèn)為false(如果不配置有可能會(huì)進(jìn)入死循環(huán))
                SerializerFeature.DisableCircularReferenceDetect,
                // 是否輸出值為null的字段,默認(rèn)為false
                SerializerFeature.WriteMapNullValue,
                // 字符類型字段如果為null,輸出為"",而非null
                SerializerFeature.WriteNullStringAsEmpty,
                // List字段如果為null,輸出為[],而非null
                SerializerFeature.WriteNullListAsEmpty
        )
        // 日期格式
        fastJsonConfig.dateFormat = "yyyy-MM-dd HH:mm:ss"
        
        // long精度問題
        var serializeConfig = SerializeConfig.globalInstance
        serializeConfig.put(Integer::class.java, ToStringSerializer.instance)
        serializeConfig.put(BigInteger::class.java, ToStringSerializer.instance)
        serializeConfig.put(Long::class.java, ToStringSerializer.instance)
        serializeConfig.put(Long::class.javaObjectType, ToStringSerializer.instance)
        fastJsonConfig.setSerializeConfig(serializeConfig)
        
        //處理中文亂碼問題
        var fastMediaTypes = ArrayList<MediaType>()
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8)
        fastMediaTypes.add(MediaType(MediaType.TEXT_HTML, Charset.forName("UTF-8")))
        fastMediaTypes.add(MediaType(MediaType.TEXT_PLAIN, Charset.forName("UTF-8")))
        fastMediaTypes.add(MediaType(MediaType.APPLICATION_FORM_URLENCODED, Charset.forName("UTF-8")))
        fastMediaTypes.add(MediaType.MULTIPART_FORM_DATA)
        
        fastJsonConverter.setSupportedMediaTypes(fastMediaTypes)
        fastJsonConverter.setFastJsonConfig(fastJsonConfig)
        //將fastjson添加到視圖消息轉(zhuǎn)換器列表內(nèi)
        return HttpMessageConverters(fastJsonConverter)
    }

    /**
     * 攔截器
     */
    open override fun addInterceptors(registry: InterceptorRegistry) {
        //registry.addInterceptor(logInterceptor).addPathPatterns("/**")
        //registry.addInterceptor(apiInterceptor).addPathPatterns("/**")
    }

    /**
     * cors 跨域支持 可以用@CrossOrigin在controller上單獨(dú)設(shè)置
     */
    open override fun addCorsMappings(registry: CorsRegistry) {
        registry.addMapping("/**")
                //設(shè)置允許跨域請(qǐng)求的域名
                .allowedOrigins("*")
                //設(shè)置允許的方法
                .allowedMethods("*")
                //設(shè)置允許的頭信息
                .allowedHeaders("*")
                //是否允許證書 不再默認(rèn)開啟
                .allowCredentials(java.lang.Boolean.TRUE)
    }
}
  • BaseServcie的業(yè)務(wù)邏輯基類的封裝
  • 【注意】:BaseServcie里面必須要加上構(gòu)造方法 constructor() : super()實(shí)現(xiàn)ServiceImpl底層的代碼,但有些不需要你實(shí)現(xiàn)逊朽,來解決內(nèi)部報(bào) (This type has a constructor, and thus must be initialized here) 錯(cuò)誤
  • 截圖
    ServiceImpl底層的代碼罕伯,
  • 單冒號(hào)的作用:在Kotlin編程里面單個(gè)冒號(hào)代表實(shí)現(xiàn)接口或繼承父類接口的作用。如下代碼:
//BaseService實(shí)現(xiàn)ServiceImpl
BaseService<M : BaseMapper<T>, T, Q : Query> : ServiceImpl<M, T> {
open class BaseService<M : BaseMapper<T>, T, Q : Query> : ServiceImpl<M, T> {
    //構(gòu)造方法
    constructor() : super()
    var IN_SIZE: Int = 1000;
    //新增
    fun add(obj: T): Boolean {
        var affCnt = baseMapper.insert(obj);
        return null != affCnt && affCnt > 0;
    }
       //更加id去更新數(shù)據(jù)
    override fun updateById(obj: T): Boolean {
        var affCnt = baseMapper.updateById(obj);
        return null != affCnt && affCnt > 0;
    }
    /**
     * 刪除
     */
    override fun deleteById(id: Serializable): Boolean {
        var affCnt = baseMapper.deleteById(id);
        return null != affCnt && affCnt > 0;
    }
    /**
     * ID 取對(duì)象,取不到為空
     */
    fun get(id: Serializable): T {
        return baseMapper.selectById(id);
    }
    fun getMapper(): M {
        return this.baseMapper;
    }
    fun buildQuery(query: Q): EntityWrapper<T> {
        return EntityWrapper<T>();
    }
}
@Configuration
@MapperScan(basePackages = arrayOf(DataSourceConfig.PACKAGE), sqlSessionFactoryRef = "sessionFactory")
open class DataSourceConfig  {
    //靜態(tài)常量
    companion object {
        //const 關(guān)鍵字用來修飾常量叽讳,且只能修飾  val追他,不能修飾var,  companion object 的名字可以省略,可以使用 Companion來指代
         const val  PACKAGE = "com.flong.kotlin.*.mapper";
         const val TYPEALIASESPACKAGE = "com.flong.kotlin.modules.entity";
    }

    final var MAPPERLOCATIONS = "classpath*:mapper/*.xml";
    @Primary
    @Bean("dataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    open fun dataSource(): DataSource {
        return DruidDataSource();
    }


    @Bean
    open fun transactionManager(@Qualifier("dataSource") dataSource: DataSource): DataSourceTransactionManager {
        return DataSourceTransactionManager(dataSource);
    }

    @Bean
    open fun sessionFactory(dataSource: DataSource):SqlSessionFactory {
        //===1.mybatis-plus globalConfig配置
        var cfg =  GlobalConfiguration();
        
        // 字段的駝峰下劃線轉(zhuǎn)換
        cfg.setDbColumnUnderline(true);
        // 全局主鍵策略
        cfg.setIdType(IdType.AUTO.key);
        // 全局邏輯刪除
        cfg.sqlInjector         = LogicSqlInjector()
        cfg.logicDeleteValue    = "1"
        cfg.logicNotDeleteValue = "0"
    
        //===2.構(gòu)造sessionFactory(mybatis-plus)
        var sf = MybatisSqlSessionFactoryBean();
        sf.setDataSource(dataSource);
        sf.setMapperLocations(PathMatchingResourcePatternResolver().getResources(MAPPERLOCATIONS));
        sf.setGlobalConfig(cfg);
        sf.setTypeAliasesPackage(TYPEALIASESPACKAGE) 
        // 分頁插件
        sf.setPlugins(arrayOf(PaginationInterceptor()))
        //請(qǐng)注意:這種return sf.getObject();與return sf.`object`寫法完全一樣,但是object是kotiln的關(guān)鍵字岛蚤,所以要用 【單引號(hào)】引起來
        return sf.`object`
    }
    
    /**
     * @Description 初始化druid
     * @Author      liangjl
     * @Date        2018年1月17日 下午4:52:05
     * @return 參數(shù)
     * @return ServletRegistrationBean 返回類型 
     */
    @Bean
    open fun druidServlet() : ServletRegistrationBean<Servlet>{
        var bean:ServletRegistrationBean<Servlet> = ServletRegistrationBean(StatViewServlet(), "/druid/*") ;
        /** 初始化參數(shù)配置邑狸,initParams**/
        //登錄查看信息的賬號(hào)密碼.
        bean.addInitParameter("loginUsername", "root");
        bean.addInitParameter("loginPassword", "root");
        //IP白名單 (沒有配置或者為空,則允許所有訪問)
        bean.addInitParameter("allow", "");
        //IP黑名單 (共存時(shí)涤妒,deny優(yōu)先于allow) : 如果滿足deny的話提示:Sorry, you are not permitted to view this page.
        bean.addInitParameter("deny", "192.88.88.88");
        //禁用HTML頁面上的“Reset All”功能
        bean.addInitParameter("resetEnable", "false");
        return bean;
    }
    @Bean
    open fun filterRegistrationBean() : FilterRegistrationBean<Filter>{
        var filterRegistrationBean =   FilterRegistrationBean<Filter>()
        filterRegistrationBean.setFilter(WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

}
  • 分頁工具類PageVO

open class PageVO<E>  {
    /**
     * 總條數(shù)
     */
    var total: Long? = null
    /**
     * 查詢結(jié)果
     */
    var records: Collection<E>? = null
    /**
     * 當(dāng)前頁
     */
    var page: Int? = null
    /**
     * 總頁數(shù)
     */
    var totalPage: Long? = null
    /**
     * 每頁顯示條數(shù)
     */
    var pageSize: Int? = null
    //init { }

    //多重構(gòu)造方法
    constructor(records: Collection<E>?, pageVO: PageVO<E>){
        this.records = records;
        this.total = pageVO.total;
        this.totalPage = pageVO.totalPage;
        this.page = pageVO.page;
        this.pageSize = pageVO.pageSize;
        
    }
    //多重構(gòu)造方法
    constructor(records: Collection<E>?, page: Page<Any>){
        this.records = records;
        this.total = page.getTotal();
        this.totalPage = page.getPages();
        this.page = page.getCurrent();
        this.pageSize = page.getSize();
        
    }
}
  • 分頁工具類PageUtil

open class PageUtil {
  /**
   * 取mybatis-plus分頁對(duì)象
   */
  open fun getPage(query : Query):Page<Any>? {
      return Page(query.page, query.pageSize);
  }
}
  • 表名與字段的映射的JavaBean實(shí)體類

【注意點(diǎn)】:這里的實(shí)體有一個(gè)是否刪除是is開頭的单雾,轉(zhuǎn)換有點(diǎn)麻煩,建議建數(shù)據(jù)庫表字段不要以is開頭届腐,處理方案@JSONField(name = "isDeleted")@field:JSONField(name="isDeleted") 詳細(xì)請(qǐng)參考Use-Fastjson-in-Kotlin Use-Fastjson-in-Kotlin,跟蹤SQL確實(shí)是打印該字段铁坎,但是瀏覽器訪問是無法顯示。

Use-Fastjson-in-Kotlin.png

【屬性(properties)犁苏,建議與處理方法】處理方法如下:
如果Kotlin屬性名是以is開頭硬萍,那么命名的約定會(huì)發(fā)生一些變化
1、getter方法名與屬性名一樣
2围详、setter方法名則是將is替換為set
這種規(guī)則適用于任何類型朴乖,而不單單是Boolean類型
通過jvm的命令javap -c -p 類名 反編譯字節(jié)碼就可以看到對(duì)應(yīng)的信息

image.png

@TableLogic //mybatisplus的邏輯刪除標(biāo)識(shí)
//指定表字段進(jìn)行綁定實(shí)體字段屬性,is_deleted表示在表實(shí)在存在的字段.
@TableField(value="is_deleted")
//轉(zhuǎn)換成跟數(shù)據(jù)庫表一樣的屬性字段
@field:JSONField(name="isDeleted")
var delFlag: String? = null,//刪除
建議與處理方法.png
 @TableName("t_user")
data  class User constructor(
    @TableId(value = "user_id", type = IdType.ID_WORKER)
    var userId: Long?= null,//用戶Id主鍵,IdWork生成
    var userName: String? = null,//用戶名
    var passWord: String? = null,//密碼
    @TableLogic
    //指定表字段進(jìn)行綁定實(shí)體字段屬性
    @TableField(value="is_deleted")
    //轉(zhuǎn)換成跟數(shù)據(jù)庫一樣
    @field:JSONField(name="isDeleted")
    var delFlag: String? = null,//刪除
    var createTime: Date? = null //創(chuàng)建時(shí)間,允許為空,讓數(shù)據(jù)庫自動(dòng)生成即可
  ) :Serializable{
 
    //手動(dòng)重寫toString方法
    override fun toString(): String {
        return "[User(userId = $userId,userName = $userName, passWord=$passWord,isDeleted=$delFlag,createTime=$createTime)]"
    }

    //equals
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        other as User
        if (userId != other.userId) return false
        if (userName != other.userName) return false
        if (passWord != other.passWord) return false
        if (delFlag != other.delFlag) return false
        if (createTime != other.createTime) return false
        return true
    }
    override fun hashCode(): Int {
        
        var result = userId?.hashCode() ?: 0
        result = 31 * result + (userName?.hashCode() ?: 0)
        result = 31 * result + (passWord?.hashCode() ?: 0)
        result = 31 * result + (delFlag?.hashCode() ?: 0)
        result = 31 * result + (createTime?.hashCode() ?: 0)
        
        return result
    }

}

data class UserRespVo constructor(
    var userId: Long?= null,//用戶Id主鍵,IdWork生成
    var userName: String? = null,//用戶名
    var passWord: String? = null,//密碼
    @field:JSONField(name="isDeleted")
    var delFlag: String? = null,//是否刪除
    var createTime: Date? = null //創(chuàng)建時(shí)間,允許為空,讓數(shù)據(jù)庫自動(dòng)生成即可
    ):Serializable{
}
  • MyBatis的Mapper接口

interface IUserMapper : BaseMapper<User>{

    //這里的?表示當(dāng)前是否對(duì)象可以為空 
    //@see http://blog.csdn.net/android_gjw/article/details/78436707
    fun getUserList(query : UserQuery , page : Page<Any>): List<UserRespVo>?
 
}
  • IUserMapper對(duì)應(yīng)的MyBatis的XMl

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.flong.kotlin.modules.mapper.IUserMapper">
    <!-- 抽取公共字段 -->
    <sql id="find_AS_R">
        <![CDATA[
            SELECT A.user_Id as userId, A.USER_NAME as userName, 
            A.PASS_WORD as passWord,is_deleted as delFlag,
            A.create_time as createTime
        ]]>
    </sql>

    <!-- 用戶查詢 -->
    <select id="getUserList" resultType="com.flong.kotlin.modules.vo.resp.UserRespVo" >
        <include refid="find_AS_R" /> FROM T_USER A 
        <where>
            <if test="userId != null">  and A.user_Id = #{userId}</if>
            <if test="userName != null and  userName !='' ">and A.USER_NAME = #{userName}</if>
        </where>
    </select>
</mapper>

  • 業(yè)務(wù)Service代碼ORM

  • 對(duì)象關(guān)系映射(英語:Object Relational Mapping助赞,簡稱ORM买羞,或O/RM,或O/R mapping)雹食,是一種程序技術(shù)畜普,用于實(shí)現(xiàn)面向?qū)ο缶幊陶Z言里不同類型系統(tǒng)的數(shù)據(jù)之間的轉(zhuǎn)換。
  • 此類是用于與MyBatis的Mapper進(jìn)行映射進(jìn)行操作ORM操作群叶,在這里不過多的詳解ORM吃挑,在代碼中看到操作幾乎都是MyBatisPlus自身封裝的CRUD的操作钝荡,除了分頁是自定義MyBatis的XML進(jìn)行處理。這里提供這樣的支持舶衬,主要是為了處理對(duì)業(yè)務(wù)復(fù)雜拓展查詢場(chǎng)景埠通。

初窺理解Kotlin的ORM框架Ktorm

@Service
open class UserService : BaseService<IUserMapper,User,UserQuery>() {
    //Kotlin lateinit 和 by lazy 的區(qū)別
    //http://blog.csdn.net/Sherlbon/article/details/72769843
    @Autowired lateinit var userMapper: IUserMapper;
    //根據(jù)
    open fun queryAllUser(): List<User>? {
        var wrapper = createWrapper();
        return this.selectList(wrapper);
    }
    open fun listPage(query : UserQuery) : PageVO<UserRespVo> ? {
        var page = PageUtil().getPage(query)  ;// 設(shè)置分頁
        var dataList = userMapper.getUserList(query, page!!);//page!!強(qiáng)制告訴編輯器不可能為空
        
        var json = JSON.toJSONString(dataList);
        println(json)
        return  PageVO(dataList, page);// 獲取分頁數(shù)和總條數(shù)
    }
        
       //通過主鍵id進(jìn)行查詢
    open fun getUserId(userId: Long): User? {
        return get(userId);
    }

    //插入用戶
    open fun addUser() {
        var userId = IdWorker.getId();
        var u = User(userId, "liangjl", "123456",null, Date());
        var json = JSON.toJSONString(u);
        println(json)
        add(u);
    }
    fun createWrapper(): Wrapper<User> {
        var wrapper = EntityWrapper<User>();
        wrapper.setEntity(User());//設(shè)置實(shí)體
        return wrapper;
    }

}
  • Controller代碼的CRUD的代碼實(shí)現(xiàn)

  • C - Create 對(duì)數(shù)據(jù)添加或增加操作
  • R - Retrieve 對(duì)數(shù)據(jù)讀取查詢操作
  • U - Update 對(duì)數(shù)據(jù)更新或修改操作
  • D - Delete 對(duì)數(shù)據(jù)刪除操作
@RestController
@RequestMapping("/rest")
open class UserController : BaseController(){
    
    @Autowired private lateinit var userService: UserService
 
  @RequestMapping("/list1")
    fun list1():  String{
        return "NewFile" //跳轉(zhuǎn)頁面
    }
    
    //添加
   @RequestMapping("/add")
    fun add():  Unit{
        userService.addUser()
    }
   //根據(jù)用戶Id進(jìn)行刪除用戶信息
   @RequestMapping("/deletedById")
    fun deletedById(userId : Long):  Unit{
        userService.deleteById(userId);
    }

   //更新用戶信息,通過Id唯一主鍵進(jìn)行操作逛犹。
   @RequestMapping("/update")
    fun update(user : User):  Unit{
        userService.updateById(user)
    }
    
    //根據(jù)Id查詢用戶
    @RequestMapping("/getUserId")
    fun getUserId(userId :Long):Any?{
        var user = userService.getUserId(userId);
        if(user ==null){
            var msgCode = UserMsgCode.FIND_NOT_USER;
            throw BaseException(msgCode.code!! ,msgCode.message!!)
        }
        return userService.getUserId(userId)
    }
    
   //查詢所有用戶信息
    @RequestMapping("/queryAllUser")
    fun queryAllUser():List<User>?{
       return userService.queryAllUser()
    }
    
    //分頁查詢
    @RequestMapping("listPage")
    fun listPage(query :UserQuery) :PageVO<UserRespVo> ? {
           var listPage = userService.listPage(query);
           return listPage;
    }
    
    @RequestMapping("/getBody")
    fun getBody(@RequestBody jsonText:UserRespVo){
       println(jsonText.component2())
       println(jsonText.userName)
    }
}
  • properties數(shù)據(jù)庫配置

logging.evel.root=info
logging.evel.com.flong.kotlin=debug

# =======mybatis_config=====
 ## 指定是開發(fā)文件
spring.profiles.active=dev
#=======datasource========
#spring.datasource.name=na
spring.datasource.url=jdbc:mysql://localhost:3306/kotlin?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
#spring.datasource.filters=stat,wall,log4j,slf4j
##配置支持【emoji表情】與【不合法輸入?yún)?shù)】
##作用:用于處理請(qǐng)求與響應(yīng)一些不可信的字符串
spring.datasource.connectionInitSqls=set names utf8mb4
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

spring.devtools.restart.enabled=false


spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
 

  • Apache HttpClient的網(wǎng)絡(luò)Http通信工具類封裝

  • HttpClient的網(wǎng)絡(luò)Http通信工具類封裝請(qǐng)看下一篇文章
package com.flong.kotlin.utils
/**
 * object表示這個(gè)類是單例模式
 * @Description HttpClientUtil幫助類
 * @Author      liangjilong
 * @Date        2018年12月17日
 */
 object HttpClientUtil {
    @JvmStatic //main測(cè)試
    fun main(args: Array<String>) {
        //var b = mapOf("age" to 23, "userName" to "ljl")
        var map3 = HashMap<String,String>()
        map3.put("age","23")
        map3.put("userName","寅務(wù)")
        //var p = getConcatParams(map3,null)
        //var p = paramsToQueryString(map3)
        var reqUrl = "http://localhost:8080/rest/getBody"
        var method = "POST"
        var json = JSONObject()
        json.put("passWord","123456")
        json.put("userName","寅務(wù)")
        var header =   HashMap<String, String>()
        header.put("Content-Type", JSON_APPLICATION)
        var retMsg = 
 createHttp(reqUrl,method,json.toJSONString(),header,"utf-8")
        println("retMsg=" +retMsg)

    }
}
  • LiveRequestBodyAdvice全局處理請(qǐng)求Body參數(shù)工具類

  • 【請(qǐng)注意】:InputMessage輸出流信息端辱,這里的類和重寫方法要加上openpublic關(guān)鍵字修飾,因?yàn)閗otlin默認(rèn)是private虽画,不加上會(huì)報(bào)HttpHeaders defined in ......InputStream defined in ......

/**
 * 針對(duì)@RequestBody請(qǐng)求的處理
 */
@ControllerAdvice
class LiveRequestBodyAdvice : RequestBodyAdvice {

    companion object {
        var DEFAULT_CHARSET = "UTF-8";
        private val logger: Logger = LoggerFactory.getLogger(LiveRequestBodyAdvice::class.java)
    }

    override fun handleEmptyBody(body: Any?, inputMessage: HttpInputMessage?, parameter: MethodParameter?, targetType: Type?, converterType: Class<out HttpMessageConverter<*>>?): Any? {
        return body;
    }

    override fun afterBodyRead(body: Any?, inputMessage: HttpInputMessage?, parameter: MethodParameter?, targetType: Type?, converterType: Class<out HttpMessageConverter<*>>?): Any? {
        return body;
    }

    override fun beforeBodyRead(inputMessage: HttpInputMessage?, parameter: MethodParameter?, targetType: Type?, converterType: Class<out HttpMessageConverter<*>>?): HttpInputMessage? {
        var body    = IOUtils.toString(inputMessage?.getBody(), DEFAULT_CHARSET) as java.lang.String;
        var headers = inputMessage!!.getHeaders();
        var bis     = ByteArrayInputStream(body.getBytes(DEFAULT_CHARSET));
        logger.info("request body params : {}", body);

        return InputMessage(headers, bis);
    }

    override fun supports(methodParameter: MethodParameter?, targetType: Type?, converterType: Class<out HttpMessageConverter<*>>?): Boolean {
        return true;
    }


    //【請(qǐng)注意】:InputMessage輸出流信息舞蔽,這里的重寫方法必須要加上open,因?yàn)閗otlin默認(rèn)是private狸捕,不加上會(huì)報(bào)HttpHeaders defined in 
    open class InputMessage : HttpInputMessage {
        private var headers: HttpHeaders
        private var body: InputStream
        
        //構(gòu)造方法
        constructor(headers: HttpHeaders, body: InputStream) {
            this.headers = headers;
            this.body = body;
        }
        open override fun getBody(): InputStream    = this.body
        open override fun getHeaders(): HttpHeaders = this.headers
    }

}
  • LiveRespBodyAdvice處理統(tǒng)一響應(yīng)Body的JSON體工具類

@RestControllerAdvice
@Slf4j
@Order(Integer.MIN_VALUE)
class LiveRespBodyAdvice : ResponseBodyAdvice<Any> {
    
    companion object {
        //const 關(guān)鍵字用來修飾常量喷鸽,且只能修飾  val,不能修飾var,  companion object 的名字可以省略灸拍,可以使用 Companion來指代
         const val LIFE_PACKAGE = "com.flong.kotlin";
    }
    
    //Class<out HttpMessageConverter<*>>?) 相當(dāng)于Java的  Class<? extends HttpMessageConverter<?>>
    //list :ArrayList<in Fruit>做祝,相當(dāng)于Java的 ArrayList< ? super Fruit> 
    //*,相當(dāng)于Java的?
    override fun supports(methodParameter: MethodParameter?, converterType: Class<out HttpMessageConverter<*>>?): Boolean {
        //處理類型 
        var className   = methodParameter?.getContainingClass()?.name;
        var sw          = className?.startsWith(LIFE_PACKAGE);
        var eaf         = ErrorResp::class.java.isAssignableFrom(methodParameter?.getParameterType());
        var laf         = LiveResp::class.java.isAssignableFrom(methodParameter?.getParameterType());
        var saf         = String::class.java.isAssignableFrom(methodParameter?.getParameterType());
        
        return (sw==true && !eaf && !laf && !saf);
    }
    
    override fun beforeBodyWrite(body: Any?, returnType: MethodParameter?, selectedContentType: MediaType?, selectedConverterType: Class<out HttpMessageConverter<*>>?, request: ServerHttpRequest?, response: ServerHttpResponse?): Any? {
        //?: elvis操作符(貓王),age?.toInt()表示if(age!=null) age.toInt else 返回 默認(rèn)就給10
        //var path = request?.getURI()?.getPath();
        if(body != null){
            return LiveResp(body);
        }else{
            return LiveResp("");
        }
         
    }
}

  • 統(tǒng)一處理異常工具類

@Slf4j
@RestControllerAdvice
class ExceptionAdvice{
    companion object {
        private val log: Logger = LoggerFactory.getLogger(ExceptionAdvice::class.java)
    }
    @ExceptionHandler(HttpRequestMethodNotSupportedException::class)
    fun onException(e : HttpRequestMethodNotSupportedException, request: HttpServletRequest): ErrorResp? {
        var uri = request.getRequestURI();
        createLog(e, uri, "找不到請(qǐng)求的方法");
        return ErrorResp(CommMsgCode.NOT_SUPPORTED.code!!, CommMsgCode.DB_ERROR.message!!)
    }

    //is 相當(dāng)于Java的 instanceof 鸡岗,as就是強(qiáng)制轉(zhuǎn)換(對(duì)象)
    @ExceptionHandler(Exception::class)
    fun onException(e: Exception, request: HttpServletRequest): ErrorResp? {
        
        var uri     = request.getRequestURI();
        var params  = JSONObject.toJSONString(request.getParameterMap());
        if (e is SQLException || e is DataAccessException) {
            createLog(e, uri, params);
            return ErrorResp(CommMsgCode.DB_ERROR.code!!, CommMsgCode.DB_ERROR.message!!)

        } else if (e is BaseException) {
            var be = e as BaseException
            log.error("uri:{},params:{},code:{},message:{}", uri, params)
            return ErrorResp(be.code,be.msg)
            
        } else if (e is MissingServletRequestParameterException
                || e is BindException
                || e is ConstraintViolationException
                || e is TypeMismatchException
                || e is ServletRequestBindingException) {

            createLog(e, uri, params);
            
            return ErrorResp(CommMsgCode.PARAM_ERROR.code!!, CommMsgCode.PARAM_ERROR.message!!)
        } else {
            return ErrorResp(CommMsgCode.SERVER_ERROR.code!!, CommMsgCode.SERVER_ERROR.message!!)
        }
    }
    
    
    //錯(cuò)誤信息
    fun createErrorResp(msgCode : MsgCode,  message : String?) :ErrorResp {
        var msg = "";
        if(ObjectUtil().isNotEmpty(message)){
            msg = message +"";
        }else{
            msg = msgCode.getMessage();
        }
        return ErrorResp(msgCode.getCode(), msg);
    }

    //打印log
    fun createLog(e: Exception, uri: String, params: String) {
        log.error("uri:" + uri + ",params:" + params, e);
    }
    
}
  • Jsoup 防止 XSS 攻擊工具類


/**
 * Jsoup 防止 XSS 攻擊 工具類
 */
object Jsoup2XssUtils {
    /**
     * 使用自帶的 basicWithImages 白名單
     * 允許的便簽有 a,b,blockquote,br,cite,code,dd,dl,dt,em,i,li,ol,p,pre,q,small,span,
     * strike,strong,sub,sup,u,ul,img
     * 以及 a 標(biāo)簽的 href,img 標(biāo)簽的 src,align,alt,height,width,title 屬性
     */
    private val whitelist: Whitelist = Whitelist.basicWithImages()

    /**
     * 配置過濾化參數(shù) 不對(duì)代碼進(jìn)行格式化
     */
    private val outputSettings: Document.OutputSettings = Document.OutputSettings().prettyPrint(false)
    init {
        // 富文本編輯時(shí)一些樣式是使用style來進(jìn)行實(shí)現(xiàn)的
        // 比如紅色字體 style="color:red;"
        // 所以需要給所有標(biāo)簽添加 style 屬性
        whitelist.addAttributes(":all", "style")
    }
    /**
     * 清除
     * @param content 內(nèi)容
     */
    fun clean(content: String): String {
        var contentStr: String = content
        if (contentStr.isNotBlank()) {
            contentStr = content.trim { it <= ' ' }
        }
        return Jsoup.clean(contentStr, "", whitelist, outputSettings)
    }
}
  • 數(shù)據(jù)庫腳本(t_user)以用戶表為代表

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `user_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用戶Id主鍵,IdWork生成',
  `user_name` varchar(255) DEFAULT '' COMMENT '用戶名',
  `pass_word` varchar(255) DEFAULT '' COMMENT '密碼',
  `is_deleted` int(2) unsigned NOT NULL DEFAULT '0' COMMENT '是否刪除,0-不刪除,1-刪除',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `id` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='用戶表';

--  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',是mysql5.6以上的版本

  • 工程啟動(dòng)類Appalachian.kt與運(yùn)行結(jié)果

// 20190319105420
// 訪問路徑 :http://localhost:8080/rest/listPage
//訪問結(jié)果
{
  "code": "200",
  "data": {
    "page": "1",
    "pageSize": "20",
    "records": [
      {
        "createTime": "2019-01-04 16:01:34",
        "passWord": "123456",
        "userId": "1081098298906394625",
        "userName": "liangjl"
      },
      {
        "createTime": "2019-01-04 16:14:50",
        "passWord": "123456",
        "userId": "1081101635991232513",
        "userName": "liangjl"
      }
    ],
    "total": "2",
    "totalPage": "1"
  },
  "msg": "",
  "timestamp": "1552964058194"
}
項(xiàng)目工程

CSDN源代碼下載需要積分
Github下載不需要積分

十四混槐、為什么記錄筆記

  • 1、在平時(shí)工作下來轩性,一天認(rèn)真工作下來声登,腦子肯定非常很疲倦,故容易導(dǎo)致腦子健忘揣苏。也許跟年齡的歲數(shù)增大有關(guān)系悯嗓。人精力與體力等各方面都有限的,畢竟人不是機(jī)器卸察。
  • 2脯厨、常言道:好記性不如爛筆頭。切記千萬別以自己有點(diǎn)小聰明對(duì)自己懶惰坑质。
  • 3合武、如果經(jīng)常健忘的時(shí)候,不防借助一些軟件工具或者云服務(wù)進(jìn)行幫助存儲(chǔ)生活中的痕跡涡扼。比如:思維導(dǎo)圖(XMind, MindManager,FreeMind)云道筆記等等方式稼跳。

十五、總結(jié)與推薦學(xué)習(xí)

15.1 總結(jié)

  • 1 吃沪、以上問題都是根據(jù)自己學(xué)習(xí)實(shí)際情況進(jìn)行總結(jié)整理汤善,除了技術(shù)問題查很多網(wǎng)上資料通過進(jìn)行學(xué)習(xí)之后梳理。

  • 2、 在學(xué)習(xí)過程中也遇到很多困難和疑點(diǎn)红淡,如有問題或誤點(diǎn)卸伞,望各位老司機(jī)多多指出或者提出建議。本人會(huì)采納各種好建議和正確方式不斷完善現(xiàn)況锉屈,人在成長過程中的需要優(yōu)質(zhì)的養(yǎng)料

  • 3垮耳、 導(dǎo)入代碼的時(shí)候遇到最多的問題颈渊,我想應(yīng)該是Maven較多,此時(shí)不懂maven的童鞋們可以通過自身情況终佛,進(jìn)行網(wǎng)上查資料學(xué)習(xí)俊嗽。如通過網(wǎng)上找資料長時(shí)間解決不了,或者框架有不明白可以通過博客留言,在能力范圍內(nèi)會(huì)盡力幫助大家解決問題所在铃彰,希望在過程中一起進(jìn)步绍豁,一起成長。

  • 4 牙捉、當(dāng)遇到問題的時(shí)候建議多問『谷歌 竹揍、必應(yīng)、stackoverflow邪铲、度娘』這些大神芬位。

此文章屬于本人原創(chuàng)可以轉(zhuǎn)載或收藏
今后我可能不在 Iteye CSDN 的更新博客文章,會(huì)選型簡書.后面有時(shí)間會(huì)針對(duì)這個(gè)框架進(jìn)行細(xì)化寫簡書進(jìn)行講解。

15.2 個(gè)人撰寫Kotlin的文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末带到,一起剝皮案震驚了整個(gè)濱河市昧碉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌揽惹,老刑警劉巖被饿,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異搪搏,居然都是意外死亡狭握,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門慕嚷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哥牍,“玉大人,你說我怎么就攤上這事喝检⌒崂保” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵挠说,是天一觀的道長澡谭。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么蛙奖? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任潘酗,我火速辦了婚禮,結(jié)果婚禮上雁仲,老公的妹妹穿的比我還像新娘仔夺。我一直安慰自己,他們只是感情好攒砖,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布缸兔。 她就那樣靜靜地躺著,像睡著了一般吹艇。 火紅的嫁衣襯著肌膚如雪惰蜜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天受神,我揣著相機(jī)與錄音抛猖,去河邊找鬼。 笑死鼻听,一個(gè)胖子當(dāng)著我的面吹牛财著,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播精算,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼瓢宦,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了灰羽?” 一聲冷哼從身側(cè)響起驮履,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎廉嚼,沒想到半個(gè)月后玫镐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怠噪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年恐似,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傍念。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矫夷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出憋槐,到底是詐尸還是另有隱情双藕,我是刑警寧澤贴唇,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布纠屋,位于F島的核電站,受9級(jí)特大地震影響浆洗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嘶摊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一延蟹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧叶堆,春花似錦阱飘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至上枕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弱恒,已是汗流浹背辨萍。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留返弹,地道東北人锈玉。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像义起,于是被迫代替她去往敵國和親拉背。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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