從壹開始前后端分離【 .NET Core2.0 Api + Vue 3.0 + AOP + 分布式】框架之九 || 依賴注入IoC學(xué)習(xí) + AOP界面編程初探

代碼已上傳Github涧尿,文末有地址

說接上文勒叠,上回說到了《從壹開始前后端分離【 .NET Core2.0 Api + Vue 2.0 + AOP + 分布式】框架之八 || API項目整體搭建 6.3 異步泛型+依賴注入初探》,后來的標(biāo)題中闸拿,我把倉儲兩個字給去掉了仰禀,因為好像大家對這個模式很有不同的看法,嗯~可能還是我學(xué)藝不精秋泄,沒有說到其中的好處,現(xiàn)在在學(xué)DDD領(lǐng)域驅(qū)動設(shè)計相關(guān)資料规阀,有了好的靈感再給大家分享吧恒序。

到目前為止我們的項目已經(jīng)有了基本的雛形,后端其實已經(jīng)可以搭建自己的接口列表了谁撼,框架已經(jīng)有了規(guī)模歧胁,原本應(yīng)該說vue了,但是呢彤敛,又聽說近來Vue已經(jīng)從2.0升級到了3.0了与帆,還變化挺大,前端大佬們墨榄,都不停歇呀玄糟。細心的讀者已經(jīng)看到了我已經(jīng)把項目改成了3.0,當(dāng)然我還在學(xué)習(xí)當(dāng)中袄秩,我也需要了解下有關(guān)3.0的特性阵翎,希望給沒有接觸到,或者剛剛接觸到的朋友們之剧,有一些幫助郭卫,當(dāng)然我這個不是隨波逐流,只是在眾多的博文中背稼,給大家一個入門參考贰军,屆時說3.0的時候,還是會說2.0的相關(guān)問題的蟹肘。

雖然項目整體可以運行了词疼,但是我還有幾個小知識點要說下,主要是1帘腹、依賴注入和AOP相關(guān)知識贰盗;2、跨域代理等問題(因為Vue是基于Node開發(fā)的阳欲,與后臺API接口不在同一個地址)舵盈;3、實體類的DTO相關(guān)小問題球化;4秽晚、Redis緩存等;5赊窥、部署服務(wù)器中的各種坑爆惧;雖然都是很小的知識點,我還是都下給大家說下的锨能,好啦扯再,開始今天的講解芍耘;

零、今天完成的綠色部分

image

一熄阻、依賴注入的理解和思考

說到依賴斋竞,我就想到了網(wǎng)上有一個例子,依賴注入和工廠模式中的相似和不同:

(1)原始社會里秃殉,沒有社會分工坝初。須要斧子的人(調(diào)用者)僅僅能自己去磨一把斧子(被調(diào)用者)。相應(yīng)的情形為:Java程序里的調(diào)用者自己創(chuàng)建被調(diào)用者钾军。

(2)進入工業(yè)社會鳄袍,工廠出現(xiàn)。斧子不再由普通人完畢吏恭,而在工廠里被生產(chǎn)出來拗小,此時須要斧子的人(調(diào)用者)找到工廠,購買斧子樱哼,無須關(guān)心斧子的制造過程哀九。相應(yīng)Java程序的簡單工廠的設(shè)計模式。

(3)進入“按需分配”社會搅幅,須要斧子的人不須要找到工廠阅束,坐在家里發(fā)出一個簡單指令:須要斧子。斧子就自然出如今他面前茄唐。相應(yīng)Spring的依賴****注入息裸。

在上篇文章中,我們已經(jīng)了解到了沪编,什么是依賴倒置界牡、控制反轉(zhuǎn)(IOC),什么是依賴注入(DI)漾抬,網(wǎng)上這個有很多很多的講解,我這里就不說明了常遂,其實主要是見到這樣的纳令,就是存在依賴

public class A : D
{ public A(B b)
{ // do something
}
C c = new C();
}

就比如我們的項目中的BlogController,只要是通過new 實例化的克胳,都是存在依賴

public async Task<List<Advertisement>> Get(int id)
{
    IAdvertisementServices advertisementServices = new AdvertisementServices();
    return await advertisementServices.Query(d => d.Id == id);
}

