15、SpringBoot實現Excel的導入導出


前言

需求:正如標題所言,需求有數據的導入、導出

導入:給用戶提供給一個導入數據的模板,用戶填寫數據后上傳,實現文件的批量導入。

導出:將數據列表直接導進excel,用戶通過瀏覽器下載。

首先考慮用經典的apache poi實現這一功能,但是發現不是很好用,后面有換了阿里的 easy excel,效率比較高。

如果需求不高,只是簡單的導入導出,不涉及復雜對象,可以直接使用第一版的代碼。

excel基本構成

雖然只寫個導入導出並不要求我們對excel有多熟悉,但是最起碼得知道excel有哪些構成。

整個文件:student.xlsx,對應與poi中的Workbook

sheet:一張表

 

 cell:單元格,每一小格

apache poi

依賴

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi 03版的excel -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml 新版的excel -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

實體類

@Data
@AllArgsConstructor
public class Student {
    private String name;

    private Integer age;

    private String hobby;

    private Float score;

    private Date birth;
}

讀取

HSSF開頭是老版本的excel,拓展名為xls

我們這里用的是新版本得,拓展名xlsx

public class TestRead {

    public static void main(String[] args) throws IOException {
        XSSFWorkbook workbook = new XSSFWorkbook("E:\\personcode\\office-learn\\src\\main\\resources\\excel\\test.xlsx");

        XSSFSheet sheet = workbook.getSheetAt(0);
        for (Row row : sheet) {
            for (Cell cell : row) {
                System.out.print(cell.toString() +"\t");
            }
            System.out.println();
        }

        workbook.cloneSheet(0);
    }
}

寫入

public class TestWrite {

    public static List<Student> getStudentList() {
        return Arrays.asList(
                new Student("學生1", 18, "學習", 59.5F, new Date()),
                new Student("學生2", 19, "游泳", 68F, new Date()),
                new Student("學生3", 19, "游泳", 90F, new Date()),
                new Student("學生4", 19, "游泳", 100F, new Date())
        );
    }

