Java代碼生成器更新:添加多數(shù)據(jù)源模式支持

引言

哈嘍,小伙伴們市栗,一周不見了缀拭,在這段時間咳短,我利用下班的閑暇時間更新了一版代碼生成器,添加了之前呼聲較高的多數(shù)據(jù)源模式蛛淋,這樣生成的代碼可以實現(xiàn)動態(tài)切換數(shù)據(jù)源的功能咙好,多數(shù)據(jù)源在項目當(dāng)中還算比較常用的,例如主從讀寫分離褐荷,多庫操作等都需要在同一個項目中操作多個數(shù)據(jù)庫勾效,本次更新正是解決了這個痛點,生成代碼之后叛甫,可以通過注解的方式靈活切換數(shù)據(jù)源层宫,并且支持多庫事務(wù)一致性,下面就讓我們一起看一下具體的實現(xiàn)效果其监,順便講一下動態(tài)多數(shù)據(jù)源的內(nèi)部原理萌腿!

生成器界面調(diào)整

為了實現(xiàn)多數(shù)據(jù)源模式,代碼生成器對界面進(jìn)行了調(diào)整抖苦,如下:


界面調(diào)整

主界面添加了選擇數(shù)據(jù)源的功能毁菱,并且現(xiàn)在數(shù)據(jù)庫信息需要點擊數(shù)據(jù)源配置來進(jìn)行配置,點擊后會彈出如下窗口:


數(shù)據(jù)源配置

在這里我們可以配置數(shù)據(jù)庫信息锌历,配置完畢后點擊保存贮庞,在主界面即可進(jìn)行選擇,在主界面被選擇的數(shù)據(jù)源將在生成的代碼中作為默認(rèn)數(shù)據(jù)源使用辩涝。

勾選多數(shù)據(jù)源模式可以生成多數(shù)據(jù)源模式代碼贸伐,不勾選則與之前一樣,生成的是常規(guī)單數(shù)據(jù)源項目怔揩。

總體跟原來區(qū)別不大捉邢,使用多數(shù)據(jù)源模式生成代碼基本步驟如下:

  1. 配置數(shù)據(jù)源保存
  2. 主界面依次選擇數(shù)據(jù)源,配置數(shù)據(jù)項信息
  3. 勾選多數(shù)據(jù)源模式商膊,點擊生成代碼即可

生成代碼展示

代碼展示

雙數(shù)據(jù)源

多數(shù)據(jù)源模式下會在 config 包下生成多數(shù)據(jù)源相關(guān)的配置類及切面伏伐,如果大家有個性化需求可以通過修改 DynamicDataSourceAspect 切面來實現(xiàn)動態(tài)切換邏輯,現(xiàn)有切換邏輯基本足夠晕拆。

多數(shù)據(jù)源其實還可以通過代碼分包的方式實現(xiàn)藐翎,這種方式實現(xiàn)起來易于理解:配置多個數(shù)據(jù)源,掃描不同的包实幕,創(chuàng)建屬于自己的 sqlSessionFactory 和 txManager(事務(wù)管理器)吝镣,在使用的時候可以通過調(diào)用不同包下的 mapper 來實現(xiàn)多數(shù)據(jù)源的效果,但是這種方式的弊端也較為明顯昆庇,分包稍有不慎便會出錯末贾,并且如果想要實現(xiàn)不同數(shù)據(jù)源下的事務(wù)一致性也較為麻煩,在同一個 service 方法中操作多個數(shù)據(jù)庫因此受限整吆。

動態(tài)多數(shù)據(jù)源則不會有以上問題拱撵,因此代碼生成器選擇了動態(tài)多數(shù)據(jù)源的生成模式辉川,利用 aop 實現(xiàn)數(shù)據(jù)源的動態(tài)切換,并且可以保證多庫操作事務(wù)一致性拴测,后面會詳細(xì)講解乓旗。

代碼運行效果

在 idea 中運行生成的代碼,啟動完畢登錄集索,點擊左側(cè)菜單查詢:


