这些年,这些事(权限中心整理)之二

前言

   任意一个系统,权限系统都属于“非功能”模块,你要做的细那只是“锦上添花”而不是“雪中送炭”。所以我们往往都是参考之前的项目然后改来改去。最近静下心来做了一个通用的权限中心系统(因为最近一直做微服务,并且做的都是基础服务,所以单独做了权限中心)。

章节

  • 系统架构
  • 系统优势
  • 数据结构
  • 权限资源
  • 权限管理中心
  • 权限二次开发
  • 接入权限服务

系统架构

技术框架
  • 开发语言:Java
  • 数据存储:MongoDB和Redis
  • 前台框架:DWZ+thymeleaf
  • 后台框架:基于Spring Boot二次封装
Spring Boot二次封装业务框架
  • 在线API:Swagger
在线Rest API
  • 开发规约:阿里开发规约
  • UML图:PlantUML
  • 白盒测试: Junit
package com.micro.service.basic.service.authority.services;

import com.micro.service.basic.service.authority.BaseTest;
import com.micro.service.basic.service.authority.domain.condition.SystemCondition;
import com.micro.service.basic.service.authority.domain.entity.System;
import com.micro.service.doamin.param.PageParam;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

public class SystemServiceTest extends BaseTest{
    @Autowired
    private SystemService systemService;
    @Test
    public void testSave(){
        System system = new System();
        system.setSystemCode("bbb");
        system.setSystemName("aaaaa");
        system.setSystemIcon("aa");
        system.setSystemUrl("aaaa");
        this.systemService.save(system);
    }
    @Test
    public void testFindPageResult(){
        SystemCondition systemCondition = new SystemCondition();
        systemCondition.setSystemCode("bbb");
        PageParam pageParam = new PageParam();
        pageParam.setCurrentPage(1);    
 this.systemService.findPageResult(systemCondition,pageParam,null);
    }
}
整体架构
image.png
  • 实际情况可能不会出现业务系统之间访问权限中心,对于接口通常我们是在Zuul中处理
  • 页面登录和获得权限通常会使用SSO做担当登录然后使用拦截器或者Filter做权限验证。

系统优势

  • 采用最新的Spring Boot版本进行开发,可以最简单部署和发布
  • 采用MongoDB和Redis进行数据持久化(权限数据是树形结构数据)
  • 提供在线API测试(Swagger)
  • 本权限系统只是对于权限和资源进行管理,对于用户可以独立开发(后期可以将用户作为可选择模块进行自由扩展)
  • 本系统提供Rest接口提供给任何第三方和语言进行调用
  • 权限管理时,可通过系统配置来满足不同的需求
  • 系统拥有导入、导出功能,部署方便
  • 完整的API和开发文档可以自由进行二次开发,并且该系统版权使用MIT协议。

数据结构

整体资源数据结构

根据以上结构可以很明确的看出,对于页面资源其实就是一个树形结构的数据。所以在数据存储的介质上面选择了MongoDB。

整体数据结构UML

权限资源

权限整体资源
权限分配
权限访问资源
  • 功能访问权限:是指拥有菜单的权限。菜单为树形结构,可调整菜单的层级和顺序
  • 操作访问权限 :是指页面的操作点,可定义N个操作点。操作代码的定义与权限的使用者为约定关系。通过操作代码来或者自定义标签判断是否拥有该菜单某个功能的操作权限。
  • 接口访问权限:是指每一个接口第三方使用者可以访问哪些现有的接口URL

权限管理中心

权限管理中心
导入系统数据模板
导入模块数据模板
  • 导入可以通过页面和Junit将数据到MongoDB中
Swagger返回数据
  • 根据Swagger可以一次性将数据导入,然后通过Junit将数据导入到MongoDB中,避免数据遗漏
JavaBean定义文档
  • JavaBean定义文档,并且生成对应的JavaBean对象
接口导出模板
  • 根据Swagger生成的API Excel文档

权限二次开发

  • 完整的注释
package com.micro.service.basic.service.authority.services;

import com.micro.service.doamin.constant.NumberConstant;
import com.micro.service.doamin.exception.BusinessException;
import com.micro.service.doamin.param.PageParam;
import com.micro.service.doamin.result.BaseResult;
import com.micro.service.doamin.result.PageResult;
import com.micro.service.mongodb.condition.CriteriaUtils;
import com.micro.service.mongodb.condition.annotation.Condition;
import com.micro.service.mongodb.condition.enums.Where;
import com.micro.service.mongodb.domain.entity.BaseEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.util.ReflectionUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

/**
 * MongoDB Base Service.
 *
 * @param <T> 实体对象
 */