使用依賴注入呢平绩,有以下優(yōu)點:

  • 傳統(tǒng)的代碼,每個對象負責(zé)管理與自己需要依賴的對象漠另,導(dǎo)致如果需要切換依賴對象的實現(xiàn)類時捏雌,需要修改多處地方。同時笆搓,過度耦合也使得對象難以進行單元測試性湿。
  • 依賴注入把對象的創(chuàng)造交給外部去管理,很好的解決了代碼緊耦合(tight couple)的問題纬傲,是一種讓代碼實現(xiàn)松耦合(loose couple)的機制。
  • 松耦合讓代碼更具靈活性肤频,能更好地應(yīng)對需求變動叹括,以及方便單元測試。

舉個栗子宵荒,就是關(guān)于日志記錄的

日志記錄:有時需要調(diào)試分析汁雷,需要記錄日志信息,這時可以采用輸出到控制臺报咳、文件侠讯、數(shù)據(jù)庫、遠程服務(wù)器等暑刃;假設(shè)最初采用輸出到控制臺厢漩,直接在程序中實例化ILogger logger = new ConsoleLogger(),但有時又需要輸出到別的文件中稍走,也許關(guān)閉日志輸出袁翁,就需要更改程序,把ConsoleLogger改成FileLogger或者NoLogger婿脸, new FileLogger()或者new SqlLogger() 粱胜,此時不斷的更改代碼,就顯得心里不好了狐树,如果采用依賴注入焙压,就顯得特別舒暢。

我有一個個人的理解抑钟,不知道恰當(dāng)與否涯曲,比如我們平時食堂吃飯,都是食堂自己炒的菜在塔,就算是配方都一樣幻件,控制者(廚師)還是會有些變化,或者是出錯蛔溃,但是肯德基這種绰沥,由總店提供的,店面就失去了控制贺待,就出現(xiàn)了第三方(總店或者工廠等)徽曲,這就是實現(xiàn)了控制反轉(zhuǎn),我們只需要說一句麸塞,奧爾良雞翅秃臣,嗯就拿出來一個,而且全部分店的都一樣哪工,我們不用管是否改配方奥此,不管是否依賴哪些調(diào)理食材弧哎,哈哈。

二得院、常見的IoC框架有哪些

Autofac:貌似目前net下用的最多吧
Ninject:目前好像沒多少人用了
Unity:也是較為常見

其實.Net Core 有自己的輕量級的IoC框架傻铣,

ASP.NET Core本身已經(jīng)集成了一個輕量級的IOC容器,開發(fā)者只需要定義好接口后祥绞,在Startup.cs的ConfigureServices方法里使用對應(yīng)生命周期的綁定方法即可非洲,常見方法如下

services.AddTransient<IApplicationService,ApplicationService>//服務(wù)在每次請求時被創(chuàng)建,它最好被用于輕量級無狀態(tài)服務(wù)(如我們的Repository和ApplicationService服務(wù))

services.AddScoped<IApplicationService,ApplicationService>//服務(wù)在每次請求時被創(chuàng)建蜕径,生命周期橫貫整次請求

services.AddSingleton<IApplicationService,ApplicationService>//Singleton(單例) 服務(wù)在第一次請求時被創(chuàng)建(或者當(dāng)我們在ConfigureServices中指定創(chuàng)建某一實例并運行方法)两踏,其后的每次請求將沿用已創(chuàng)建服務(wù)。如果開發(fā)者的應(yīng)用需要單例服務(wù)情景兜喻,請設(shè)計成允許服務(wù)容器來對服務(wù)生命周期進行操作梦染,而不是手動實現(xiàn)單例設(shè)計模式然后由開發(fā)者在自定義類中進行操作。

當(dāng)然.Net Core自身的容器還是比較簡單朴皆,如果想要更多的功能和擴展帕识,還是需要使用上邊上個框架。

三遂铡、較好用的IoC框架使用——Autofac

首先呢肮疗,我們要明白,我們注入是要注入到哪里——Controller API層扒接。然后呢伪货,我們看到了在接口調(diào)用的時候浑度,如果需要其中的方法惧互,需要using兩個命名空間

[HttpGet("{id}", Name = "Get")]
public async Task<List<Advertisement>> Get(int id)
{
    IAdvertisementServices advertisementServices = new AdvertisementServices();//需要引用兩個命名空間Blog.Core.IServices;Blog.Core.Services;

    return await advertisementServices.Query(d => d.Id == id);
}

