基于MVC+EasyUI的Web開發(fā)框架形成之旅--權(quán)限控制

我在上一篇隨筆《基于MVC4+EasyUI的Web開發(fā)框架形成之旅--框架總體界面介紹》中大概介紹了基于MVC的Web開發(fā)框架的權(quán)限控制總體思路。其中的權(quán)限控制就是分為“用戶登錄身份驗(yàn)證”、“控制器方法權(quán)限控制”颈娜、“界面元素權(quán)限控制”三種控制方式口锭,可以為Web開發(fā)框架本身提供了很好用戶訪問控制和權(quán)限控制伏伐,使得用戶界面呈現(xiàn)菜單桩警、Web界面的按鈕和內(nèi)容炒瘸、Action的提交控制捞慌,均能在總體權(quán)限功能分配和控制之下耀鸦。


本篇文章主要細(xì)化這三個(gè)方面的介紹,重點(diǎn)介紹“控制器方法權(quán)限控制”啸澡、“界面元素權(quán)限控制”這兩種權(quán)限控制方式袖订。

1、用戶登錄控制

登錄界面如下所示嗅虏。



其中登錄的前臺(tái)頁(yè)面代碼如下所示著角,其中可以在登錄界面接收驗(yàn)證碼(如果必要的話)。

//實(shí)現(xiàn)用戶登錄
function LoginUserInfo() {
    //獲取單擊用戶登錄按鈕的事件
    $("#btnLogin").click(function () {
        //首先獲取到要傳遞到控制器的參數(shù)旋恼,并且狗造成Json吏口。UserName,UserPassword冰更,Code
        var postData = {
            UserName: $("#UserName").val(),
            Password: $("#Password").val(),
            Code: $("#Code").val()
        };

        //發(fā)送異步請(qǐng)求實(shí)現(xiàn)登錄 ajax
        $.ajax({
            url: '/Login/CheckUser',
            data: postData,
            cache: false,
            async: true,
            type: 'post',
            success: function (data) {
                if (data == "OK") {
                    window.location.href = "/Home/Index";

                } else {
                    alert(data);
                    window.location.href = "/Login/Index";
                }
            }
        });
    });
}

用戶登錄的后臺(tái)控制器方法如下所示:

/// <summary>
/// 對(duì)用戶登錄的操作進(jìn)行驗(yàn)證
/// </summary>
/// <param name="username">用戶賬號(hào)</param>
/// <param name="password">用戶密碼</param>
/// <param name="code">驗(yàn)證碼</param>
/// <returns></returns>
public ActionResult CheckUser(string username, string password, string code)
{
    string result = "";

    bool codeValidated = true;
    if (this.TempData["ValidateCode"] != null)
    {
        codeValidated = (this.TempData["ValidateCode"].ToString() == code);
    }

    if (string.IsNullOrEmpty(username))
    {
        result = "用戶名不能為空";
    }
    else if (!codeValidated)
    {
        result = "驗(yàn)證碼輸入有誤";
    }
    else
    {
        string ip = GetClientIp();
        string macAddr = "";
        string identity = BLLFactory<WHC.Security.BLL.User>.Instance.VerifyUser(username, password, MyConstants.SystemType, ip, macAddr);
        if (!string.IsNullOrEmpty(identity))
        {
            UserInfo info = BLLFactory<WHC.Security.BLL.User>.Instance.GetUserByName(username);
            if (info != null)
            {
                result = "OK";
                Session["UserInfo"] = info;
                Session["Identity"] = info.Name.Trim();

                #region 取得用戶的授權(quán)信息产徊,并存儲(chǔ)在Session中

                List<FunctionInfo> functionList = BLLFactory<Function>.Instance.GetFunctionsByUser(info.ID, MyConstants.SystemType);
                Dictionary<string, string> functionDict = new Dictionary<string, string>();
                foreach (FunctionInfo functionInfo in functionList)
                {
                    if (!string.IsNullOrEmpty(functionInfo.ControlID) &&
                        !functionDict.ContainsKey(functionInfo.ControlID))
                    {
                        functionDict.Add(functionInfo.ControlID, functionInfo.ControlID);
                    }
                }
                Session["Functions"] = functionDict;

                #endregion
            }
        }
        else
        {
            result = "用戶名輸入錯(cuò)誤或者您已經(jīng)被禁用";
        }
    }

    return Content(result);
}

從上面的代碼,我們可以看到蜀细,在用戶登錄成功后舟铜,后臺(tái)把用戶信息、用戶權(quán)限列表信息放到了Session里面奠衔,方便進(jìn)行后面的權(quán)限控制谆刨。

然后當(dāng)前端頁(yè)面獲得成功響應(yīng)并切換到Home的Index視圖前塘娶,后臺(tái)會(huì)調(diào)用Home的控制器,把一些用戶信息放到了ViewBag對(duì)象里面痊夭,并構(gòu)造用戶的相關(guān)菜單項(xiàng)目刁岸,代碼如下所示。

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        if (CurrentUser != null)
        {
            ViewBag.FullName = CurrentUser.FullName;
            ViewBag.Name = CurrentUser.Name;

            StringBuilder sb = new StringBuilder();
            List<MenuInfo> menuList = BLLFactory<Menu>.Instance.GetTopMenu(MyConstants.SystemType);
            int i = 0;
            foreach (MenuInfo menuInfo in menuList)
            {
                sb.Append(GetMenuItemString(menuInfo, i));
                i++;
            }
            ViewBag.HeaderScript = sb.ToString();//一級(jí)菜單代碼
        }
        return View();            
    }

2她我、控制器方法權(quán)限控制

我們知道虹曙,對(duì)頁(yè)面的權(quán)限控制,可以分為前端控制和后臺(tái)代碼的控制番舆,控制器方法的權(quán)限控制屬于后臺(tái)代碼的控制酝碳。為了方便基類代碼的權(quán)限控制,我們定義一個(gè)權(quán)限控制鍵的類恨狈,用來記錄通用的增加疏哗、修改、刪除禾怠、查看返奉、列表、導(dǎo)出等傳統(tǒng)控制元素刃宵,代碼如下所示衡瓶。

/// <summary>
/// 定義常用功能的控制ID,方便基類控制器對(duì)用戶權(quán)限的控制
/// </summary>
[DataContract]
[Serializable]
public class AuthorizeKey
{
    #region 常規(guī)功能控制ID
    /// <summary>
    /// 新增記錄的功能控制ID
    /// </summary>
    public string InsertKey { get; set; }

    /// <summary>
    /// 更新記錄的功能控制ID
    /// </summary>
    public string UpdateKey { get; set; }

    /// <summary>
    /// 刪除記錄的功能控制ID
    /// </summary>
    public string DeleteKey { get; set; }

    /// <summary>
    /// 查看列表的功能控制ID
    /// </summary>
    public string ListKey { get; set; }

    /// <summary>
    /// 查看明細(xì)的功能控制ID
    /// </summary>
    public string ViewKey { get; set; }

    /// <summary>
    /// 導(dǎo)出記錄的功能控制ID
    /// </summary>
    public string ExportKey { get; set; } 
    #endregion

    #region 常規(guī)權(quán)限判斷
    /// <summary>
    /// 判斷是否具有插入權(quán)限
    /// </summary>
    public bool CanInsert { get; set; }

    /// <summary>
    /// 判斷是否具有更新權(quán)限
    /// </summary>
    public bool CanUpdate { get; set; }

    /// <summary>
    /// 判斷是否具有刪除權(quán)限
    /// </summary>
    public bool CanDelete { get; set; }

    /// <summary>
    /// 判斷是否具有列表權(quán)限
    /// </summary>
    public bool CanList { get; set; }

    /// <summary>
    /// 判斷是否具有查看權(quán)限
    /// </summary>
    public bool CanView { get; set; }

    /// <summary>
    /// 判斷是否具有導(dǎo)出權(quán)限
    /// </summary>
    public bool CanExport { get; set; }

    #endregion

    /// <summary>
    /// 默認(rèn)構(gòu)造函數(shù)
    /// </summary>
    public AuthorizeKey() { }

    /// <summary>
    /// 常用構(gòu)造函數(shù)
    /// </summary>
    public AuthorizeKey(string insert, string update, string delete, string view = "") 
    {
        this.InsertKey = insert;
        this.UpdateKey = update;
        this.DeleteKey = delete;
        this.ViewKey = view;
    }
}

有了這個(gè)實(shí)體類牲证,我們就可以在控制器的基類BaseController里面實(shí)現(xiàn)一些控制邏輯了哮针。首先我們?cè)诳刂破髅看螆?zhí)行方法前,都對(duì)權(quán)限進(jìn)行一個(gè)轉(zhuǎn)換坦袍,并把控制鍵存儲(chǔ)到ViewBage里面十厢,方便前端頁(yè)面的控制,如下代碼所示捂齐。