查看

查看后臺日志屿愚,發(fā)現(xiàn)會切換不同的數(shù)據(jù)庫執(zhí)行sql:


切換

下面以 springboot 為例,講一下多數(shù)據(jù)源內(nèi)部原理抄谐。

動態(tài)多數(shù)據(jù)源內(nèi)部原理及核心代碼

動態(tài)多數(shù)據(jù)源的內(nèi)部原理其實就是 aop渺鹦,只不過復(fù)雜的是 aop 的實現(xiàn)過程。

mybatis 為我們提供了一個抽象類 AbstractRoutingDataSource蛹含,通過繼承此類毅厚,重寫 determineCurrentLookupKey 方法可以根據(jù)返回值決定當(dāng)前使用哪個數(shù)據(jù)源,因此我們創(chuàng)建類 DynamicDataSource 繼承 AbstractRoutingDataSource 并重寫 determineCurrentLookupKey 方法:

/**
 * 重寫數(shù)據(jù)源選擇方法(獲取當(dāng)前線程設(shè)置的數(shù)據(jù)源)
 * @author zrx
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
    
    }
}

先不忙著實現(xiàn)浦箱,如果想要正確匹配數(shù)據(jù)源吸耿,我們還需要向 DynamicDataSource 類中注冊數(shù)據(jù)源,所以需要先對數(shù)據(jù)源進(jìn)行配置酷窥,這里注冊兩個數(shù)據(jù)源 db1(mysql) 和 db2(oracle)咽安,我們使用枚舉值 DB1 和 DB2 作為數(shù)據(jù)源 db1 和 db2 的 key:

package mutitest.config.mutidatasource;

/**
 * 數(shù)據(jù)源枚舉
 * @author zrx
 */
public enum DataSourceType {

    /**
    * DB1
    */
    DB1,
    /**
    * DB2
    */
    DB2,

}
package mutitest.config.mutidatasource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 數(shù)據(jù)源配置類
 *
 * @author zrx
 */
@Configuration
public class DynamicDataSourceConfig {

    @Bean(name = "db1")
    @ConfigurationProperties(prefix = "spring.datasource.db1")
    public DataSource db1DataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean(name = "db2")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource db2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource(@Qualifier(value = "db1") DataSource db1,@Qualifier(value = "db2") DataSource db2) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //設(shè)置默認(rèn)數(shù)據(jù)源
        dynamicDataSource.setDefaultTargetDataSource(db1);
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DataSourceType.DB1, db1);
        dataSourceMap.put(DataSourceType.DB2, db2);
        //向動態(tài)數(shù)據(jù)源中注冊所有數(shù)據(jù)源信息
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }

    @Bean
    public PlatformTransactionManager txManager(DataSource dataSource) {
        //返回動態(tài)數(shù)據(jù)源的事務(wù)管理器
        return new DataSourceTransactionManager(dataSource);
    }

}

通過以上配置,我們成功向 DynamicDataSource 中注冊了 db1 和 db2蓬推,如何才能獲取當(dāng)前程序運行中的數(shù)據(jù)源呢妆棒?這就需要我們用到 ThreadLocal,ThreadLocal 可以向當(dāng)前線程中 set 和 get 值并且不受其他線程影響沸伏,而我們服務(wù)器的每一個請求都由一個工作線程來處理(nio 模式也是一個請求一個工作線程處理糕珊,只是在接收請求的時候使用了 io 多路復(fù)用),所以可以使用 ThreadLocal 存儲當(dāng)前工作線程的數(shù)據(jù)源毅糟,ThreadLocal 在很多開源框架中都有使用红选,主要用于線程隔離。

創(chuàng)建 DynamicDataSourceHolder 類姆另,存儲當(dāng)前線程中的數(shù)據(jù)源:

package mutitest.config.mutidatasource;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 數(shù)據(jù)源選擇器
 *
 * @author zrx
 */
public class DynamicDataSourceHolder {
    private static final ThreadLocal<DataSourceType> DATA_SOURCE_HOLDER = new ThreadLocal<>();