    public static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) throws IllegalAccessException {
        //創建一個表格
        XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
        List<Student> studentList = getStudentList();
        //創建一個sheet
        XSSFSheet sheet = xssfWorkbook.createSheet("學生成績");

        //單元格格式
        XSSFCellStyle cellStyle = xssfWorkbook.createCellStyle();
        cellStyle.setFillBackgroundColor(IndexedColors.PINK.getIndex());


        //字體樣式
        XSSFFont font = xssfWorkbook.createFont();
        font.setFontName("黑體");
        font.setColor(IndexedColors.BLUE.getIndex());
        cellStyle.setFont(font);


        //設置第一行
        XSSFRow row = sheet.createRow(0);
        row.createCell(0).setCellValue("姓名");
        row.createCell(1).setCellValue("年齡");
        row.createCell(2).setCellValue("興趣");
        row.createCell(3).setCellValue("分數");
        row.createCell(4).setCellValue("日期");

        for (int i = 1; i < studentList.size(); i++) {
            Student student = studentList.get(i);
            XSSFRow xrow = sheet.createRow(i);
            //如果不設置格式
//            xrow.createCell(0).setCellValue(student.getName());
//            xrow.createCell(1).setCellValue(student.getAge());
//            xrow.createCell(2).setCellValue(student.getHobby());
//            xrow.createCell(3).setCellValue(student.getScore());
//            xrow.createCell(4).setCellValue(student.getBirth());
            XSSFCell cell1 = xrow.createCell(0);
            cell1.setCellValue(student.getName());
            cell1.setCellStyle(cellStyle);

            XSSFCell cell2 = xrow.createCell(1);
            cell2.setCellValue(student.getAge());
            cell2.setCellStyle(cellStyle);

            XSSFCell cell3 = xrow.createCell(2);
            cell3.setCellValue(student.getHobby());
            cell3.setCellStyle(cellStyle);

            XSSFCell cell4 = xrow.createCell(3);
            cell4.setCellValue(student.getScore());
            cell4.setCellStyle(cellStyle);

            XSSFCell cell5 = xrow.createCell(4);
            cell5.setCellValue(format.format(student.getBirth()));
            cell5.setCellStyle(cellStyle);



        }

        //獲取樣式
//        XSSFCellStyle cellStyle = xssfWorkbook.createCellStyle();
//        XSSFDataFormat format = xssfWorkbook.createDataFormat();
//        cellStyle.setDataFormat(format.getFormat("yyyy年m月d日"));

        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream("E:\\personcode\\office-learn\\src\\main\\resources\\excel\\student.xlsx");
            xssfWorkbook.write(fileOutputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                xssfWorkbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 Easy Excel

我們先用簡單的數據結構測試一下,能跑起來才是王道,然后再考慮集成進SpringBoot,以及復雜數據。

依賴

spring之類的依賴就不贅述了

        <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.6</version>
        </dependency>

實體類

@Data
@AllArgsConstructor
@NoArgsConstructor //如果加上面一個注解,這個必須加上,否則easy excel會報無法實例化
public class Student {

    @ExcelProperty("姓名")
    private String name;

    @ExcelProperty("年齡")
    private Integer age;

    @ExcelProperty("愛好")
    private String hobby;

    @ExcelProperty("分數")
    private Float score;

    @ExcelProperty("生日")
    private Date birth;
}

簡單寫入

public class TestWrite {

    public static List<Student> getStudentList() {
        return Arrays.asList(
                new Student("學生1", 18, "學習", 59.5F, new Date()),
                new Student("學生2", 19, "游泳", 68F, new Date()),
                new Student("學生3", 19, "游泳", 90F, new Date()),
                new Student("學生4", 19, "游泳", 100F, new Date())
        );
    }

    public static void main(String[] args) {
        String fileName = "E:\\personcode\\office-learn\\src\\main\\resources\\excel\\student.xlsx";
        // 這里 需要指定寫用哪個class去讀,然后寫到第一個sheet,名字為模板 然后文件流會自動關閉
        // 如果這里想使用03 則 傳入excelType參數即可
        EasyExcel.write(fileName, Student.class).sheet("學生信息").doWrite(getStudentList());
    }
}

執行結果

 

 

 

對比POI,有一種從SSM換到了SpringBoot的感覺。

簡單讀取

public class TestRead {

    public static void main(String[] args) {
        String fileName = "E:\\personcode\\office-learn\\src\\main\\resources\\excel\\student.xlsx";
        // 這里 需要指定讀用哪個class去讀,然后讀取第一個sheet 文件流會自動關閉
        EasyExcel.read(fileName, Student.class, new DemoDataListener()).sheet().doRead();
    }
}

簡單讀取需要配置一個監聽類,在這個監聽類里處理數據,可以邊讀取邊處理。

下面監聽類的作用是,打印每一行數據

public class DemoDataListener extends AnalysisEventListener<Student> {

    public DemoDataListener() {}

    //解析每條數據的時候會調用這個方法
    @Override
    public void invoke(Student student, AnalysisContext analysisContext) {
        System.out.println(student);
    }

    //解析完成后調用這個方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

控制台輸出

Student(name=學生1, age=18, hobby=學習, score=59.5, birth=Wed Nov 11 20:32:20 CST 2020)
Student(name=學生2, age=19, hobby=游泳, score=68.0, birth=Wed Nov 11 20:32:20 CST 2020)
Student(name=學生3, age=19, hobby=游泳, score=90.0, birth=Wed Nov 11 20:32:20 CST 2020)
Student(name=學生4, age=19, hobby=游泳, score=100.0, birth=Wed Nov 11 20:32:20 CST 2020)

集成進SpringBoot

在完成基本的導入導出功能后,我們就可以考慮將代碼集成進SpringBoot了。

第一版

僅僅針對上面的代碼,做初步集成,暫不考慮其他需求,例如多個sheet、日期轉換等

既然要繼承進Spring環境,我主張完全交給Spring來管理,不要再new對象了。

依賴

模擬真實開發環境,需要加入數據庫相關的依賴,這里我還是用的mybatis plus:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

配置文件

server:
  port: 8080

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root


mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  type-enums-package: com.dayrain.nums
  global-config:
    db-config:
      logic-not-delete-value: 1
      logic-delete-value: 0

  mapper-locations: classpath*:/mapper/**/*.xml

實體類

與上面相比,添加了一個id作為主鍵,

其中@Excelgnore表示導出的時候忽略該字段。

@Data
@AllArgsConstructor
@NoArgsConstructor //如果加上面一個注解,這個必須加上,否則easy excel會報無法實例化
public class Student {

    @TableId(type = IdType.AUTO)
    @ExcelIgnore
    private Integer id;

    @ExcelProperty("姓名")
    private String name;

    @ExcelProperty("年齡")
    private Integer age;

    @ExcelProperty("愛好")
    private String hobby;

    @ExcelProperty("分數")
    private Float score;

    @ExcelProperty("生日")
    private Date birth;
}

DAO

@Mapper
public interface StudentMapper extends BaseMapper<Student> {
}

service

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    StudentMapper studentMapper;

    @Override
    public void insertStudent(Student student) {
        studentMapper.insert(student);
    }

    @Override
    public void insertStudents(List<Student> students) {
        students.forEach(student -> studentMapper.insert(student));
    }

    @Override
    public List<Student> selectStudents() {
        return studentMapper.selectList(null);
    }
}
public interface StudentService {
    void insertStudent(Student student);

    void insertStudents(List<Student>students);

    List<Student> selectStudents();
}

excel

excel相關的類我也寫成了service,便於拓展和使用

public interface ExcelService {
    void simpleDownload(HttpServletResponse response) throws IOException;

    void simpleUpload(MultipartFile file) throws IOException;
}

 

@Service
public class ExcelServiceImpl implements ExcelService {
    @Autowired
    StudentServiceImpl studentService;

    @Autowired
    StudentDataListener studentDataListener;

    @Override
    public void simpleDownload(HttpServletResponse response) throws IOException {
        // 這里注意 有同學反應使用swagger 會導致各種問題,請直接用瀏覽器或者用postman
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 這里URLEncoder.encode可以防止中文亂碼
        String fileName = URLEncoder.encode("測試", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream(), Student.class).sheet("學生信息").doWrite(studentService.selectStudents());
    }

    @Override
    public void simpleUpload(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), Student.class, studentDataListener).sheet().doRead();
    }
}

 

