Java Excel (Apache POI + annotation)

思路: 自定义注解, 导出/读取 excel 根据注解自动解析字段

  • 注解
package com.excel;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelClassAnnotation {

    String sheetName();

}

package com.excel;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelFieldAnnotation {

    int columnIndex();

    String headerName() default "";
    
}


  • 注解与excel mapping
package com.excel;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;

public class CellTemplate implements Comparable<CellTemplate> {

    private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:ss:mm");

    private Field field;
    private String annotation_headerName;
    private int annotation_columnIndex;

    public static CellTemplate getInstance(Field field) {
        CellTemplate mappingCell = new CellTemplate();
        Annotation[] annotations = field.getAnnotations();
        if (annotations == null || annotations.length == 0) {
            return null;
        }
        for (Annotation annotation : annotations) {
            if (annotation instanceof ExcelFieldAnnotation) {
                ExcelFieldAnnotation res = (ExcelFieldAnnotation) annotation;
                mappingCell.field = field;
                mappingCell.annotation_columnIndex = res.columnIndex();
                mappingCell.annotation_headerName = res.headerName();
                if (mappingCell.annotation_headerName == null || mappingCell.annotation_headerName.trim().length() == 0) {
                    mappingCell.annotation_headerName = field.getName();
                }
                return mappingCell;
            }
        }

        return null;
    }

    public void createCell(Row row, Object obj) throws IllegalArgumentException, IllegalAccessException {
        Cell cell = row.createCell(annotation_columnIndex);
        boolean isAccessible = field.isAccessible();
        field.setAccessible(true);

        Class<?> fieldClass = field.getType();

        if (fieldClass == Integer.class || fieldClass == int.class) {
            int value = field.getInt(obj);
            cell.setCellValue(value);
            cell.setCellType(CellType.NUMERIC);
        } else if (fieldClass == Short.class || fieldClass == short.class) {
            short value = field.getShort(obj);
            cell.setCellValue(value);
            cell.setCellType(CellType.NUMERIC);
        } else if (fieldClass == Long.class || fieldClass == long.class) {
            long value = field.getShort(obj);
            cell.setCellValue(value);
            cell.setCellType(CellType.NUMERIC);
        } else if (fieldClass == String.class) {
            String value = field.get(obj).toString();
            cell.setCellValue(value);
            cell.setCellType(CellType.STRING);
        } else if (fieldClass == Double.class || fieldClass == double.class) {
            double value = field.getDouble(obj);
            cell.setCellValue(value);
            cell.setCellType(CellType.NUMERIC);
        } else if (fieldClass == Float.class || fieldClass == float.class) {
            float value = field.getFloat(obj);
            cell.setCellValue(value);
            cell.setCellType(CellType.NUMERIC);
        } else if (fieldClass == Byte.class || fieldClass == byte.class) {
            byte value = field.getByte(obj);
            cell.setCellValue(value);
            cell.setCellType(CellType.NUMERIC);
        } else if (fieldClass == Character.class || fieldClass == char.class) {
            char value = field.getChar(obj);
            cell.setCellValue(value);
            cell.setCellType(CellType.STRING);
        } else if (fieldClass == Boolean.class) {
            boolean value = field.getBoolean(obj);
            cell.setCellValue(value);
            cell.setCellType(CellType.BOOLEAN);
        } else if (fieldClass == Date.class) {
            String value = DATE_FORMAT.format((Date) field.get(obj));
            cell.setCellValue(value);
            cell.setCellType(CellType.STRING);
        } else {
            throw new RuntimeException(fieldClass + " is not supported.");
        }

        field.setAccessible(isAccessible);
    }

    public void createHeaderCell(Row row) {
        Cell cell = row.createCell(annotation_columnIndex);
        cell.setCellValue(annotation_headerName);
        cell.setCellType(CellType.STRING);
    }