接下來我們就需要做處理:

1、在Nuget中引入兩個:Autofac.Extras.DynamicProxy(Autofac的動態(tài)代理蔗牡,它依賴Autofac宗侦,所以可以不用單獨引入Autofac)愚臀、Autofac.Extensions.DependencyInjection(Autofac的擴展)

image

2、讓Autofac接管Starup中的ConfigureServices方法矾利,記得修改返回類型IServiceProvider

image
 public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    #region 配置信息
    //Blog.Core.Repository.BaseDBConfig.ConnectionString = Configuration.GetSection("AppSettings:SqlServerConnection").Value;
    #endregion

    #region Swagger 
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info
        {
            Version = "v0.1.0",
            Title = "Blog.Core API",
            Description = "框架說明文檔",
            TermsOfService = "None",
            Contact = new Swashbuckle.AspNetCore.Swagger.Contact { Name = "Blog.Core", Email = "Blog.Core@xxx.com", Url = "http://www.reibang.com/u/94102b59cc2a" }
        }); //就是這里

        #region 讀取xml信息
        var basePath = PlatformServices.Default.Application.ApplicationBasePath; var xmlPath = Path.Combine(basePath, "Blog.Core.xml");//這個就是剛剛配置的xml文件名
        var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml");//這個就是Model層的xml文件名
        c.IncludeXmlComments(xmlPath, true);//默認的第二個參數(shù)是false懊悯,這個是controller的注釋,記得修改
        c.IncludeXmlComments(xmlModelPath);
        #endregion

        #region Token綁定到ConfigureServices
        //添加header驗證信息 //c.OperationFilter<SwaggerHeader>();
        var security = new Dictionary<string, IEnumerable<string>> { { "Blog.Core", new string[] { } }, };
        c.AddSecurityRequirement(security); //方案名稱“Blog.Core”可自定義梦皮,上下一致即可
        c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme
        {
            Description = "JWT授權(quán)(數(shù)據(jù)將在請求頭中進行傳輸) 直接在下框中輸入{token}\"",
            Name = "Authorization",//jwt默認的參數(shù)名稱
            In = "header",//jwt默認存放Authorization信息的位置(請求頭中)
            Type = "apiKey"
        });
        #endregion }); #endregion

        #region Token服務(wù)注冊 services.AddSingleton<IMemoryCache>(factory => { var cache = new MemoryCache(new MemoryCacheOptions()); return cache;
    });

    services.AddAuthorization(options =>
    {
        options.AddPolicy("Admin", policy => policy.RequireClaim("AdminType").Build());//注冊權(quán)限管理,可以自定義多個
    });
    #endregion

    #region AutoFac

    //實例化 AutoFac  容器 
    var builder = new ContainerBuilder(); //注冊要通過反射創(chuàng)建的組件
    builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); //將services填充到Autofac容器生成器中
    builder.Populate(services); //使用已進行的組件登記創(chuàng)建新容器
    var ApplicationContainer = builder.Build();
    #endregion

    return new AutofacServiceProvider(ApplicationContainer);//第三方IOC接管 core內(nèi)置DI容器
}

這個時候我們就把AdvertisementServices的new 實例化過程注入到了Autofac容器中

3桃焕、通過依賴注入的三種方式(構(gòu)造方法注入剑肯、setter方法注入和接口方式注入)中的構(gòu)造函數(shù)方式實現(xiàn)注入

在BlogController中,添加構(gòu)造函數(shù)观堂,并在Get方法中让网,去掉實例化過程呀忧;

        IAdvertisementServices advertisementServices; /// <summary>
        /// 構(gòu)造函數(shù) /// </summary>
        /// <param name="advertisementServices"></param>
        public BlogController(IAdvertisementServices advertisementServices)
        { this.advertisementServices = advertisementServices;
        }

     [HttpGet("{id}", Name = "Get")] public async Task<List<Advertisement>> Get(int id)
        { //IAdvertisementServices advertisementServices = new AdvertisementServices();//需要引用兩個命名空間Blog.Core.IServices;Blog.Core.Services;

            return await advertisementServices.Query(d => d.Id == id);
        }

4、然后運行調(diào)試溃睹,發(fā)現(xiàn)在斷點剛進入的時候而账,接口已經(jīng)被實例化了,達到了注入的目的因篇。

