团队开发框架实战——一个简单的CRUD示例

新思想、新技术、新架构——更好更快的开发现代ASP.NET应用程序

Tdf团队开发框架分层架构图

Tdf团队开发框架分层架构图.png

Tdf团队开发框架汇集了一些比较流行的技术和开源项目,也把自己的程序架构、部分代码风格、前端表现简单做了一些展示。
由于这个功能实在太简单,没有使用到领域服务、领域事件,这里可能只能说明一件事件:没有复杂业务逻辑的功能使用此DDD框架,并不会增加代码量,反而我认为这样的代码量差不多已经少到极致了。

1 Tdf.Domain.Entities

Role.cs

using System;
using Tdf.Domain.Entities;
using Tdf.Domain.Entities.Auditing;

namespace Tdf.Domain.Act.Entities
{
    public class Role : Entity
        , IFullAudited<Guid?>
        , IMustHaveTenant
    {
        public Guid TenantId { get; set; }
        public string Name { get; set; }
        public string DisplayName { get; set; }
        public bool IsStatic { get; set; }
        public bool IsDefault { get; set; }
        public bool IsDeleted { get; set; }
        public Guid? DeleterUserId { get; set; }
        public DateTime? DeletionTime { get; set; }
        public Guid? LastModifierUserId { get; set; }
        public DateTime? LastModificationTime { get; set; }
        public Guid? CreatorUserId { get; set; }
        public DateTime CreationTime { get; set; }
        public int DisOrder { get; set; }
        public int State { get; set; }
    }
}

2 Tdf.EntityFramework

ActDbContext.cs

public virtual IDbSet<Role> Roles { get; set; }
modelBuilder.Configurations.Add(new RoleMap());

RoleMap.cs

using System.Data.Entity.ModelConfiguration;
using Tdf.Domain.Act.Entities;

namespace Tdf.EntityFramework.Act.Mapping
{
    public class RoleMap : EntityTypeConfiguration<Role>
    {
        public RoleMap()
        {
            // Primary Key
            this.HasKey(t => t.Id);

            // Table & Column Mappings
            this.ToTable("Act_Role");

            this.Property(t => t.Id).HasColumnName("RoleId");
        }
    }
}

3 Tdf.Application

Dtos

CreateRoleInput.cs

using System;
using Tdf.Application.Services.Dto;

namespace Tdf.Application.Act.RoleMgr.Dtos
{
    public class CreateRoleInput : IInputDto
    {
      
        public Guid TenantId { get; set; }
        public string Name { get; set; }
        public string DisplayName { get; set; }
        public bool IsStatic { get; set; }
        public bool IsDefault { get; set; }
        public bool IsDeleted { get; set; }
        public Guid? DeleterUserId { get; set; }
        public DateTime? DeletionTime { get; set; }
        public Guid? LastModifierUserId { get; set; }
        public DateTime? LastModificationTime { get; set; }
        public Guid? CreatorUserId { get; set; }
        public DateTime CreationTime { get; set; }
        public int DisOrder { get; set; }
        public int State { get; set; }
    }
}

UpdateRoleInput.cs

using System;

namespace Tdf.Application.Act.RoleMgr.Dtos
{
    public class UpdateRoleInput : CreateRoleInput
    {
        public Guid Id { get; set; }
    }
}

RoleDto.cs

using System;

namespace Tdf.Application.Act.RoleMgr.Dtos
{
    public class RoleDto
    {
        public Guid Id { get; set; }
        public Guid TenantId { get; set; }
        public string Name { get; set; }
        public string DisplayName { get; set; }
        public bool IsStatic { get; set; }
        public bool IsDefault { get; set; }
        public bool IsDeleted { get; set; }
        public Guid? DeleterUserId { get; set; }
        public DateTime? DeletionTime { get; set; }
        public Guid? LastModifierUserId { get; set; }
        public DateTime? LastModificationTime { get; set; }
        public Guid? CreatorUserId { get; set; }
        public DateTime CreationTime { get; set; }
        public string DisOrder { get; set; }
        public string State { get; set; }
    }
}

IRoleAppService.cs

using System.Threading.Tasks;
using Tdf.Application.Act.RoleMgr.Dtos;
using Tdf.Application.Services;
using Tdf.Application.Services.Dto;