public interface BaseService<T extends BaseEntity> {
    /**
     * 默认分页页数:10
     */
    int DEFAULT_PAGE_SIZE = NumberConstant.Int.INT_TEN;
    /**
     * 日志
     */
    Logger logger = LoggerFactory.getLogger(BaseService.class);

    /**
     * 插入操作
     *
     * @param entity 实体对象
     * @return 插入成功返回DB中的实体对象结果
     */
    default T save(T entity) {
        //设置主键,主键为UUID
        return this.save(this.getId(), entity);
    }

    default T save(String id, T entity) {
        entity.setId(id);
        //检查业务Code是否存在
        if (!this.checkBusinessCode(entity)) {
            throw new BusinessException();
        }
        return this.getMongoRepository().save(entity);
    }

    /**
     * 根据主键删除DB中数据
     *
     * @param serializable 主键
     */
    default void remove(Serializable serializable) {
        logger.info("删除操作,主键为:{}", serializable);
        this.getMongoRepository().delete(serializable);
    }

    /**
     * 删除所有数据
     */
    default void removeAll() {
        logger.info("删除所有数据!");
        this.getMongoRepository().deleteAll();
    }

    /**
     * 根据主键获得当前数据
     *
     * @param serializable 主键
     * @return 数据库返回的数据对象
     */
    default T findOne(Serializable serializable) {
        logger.info("通过主键获得唯一数据,主键为:{}", serializable);
        return this.getMongoRepository().findOne(serializable);
    }

    /**
     * 获得所有DB中数据
     *
     * @return 所有数据集合
     */
    default List<T> findAll() {
        logger.info("获得所有数据!");
        return this.getMongoRepository().findAll();
    }

    /**
     * 更加业务参数查询条件,进行查询
     *
     * @param param 业务参数
     * @param clazz 返回数据类型
     * @param <P>   业务参数类型
     * @return 查询返回结果集
     */
    default <P> List<T> findList(P param, Class<T> clazz) {
        Query query = new Query();
        //设置查询业务查询条件
        List<Criteria> criteriaList = CriteriaUtils.createCriteriaList(param);
        for (Criteria criteria : criteriaList) {
            query.addCriteria(criteria);
        }
        return this.getMongoTemplate().find(query, clazz);
    }

    /**
     * 页面分页查询
     *
     * @param param     查询业务条件
     * @param pageParam 分页参数
     * @param clazz     返回值类型
     * @return 分页数据结果
     * @parm pageParam 分页查询条件
     */
    default <P, R extends BaseResult> PageResult<R> findPageResult(P param, PageParam pageParam, Class<R> clazz) {
        PageResult<R> result = new PageResult<>();
        int currentPage = pageParam.getCurrentPage();
        result.setCurrentPage(currentPage);
        int pageSize = pageParam.getPageSize();
        //页面传入的分页,一页显示页数小于等于0,则使用默认10条数据一页进行分页
        if (pageSize <= NumberConstant.Int.INT_ZERO) {
            pageSize = DEFAULT_PAGE_SIZE;
        }
        //Spring Data Jpa分页从0开始,所有传入页数必须减1
        PageRequest pageable = new PageRequest(currentPage - NumberConstant.Int.INT_ONE, pageSize);
        Query query = new Query();
        //设置查询业务查询条件
        List<Criteria> criteriaList = CriteriaUtils.createCriteriaList(param);
        for (Criteria criteria : criteriaList) {
            query.addCriteria(criteria);
        }
        //设置分页参数
        query.with(pageable);
        //获得所有行数
        long totalCount = this.getMongoTemplate().count(query, this.getEntityClass());
        result.setTotalCount(totalCount);
        List<T> dataList = this.getMongoTemplate().find(query, this.getEntityClass());
        List<R> items = new ArrayList<>();
        com.micro.service.core.utils.CollectionUtils.copyCollection(dataList, items, clazz);
        result.setItems(items);
        return result;
    }
    /**
     * 获得MongoDB Id值
     *
     * @return MongoDB ID
     */
    default String getId() {
        String uuid = UUID.randomUUID().toString();
        return uuid;
    }

    /**
     * 检查业务Code是否存在
     *
     * @param entity 实体对象
     * @return 是否存在
     */
    default boolean checkBusinessCode(T entity) {
        return Boolean.TRUE;
    }

    /**
     * Spring data 包装的Repository
     *
     * @return MongoRepository
     */
    MongoRepository<T, Serializable> getMongoRepository();

    /**
     * 自定义分页使用Mongo Template进行分页查询
     *
     * @return MongoTemplate
     */
    default MongoTemplate getMongoTemplate() {
        return null;
    }

    default Class<T> getEntityClass() {
        return null;
    }

    default T getEntity() {
        try {
            return this.getEntityClass().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 根据业务Code获得唯一值
     *
     * @param businessCode 业务Code
     * @return 数据库对象
     */
    default T findOneByBusinessCode(String businessCode) {
        return null;
    }

}
package com.micro.service.basic.service.authority.services;

import com.micro.service.doamin.constant.NumberConstant;
import com.micro.service.doamin.param.PageParam;
import com.micro.service.mongodb.condition.CriteriaUtils;
import com.micro.service.mongodb.domain.entity.BaseEntity;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.util.List;

/**
 * 分页操作
 * @param <T> 返回Entity
 * @author jackjiang 
 */
public interface BasePageableService<T extends BaseEntity> extends BaseService<T>{
    /**
     * 分页查询返回List数据
     * @param param 查询参数
     * @param pageParam 分页参数
     * @param <P> 分页参数类型
     * @return List数据
     */
    default <P> List<T> findPageList(P param, PageParam pageParam) {
        int currentPage = pageParam.getCurrentPage();
        int pageSize = pageParam.getPageSize();
        //页面传入的分页,一页显示页数小于等于0,则使用默认10条数据一页进行分页
        if (pageSize <= NumberConstant.Int.INT_ZERO) {
            pageSize = DEFAULT_PAGE_SIZE;
        }
        //Spring Data Jpa分页从0开始,所有传入页数必须减1
        PageRequest pageable = new PageRequest(currentPage - NumberConstant.Int.INT_ONE, pageSize);
        Query query = this.createQuery(param);
        //设置分页参数
        query.with(pageable);
        List<T> dataList = this.getMongoTemplate().find(query, this.getEntityClass());
        return dataList;
    }

    /**
     * 数据Count
     * @param param 查询参数
     * @param <P> 参数类型
     * @return 数据Count
     */
    default <P> long totalCount(P param) {
        Query query = this.createQuery(param);
        long totalCount = this.getMongoTemplate().count(query, this.getEntityClass());
        return totalCount;
    }

    /**
     * 拼接查询条件
     * @param param 查询条件
     * @param <P> 参数类型
     * @return 查询条件
     */
    default <P> Query createQuery(P param){
        Query query = new Query();
        //设置查询业务查询条件
        List<Criteria> criteriaList = CriteriaUtils.createCriteriaList(param);
        for (Criteria criteria : criteriaList) {
            query.addCriteria(criteria);
        }
        return query;
    }
}
package com.micro.service.basic.service.authority.base.controller;

import com.micro.service.basic.service.authority.logic.BaseLogic;

/**
 * Base Controller.
 *
 * @author jiang_nan
 */
public interface BaseController {
    /**
     * Get Base Logic
     *
     * @return BaseLogic
     */
    BaseLogic getBaseLogic();

}
package com.micro.service.basic.service.authority.base.controller;

import com.micro.service.doamin.condition.BasePageableCondition;
import com.micro.service.doamin.result.BaseResult;
import com.micro.service.doamin.result.PageResult;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 列表页面基础Controller。
 *
 * @param <C> 分页条件
 */
public interface BaseListController<C extends BasePageableCondition, R extends BaseResult> extends BaseController {
    /**
     * Get方式分页页面
     *
     * @param modelMap Module Map
     * @return 分页页面
     */
    @RequestMapping(value = "list", method = RequestMethod.GET)
    default String list(ModelMap modelMap) {
        this.initializeListPageParam(modelMap);
        return this.list(this.getPageableCondition(), modelMap);
    }

    /**
     * Post请求分页页面
     *
     * @param pageableCondition 查询条件
     * @param modelMap          Module Map
     * @return 分页页面
     */
    @RequestMapping(value = "list", method = RequestMethod.POST)
    default String list(C pageableCondition, ModelMap modelMap) {
        PageResult<R> result = this.getBaseLogic().findPageResult(pageableCondition);
        modelMap.put("result", result);
        return this.listPageUrl();
    }

    /**
     * 初始化List页面参数
     * @param modelMap Model Map
     */
    default void initializeListPageParam(ModelMap modelMap){

    }

    /**
     * List页面Url
     *
     * @return List页面Url
     */
    String listPageUrl();

    /**
     * 分页查询条件
     *
     * @return 分页查询条件
     */
    C getPageableCondition();


}
package com.micro.service.basic.service.authority.base.controller;

import com.micro.service.doamin.condition.BaseCondition;
import com.micro.service.doamin.result.WebPageResult;
import com.micro.service.redis.NumberGenerateComponent;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 插入操作基础Controller
 *
 * @param <C> 保存条件
 */
public interface BaseSaveController<C extends BaseCondition> extends BaseController {
    /**
     * 保存操作,默认提供生成Number Code。
     *
     * @param modelMap Model Map
     * @return 保存页面
     */
    @RequestMapping(value = "save", method = RequestMethod.GET)
    default String save(ModelMap modelMap) {
        modelMap.put("id", NumberGenerateComponent.generateNumber());
        return savePageUrl();
    }

    /**
     * 保存操作
     *
     * @param condition 保存条件
     * @param modelMap  Model Map
     * @return JSON返回值
     */
    @RequestMapping(value = "save", method = RequestMethod.POST)
    default @ResponseBody
    WebPageResult save(C condition, ModelMap modelMap) {
        this.getBaseLogic().save(condition);
        return new WebPageResult();
    }

    /**
     * 保存页面
     *
     * @return 保存页面
     */
    String savePageUrl();

}
  • BaseService 接口,因为使用JDK1.8所以可以使用default,实现一个类多继承
  • BasePageableService专门为分页特殊业务处理的基类
  • BaseController接口,所有的Controller必须继承BaseController
  • BaseListController接口,列表页面必须继承BaseListController将列表页面Controller简单化
  • BaseSaveController接口,编辑页面必须继承BaseSaveController将View页面Controller简单化
package com.micro.service.basic.service.authority.controller;

import com.micro.service.basic.service.authority.base.controller.BaseListController;
import com.micro.service.basic.service.authority.base.controller.BaseSaveController;
import com.micro.service.basic.service.authority.domain.condition.SystemCondition;
import com.micro.service.basic.service.authority.domain.condition.SystemPageableCondition;
import com.micro.service.basic.service.authority.domain.result.SystemResult;
import com.micro.service.basic.service.authority.logic.BaseLogic;
import com.micro.service.basic.service.authority.logic.SystemLogic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * 系统操作Controller
 *
 * @author jiang_nan
 */
@Controller
@RequestMapping(value = "system")
public class SystemController implements BaseListController<SystemPageableCondition, SystemResult>, BaseSaveController<SystemCondition> {
    /**
     * logger
     */
    private static Logger logger = LoggerFactory.getLogger(SystemController.class);
    /**
     * 系统List页面Url
     */
    private final static String LIST_PAGE_URL = "/system/list";
    /**
     * 系统Add页面Url
     */
    private final static String SAVE_PAGE_URL = "/system/add";
    @Autowired
    private SystemLogic systemLogic;

    @RequestMapping(value = "remove/{systemId}", method = {RequestMethod.POST, RequestMethod.GET})
    public String remove(@PathVariable("systemId") String systemId, ModelMap modelMap) {
        this.systemLogic.remove(systemId);
        return this.listPageUrl();
    }

    @Override
    public BaseLogic getBaseLogic() {
        return this.systemLogic;
    }

    @Override
    public String listPageUrl() {
        return LIST_PAGE_URL;
    }

    @Override
    public SystemPageableCondition getPageableCondition() {
        return new SystemPageableCondition();
    }

    @Override
    public String savePageUrl() {
        return SAVE_PAGE_URL;
    }
}

系统管理模块实现代码。

代码结构
  • base:业务需要的基类定义
  • base.controller: Controller类的一些列基类
  • config:相关配置
  • controller:业务页面相关Controller
  • domain:一些参数JavaBean
  • domain.condition:页面传入的参数JavaBean
  • entity:MongoDB表对应的JavaBean
  • param:Rest接口参数JavaBean
  • result:页面和接口返回数据JavaBean
  • logic:业务处理
  • properties:读取配置参数
  • repository:单表JPA Repository
  • rest:Rest接口
  • services:单表Service接口
  • services.impl:单表Service接口实现
  • Application:启动类
  • config:配置文件
  • i18n:国际化配置文件
  • static:页面静态资源
  • templates:静态文件
  • application.yml:基础配置
  • logback.xml 日志配置

接入权限服务

完整的API列表
完整API测试

PS:
1.MIT协议说明

Copyright <YEAR> <COPYRIGHT HOLDER>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

2.该权限将在11中旬加代码全部贡献出来。
3.很不喜欢写CSS和做一个前台框架使用使用了一个比较来的框架,但是对于权限中心而言已经完全够用了,我一直对自己不擅长的事情都是信奉适用注意者。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,139评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,631评论 18 399
  • 作为一个自诩有点中文素养的女性我一直以来就很喜欢看简奥斯汀的作品,特别是她的《傲慢与偏见》,记得刚接触这本...
    最远的星阅读 1,113评论 1 4
  • 只去看那些此时此刻在眼前发生的事,只处理眼前的问题。 不是把焦点放在明天或后天,也不是明年或十年后,而是把精神集中...
    张Lin阅读 96评论 0 0
  • 昨天是星期天,他妈妈领着两个孩子去买书了。我回来一看还是拿着书在那做做样子,本来就皮气不好的我,看了就想骂人。还是...
    二年级五班崔世昊阅读 223评论 0 0