image

5泞辐、這個時候,我們發(fā)現(xiàn)已經(jīng)成功的注入了竞滓,Advertisement實體類到接口中咐吼,但是項目中有那么多的類,都要一個個手動添加么商佑,答案當(dāng)然不是滴~

四锯茄、通過反射將Blog.Core.Services和Blog.Core.Repository兩個程序集的全部方法注入

修改如下代碼,注意這個時候需要在項目依賴中茶没,右鍵肌幽,添加引用Blog.Core.Services到項目,或者把dll文件拷貝到Blog.Core的bin文件夾中抓半,否則反射會找不到喂急。

           var builder = new ContainerBuilder(); //注冊要通過反射創(chuàng)建的組件           
           //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();

            var assemblysServices = Assembly.Load("Blog.Core.Services");
            builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已掃描程序集中的類型注冊為提供所有其實現(xiàn)的接口。
            var assemblysRepository = Assembly.Load("Blog.Core.Repository");
            builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); //將services填充到Autofac容器生成器中
 builder.Populate(services); //使用已進行的組件登記創(chuàng)建新容器
            var ApplicationContainer = builder.Build();

其他不變琅关,運行項目煮岁,一切正常,換其他接口也可以

image

到這里涣易,Autofac依賴注入已經(jīng)完成画机,基本的操作就是這樣,別忙新症!現(xiàn)在還沒有真正的完成喲步氏!現(xiàn)在只是把Service和API層解耦了,Service和Repository還沒有徒爹!

注意:文中和Git代碼中荚醒,因為為了說明的方便,沒有把Api層的Service 層 和 Repository 層給去掉隆嗅,大家手動去掉界阁,我已經(jīng)更新到git上了。

最終的效果是這樣的:工程只是依賴接口層

image

2胖喳、把service.dll 和 Repository.dll 兩個文件拷貝到項目 的 bin \ debug \netcoreapp2.1 下

3泡躯、然后在Autofac依賴注入的時候,出現(xiàn)加載程序集失敗的情況,可以修改如下:

        var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;//獲取項目路徑
            var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll");//獲取注入項目絕對路徑
            var assemblysServices = Assembly.LoadFile(servicesDllFile);//直接采用加載文件的方法

還記得Blog.Core.Services中的BaseServices.cs么较剃,它還是通過new 實例化的方式在創(chuàng)建咕别,仿照contrller,修改BaseServices并在全部子類的構(gòu)造函數(shù)中注入:

    public class BaseServices<TEntity> : IBaseServices<TEntity> where TEntity : class, new()
    { //public IBaseRepository<TEntity> baseDal = new BaseRepository<TEntity>();
        public IBaseRepository<TEntity> baseDal;//通過在子類的構(gòu)造函數(shù)中注入写穴,這里是基類惰拱,不用構(gòu)造函數(shù)
      //... 
   } public class AdvertisementServices : BaseServices<Advertisement>, IAdvertisementServices
    {
        IAdvertisementRepository dal; public AdvertisementServices(IAdvertisementRepository dal)
        { this.dal = dal; base.baseDal = dal;
        }       
    }

好啦,現(xiàn)在整個項目已經(jīng)完成了相互直接解耦的功能啊送,以后就算是Repository和Service如何變化偿短,接口層都不用修改,因為已經(jīng)完成了注入删掀,第三方Autofac會做實例化的過程翔冀。

五、 當(dāng)然披泪,你也可以直接將一個類進行注入纤子,而不一定要繼承接口的方式;

//1\. 定義一個服務(wù)款票,包含一個方法
public class PeopleService
{ public string Run(string m) { return m; }
} //2\. 寫一個擴展方法用來注入服務(wù)控硼,與直接在ConfigureServices返回是一個道理
namespace Haos.Develop.CoreTest
{ public static class Extension
    { public static IServiceCollection AddTestService(this IServiceCollection service)
        { return service.AddScoped(factory => new PeopleService());
        }
    }
} //3\. 回到Startup類中找到ConfigureServices方法添加如下代碼
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(); 
    services.AddTestService();//將上邊的方法注入
} //4.我們可以采用構(gòu)造函數(shù)方式來注入和直接獲取
public PeopleService People; public HomeController(PeopleService people)//構(gòu)造函數(shù)
{
    People = people;
} public JsonResult Test()
{
    People.Run("111"); 
 return Json("");
}