/// <summary>
/// 重新基類在Action執(zhí)行之前的事情
/// </summary>
/// <param name="filterContext">重寫方法的參數(shù)</param>
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    base.OnActionExecuting(filterContext);

    //得到用戶登錄的信息
    CurrentUser = Session["UserInfo"] as UserInfo;            
    if (CurrentUser == null)
    {
        Response.Redirect("/Login/Index");//如果用戶為空跳轉(zhuǎn)到登錄界面
    }

    //設(shè)置授權(quán)屬性蛮放,然后賦值給ViewBag保存
    ConvertAuthorizedInfo();
    ViewBag.AuthorizeKey = AuthorizeKey;
}

其中ConvertAuthorizedInfo()函數(shù)是驗(yàn)證登陸用戶是否具有相應(yīng)的權(quán)限的。

/// <summary>
/// 對(duì)AuthorizeKey對(duì)象里面的操作權(quán)限進(jìn)行賦值奠宜,用于頁(yè)面判斷
/// </summary>
protected virtual void ConvertAuthorizedInfo()
{
    //判斷用戶權(quán)限
    AuthorizeKey.CanInsert = HasFunction(AuthorizeKey.InsertKey);
    AuthorizeKey.CanUpdate = HasFunction(AuthorizeKey.UpdateKey);
    AuthorizeKey.CanDelete = HasFunction(AuthorizeKey.DeleteKey);
    AuthorizeKey.CanView = HasFunction(AuthorizeKey.ViewKey);
    AuthorizeKey.CanList = HasFunction(AuthorizeKey.ListKey);
    AuthorizeKey.CanExport = HasFunction(AuthorizeKey.ExportKey);
}

其中BaseController的控制器基類還定義了判斷用戶是否有某些權(quán)限的邏輯包颁,如果沒有沒有權(quán)限,就會(huì)拋出自定義異常(MyDenyAccessException)压真,代碼如下娩嚼。

/// <summary>
/// 用于檢查方法執(zhí)行前的權(quán)限,如果未授權(quán)滴肿,返回MyDenyAccessException異常
/// </summary>
/// <param name="functionId"></param>
protected virtual void CheckAuthorized(string functionId)
{
    if(!HasFunction(functionId))
    {
        string errorMessage = "您未被授權(quán)使用該功能岳悟,請(qǐng)重新登錄測(cè)試或聯(lián)系管理員進(jìn)行處理。";
        throw new MyDenyAccessException(errorMessage);
    }
}

有了上面的這些邏輯,我們?cè)跇I(yè)務(wù)控制器基類(BusinessController<B, T>)里面贵少,就可以實(shí)現(xiàn)對(duì)一些基本操作的API的權(quán)限控制了呵俏。

/// <summary>
/// 本控制器基類專門為訪問數(shù)據(jù)業(yè)務(wù)對(duì)象而設(shè)的基類
/// </summary>
/// <typeparam name="B">業(yè)務(wù)對(duì)象類型</typeparam>
/// <typeparam name="T">實(shí)體類類型</typeparam>
public class BusinessController<B, T> : BaseController
    where B : class
    where T : WHC.Framework.ControlUtil.BaseEntity, new()
{

    /// <summary>
    /// 插入指定對(duì)象到數(shù)據(jù)庫(kù)中
    /// </summary>
    /// <param name="info">指定的對(duì)象</param>
    /// <returns>執(zhí)行操作是否成功。</returns>
    public virtual ActionResult Insert(T info)
    {
        //檢查用戶是否有權(quán)限滔灶,否則拋出MyDenyAccessException異常
        base.CheckAuthorized(AuthorizeKey.InsertKey);

        bool result = false;
        if (info != null)
        {
            result = baseBLL.Insert(info);
        }
        return Content(result);
    }

    /// <summary>
    /// 更新對(duì)象屬性到數(shù)據(jù)庫(kù)中
    /// </summary>
    /// <param name="info">指定的對(duì)象</param>
    /// <param name="id">主鍵ID的值</param>
    /// <returns>執(zhí)行成功返回<c>true</c>普碎,否則為<c>false</c>。</returns>
    public virtual ActionResult Update(string id, FormCollection formValues)
    {
        //檢查用戶是否有權(quán)限宽气,否則拋出MyDenyAccessException異常
        base.CheckAuthorized(AuthorizeKey.UpdateKey);

        T obj = baseBLL.FindByID(id);
        if (obj != null)
        {
            //遍歷提交過來的數(shù)據(jù)(可能是實(shí)體類的部分屬性更新)
            foreach (string key in formValues.Keys)
            {
                string value = formValues[key];
                System.Reflection.PropertyInfo propertyInfo = obj.GetType().GetProperty(key);
                if (propertyInfo != null)
                {
                    try
                    {
                        // obj對(duì)象有key的屬性随常,把對(duì)應(yīng)的屬性值賦值給它(從字符串轉(zhuǎn)換為合適的類型)
                        //如果轉(zhuǎn)換失敗潜沦,會(huì)拋出InvalidCastException異常
                        propertyInfo.SetValue(obj, Convert.ChangeType(value, propertyInfo.PropertyType), null);
                    }
                    catch { }
                }
            }
        }

        bool result = baseBLL.Update(obj, id);
        return Content(result);
    }

3萄涯、界面元素權(quán)限控制

我們從上面那個(gè)Web開發(fā)框架的主界面圖可以看到,里面對(duì)于某個(gè)特定的業(yè)務(wù)唆鸡,增加涝影、修改、争占、查看燃逻、刪除等操作都放在了EasyUI的DataGrid工具欄里面了,為了動(dòng)態(tài)控制用戶能訪問的界面按鈕臂痕,我們需要結(jié)合用戶權(quán)限集合進(jìn)行界面呈現(xiàn)伯襟,首先我們把ToolBar放到一個(gè)層里面進(jìn)行定義,如下代碼所示握童。

//實(shí)現(xiàn)對(duì)DataGird控件的綁定操作
function InitGrid(queryData) {
    $('#grid').datagrid({   //定位到Table標(biāo)簽姆怪,Table標(biāo)簽的ID是grid
        url: '/Information/FindWithPager',   //指向后臺(tái)的Action來獲取當(dāng)前用戶的信息的Json格式的數(shù)據(jù)
        title: '通知公告',
        iconCls: 'icon-view',
        height: 650,
        width: function () { return document.body.clientWidth * 0.9 },//自動(dòng)寬度
        nowrap: true,
        autoRowHeight: true,
        striped: true,
        collapsible: true,
        pagination: true,
        pageSize: 50,
        pageList: [50, 100, 200],
        rownumbers: true,
        //sortName: 'ID',    //根據(jù)某個(gè)字段給easyUI排序
        sortOrder: 'asc',
        remoteSort: false,
        idField: 'ID',
        queryParams: queryData,  //異步查詢的參數(shù)
        columns: [[
             { field: 'ck', checkbox: true },   //選擇
             { title: '標(biāo)題', field: 'Title', width: 350, sortable: true },
             { title: '編輯者', field: 'Editor', width: 80, sortable: true },
             { title: '編輯時(shí)間', field: 'EditTime', width: 150, sortable: true },
             { title: '附件', field: 'Attachment_GUID', width: 250, sortable: true }
        ]],
        toolbar: "#gridtoolbar",

然后在HTML里面添加gridtoolbar的層定義,作為easyUI的表格控件的工具條澡绩。由于使用了HTML輔助類來實(shí)現(xiàn)界面控件代碼控制生成稽揭,因此已經(jīng)可以達(dá)到了界面權(quán)限的控制了。使用這種HTML層定義的工具條定義方式肥卡,比通過腳本定義的工具條效果少了一個(gè)分隔線溪掀,其他的都還是一致的。


<div id="gridtoolbar" style="padding: 5px; height: auto">
    <div style="margin-bottom: 5px">
        @if (@ViewBag.AuthorizeKey.CanInsert)
        {
            @Html.ActionLink("添加", null, null, new {onclick="ShowAddDialog()", data_options="iconCls:'icon-add', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"})
        }
        @if (@ViewBag.AuthorizeKey.CanUpdate)
        {
            @Html.ActionLink("修改", null, null, new {onclick="ShowEditOrViewDialog()", data_options="iconCls:'icon-edit', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"})
        }
        @if (@ViewBag.AuthorizeKey.CanDelete)
        {
            @Html.ActionLink("刪除", null, null, new {onclick="Delete()", data_options="iconCls:'icon-remove', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"})
        }
        @if (@Html.HasFunction("Information/View"))
        {
            @Html.ActionLink("查看", null, null, new {onclick="ShowEditOrViewDialog('view')", data_options="iconCls:'icon-table', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"})
        }
        @Html.ActionLink("刷新", null, null, new {onclick="$('#grid').datagrid('reload');", data_options="iconCls:'icon-reload', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"}) 
                  
    </div>
</div>

上面使用了兩種方式來判斷用戶的權(quán)限的步鉴,一種是使用這種ViewBag對(duì)象的樹形進(jìn)行判斷揪胃,如下所示。

@if (@ViewBag.AuthorizeKey.CanDelete)

還有一種是使用HTML輔助類的擴(kuò)展方法進(jìn)行判斷氛琢,這種方法適用于一些非常規(guī)的權(quán)限控制集合的判斷喊递,如下所示

@if (@Html.HasFunction("Information/View"))

其中HTML輔助類方法是通過擴(kuò)展靜態(tài)方法進(jìn)行實(shí)現(xiàn),代碼如下所示艺沼。

public static class HtmlHelpers
{
    public static bool HasFunction(this HtmlHelper helper, string functionId)
    {
        return Permission.HasFunction(functionId);
    }

    public static bool IsAdmin()
    {
        return Permission.IsAdmin();
    }
}

上面的界面控制方法册舞,是通過控制界面代碼的生成與否進(jìn)行權(quán)限控制的,前面我們講了障般,通過后臺(tái)代碼的控制器方法也是可以實(shí)現(xiàn)控制调鲸,而且是拋出自定義的錯(cuò)誤盛杰,那么我們?cè)谑褂肁jax方法調(diào)用的時(shí)候,也可以對(duì)這個(gè)錯(cuò)誤信息進(jìn)行友好顯示藐石,提示用戶權(quán)限不足即供,前端頁(yè)面操作代碼如下。

//綁定添加按鈕的事件
function BindAddEvent() {
    $("#btnAddOK").click(function () {
        //判斷表單的信息是否通過驗(yàn)證
        var validate = $("#ffAdd").form('validate');
        if (validate == false) {
            return false;
        }
        
        var postData = $("#ffAdd").serializeArray();
        $.post("/Information/Insert", postData, function (data) {
            if (data = "true") {
                //添加成功  1.關(guān)閉彈出層于微,2.刷新DataGird
                $.messager.alert("提示", "添加成功");
                $("#DivAdd").dialog("close");
                $("#grid").datagrid("reload");
                $("#ffAdd").form("clear");

                //本頁(yè)面的類型為【通知公告】逗嫡,固定不變
                $("#Category").val("通知公告");
            }
            else {
                $.messager.alert("提示", "添加失敗,請(qǐng)您檢查");
            }
        }).error(function () {
            $.messager.alert("提示", "您未被授權(quán)使用該功能株依,請(qǐng)聯(lián)系管理員進(jìn)行處理驱证。", 'warning');
        });
    });
}

以上就是我對(duì)Web開發(fā)框架中的權(quán)限控制幾個(gè)方面的思路和代碼,希望拋磚引玉恋腕,獲得大家更好的反饋和支持抹锄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市荠藤,隨后出現(xiàn)的幾起案子伙单,更是在濱河造成了極大的恐慌,老刑警劉巖哈肖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吻育,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡淤井,警方通過查閱死者的電腦和手機(jī)布疼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庄吼,“玉大人缎除,你說我怎么就攤上這事∽苎埃” “怎么了器罐?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)渐行。 經(jīng)常有香客問我轰坊,道長(zhǎng),這世上最難降的妖魔是什么祟印? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任肴沫,我火速辦了婚禮,結(jié)果婚禮上蕴忆,老公的妹妹穿的比我還像新娘颤芬。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布站蝠。 她就那樣靜靜地躺著汰具,像睡著了一般。 火紅的嫁衣襯著肌膚如雪菱魔。 梳的紋絲不亂的頭發(fā)上留荔,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音澜倦,去河邊找鬼聚蝶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛藻治,可吹牛的內(nèi)容都是我干的碘勉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼栋艳,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼恰聘!你這毒婦竟也來了句各?” 一聲冷哼從身側(cè)響起吸占,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凿宾,沒想到半個(gè)月后矾屯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡初厚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年件蚕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片产禾。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡排作,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出亚情,到底是詐尸還是另有隱情妄痪,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布楞件,位于F島的核電站衫生,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏土浸。R本人自食惡果不足惜罪针,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黄伊。 院中可真熱鬧泪酱,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至岂津,卻和暖如春虱黄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吮成。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工橱乱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粱甫。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓泳叠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親茶宵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子危纫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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