    private static final Set<DataSourceType> DATA_SOURCE_TYPES = new HashSet<>();

    static {
        //添加全部枚舉
        DATA_SOURCE_TYPES.addAll(Arrays.asList(DataSourceType.values()));
    }

    public static void setType(DataSourceType dataSourceType) {
        if (dataSourceType == null) {
            throw new NullPointerException();
        }
        DATA_SOURCE_HOLDER.set(dataSourceType);
    }

    public static DataSourceType getType() {
        return DATA_SOURCE_HOLDER.get();
    }

    static void clearType() {
        DATA_SOURCE_HOLDER.remove();
    }

    static boolean containsType(DataSourceType dataSourceType) {
        return DATA_SOURCE_TYPES.contains(dataSourceType);
    }
}

然后喇肋,實現(xiàn) determineCurrentLookupKey 方法,一行代碼即可:

@Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getType();
    }

最后一步迹辐,我們要實現(xiàn)數(shù)據(jù)源的動態(tài)切換蝶防,則需要自己實現(xiàn)一個數(shù)據(jù)源動態(tài)切面,改變當(dāng)前線程中的數(shù)據(jù)源明吩,我們可以使用注解來輔助實現(xiàn)间学,在切面中通過掃描方法上的注解來得知具體切換到哪個數(shù)據(jù)源。

創(chuàng)建 DBType 注解:

package mutitest.config.mutidatasource;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 多數(shù)據(jù)源注解
* @author zrx
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DBType {
DataSourceType value() default DataSourceType.DB1;
}

創(chuàng)建數(shù)據(jù)源動態(tài)切面 DynamicDataSourceAspect:

package mutitest.config.mutidatasource;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 動態(tài)數(shù)據(jù)源切面(order 必須要設(shè)置贺喝,否則事務(wù)的切面會優(yōu)先執(zhí)行菱鸥,數(shù)據(jù)源已經(jīng)設(shè)置完了,再設(shè)置就無效了)
 * @author zrx
 */
@Aspect
@Component
@Order(1)
public class DynamicDataSourceAspect {

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

    @Before("@annotation(dbType)")
    public void changeDataSourceType(JoinPoint joinPoint, DBType dbType) {
        DataSourceType curType = dbType.value();
        //判斷注解類型
        if (!DynamicDataSourceHolder.containsType(curType)) {
            logger.info("指定數(shù)據(jù)源[{}]不存在躏鱼,使用默認(rèn)數(shù)據(jù)源-> {}", dbType.value(), joinPoint.getSignature());
        } else {
            logger.info("use datasource {} -> {}", dbType.value(), joinPoint.getSignature());
            // 切換當(dāng)前線程的數(shù)據(jù)源
            DynamicDataSourceHolder.setType(dbType.value());
        }

    }

    @After("@annotation(dbType)")
    public void restoreDataSource(JoinPoint joinPoint, DBType dbType) {
        logger.info("use datasource {} -> {}", dbType.value(), joinPoint.getSignature());
        //方法執(zhí)行完氮采,清空,防止內(nèi)存泄漏
        DynamicDataSourceHolder.clearType();
    }

}

在數(shù)據(jù)源切面上需要添加 @Order 注解染苛,值取1鹊漠,這是因為之前我們配置了動態(tài)數(shù)據(jù)源事務(wù),spring 會因此生成事務(wù)代理并且會優(yōu)先于切面執(zhí)行茶行,事務(wù)代理一旦生成躯概,數(shù)據(jù)源便被固定,這樣我們在切面中切換數(shù)據(jù)源就會無效畔师,所以切面邏輯需要在事務(wù)代理之前執(zhí)行才可生效娶靡。

至此,動態(tài)多數(shù)據(jù)源基本實現(xiàn)完畢看锉!

事務(wù)一致性問題

使用動態(tài)多數(shù)據(jù)源的同時姿锭,也要注意保證事務(wù)一致性,大家可能遇到這種情況伯铣,傳統(tǒng)單數(shù)據(jù)源應(yīng)用中呻此,同一個 service ,在沒有開啟事務(wù)的方法里調(diào)用開啟事務(wù)的方法會導(dǎo)致事務(wù)失效腔寡,這是因為 spring 只會對相同的 service 代理一次焚鲜,否則如果在沒有開啟事務(wù)的方法中再次開啟自身代理會導(dǎo)致循環(huán)依賴問題出現(xiàn),類似 “無限套娃”:自己代理的方法調(diào)用自己代理的另一個方法放前,并且另一個方法還需要自己的代理忿磅。解決此類問題的方法很簡單,讓調(diào)用方開啟事務(wù)即可犀斋,多數(shù)據(jù)源模式中同樣適用贝乎。

除此之外,多數(shù)據(jù)源模式中還存在如下場景:serviceA 中的 A 和 B 方法都開啟了事務(wù)叽粹,但操作的是不同的數(shù)據(jù)庫(ip不同)览效,這個時候 A 調(diào)用 B,使用的是 A 的代理虫几,對 B 不適用锤灿,便會報錯,對此我們可以把 B 方法移入另一個 serviceB 中辆脸,在 serviceA 中注入 serviceB 但校,在 A 方法中使用 serviceB 調(diào)用 B 方法,這樣執(zhí)行到 B 方法的時候使用的便是 serviceB 的代理啡氢,看起來沒有問題状囱,但還有一點遺漏术裸,那就是事務(wù)的傳播行為。

我們都知道亭枷,Spring 中默認(rèn)的事務(wù)傳播行為是 required:如果需要開啟事務(wù)袭艺,則開啟事務(wù),如果已經(jīng)開啟事務(wù)叨粘,則加入當(dāng)前事務(wù)猾编。上文中,執(zhí)行 B 方法的時候雖然使用的是 serviceB 的代理升敲,但是由于其事務(wù)傳播行為是 required答倡,A 方法執(zhí)行的時候已經(jīng)開啟了事務(wù),所以導(dǎo)致 B 方法加入到了 A 方法的事務(wù)中驴党,但 A 和 B 屬于兩個不同的數(shù)據(jù)庫瘪撇,使用相同的事務(wù)管理器必然會出現(xiàn)問題。為了解決此問題港庄,我們可以把事務(wù)傳播行為改為 required_new:如果需要開啟事務(wù)设江,則開啟事務(wù),并且總是開啟新的事務(wù)攘轩。這樣執(zhí)行 B 方法的時候會開啟新的事務(wù)叉存,使用的便是 B 所在數(shù)據(jù)庫的事務(wù)管理器,B 方法也就可以正常執(zhí)行了度帮,并且如果 B 出現(xiàn)異常歼捏,如果 A 不主動捕獲,則 A笨篷,B 都會回滾瞳秽。

也許有人會問,單數(shù)據(jù)源模式下使用 required 為什么不會有上述問題呢率翅,因為單數(shù)據(jù)源模式下使用的是同一個數(shù)據(jù)庫练俐,在事務(wù)執(zhí)行過程中,當(dāng)前事務(wù)是共享且通用的冕臭,所以沒問題腺晾。除此之外,使用 required 不必頻繁重開事務(wù)辜贵,也一定程度上提升了系統(tǒng)性能悯蝉,多數(shù)據(jù)源模式下由于不同數(shù)據(jù)庫之間事務(wù)是完全隔離的,所以才需要使用 required_new 重開事務(wù)托慨,當(dāng)然鼻由,也需要根據(jù)業(yè)務(wù)具體場景具體分析,這里討論的只是較為通用的情況。

代碼生成器多數(shù)據(jù)源模式下使用的事務(wù)傳播行為正是 required_new蕉世,全局配置類如下:

package mutitest.config;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;

/**
 * 全局事務(wù)支持
 *
 * @author zrx
 *
 */
@Aspect
@Configuration
public class TransactionAdviceConfig {

    private static final String AOP_POINTCUT_EXPRESSION = "execution(* mutitest.service.impl.*.*(..))";

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Bean
    public TransactionInterceptor txAdvice() {

        DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
        txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

        DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
        txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        txAttr_REQUIRED_READONLY.setReadOnly(true);

        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        //可以根據(jù)業(yè)務(wù)需要自行添加需要被事務(wù)代理的方法
        source.addTransactionalMethod("add*", txAttr_REQUIRED);
        source.addTransactionalMethod("delete*", txAttr_REQUIRED);
        source.addTransactionalMethod("update*", txAttr_REQUIRED);
        source.addTransactionalMethod("select*", txAttr_REQUIRED_READONLY);
        source.addTransactionalMethod("likeSelect*", txAttr_REQUIRED_READONLY);
        return new TransactionInterceptor(transactionManager, source);
    }

    @Bean
    public Advisor txAdviceAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, txAdvice());
    }
}

到此為止蔼紧,我們才算實現(xiàn)了一個完整的動態(tài)多數(shù)據(jù)源功能,可見是有許多技術(shù)細(xì)節(jié)潛藏在里面的狠轻,朋友們可以使用代碼生成器生成多數(shù)據(jù)源模式下的代碼自行運行體會歉井。

結(jié)語

本文到這里就結(jié)束了,寫這個多數(shù)據(jù)源生成功能其實也算花了一番心思哈误,正著寫代碼容易,反過來生成是真不容易躏嚎,并且由于最開始做的時候沒有考慮到多數(shù)據(jù)源的情況蜜自,導(dǎo)致最開始的設(shè)計全都是針對單個數(shù)據(jù)庫的,這次強行在外面包了一層卢佣,總歸是實現(xiàn)了重荠,在這個過程中,順便也復(fù)習(xí)了一下 Spring 的循環(huán)依賴虚茶,Bean 加載周期等老生常談的問題戈鲁,也算有所收獲。作為開發(fā)人員嘹叫,我們要多關(guān)注一些功能底層的東西婆殿,而不是簡單的 api 調(diào)用,這樣才能不斷突破瓶頸罩扇,取得成長婆芦。碼字不易,各位看官可以點贊喂饥,在看消约,星標(biāo)關(guān)注哦,我們下次再見员帮!

代碼生成器鏈接

關(guān)注公眾號 螺旋編程極客 獲取代碼生成器最新動態(tài)或粮,同時第一時間解鎖更多精彩內(nèi)容!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捞高,一起剝皮案震驚了整個濱河市氯材,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硝岗,老刑警劉巖浓体,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辈讶,居然都是意外死亡命浴,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來生闲,“玉大人媳溺,你說我怎么就攤上這事“叮” “怎么了悬蔽?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捉兴。 經(jīng)常有香客問我蝎困,道長,這世上最難降的妖魔是什么倍啥? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任禾乘,我火速辦了婚禮,結(jié)果婚禮上虽缕,老公的妹妹穿的比我還像新娘始藕。我一直安慰自己,他們只是感情好氮趋,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布伍派。 她就那樣靜靜地躺著,像睡著了一般剩胁。 火紅的嫁衣襯著肌膚如雪诉植。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天昵观,我揣著相機與錄音倍踪,去河邊找鬼。 笑死索昂,一個胖子當(dāng)著我的面吹牛建车,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播椒惨,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缤至,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了康谆?” 一聲冷哼從身側(cè)響起领斥,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沃暗,沒想到半個月后月洛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡孽锥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年嚼黔,在試婚紗的時候發(fā)現(xiàn)自己被綠了细层。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出禽炬,到底是詐尸還是另有隱情,我是刑警寧澤捧搞,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站狮荔,受9級特大地震影響胎撇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜殖氏,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一晚树、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧受葛,春花似錦、人聲如沸偎谁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巡雨。三九已至闰渔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铐望,已是汗流浹背冈涧。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留正蛙,地道東北人督弓。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像乒验,于是被迫代替她去往敵國和親愚隧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

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