namespace Tdf.Application.Act.RoleMgr
{
    public interface IRoleAppService : IApplicationService
    {
        Task<JsonMessage> Create(CreateRoleInput model);
        Task<JsonMessage> Update(UpdateRoleInput model);
        Task<JsonMessage> GetInfo(DetailInfoInput model);
        Task<JsonMessage> BatchDelete(BatchDeleteInput model);
        JsonMessage GetPage(PageInput model);
    }
}

RoleAppService

using AutoMapper;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Tdf.Application.Act.RoleMgr.Dtos;
using Tdf.Application.Services.Dto;
using Tdf.Domain.Act.Entities;
using Tdf.Domain.Repositories;
using Tdf.Domain.Uow;
using Tdf.Utils.Excp;
using Tdf.Utils.Networking;

namespace Tdf.Application.Act.RoleMgr
{
    public class RoleAppService : IRoleAppService
    {
        private readonly IUnitOfWork _uow;

        private readonly IRepository<Role> _roleRepository;

        public RoleAppService(IUnitOfWork uow, IRepository<Role> roleRepository)
        {
            this._roleRepository = roleRepository;
            this._uow = uow;
        }

        public async Task<JsonMessage> Create(CreateRoleInput model)
        {

            var entity = Mapper.Map<CreateRoleInput, Role>(model);
            entity.Id = Guid.NewGuid();

            await _roleRepository.InsertAsync(entity);
            await _uow.SaveChangesAsync();

            return ServiceResult.GetErrorMsgByErrCode(0);
        }

        public async Task<JsonMessage> Update(UpdateRoleInput model)
        {
            Role entity = await _roleRepository.SingleAsync(b => b.Id == model.Id);
            entity.Name = model.Name;
            entity.DisplayName = model.DisplayName;
            entity.State = model.State;
            entity.DisOrder = model.DisOrder;

            await _roleRepository.UpdateAsync(entity);
            await _uow.SaveChangesAsync();

            return ServiceResult.GetErrorMsgByErrCode(0);
        }

        public async Task<JsonMessage> GetInfo(DetailInfoInput model)
        {
            var entity = await _roleRepository.SingleAsync(b => b.Id == model.Id);

            var jsonMsg = ServiceResult.GetErrorMsgByErrCode(0);
            jsonMsg.Result = Mapper.Map<Role, RoleDto>(entity);
            return jsonMsg;
        }

        public async Task<JsonMessage> BatchDelete(BatchDeleteInput model)
        {
            if (model.IdList.Count == 0)
                throw new CustomException(-1, "没有删除的对象!");

            Role entity;
            foreach (var item in model.IdList)
            {
                entity = await _roleRepository.SingleAsync(b => b.Id == item);
                await _roleRepository.DeleteAsync(entity);
            }

            var cnt = await _uow.SaveChangesAsync();
            var jsonMsg = ServiceResult.GetErrorMsgByErrCode(0);
            jsonMsg.Result = $"成功删除{cnt}条数据";
            return jsonMsg;
        }

        public JsonMessage GetPage(PageInput model)
        {
            if (string.IsNullOrEmpty(model.OrderBy))
                model.OrderBy = "DisOrder";

            object entity;
            int total = 0;

            if (!string.IsNullOrEmpty(model.Keyword))
            {
                entity = _roleRepository.Get(p => p.Name.Contains(model.Keyword), model.OrderBy, model.PageIndex, model.PageSize);
                total = _roleRepository.Count(p => p.Name.Contains(model.Keyword));
            }
            else
            {
                entity = _roleRepository.Get(model.OrderBy, model.PageIndex, model.PageSize);
                total = _roleRepository.Count();
            }

            var jsonMsg = ServiceResult.GetErrorMsgByErrCode(0);
            jsonMsg.Result = new PageOutput() { Total = total, Records = Mapper.Map<List<RoleDto>>(entity) };
            return jsonMsg;

        }
    }
}

4 Tdf.WebApi

** UnityConfig.cs **

container.RegisterType<ActDbContext>(new HierarchicalLifetimeManager());
container.RegisterType<IUnitOfWork, ActUnitOfWork>(new HierarchicalLifetimeManager());

container.RegisterType<IRepository<Role>, ActRepositoryBase<Role>>(new HierarchicalLifetimeManager());
container.RegisterType<IRoleAppService, RoleAppService>(new HierarchicalLifetimeManager());

** ActProfile.cs **

CreateMap<CreateRoleInput, Role>();
CreateMap<UpdateRoleInput, Role>();
CreateMap<Role, RoleDto>();

ActRoleController.cs

using System;
using System.Threading.Tasks;
using System.Web.Http;
using Tdf.Application.Act.RoleMgr;
using Tdf.Application.Act.RoleMgr.Dtos;
using Tdf.Application.Services.Dto;

namespace Tdf.WebApi.Controllers.ActMgr
{
    public class ActRoleController : ApiController
    {

        private readonly IRoleAppService _roleAppService;

        public ActRoleController(IRoleAppService roleAppService)
        {
            _roleAppService = roleAppService;
        }

        [Route("api/ActRole/CreateRole")]
        [HttpPost]
        public async Task<JsonMessage> CreateRole([FromBody]CreateRoleInput input)
        {
            string defaultGuid = "{41643A10-7D28-4692-94FA-4A427FE22147}";
            input.TenantId = new Guid(defaultGuid);
            input.IsStatic = true;
            input.IsDefault = true;
            input.IsDeleted = false;
            input.CreatorUserId = Guid.NewGuid();
            return await _roleAppService.Create(input);
        }

        [Route("api/ActRole/UpdateRole")]
        [HttpPost]
        public async Task<JsonMessage> UpdateRole([FromBody]UpdateRoleInput input)
        {
            return await _roleAppService.Update(input);
        }

        [Route("api/ActRole/GetPageRole")]
        [HttpGet]
        public JsonMessage GetPageRole([FromUri]PageInput input)
        {
            return _roleAppService.GetPage(input);
        }

        [Route("api/ActRole/GetRoleInfo")]
        [HttpGet]
        public async Task<JsonMessage> GetRoleInfo([FromUri]DetailInfoInput input)
        {
            return await _roleAppService.GetInfo(input);
        }

        [Route("api/ActRole/BatchDeleteRole")]
        [HttpPost]
        public async Task<JsonMessage> BatchDeleteRole([FromBody]BatchDeleteInput input)
        {
            return await _roleAppService.BatchDelete(input);
        }

    }
}

5 Tdf.Web

roleList.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="../../lib/jquery-easyui-1.4.1/themes/gray/easyui.css" rel="stylesheet" type="text/css" />
    <link href="../../lib/jquery-easyui-1.4.1/themes/icon.css" rel="stylesheet" type="text/css" />
    <link href="../../css/main.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div id="toolbar">
        <div>
            <a href="javascript:void(0)" class="easyui-linkbutton" iconcls="icon-add" onclick="add()" plain="true">新增</a>
            <a href="javascript:void(0)" class="easyui-linkbutton" iconcls="icon-edit" onclick="edit()" plain="true">修改</a>
            <a href="javascript:void(0)" class="easyui-linkbutton" iconcls="icon-cancel" onclick="del()" plain="true">删除</a>
            <label style="margin-left:20px">关键字:</label><input id="txtKeyword" class="easyui-textbox" data-options="prompt:'请输入关键字'" style="width:150px;height:25px" value="" />
            <a href="#" class="easyui-linkbutton" onclick="searchInfo()" iconCls="icon-search">查询</a>
        </div>
    </div>
    <div id="dg">
    </div>

    <script src="../../js/jquery-1.10.2.min.js"></script>
    <script src="../../lib/jquery-easyui-1.4.1/jquery.easyui.min.js"></script>
    <script src="../../lib/jquery-easyui-1.4.1/locale/easyui-lang-zh_CN.js"></script>
    <script src="../../js/boer_util.js"></script>
    <script src="../../js/jQuery.extend.js"></script>

    <script type="text/javascript">
        var title = '角色管理';           // 1.dialogTitle
        var dataKey = "Id";              // 2.dataKey
        $(document).ready(function () {
            var h = $(window).height();

            $("#dg").datagrid({
                title: title,
                autoRowHeight: false,
                loadMsg: "数据加载中,请稍候...",
                pageList: [50, 40, 30, 20, 10],
                pageSize: 20,
                striped: true,
                height: h,
                fitColumns: true,
                singleSelect: false,
                rownumbers: true,
                pagination: true,
                nowrap: false,
                showFooter: false,
                toolbar: '#toolbar',
                remotesort: true,
                sortName: 'DisOrder',    // 3.sortName
                sortOrder: 'asc',
                columns: [[              // 4.columns
                    { field: dataKey, halign: 'center', checkbox: true },
                    {
                        field: 'Name', title: '角色名称', sortable: true, width: 200, halign: 'center', align: 'center', fixed: true,
                        formatter: function (value, row, index) {
                            return value;
                        }
                    },
                    {
                        field: 'DisplayName', title: '角色描述', sortable: true, width: 250, halign: 'center', align: 'center', fixed: true,
                        formatter: function (value, row, index) {
                            return value;
                        }
                    },
                    {
                        field: 'State', title: '使用状态', sortable: true, width: 100, halign: 'center', align: 'center', fixed: true,
                        formatter: function (value, row, index) {
                            switch (value) {
                                case '1': return "正在使用";
                                case '0': return "停止使用";
                                default: return value;
                            }
                        }
                    },
                    {
                        field: 'DisOrder', title: '显示顺序', sortable: true, width: 80, halign: 'center', align: 'center', fixed: true,
                        formatter: function (value, row, index) {
                            return value;
                        }
                    }
                ]],
                onSortColumn: function (sort, order) {
                    loadData();
                }
            });

            loadData();
        });

        function loadData() {
            $('#dg').datagrid('getPager').pagination({
                displayMsg: '当前显示从 [{from}] 到 [{to}] 共[{total}]条记录',
                onSelectPage: function (pPageIndex, pPageSize) {
                    loadData();
                }
            });

            var ajaxData = {};
            var options = $('#dg').datagrid('getPager').data("pagination").options;
            if (options.pageNumber < 1) {
                options.pageNumber = 1;
            }
            var orderby = $('#dg').datagrid('options').sortName + ' ' + $('#dg').datagrid('options').sortOrder;
            var keyword = $("#txtKeyword").textbox("getValue").trim();

            ajaxData.PageIndex = options.pageNumber;
            ajaxData.PageSize = options.pageSize;
            ajaxData.OrderBy = orderby;
            ajaxData.Keyword = keyword;

            // 5.loadData request api
            BoerAjax("/api/ActRole/GetPageRole", ajaxData, "get", function (data) {
                if (data.ErrCode == 0) {
                    $('#dg').datagrid('clearSelections');
                    $('#dg').datagrid('loadData', GetEasyUIData(data.Result));
                } else {
                    $.messager.alert('警告', '错误代码:' + data.ErrCode + ',' + data.ErrMsg, 'warning');
                }
            });
        }

        function add() {
            top.$.formdialog({
                id: "dlg_roleDetail",
                title: "新增" + title,
                src: VirtualAdminPath + "/act/roleDetail.html",  // 6.add dialog path
                width: 550,
                height: 350,
                iconCls: 'icon-edit',
                handler: "formSubmit('dlg_roleDetail','" + title + "')"
            });
            top.$('#dlg_roleDetail').dialog("open");
        }

        function edit() {
            var id = $.getSelected("dg", dataKey);

            if (isEmpty(id)) {
                $.messager.alert('警告', '请选择一项数据!');
                return;
            }
            top.$.formdialog({
                id: "dlg_roleDetail",
                title: "修改" + title,
                src: VirtualAdminPath + "/act/roleDetail.html?id=" + id,   // 7.edit dialog path
                width: 550,
                height: 350,
                iconCls: 'icon-edit',
                handler: "formSubmit('dlg_roleDetail','" + title + "')"
            });
            top.$('#dlg_roleDetail').dialog("open");
        }

        function del() {
            var ids = $.getSelections("dg", dataKey);
            if (isEmpty(ids)) {
                $.messager.alert('警告', '请至少选择一项!');
                return;
            }

            $.messager.confirm('确定删除', '您确认想要删除这' + $('#dg').datagrid('getSelections').length + '条记录吗?', function (r) {
                if (r) {
                    var ajaxData = {};
                    ajaxData.IdList = ids.split(',');
                    // 8.del request api
                    BoerAjax("/api/ActRole/BatchDeleteRole", ajaxData, "post", function (data) {
                        if (data.ErrCode == 0) {
                            $.messager.alert('成功', data.Result, 'info');
                            loadData();
                        } else {
                            $.messager.alert('警告', '错误代码:' + data.ErrCode + ',' + data.ErrMsg, 'warning');
                        }
                    });
                };
            });
        }

        function searchInfo() {
            loadData();
        }

        function dataReload() {
            $("#txtKeyword").val('');
            searchInfo();
        }

        $(window).resize(function () {
            onResize();
        });

        function onResize() {
            $("#dg").height = $(window).height();
            $("#dg").datagrid("options").width = $(window).width();
            $("#dg").datagrid("resize");
        }
    </script>
</body>
</html>

roleDetail.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="../../css/formmain.css" rel="stylesheet" />
    <link href="../../lib/jquery-easyui-1.4.1/themes/icon.css" rel="stylesheet" />
    <link href="../../lib/jquery-easyui-1.4.1/themes/gray/easyui.css" rel="stylesheet" />
</head>
<body>
    <div style="padding: 10px 10px 10px 10px">
        <!-- 1.page columns -->
        <table cellpadding="5">
            <tr>
                <td>角色名称:</td>
                <td><input class="easyui-textbox" type="text" id="txtName" data-options="required:true" /></td>
            </tr>
            <tr>
                <td>角色描述:</td>
                <td><input class="easyui-textbox" data-options="multiline:true" style="width:300px;height:100px" type="text" id="txtDisplayName" /></td>
            </tr>
            <tr>
                <td>使用状态:</td>
                <td>
                    <select id="ddlState" class="easyui-combobox">
                        <option value="1">正在使用</option>
                        <option value="0">停止使用</option>
                    </select>
                </td>
            </tr>
            <tr>
                <td>显示顺序:</td>
                <td><input class="easyui-textbox" type="text" id="txtDisOrder" data-options="required:true" /></td>
            </tr>
        </table>
    </div>
    <script src="../../js/jquery-1.10.2.min.js"></script>
    <script src="../../lib/jquery-easyui-1.4.1/jquery.easyui.min.js" type="text/javascript"></script>
    <script src="../../lib/jquery-easyui-1.4.1/locale/easyui-lang-zh_CN.js" type="text/javascript"></script>
    <script src="../../js/boer_util.js"></script>
    <script src="../../js/jQuery.extend.js"></script>
    <script>
        $(document).ready(function () {
            var id = $.url("id");
            if (!isEmpty(id)) {
                loadInfo(id);
            }
        });

        function loadInfo(id) {
            // 2.loadInfo inDto parm
            var ajaxData = {};
            ajaxData.Id = id;

            // 3.loadInfo request api
            BoerAjax("/api/ActRole/GetRoleInfo", ajaxData, "get", function (data) {
                if (data.ErrCode == 0) {

                    // 4.getInfo outDto to page
                    $("#txtName").textbox("setValue", data.Result.Name);
                    $("#txtDisplayName").textbox("setValue", data.Result.DisplayName);
                    $("#ddlState").combobox("setValue", data.Result.State);
                    $("#txtDisOrder").textbox("setValue", data.Result.DisOrder);
                } else {
                    $.messager.alert('警告', '错误代码:' + data.ErrCode + ',' + data.ErrMsg, 'warning');
                }
            });
        }

        function formSubmit(obj, iframeName) {
            var id = $.url("id");

            // 5.page to inDto
            var ajaxData = {};
            ajaxData.Name = $("#txtName").textbox('getValue').trim();
            ajaxData.DisplayName = $("#txtDisplayName").textbox('getValue').trim();
            ajaxData.State = $("#ddlState").combobox('getValue').trim();
            ajaxData.DisOrder = $("#txtDisOrder").textbox('getValue').trim();

            if (ajaxData.Name == "") {
                $.messager.alert('警告', '请输入角色名称!', 'warning');
                return;
            }

            // 6.request create api
            var url = "/api/ActRole/CreateRole";
            if (!isEmpty(id)) {
                // 7.request update api
                url = "/api/ActRole/UpdateRole";
                ajaxData.Id = id;
            }
            BoerAjax(url, ajaxData, "post", function (data) {
                if (data.ErrCode == 0) {
                    top.window.frames[iframeName].dataReload();
                    top.$('#' + obj).dialog("close");
                } else {
                    $.messager.alert('警告', '错误代码:' + data.ErrCode + ',' + data.ErrMsg, 'warning');
                }
            });
        }
    </script>
</body>
</html>

界面截图

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

推荐阅读更多精彩内容