上邊這個方法采用的是單獨注冊一個擴展方法來注入服務(wù),和我們的直接return是一樣的艾少,感興趣的可以試一試卡乾。

同時可以參考這個網(wǎng)友的:

https://www.cnblogs.com/stulzq/p/6880394.html

六、簡單了解通過AOP切面實現(xiàn)日志記錄

什么是AOP缚够?引用百度百科:AOP為Aspect Oriented Programming的縮寫幔妨,意為:面向切面編程,通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的一種技術(shù)谍椅。實現(xiàn)AOP主要由兩種方式误堡,

一種是編譯時靜態(tài)植入,優(yōu)點是效率高雏吭,缺點是缺乏靈活性锁施,.net下postsharp為代表者(好像是付費了。杖们。)悉抵。

另一種方式是動態(tài)代理,優(yōu)點是靈活性強摘完,但是會影響部分效率姥饰,動態(tài)為目標(biāo)類型創(chuàng)建代理,通過代理調(diào)用實現(xiàn)攔截孝治。

AOP能做什么列粪,常見的用例是事務(wù)處理栅螟、日志記錄等等。

常見的AOP都是配合在Ioc的基礎(chǔ)上進行操作篱竭,上邊咱們講了Autofac這個簡單強大的Ioc框架,下面就講講Autofac怎么實現(xiàn)AOP步绸。Autofac的AOP是通過Castle(也是一個容器)項目的核心部分實現(xiàn)的掺逼,名為Autofac.Extras.DynamicProxy,顧名思義,其實現(xiàn)方式為動態(tài)代理瓤介。當(dāng)然AOP并不一定要和依賴注入在一起使用吕喘,自身也可以單獨使用。

網(wǎng)上有一個博友的圖片刑桑,大概講了AOP切面編程

image

七氯质、結(jié)語

今天的文章呢,主要說了依賴注入IoC在項目中的使用祠斧,從上邊的說明中闻察,可以看到,最大的一個優(yōu)點就是實現(xiàn)解耦的作用琢锋,最明顯的就是辕漂,在Controller中,不用在實例服務(wù)層或者業(yè)務(wù)邏輯層了吴超,不過還是有些缺點的钉嘹,缺點之一就是會占用一定的時間資源,效率稍稍受影響鲸阻,不過和整體框架相比跋涣,這個影響應(yīng)該也是值得的。

明天鸟悴,我們繼續(xù)將面向切面編程AOP中的陈辱,日志記錄和面向AOP的緩存使用。

八遣臼、CODE

https://github.com/anjoy8/Blog.Core

QQ群:
867095512 (blod.core)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末性置,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子揍堰,更是在濱河造成了極大的恐慌鹏浅,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屏歹,死亡現(xiàn)場離奇詭異隐砸,居然都是意外死亡,警方通過查閱死者的電腦和手機蝙眶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門季希,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褪那,“玉大人,你說我怎么就攤上這事式塌〔┚矗” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵峰尝,是天一觀的道長偏窝。 經(jīng)常有香客問我,道長武学,這世上最難降的妖魔是什么祭往? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮火窒,結(jié)果婚禮上硼补,老公的妹妹穿的比我還像新娘。我一直安慰自己熏矿,他們只是感情好已骇,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著曲掰,像睡著了一般疾捍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上栏妖,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天乱豆,我揣著相機與錄音,去河邊找鬼吊趾。 笑死宛裕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的论泛。 我是一名探鬼主播揩尸,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屁奏!你這毒婦竟也來了岩榆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤坟瓢,失蹤者是張志新(化名)和其女友劉穎勇边,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體折联,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡粒褒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诚镰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奕坟。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡祥款,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出月杉,到底是詐尸還是另有隱情刃跛,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布苛萎,位于F島的核電站奠伪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏首懈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一谨敛、第九天 我趴在偏房一處隱蔽的房頂上張望究履。 院中可真熱鬧,春花似錦脸狸、人聲如沸最仑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泥彤。三九已至,卻和暖如春卿啡,著一層夾襖步出監(jiān)牢的瞬間吟吝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工颈娜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留剑逃,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓官辽,卻偏偏與公主長得像蛹磺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子同仆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

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