基于SpringBoot 2.x后台管理系统

此项目地址已在码云上,地址:https://gitee.com/youxiaxiaomage/jfl-platform-parent
此项目主要使用了DruidMyBatis-PlusredisdubboshiroSpringMVCthymeleaflayUIBootstrap后台管理系统,以及相应的restful接口。
1.基类实体类(BaseModel

@Data
public abstract class BaseModel implements Serializable
{
    /**
     * id
     */
    @JsonSerialize(using=ToStringSerializer.class)
    @TableId(value = "id", type = IdType.ID_WORKER)
    private Long id;
    
    /**
     * 状态
     */
    @TableField("enable")
    public Integer enable;
    
    /**
     * 备注
     */
    @TableField("remark")
    private String remark;
    
    /**
     * 创建人
     */
    @TableField("create_by")
    private Long createBy;
    
    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
    @TableField("create_time")
    private Date createTime;
    
    /**
     * 更新时间
     */
    @TableField("update_by")
    private Long updateBy;
    
    /**
     * 更新时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
    @TableField("update_time")
    private Date updateTime;
    
    /** 请求参数 */
    @TableField(exist=false)
    private Map<String, Object> params;
    
}

此基类实体类主要是基于Mybatis-Plus插件,表名定义,字段定义都使用了该方式。其中,主键id使用长整型,其长度为20位,页面展示时,精度会丢失,因此该字段序列化为字符串返回。
这些字段都是在定义表时,必须包含的字段,其中请求参数params用于接收前端的参数,非数据库字段。
2.基类Mapper(BaseMapper

public interface BaseMapper<T extends BaseModel> extends com.baomidou.mybatisplus.core.mapper.BaseMapper<T>
{
    List<Long> selectIdPage(@Param("cm") T paramT);
    
    List<Long> selectIdPage(@Param("cm") Map<String, Object> paramMap);
    
    List<Long> selectIdPage(RowBounds paramRowBounds, @Param("cm") Map<String, Object> paramMap);
    
    List<Long> selectIdPage(RowBounds paramRowBounds, @Param("cm") T paramT);
    
    List<T> selectPage(RowBounds paramRowBounds, @Param("cm") Map<String, Object> paramMap);
    
    Integer selectCount(@Param("cm") Map<String, Object> paramMap);
}

分页重写了mybatisplusBaseMapper,其中列出的方法需要在***Mapper.xml中实现,具体参见对应的xml文件。
3.基类Service(BaseService

public interface BaseService<T extends BaseModel> extends IService<T>
{
    /** 
     * 修改
     * @param record
     * @param userId
     * @return
     * @throws BusinessException
     * @throws ValidateException
     * @see [类、类#方法、类#成员]
     */
    @Transactional
    T update(T record) throws BusinessException, ValidateException;
    
    /** 
     * 逻辑删除
     * @param ids 删除的id
     * @param userId 用户id
     * @throws BusinessException
     * @throws ValidateException
     * @see [类、类#方法、类#成员]
     */
    @Transactional
    void del(List<Long> ids, Long userId) throws BusinessException, ValidateException;
    
    /** 
     * 逻辑删除单条
     * @param id
     * @param userId
     * @throws BusinessException
     * @throws ValidateException
     * @see [类、类#方法、类#成员]
     */
    @Transactional
     void del(Long id, Long userId)  throws BusinessException, ValidateException;
    
    /** 
     * 物理删除
     * @param id
     * @throws BusinessException
     * @throws ValidateException
     * @see [类、类#方法、类#成员]
     */
    @Transactional
     void delete(Long id) throws BusinessException, ValidateException;
    
    /** 
     * 物理删除
     * @param entity
     * @return
     * @throws BusinessException
     * @throws ValidateException
     * @see [类、类#方法、类#成员]
     */
    @Transactional
     Integer deleteByEntity(T entity) throws BusinessException, ValidateException;
    
    /** 
     * 物理删除
     * @param columnMap
     * @return
     * @throws BusinessException
     * @throws ValidateException
     * @see [类、类#方法、类#成员]
     */
    @Transactional
     Integer deleteByMap(Map<String, Object> columnMap) throws BusinessException, ValidateException;
      ...
}

此类中部分方法未列举。
4.基类Service实现类(BaseServiceImpl

public class BaseServiceImpl<T extends BaseModel, M extends BaseMapper<T>> extends ServiceImpl<BaseMapper<T>, T> implements BaseService<T>
{
    /**
     * logger 日志
     */
    protected Logger logger = LoggerFactory.getLogger(getClass());
    
    /**
     * mapper
     */
    @Autowired
    protected M mapper;
    

    @Transactional
    @Override
    public T update(T record) throws BusinessException, ValidateException
    {
        if (record.getId() != null)
        {
          record.setUpdateTime(new Date());
          this.mapper.updateById(record);  
        }
        else
        {
            record.setCreateTime(new Date());
            record.setUpdateTime(new Date());
            record.setUpdateBy(record.getCreateBy());
            this.mapper.insert(record);
        }
        return this.mapper.selectById(record.getId());
    }

    /**
     * 逻辑删除
     * @param ids
     * @param userId
     * @throws BusinessException
     * @throws ValidateException
     * @see com.jfl.base.BaseService#del(java.util.List, java.lang.Long)
     */
    @Override
    @Transactional
    public void del(List<Long> ids, Long userId) throws BusinessException, ValidateException
    {
        // lamda表达式 JDK1.8才支持
        ids.forEach(id -> del(id, userId));
    }

    /**
     * 逻辑删除
     * @param id
     * @param userId
     * @throws BusinessException
     * @throws ValidateException
     * @see com.jfl.base.BaseService#del(java.lang.Long, java.lang.Long)
     */
    @Override
    @Transactional
    public void del(Long id, Long userId) throws BusinessException, ValidateException
    {
        try
        {
            T record = this.queryById(id);
            record.setEnable(0);
            record.setUpdateBy(userId);
            record.setUpdateTime(new Date());
            this.mapper.updateById(record);
        }
        catch (Exception e)
        {
           throw new RuntimeException(e.getMessage(), e);
        }
        
    }

    /**
     * 物理删除
     * @param id
     * @throws BusinessException
     * @throws ValidateException
     * @see com.jfl.base.BaseService#delete(java.lang.Long)
     */
    @Override
    @Transactional
    public void delete(Long id) throws BusinessException, ValidateException
    {
        try
        {
            this.mapper.deleteById(id);
        }
        catch (Exception e)
        {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * 物理删除
     * @param entity
     * @return
     * @throws BusinessException
     * @throws ValidateException
     * @see com.jfl.base.BaseService#deleteByEntity(com.jfl.base.BaseModel)
     */
    @Override
    @Transactional
    public Integer deleteByEntity(T entity) throws BusinessException, ValidateException
    {
        Wrapper<T> wrapper = new UpdateWrapper<T>(entity);
        return this.mapper.delete(wrapper);
    }

    /**
     * 物理删除
     * @param columnMap
     * @return
     * @throws BusinessException
     * @throws ValidateException
     * @see com.jfl.base.BaseService#deleteByMap(java.util.Map)
     */
    @Override
    @Transactional
    public Integer deleteByMap(Map<String, Object> columnMap) throws BusinessException, ValidateException
    {
        return this.mapper.deleteByMap(columnMap);
    }

    /**
     * 根据Id查询
     * @param id
     * @return
     * @see com.jfl.base.BaseService#queryById(java.lang.Long)
     */
    @Override
    public T queryById(Long id)
    {   
        return this.mapper.selectById(id);
    }

    /**
     * 分页查询
     * @param params 其中params必须为数据库中字段
     * @return
     * @see com.jfl.base.BaseService#query(java.util.Map)
     */
    @Override
    public PageInfo<T> query(Map<String, Object> params)
    {
        // 默认当前页为1
        int pageNum = 1;
        // 默认页码大小为10
        int pageSize = 10;
        // 默认计算count
        String orderBy = null;
        if(params.get("pageNum") != null && StringUtils.isNotBlank(params.get("pageNum") + ""))
        {
            pageNum = Integer.valueOf(params.get("pageNum").toString());
            params.remove("pageNum");
        }
        if(params.get("pageSize") != null && StringUtils.isNotBlank(params.get("pageSize") + ""))
        {
            pageSize = Integer.valueOf(params.get("pageSize").toString());
            params.remove("pageSize");
        }
        if(params.get("orderBy")!= null && StringUtils.isNotBlank(params.get("orderBy") + ""))
        {
            orderBy = params.get("orderBy").toString();
            params.remove("orderBy");
        }
        // 设置分页的参数
        PageHelper.startPage(pageNum, pageSize, orderBy);
        // 有效数据
        params.put("enable", 1);
        // 根据条件查询
        List<T> list = this.mapper.selectByMap(params);
        // 分装成分页对象
        return new PageInfo<T>(list);
    }

    /**
     * 根据实体参数分页查询
     * @param entity
     * @param rowBounds
     * @return
     * @see com.jfl.base.BaseService#query(com.jfl.base.BaseModel, com.github.pagehelper.PageInfo)
     */
    @Override
    public PageInfo<T> query(T entity, PageInfo<T> rowBounds)
    {
        Page<T> page = new Page<T>();
        try
        {
            BeanUtils.copyProperties(page, rowBounds);
        }
        catch (Exception e)
        {
          logger.error(ExceptionUtil.getStackTraceAsString(e));
        }
        // List<Long> ids = this.mapper.selectIdPage(page,entity);
        return new PageInfo<T>(null);
    }

    /**
     * 根据参数查询
     * @param params
     * @return
     * @see com.jfl.base.BaseService#queryList(java.util.Map)
     */
    @Override
    public List<T> queryList(Map<String, Object> params)
    {
        // 根据参数获取全部数据的Id 从DB中查询
        List<Long> ids = this.mapper.selectIdPage(params);
        List<T> list = queryList(ids);
        return list;
    }

    /**
     * 根据Id查询 如果缓存中有则从缓存中获取,否则从DB中获取
     * @param ids
     * @return
     * @see com.jfl.base.BaseService#queryList(java.util.List)
     */
    @Override
    public List<T> queryList(List<Long> ids)
    {
        final List<T> list = Lists.newArrayList();
        if (ids != null)
        {
            // lamda表达式
            ids.forEach(id -> list.add(queryById(id)));
        }
        return list;
    }

    @Override
    public <K> List<K> queryList(List<Long> ids, Class<K> clazz)
    {
        final List<K> list = Lists.newArrayList();
        if(ids != null)
        {
            for (int i = 0; i < ids.size(); i++)
            {
                T t = queryById(ids.get(i));
                K k = InstanceUtil.to(t, clazz);
                list.set(i, k);
            }
        }
        return list;
    }

    /**
     * 根据实体参数查询
     * @param entity
     * @return
     * @see com.jfl.base.BaseService#queryList(com.jfl.base.BaseModel)
     */
    @Override
    public List<T> queryList(T entity)
    {
        // 先查出所有有关的id
        List<Long> ids = this.mapper.selectIdPage(entity);
        // 缓存中有则从缓存中取值,否则从数据库取值
        List<T> list = queryList(ids);
        return list;
    }

    /**
     * 从数据库中查询
     * @param params
     * @return
     * @see com.jfl.base.BaseService#queryFromDB(java.util.Map)
     */
    @Override
    public PageInfo<T> queryFromDB(Map<String, Object> params)
    {
        
        return null;
    }

    @Override
    public PageInfo<T> queryFromDB(T entity, PageInfo<T> rowBounds)
    {
        return null;
    }

    /**
     * 从数据库中查询
     * @param params 表字段
     * @return
     * @see com.jfl.base.BaseService#queryListFromDB(java.util.Map)
     */
    @Override
    public List<T> queryListFromDB(Map<String, Object> params)
    {
        return this.mapper.selectByMap(params);
    }

    @Override
    public PageInfo<T> selectList(PageRequest request, T record)
    {
        PageHelper.startPage(request.getPageNum(), request.getPageSize(), request.getOrderBy());
        QueryWrapper<T> wrapper = new QueryWrapper<T>();
        if (record != null)
        {
            wrapper = tranform(record);
            if(record.getParams() != null)
            {
                if(StringUtils.isNotBlank(record.getParams().get("beginTime") + "") && StringUtils.isNotBlank(record.getParams().get("endTime") + ""))
                {
                    wrapper.between("create_time", record.getParams().get("beginTime") , record.getParams().get("endTime"));
                }
            }
            
        }
        wrapper.eq("enable", 1);
        List<T> list = this.mapper.selectList(wrapper);
        return new PageInfo<T>(list);
    }

    /** 
     * 转换查询
     * @param record
     * @return
     * @see [类、类#方法、类#成员]
     */
    private QueryWrapper<T> tranform(T record)
    {
        QueryWrapper<T> wrapper = new QueryWrapper<T>();
        Field[] fields = record.getClass().getDeclaredFields();
        try
        {
            for (Field field : fields)
            {
                field.setAccessible(true);
                String fieldName = field.getName();
                String clazz = field.getType().getTypeName();
                Method method = record.getClass().getDeclaredMethod("get"+ fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1));
                Object obj = method.invoke(record);
                if(obj != null && StringUtils.isNotBlank(obj + ""))
                {
                    if ("java.lang.String".equals(clazz))
                    {
        
                        if("status".equals(fieldName))
                        {
                            wrapper.eq(ConvertUtil.underLine2Camel(fieldName), obj);
                        } 
                        else
                        {
                            wrapper.like(ConvertUtil.underLine2Camel(fieldName), obj);
                        }
                    }
                    else if ("java.lang.Integer".equals(clazz) || "java.lang.Short".equals(clazz) || "java.lang.Long".equals(clazz))
                    {
                        wrapper.eq(ConvertUtil.underLine2Camel(fieldName), obj);
                    } 
                }

                
            }
        }
        catch (Exception e)
        {
            logger.error("转换异常", e);
        }

        return wrapper;
    }
    
    /** 
     * 唯一性统一返回
     * @param record 参数对象
     * @param id 数据库中数据Id
     * @return "0":存在  "1":唯一
     */
    public String result(T record, Long id)
    {
      
        if(record != null && record.getId().longValue() != id.longValue())
        {
            return Constants.DATA_NOT_UNIQUE;
        }
        return Constants.DATA_UNIQUE;
    }

}

主要注意分页动态sql的拼接问题,比如精确查询,模糊查询等,这个也可以转化为动态***Mapper.xml文件。
5.控制器

@Controller
@RequestMapping("${jfl.module.system}/user")
public class SysUserController extends AbstractController
{
    /**
     * dubbo接口 @Reference 通过配置文件连接服务
     */
    @Reference(version = "${jfl.version}")
    private SysUserService sysUserService;
    
    @Reference(version = "${jfl.version}")
    private SysDeptService sysDeptService;
    
    @Reference(version = "${jfl.version}")
    private SysRoleService sysRoleService;
    
    @Reference(version = "${jfl.version}")
    private SysPostService sysPostService;
    
    /**
     * 分隔符
     */
    private static final String SEG_CHAR = ",";
    /**
     * 用户管理界面
     * 
     * @param modelMap
     * @return
     */
    @RequiresPermissions("system:user:view")
    @GetMapping
    public String user(ModelMap modelMap)
    {
        modelMap.put("user", ShiroUtils.getCurrentUser());
        return Constants.MODULE_SYS_USER_PREFIX + "user";
    }
    
    /**
     * 跳转用户添加页面
     * 
     * @param modelMap
     * @return
     */
    @GetMapping("/add")
    public String add(ModelMap modelMap)
    {
        modelMap.put("roles", this.sysRoleService.queryList(Maps.newHashMap()));
        modelMap.put("posts", this.sysPostService.queryList(Maps.newHashMap()));
        return Constants.MODULE_SYS_USER_PREFIX + "add";
    }
    
    /**
     * 添加用户
     * 
     * @param user
     * @return
     */
    @Log(module = Module.ROLE, value = "添加用户", type = LogType.INSERT)
    @RequiresPermissions("system:user:add")
    @PostMapping("/add")
    @ResponseBody
    public ResponseEntity<ModelMap> addSave(SysUser user)
    {
        user.setSalt(EncryptUtils.randomSalt());
        user.setPassword(EncryptUtils.encryptPassword(user.getUserName(), user.getPassword(), user.getSalt()));
        user.setCreateBy(ShiroUtils.getCurrentUserId());
        this.sysUserService.saveUser(user);
        return setSuccessModelMap();
    }
    
    /**
     * 分页查询
     * 
     * @param sysUser
     * @return
     * @see [类、类#方法、类#成员]
     */
    @RequiresPermissions("system:user:list")
    @PostMapping("/list")
    @ResponseBody
    public ResponseEntity<ModelMap> list(SysUser sysUser)
    {
        PageInfo<SysUser> pageInfo = this.sysUserService.selectList(ConvertRequestUtil.pageRequest(), sysUser);
        return setSuccessModelMap(pageInfo);
    }
    
    /**
     * 跳转用户编辑页面
     * 
     * @param userId
     * @param modelMap
     * @return
     */
    @GetMapping("/edit/{userId}")
    public String edit(@PathVariable("userId") Long userId, ModelMap modelMap)
    {
        modelMap.put("user", this.sysUserService.queryById(userId));
        modelMap.put("roles", this.sysRoleService.selectRolesByUserId(userId));
        modelMap.put("posts", this.sysPostService.selectPostsByUserId(userId));
        return Constants.MODULE_SYS_USER_PREFIX + "edit";
    }
    
    /**
     * 修改保存用户
     * 
     * @param user
     * @return
     */
    @Log(module = Module.ROLE, value = "修改用户", type = LogType.UPDATE)
    @RequiresPermissions("system:user:edit")
    @PostMapping("/edit")
    @ResponseBody
    public ResponseEntity<ModelMap> editSave(SysUser user)
    {
        if (user.getId() != null && user.getId().longValue() == 1)
        {
            throw new BusinessException("管理员用户,不支持修改!");
        }
        return setSuccessModelMap(this.sysUserService.updateUser(user));
    }
    
    /**
     * 跳转用户重置密码页面
     * 
     * @param userId
     * @param modelMap
     * @return
     */
    @RequiresPermissions("system:user:resetPwd")
    @GetMapping("/resetPwd/{userId}")
    public String resetPwd(@PathVariable("userId") Long userId, ModelMap modelMap)
    {
        modelMap.put("user", this.sysUserService.queryById(userId));
        return Constants.MODULE_SYS_USER_PREFIX + "resetPwd";
    }
    
    /**
     * 保存密码操作
     * 
     * @param user
     * @return
     */
    @Log(module = Module.ROLE, value = "修改密码", type = LogType.UPDATE)
    @RequiresPermissions("system:user:resetPwd")
    @PostMapping("/resetPwd")
    @ResponseBody
    public ResponseEntity<ModelMap> resetPwdSave(SysUser user)
    {
        // 密码加密
        user.setSalt(EncryptUtils.randomSalt());
        user.setPassword(EncryptUtils.encryptPassword(user.getUserName(), user.getPassword(), user.getSalt()));
        this.sysUserService.update(user);
        return setSuccessModelMap();
    }
    
    /**
     * 删除用户 支持批量删除
     * 
     * @param ids
     * @return
     */
    @Log(module = Module.ROLE, value = "删除用户", type = LogType.DELETE)
    @RequiresPermissions("system:user:remove")
    @PostMapping("/remove")
    @ResponseBody
    public ResponseEntity<ModelMap> remove(String ids)
    {
        String[] idAttr = ids.split(SEG_CHAR);
        List<Long> list = Lists.newArrayList();
        for (String id : idAttr)
        {
            list.add(Long.valueOf(id));
        }
        // 逻辑删除
        this.sysUserService.deleteUsers(list, ShiroUtils.getCurrentUserId());
        return setSuccessModelMap();
    }
    
    /**
     * 校验用户名是否重复
     * 
     * @param user
     * @return
     */
    @PostMapping("/checkUserNameUnique")
    @ResponseBody
    public String checkUserNameUnique(SysUser user)
    {
        return String.valueOf(this.sysUserService.countByUserName(user));
    }
    
    /**
     * 校验邮箱是否重复
     * 
     * @param user
     * @return
     */
    @PostMapping("/checkEmailUnique")
    @ResponseBody
    public String checkEmailUnique(SysUser user)
    {
        return this.sysUserService.countByEmail(user);
    }
    
    /**
     * 校验邮箱是否重复
     * 
     * @param user
     * @return
     */
    @PostMapping("/checkPhoneUnique")
    @ResponseBody
    public String checkPhoneUnique(SysUser user)
    {
        return this.sysUserService.countByPhone(user);
    }
    
}

上述以系统用户控制器为例子,其中Service都是dubbo的接口。注意shiro权限的配置以及日志的配置。
6.国际化的配置
主要是一些异常信息的配置message_*.properties文件
7.页面
主要知道页面模板的配置,以及相应的shiro权限控制的按钮状态的控制,注意分页查询,页面数据渲染等,都可以按照其他已有模块拷贝修改即可。

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

推荐阅读更多精彩内容