    public void invokeObjectProperty(Cell cell, Object obj) throws IllegalArgumentException, IllegalAccessException, ParseException {
        boolean isAccessible = field.isAccessible();
        field.setAccessible(true);

        Class<?> fieldClass = field.getType();

        if (fieldClass == Integer.class || fieldClass == int.class) {
            int value = (int) cell.getNumericCellValue();
            field.set(obj, value);
        } else if (fieldClass == Short.class || fieldClass == short.class) {
            short value = (short) cell.getNumericCellValue();
            field.set(obj, value);
        } else if (fieldClass == Long.class || fieldClass == long.class) {
            long value = (long) cell.getNumericCellValue();
            field.set(obj, value);
        } else if (fieldClass == String.class) {
            String value = cell.getStringCellValue();
            field.set(obj, value);
        } else if (fieldClass == Double.class || fieldClass == double.class) {
            double value = cell.getNumericCellValue();
            field.set(obj, value);
        } else if (fieldClass == Float.class || fieldClass == float.class) {
            float value = (float) cell.getNumericCellValue();
            field.set(obj, value);
        } else if (fieldClass == Byte.class || fieldClass == byte.class) {
            byte value = (byte) cell.getNumericCellValue();
            field.set(obj, value);
        } else if (fieldClass == Character.class || fieldClass == char.class) {
            char value = (char) cell.getNumericCellValue();
            field.set(obj, value);
        } else if (fieldClass == Boolean.class) {
            Boolean value = (Boolean) cell.getBooleanCellValue();
            field.set(obj, value);
        } else if (fieldClass == Date.class) {
            Date value = DATE_FORMAT.parse(cell.getStringCellValue());
            field.set(obj, value);
        } else {
            throw new RuntimeException(fieldClass + " is not supported.");
        }

        field.setAccessible(isAccessible);
    }

    public int compareTo(CellTemplate obj) {
        if (this.annotation_columnIndex == obj.annotation_columnIndex) {
            return 0;
        }
        return this.annotation_columnIndex > obj.annotation_columnIndex ? 1 : -1;
    }

}


  • 工厂, 根据后缀名调用不同的api
package com.excel;

import java.io.IOException;

public class ExcelUtilsFactory {

    static final String EXCEL_XLS = ".xls";
    static final String EXCEL_XLSX = ".xlsx";

    public static ExcelUtilsBase getInstance(String fileFullName) throws IOException {
        ExcelVersionEnum version = getExcelPostfix(fileFullName);
        switch (version) {
        case XLS:
            return new ExcelUtilsXls(fileFullName);
        case XLSX:
            return new ExcelUtilsXlsx(fileFullName);
        default:
            throw new IllegalArgumentException(fileFullName);
        }
    }

    private static ExcelVersionEnum getExcelPostfix(String excelFileName) {
        if (excelFileName == null) {
            throw new IllegalArgumentException(excelFileName);
        }

        if (excelFileName.endsWith(EXCEL_XLS)) {
            return ExcelVersionEnum.XLS;
        } else if (excelFileName.endsWith(EXCEL_XLSX)) {
            return ExcelVersionEnum.XLSX;
        }
        throw new IllegalArgumentException(excelFileName);
    }

}

package com.excel;
public enum ExcelVersionEnum {

    NONE, XLS, XLSX;
}

  • 核心类
package com.excel;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

public abstract class ExcelUtilsBase {

    protected String fileFullName;

    public ExcelUtilsBase(String fileFullName) {
        this.fileFullName = fileFullName;
    }

    /**
     * add a sheet to the excel located on fileFullName; <br/>
     * If the file exist, will add a new sheet to it; if not will create a new
     * excel.
     * 
     * @param srcContent
     *            the excel content
     * @param cls
     *            the type of the content pojo
     * @throws IOException
     */
    public <T> void create(List<T> srcContent, Class<T> cls) throws IOException {
        FileOutputStream outputStream = null;
        Workbook workbook = null;
        try {
            String sheetName = getExportSheetName(cls);
            List<CellTemplate> templateList = getExportableFields(cls);

            workbook = createOrGetWorkBook(true);
            fillWorkBook(workbook, sheetName, templateList, srcContent);

            outputStream = new FileOutputStream(fileFullName);
            workbook.write(outputStream);
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            close(workbook, outputStream);
        }
    }

    public <T> List<T> read(Class<T> cls) throws IOException {
        List<T> content = new ArrayList<T>();
        FileInputStream inputStream = null;
        Workbook workbook = null;

        try {
            workbook = createOrGetWorkBook(false);
            Sheet sheet = readSheet(cls, workbook);
            content = readContent(sheet, cls);
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            close(workbook, inputStream);
        }
        return content;
    }

    protected abstract Workbook createBlankNewWorkbook();

    protected abstract Workbook getWorkbook() throws FileNotFoundException, IOException;

    protected abstract <T> void fillWorkBook(Workbook workbook, String sheetName, List<CellTemplate> templateList, List<T> srcContent) throws IllegalArgumentException, IllegalAccessException;

    private <T> Workbook createOrGetWorkBook(boolean createNewIfNotFound) throws FileNotFoundException, IOException {
        if (fileExist()) {
            return getWorkbook();
        } else {
            if (createNewIfNotFound) {
                return createBlankNewWorkbook();
            } else {
                throw new FileNotFoundException(fileFullName);
            }
        }

    }

    protected boolean fileExist() {
        File file = new File(fileFullName);
        return file.exists();
    }
    

    private Sheet getSheetBySheetName(Workbook workbook, String sheetName) {
        Iterator<Sheet> iterator = workbook.sheetIterator();
        while (iterator.hasNext()) {
            Sheet sheet = iterator.next();
            if (sheet.getSheetName().equals(sheetName)) {
                return sheet;
            }
        }
        throw new RuntimeException("Can't find sheet " + sheetName);
    }

    private <T> Sheet readSheet(Class<T> cls, Workbook workbook) {
        String sheetName = getExportSheetName(cls);
        Sheet sheet = getSheetBySheetName(workbook, sheetName);
        return sheet;
    }

    private <T> List<T> readContent(Sheet sheet, Class<T> cls) throws InstantiationException, IllegalAccessException, IllegalArgumentException, ParseException {
        List<T> result = new ArrayList<T>();
        List<CellTemplate> templateList = getExportableFields(cls);
        Iterator<Row> rows = sheet.rowIterator();
        skipExcelHeader(rows);
        while (rows.hasNext()) {
            Row row = rows.next();
            T item = readRow(cls, templateList, row);
            result.add(item);
        }

        return result;
    }

    private void skipExcelHeader(Iterator<Row> rows) {
        rows.next();
    }

    private <T> T readRow(Class<T> cls, List<CellTemplate> templateList, Row row) throws InstantiationException, IllegalAccessException, IllegalArgumentException, ParseException {
        T item = cls.newInstance();
        Iterator<Cell> cells = row.cellIterator();
        int columnIndex = 0;
        while (cells.hasNext()) {
            Cell cell = cells.next();
            templateList.get(columnIndex).invokeObjectProperty(cell, item);
            columnIndex++;
        }
        return item;
    }

    private <T> String getExportSheetName(Class<T> cls) {
        Annotation[] annotations = cls.getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof ExcelClassAnnotation) {
                return ((ExcelClassAnnotation) annotation).sheetName();
            }
        }
        throw new IllegalArgumentException(cls + "is not exportable.");
    }

    private <T> List<CellTemplate> getExportableFields(Class<T> cls) {
        List<CellTemplate> templateList = new ArrayList<CellTemplate>();
        Field[] declaredFields = cls.getDeclaredFields();

        for (Field field : declaredFields) {
            CellTemplate template = CellTemplate.getInstance(field);
            if (template != null) {
                templateList.add(template);
            }
        }
        Collections.sort(templateList);
        return templateList;
    }

    protected void close(Closeable... itemsToClose) throws IOException {
        if (itemsToClose != null) {
            for (Closeable closeable : itemsToClose) {
                if (closeable != null) {
                    closeable.close();
                }
            }
        }
    }
}


package com.excel;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;

public class ExcelUtilsXls extends ExcelUtilsBase {

    public ExcelUtilsXls(String fileFullName) {
        super(fileFullName);
    }

    @Override
    protected Workbook createBlankNewWorkbook() {
        return new HSSFWorkbook();
    }

    @Override
    protected Workbook getWorkbook() throws FileNotFoundException, IOException {
        try (FileInputStream inputStream = new FileInputStream(fileFullName)) {
            return new HSSFWorkbook(inputStream);
        }
    }

    @Override
    public <T> void fillWorkBook(Workbook workbook, String sheetName, List<CellTemplate> templateList, List<T> srcContent) throws IllegalArgumentException, IllegalAccessException {
        HSSFSheet sheet = ((HSSFWorkbook) workbook).createSheet(sheetName);
        createHeaderRow(templateList, sheet);
        createBodyRows(templateList, srcContent, sheet);
    }

    private void createHeaderRow(List<CellTemplate> templateList, HSSFSheet sheet) {
        HSSFRow headerRow = sheet.createRow(0);
        for (CellTemplate template : templateList) {
            template.createHeaderCell(headerRow);
        }
    }

    private <T> void createBodyRows(List<CellTemplate> templateList, List<T> srcContent, HSSFSheet sheet) throws IllegalAccessException {
        int rownum = 1;
        for (T item : srcContent) {
            HSSFRow row = sheet.createRow(rownum++);
            for (CellTemplate template : templateList) {
                template.createCell(row, item);
            }
        }
    }

}


package com.excel;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class ExcelUtilsXlsx extends ExcelUtilsBase {

    public ExcelUtilsXlsx(String fileFullName) {
        super(fileFullName);
    }

    @Override
    protected Workbook createBlankNewWorkbook() {
        return new XSSFWorkbook();
    }

    @Override
    protected Workbook getWorkbook() throws FileNotFoundException, IOException {
        try (FileInputStream inputStream = new FileInputStream(fileFullName)) {
            return new XSSFWorkbook(inputStream);
        }
    }

    @Override
    public <T> void fillWorkBook(Workbook workbook, String sheetName, List<CellTemplate> templateList, List<T> srcContent) throws IllegalArgumentException, IllegalAccessException {
        XSSFSheet sheet = ((XSSFWorkbook) workbook).createSheet(sheetName);
        createHeaderRow(templateList, sheet);
        createBodyRows(templateList, srcContent, sheet);
    }

    private void createHeaderRow(List<CellTemplate> templateList, XSSFSheet sheet) {
        XSSFRow headerRow = sheet.createRow(0);
        for (CellTemplate template : templateList) {
            template.createHeaderCell(headerRow);
        }
    }

    private <T> void createBodyRows(List<CellTemplate> templateList, List<T> srcContent, XSSFSheet sheet) throws IllegalAccessException {
        int rownum = 1;
        for (T item : srcContent) {
            XSSFRow row = sheet.createRow(rownum++);
            for (CellTemplate template : templateList) {
                template.createCell(row, item);
            }
        }
    }

}


  • test
package pojo;


import java.util.Date;

import com.excel.ExcelClassAnnotation;
import com.excel.ExcelFieldAnnotation;

@ExcelClassAnnotation(sheetName = "staff")
public class Record {

    @ExcelFieldAnnotation(columnIndex = 1, headerName = "id")
    private int id;

    @ExcelFieldAnnotation(columnIndex = 3, headerName = "name")
    private String name;

    @ExcelFieldAnnotation(columnIndex = 0, headerName = "age")
    private int age;

    @ExcelFieldAnnotation(columnIndex = 2)
    private double salary;
    
    @ExcelFieldAnnotation(columnIndex = 4)
    private Date aaa;
    
    public Record(){}
    
    public Record(int id, String name, int age, double salary,Date date) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.aaa=date;
    }

    @Override
    public String toString() {
        return String.format("id:%s\tage:%s\tsalary:%s\tname:%s\tdate:%s", id, age, salary,name,aaa.toLocaleString());
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

}




import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.excel.ExcelUtilsFactory;

import pojo.Record;

public class Main {

    public static void main(String[] args) throws IOException, IllegalArgumentException, IllegalAccessException, InstantiationException {
        test7();
        test8();
        test9();

        // test10();
    }

    private static void test10() throws IOException {
        List<Record> records = new ArrayList<Record>();
        records.add(new Record(1, "a", 12, 11.1d,new Date()));
        records.add(new Record(2, "b", 13, 12.1d,new Date()));
        records.add(new Record(3, "c", 14, 13.1d,new Date()));

        ExcelUtilsFactory.getInstance("c:\\work\\b.xls").create(records, Record.class);
        ExcelUtilsFactory.getInstance("c:\\work\\b.xlsx").create(records, Record.class);
    }

    private static void test9() throws IOException {
        System.out.println(ExcelUtilsFactory.getInstance("c:\\work\\b.xlsx").read(Record.class));
        System.out.println(ExcelUtilsFactory.getInstance("c:\\work\\b.xls").read(Record.class));
    }

    static void test7() throws IOException, IllegalArgumentException, IllegalAccessException {
        List<Record> records = new ArrayList<Record>();
        records.add(new Record(1, "a", 12, 11.1d,new Date()));
        records.add(new Record(2, "b", 13, 12.1d,new Date()));
        records.add(new Record(3, "c", 14, 13.1d,new Date()));

        ExcelUtilsFactory.getInstance("c:\\work\\b.xls").create(records, Record.class);
    }

    static void test8() throws IOException, IllegalArgumentException, IllegalAccessException {
        List<Record> records = new ArrayList<Record>();
        records.add(new Record(1, "a", 12, 11.1d,new Date()));
        records.add(new Record(2, "b", 13, 12.1d,new Date()));
        records.add(new Record(3, "c", 14, 13.1d,new Date()));
        ExcelUtilsFactory.getInstance("c:\\work\\b.xlsx").create(records, Record.class);
    }

}


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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,732评论 6 342
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,428评论 25 707
  • 以前总认为坚持会让我们变强大 但是长大后发现 让我们强大的是放下
    沫小草阅读 109评论 0 0
  • 秋风万里覆斜阳,吾家儿郎坐书窗。 翰墨挥洒落花舞,诗酒年华不彷徨。 春风勤学需尚早,寒冬磨砺宝剑芒。 胸中成竹书有...
    倚楼听雨声阅读 514评论 0 0