@Component
public class StudentDataListener extends AnalysisEventListener<Student> {


    //因為相關的類沒有交給spring管理,所以不能直接通過注解注入
    @Autowired
   private StudentService studentService;


    //解析每條數據的時候會調用這個方法
    @Override
    public void invoke(Student student, AnalysisContext analysisContext) {
        studentService.insertStudent(student);
    }

    //解析完成后調用這個方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

controller

@RestController
public class ExcelController {
    @Autowired
    ExcelService excelService;

    //導出
    @GetMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
        excelService.simpleDownload(response);
    }

    //導入
    @PostMapping("/upload")
    public String upload(MultipartFile file) throws IOException {
        excelService.simpleUpload(file);
        return "success";
    }
}

目錄結構

 

 

 分析

1、對比

web版的excel導入導出,與之前簡單demo相比,不需要指定文件的存放路徑。

導入的時候,web端直接分析流文件,將每一行都插進數據庫。

導出的時候,web端將查到數據,直接寫入response,無需在服務器端生成臨時文件。

2、問題

一個項目里會有很多個導入導出,並不只是Student類的導出。

但是不管是何種數據的導出,api都是相同的,區別就在於handler類不一樣(因為我覺得與netty中的handler很像,故如此取名)。

例如我們現在需要導出學校信息,我們可以這么做:

第二版

新增一個實體類;

@Data
public class School {

    @TableId(type = IdType.AUTO)
    private Integer id;

    private String name;

    private Student year;
}

寫好對應的service:

@Service
public class SchoolServiceImpl implements SchoolService {

    @Autowired
    SchoolMapper schoolMapper;

    @Override
    public void insert(School school) {
        schoolMapper.insert(school);
    }
}

新增對應的處理類

@Component
public class SchoolHandler extends AnalysisEventListener<School> {//因為相關的類沒有交給spring管理,所以不能直接通過注解注入
    @Autowired
    private SchoolService schoolService;


    //解析每條數據的時候會調用這個方法
    @Override
    public void invoke(School school, AnalysisContext analysisContext) {
        schoolService.insert(school);
    }

    //解析完成后調用這個方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

    }
}

修改excel相關類,將接口設計成可以動態添加 Listen 類

public interface ExcelService {
    void simpleDownload(HttpServletResponse response) throws IOException;

    void simpleUpload(MultipartFile file, AnalysisEventListener listener) throws IOException;
}

 

@Service
public class ExcelServiceImpl implements ExcelService {
    @Autowired
    StudentServiceImpl studentService;

    @Autowired
    StudentDataListener studentDataListener;

    @Override
    public void simpleDownload(HttpServletResponse response) throws IOException {
        // 這里注意 有同學反應使用swagger 會導致各種問題,請直接用瀏覽器或者用postman
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 這里URLEncoder.encode可以防止中文亂碼
        String fileName = URLEncoder.encode("測試", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        EasyExcel.write(response.getOutputStream(), Student.class).sheet("學生信息").doWrite(studentService.selectStudents());
    }

    @Override
    public void simpleUpload(MultipartFile file, AnalysisEventListener listener) throws IOException {
        EasyExcel.read(file.getInputStream(), Student.class, listener).sheet().doRead();
    }
}

controller層調用

 

@RestController
public class ExcelController {
    @Autowired
    ExcelService excelService;

    @Autowired
    SchoolHandler schoolHandler;

    @Autowired
    StudentDataListener studentDataListener;

    //導出
    @GetMapping("/download")
    public void download(HttpServletResponse response) throws IOException {
        excelService.simpleDownload(response);
    }

    //導入
    @PostMapping("/student/upload")
    public String upload(MultipartFile file) throws IOException {
        excelService.simpleUpload(file, studentDataListener);
        return "success";
    }

    @PostMapping("/school/upload")
    public String upload2(MultipartFile file) throws IOException {
        excelService.simpleUpload(file, schoolHandler);
        return "success";
    }


}

其他代碼不變。

補充

大體框架基本拉出來了,我們需要考慮一下細節問題。

導出所有sheet

ExcelService添加一個類

注意是doReadAll()

    @Override
    public void allSheetUpload(MultipartFile file, AnalysisEventListener listener) throws IOException {
        EasyExcel.read(file.getInputStream(), Student.class, listener).doReadAll();
    }

導出部分sheet

這里寫死了只有兩個sheet。

一般用戶使用的模板都是系統提供的,所以當每個sheet存放不同類型的內容時,我們可以提前感知,在后台進行相應的映射。

@Override
    public void PartSheetUpload(MultipartFile file, List<AnalysisEventListener>listeners) {
        // 讀取部分sheet

        ExcelReader excelReader = null;
        try {
            excelReader = EasyExcel.read(file.getInputStream()).build();

            // 這里為了簡單 所以注冊了 同樣的head 和Listener 自己使用功能必須不同的Listener
            ReadSheet readSheet1 =
                    EasyExcel.readSheet(0).head(Student.class).registerReadListener(listeners.get(0)).build();
            ReadSheet readSheet2 =
                    EasyExcel.readSheet(1).head(School.class).registerReadListener(listeners.get(1)).build();
            // 這里注意 一定要把sheet1 sheet2 一起傳進去,不然有個問題就是03版的excel 會讀取多次,浪費性能
            excelReader.read(readSheet1, readSheet2);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (excelReader != null) {
                // 這里千萬別忘記關閉,讀的時候會創建臨時文件,到時磁盤會崩的
                excelReader.finish();
            }
        }
    }

 格式轉換

可以在對象上直接加注解,也可以自己寫一個轉化類。

@Data
@AllArgsConstructor
@NoArgsConstructor //如果加上面一個注解,這個必須加上,否則easy excel會報無法實例化
//@ColumnWidth(25)設置行高
public class Student {

    @TableId(type = IdType.AUTO)
    @ExcelIgnore
    private Integer id;

    @ExcelProperty("姓名")
//    @ColumnWidth(50)設置行高,覆蓋上面的設置
    private String name;

    @ExcelProperty("年齡")
    private Integer age;

    @ExcelProperty("愛好")
    private String hobby;

    @ExcelProperty("分數")
    private Float score;

    @ExcelProperty("生日")
    @DateTimeFormat("yyyy年MM月dd日")
    private Date birth;
}

如果業務比較復雜,需要自己創建轉換器

public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class supportJavaTypeKey() {
        return null;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return null;
    }

    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return null;
    }

    @Override
    public CellData convertToExcelData(String s, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
        return null;
    }
}

上面根據自己的業務進行填寫

比如要轉化成LocalDateTime,就把String替換成 LocalDateTime

可以參考:https://blog.csdn.net/weixin_47098539/article/details/109385543?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduend~default-2-109385543.nonecase&utm_term=easyexcel%20%E6%97%B6%E9%97%B4%E7%B1%BB%E5%9E%8B%E7%9A%84%E8%BD%AC%E6%8D%A2&spm=1000.2123.3001.4430

添加轉換器

    @Override
    public void Convert(MultipartFile file) throws IOException {

        // 這里 需要指定讀用哪個class去讀,然后讀取第一個sheet
        EasyExcel.read(file.getInputStream(), Student.class, studentDataListener)
                // 這里注意 我們也可以registerConverter來指定自定義轉換器, 但是這個轉換變成全局了, 所有java為string,excel為string的都會用這個轉換器。
                // 如果就想單個字段使用請使用@ExcelProperty 指定converter
                 .registerConverter(new CustomStringStringConverter())
                // 讀取sheet
                .sheet().doRead();
    }

如果工作中用到了其他功能,后面再來補充。 

 異常處理

消息轉換

控制台報的一個warning,雖然不影響結果,但是看着很煩。

SpringMvc默認的消息轉換 MessageConverters不支持我們設置的媒體類型。添加一個配置類即可解決問題。

這里用的是默認的jackson,也可以用Gson或者fastjson

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*")
                .allowedHeaders("*")
                .allowedMethods("*")
                .allowCredentials(true)
                .maxAge(30*1000);
    }

@Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setSupportedMediaTypes(getSupportedMediaTypes()); converters.add(converter); } public List<MediaType> getSupportedMediaTypes() { //創建fastJson消息轉換器 List<MediaType> supportedMediaTypes = new ArrayList<>(); supportedMediaTypes.add(MediaType.APPLICATION_JSON); supportedMediaTypes.add(MediaType.APPLICATION_ATOM_XML); supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED); supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM); supportedMediaTypes.add(MediaType.APPLICATION_PDF); supportedMediaTypes.add(MediaType.APPLICATION_RSS_XML); supportedMediaTypes.add(MediaType.APPLICATION_XHTML_XML); supportedMediaTypes.add(MediaType.APPLICATION_XML); supportedMediaTypes.add(MediaType.IMAGE_GIF); supportedMediaTypes.add(MediaType.IMAGE_JPEG); supportedMediaTypes.add(MediaType.IMAGE_PNG); supportedMediaTypes.add(MediaType.TEXT_EVENT_STREAM); supportedMediaTypes.add(MediaType.TEXT_HTML); supportedMediaTypes.add(MediaType.TEXT_MARKDOWN); supportedMediaTypes.add(MediaType.TEXT_PLAIN); supportedMediaTypes.add(MediaType.TEXT_XML); supportedMediaTypes.add(MediaType.ALL); return supportedMediaTypes; } }

 

 

如有錯誤,歡迎批評指正~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM