1.EasyExcel
Hello,大家好啊,好久不见了,自工作之后真的很难腾出时间来写博客(给大家分享我在技术中遇到的那些事),在华为真的挺忙的,这不突然来了一个双休,我也不想闲着,回想起很早之前学过的一个东西SpringBoot以及EasyExcel,于是就想着把它俩整合一下搞点简单的功能玩玩。
首先,这里给出EasyExcel的官方文档:https://easyexcel.opensource.alibaba.com/
alibaba.com不用我多说了吧,大家都认识,这个东西就是阿里开发的:EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。
他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。(这段话听着好牛的样子,没错,官方给出的你说呢)那么由于下面的案例中,涉及到Excel表格的样式问题,所以我这里采用了 EasyExcel 3.1.x的版本。https://easyexcel.opensource.alibaba.com/docs/current/
开始吧,👇👇👇
2.Excel的上传(读Excel)
我这里首先给大家分享一下SpringBoot结合EasyExcel实现对 Excel 中数据的读取,并且将读取到的数据存入MySQL数据库。
说到这里,要加什么依赖大家肯定都心知肚明了。😄😄😄
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
由于用到MyBatis,所以一定不要忘记加下面的这段代码,否则你的mapper是编译不到你的classpath中的。
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
既然要读取Excel,同时存入数据库,那么就必然需要对应的表了,这就引出了 表对应的实体类。
我这里写了两个,这是因为 Excel 对应了一个类,而数据库表对应的是另外一个,原因考虑到可能Excel表格会增加一些不必要的字段,而这些字段并不需要存入数据库中(反过来也是一样,可能数据库实体类中有10个字段,而Excel这边仅有6个字段,而剩余的4个字段我们可能会从其他渠道获取)。
上面我说的可能是在公司实际开发中会遇到的某些场景吧,但是为了方便大家理解,这两个类我还是定义的相同的字段。
package com.szh.dto;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GoodsExcelDTO {
@ExcelProperty(index = 0)
private Integer id;
@ExcelProperty(index = 1)
private String name;
@ExcelProperty(index = 2)
private String info;
@ExcelProperty(index = 3)
@DateTimeFormat(value = "yyyy-MM-dd HH:mm:ss")
private String buyTime;
}
package com.szh.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goods {
private Integer id;
private String name;
private String info;
//@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date buyTime;
}
大家应该能发现,Excel对应的类中 buyTime是 String类型,而数据库实体类的 buyTime是Date类型,这是因为:👇👇👇 (阿里官方文档给出的规定)
下面给出goods这张表的sql脚本。
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for goods
-- ----------------------------
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '商品名称',
`info` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '商品信息',
`buyTime` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '购买时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of goods
-- ----------------------------
INSERT INTO `goods` VALUES (1, 'Java核心技术卷I', 'Java四大名著之一', '2021-12-01 13:05:23');
INSERT INTO `goods` VALUES (2, 'Java核心技术卷II', 'Java四大名著之一', '2022-02-28 09:21:55');
INSERT INTO `goods` VALUES (3, 'Java编程思想', 'Java四大名著之一', '2020-05-15 14:16:18');
INSERT INTO `goods` VALUES (4, 'Effective Java', 'Java四大名著之一', '2022-07-06 22:33:44');
INSERT INTO `goods` VALUES (5, '深入理解Java虚拟机', 'JVM由此开始', '2021-06-06 20:59:37');
INSERT INTO `goods` VALUES (6, 'SpringBoot实战应用教程', 'SpringBoot极速开发框架', '2020-03-17 10:02:31');
INSERT INTO `goods` VALUES (7, '快速上手SSM', '一本优秀的书籍', '2021-08-14 19:08:26');
INSERT INTO `goods` VALUES (8, 'MySQL是怎样运行的', '深入学习MySQL', '2022-04-09 15:37:05');
INSERT INTO `goods` VALUES (9, 'Java并发编程', '并发编程之美', '2020-01-18 21:12:13');
INSERT INTO `goods` VALUES (10, 'Java从入门到精通', '小白的Java之路', '2021-09-11 17:18:21');
SET FOREIGN_KEY_CHECKS = 1;
大家参考官方文档之后,肯定知道,针对Excel的读,我们需要写一个监听器,去不断的监听读取Excel的每一行数据,最终加入数据库类似这样的操作。
下面就是监听器代码。
有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去。这也就是监听器中需要声明
private GoodsService goodsService; 这个成员变量的原因。
invoke 方法:解析Excel中的每一条数据都会来调用它。
invokeHead 方法:针对Excel表头的一些操作。
doAfterAllAnalysed 方法:所有数据解析完成了会来调用它。
package com.szh.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ConverterUtils;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import com.szh.dto.GoodsExcelDTO;
import com.szh.model.Goods;
import com.szh.service.GoodsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Excel模板的读取类
* 有个很重要的点 DemoDataListener 不能被spring管理
* 要每次读取excel都要new, 然后里面用到spring可以构造方法传进去
*/
@Slf4j
public class GoodsListener implements ReadListener<GoodsExcelDTO> {
private GoodsService goodsService;
private List<GoodsExcelDTO> goodsExcelDTOList = ListUtils.newArrayList();
private final List<Goods> goodsList = ListUtils.newArrayList();
private int count = 0;
public GoodsListener() {
}
public GoodsListener(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void invoke(GoodsExcelDTO goodsExcelDTO, AnalysisContext analysisContext) {
log.info("解析到第 {} 条数据:{}", (++count), JSON.toJSONString(goodsExcelDTO));
goodsExcelDTOList.add(goodsExcelDTO);
}
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
Map<Integer, String> stringMap = ConverterUtils.convertToStringMap(headMap, context);
Set<Integer> keySet = stringMap.keySet();
System.out.println("该Excel表头信息是:");
for (int i = 0; i < keySet.size(); i++) {
System.out.println("第 " + (i + 1) + " 列 = " + stringMap.get(i));
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
log.info("该Excel的所有数据解析完成!!!");
goodsExcelDTOList.forEach(item -> {
try {
Goods goods = new Goods();
goods.setId(item.getId());
goods.setName(item.getName());
goods.setInfo(item.getInfo());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(item.getBuyTime());
goods.setBuyTime(date);
goodsList.add(goods);
} catch (ParseException e) {
log.error(e.getMessage());
}
});
goodsExcelDTOList = ListUtils.newArrayList();
goodsService.batchInsert(goodsList);
}
}
好的,下面就是我们的mapper、service、controller了。这都是大家很熟悉的东西了。
package com.szh.mapper;
import com.szh.dto.GoodsExcelDTO;
import com.szh.model.Goods;
import java.util.List;
/**
*
*/
public interface GoodsMapper {
/**
* 批量插入
*/
void batchInsert(List<Goods> goodsList);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.szh.mapper.GoodsMapper">
<!-- 使用insert、update、delete、select标签编写sql语句 -->
<insert id="batchInsert" parameterType="java.util.List">
insert into goods
values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.id}, #{item.name}, #{item.info}, #{item.buyTime})
</foreach>
</insert>
</mapper>
package com.szh.service;
import com.szh.model.Goods;
import java.util.List;
/**
*
*/
public interface GoodsService {
void batchInsert(List<Goods> goodsList);
}
package com.szh.service.impl;
import com.szh.mapper.GoodsMapper;
import com.szh.model.Goods;
import com.szh.service.GoodsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
*
*/
@Slf4j
@Service
public class GoodsServiceImpl implements GoodsService {
@Autowired
private GoodsMapper goodsMapper;
@Transactional
@Override
public void batchInsert(List<Goods> goodsList) {
goodsMapper.batchInsert(goodsList);
}
}
package com.szh.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.fastjson.JSON;
import com.szh.dto.GoodsExcelDTO;
import com.szh.dto.StudentExcelDTO;
import com.szh.listener.GoodsListener;
import com.szh.service.GoodsService;
import com.szh.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
/**
*
*/
@RestController
@RequestMapping(value = "/excel")
public class EasyExcelController {
@Autowired
private GoodsService goodsService;
/**
* Excel文件上传
*/
@PostMapping(value = "/upload")
public String upload(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), GoodsExcelDTO.class, new GoodsListener(goodsService)).sheet().headRowNumber(1).doRead();
return "success";
}
}
再然后就是SpringBoot的核心配置文件和主启动类。
server:
port: 8001
servlet:
context-path: /boot
spring:
application:
name: web-easyexcel
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/excel?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8
username: root
password: 12345678
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
package com.szh;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.szh.mapper")
public class WebEasyExcelApplication {
public static void main(String[] args) {
SpringApplication.run(WebEasyExcelApplication.class, args);
}
}
由于这是上传操作,所以我这里也给大家提供我创建的Excel文档。可以看到和上面的goods表的字段都是对应好的。
下面我们启动项目,在postman中对上传Excel接口进行测试。
从上面的IDEA中的控制台可以看到,Excel的解析没有问题,顺利执行完毕,同时10条数据也成功写入数据库中。😄😄😄
3.Excel的下载(写Excel)
这里仍然用到的和上面的读Excel是同一个SpringBoot项目,所以依赖、核心配置文件、主启动类这些都是一样的,就不再给出了,大家参考上面的就行了。
那么对于Excel的下载,也就是说向Excel中写入数据,相较于上传操作就要简单多了。
来看下面的步骤:👇👇👇
这里模拟的场景是:从数据库中的某张表获取到对应的数据集,然后将这个List集合写入Excel中。
所以自然也是需要实体类,那么和上面的上传操作一样,我仍然是创建两个类,一个是Excel表格对应的实体类,另一个是我们数据库对应的实体类。
package com.szh.dto;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.*;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 类上面的注解,来自lombok和EasyExcel官方,用来自定义表头、宽度等信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ColumnWidth(25)
@HeadRowHeight(30)
@ContentRowHeight(20)
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER)
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER)
public class StudentExcelDTO {
@ExcelProperty({"学生综述", "学生编号"})
private Integer id;
@ExcelProperty({"学生综述", "学生姓名"})
private String name;
@ExcelProperty({"学生综述", "学生年龄"})
private Integer age;
@ExcelProperty({"学生综述", "学生爱好"})
private String hobby;
@ExcelIgnore
private String address;
}
package com.szh.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer age;
private String hobby;
private String address;
}
student这张表的sql脚本,我也给到大家。
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键,暂定学生编号',
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '学生姓名',
`age` int(11) NOT NULL COMMENT '学生年龄',
`hobby` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '学生爱好',
`address` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '学生住址',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, '张三', 41, '睡觉', '湖北省武汉市');
INSERT INTO `student` VALUES (2, '李四', 66, '吃饭', '湖南省长沙市');
INSERT INTO `student` VALUES (3, '王五', 28, '打游戏', '江苏省南京市');
INSERT INTO `student` VALUES (4, '赵六', 35, '玩手机', '浙江省杭州市');
INSERT INTO `student` VALUES (5, '田七', 52, '旅游', '上海市黄浦区');
INSERT INTO `student` VALUES (6, '小哥', 22, '盗墓', '河南省洛阳市');
INSERT INTO `student` VALUES (7, '张起灵', 18, '倒斗', '北京市海淀区');
INSERT INTO `student` VALUES (8, '闷油瓶', 20, '挖坟', '广东省深圳市');
SET FOREIGN_KEY_CHECKS = 1;
下面就是我们的mapper、service、controller了,还是大家熟悉的那一套。
package com.szh.mapper;
import com.szh.model.Student;
import java.util.List;
/**
*
*/
public interface StudentMapper {
/**
* 获取学生列表
*/
List<Student> queryAllStudents();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.szh.mapper.StudentMapper">
<!-- 使用insert、update、delete、select标签编写sql语句 -->
<sql id="student_column_list">
id, name, age, hobby, address
</sql>
<resultMap id="studentMap" type="com.szh.model.Student">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="age" property="age"></result>
<result column="hobby" property="hobby"></result>
<result column="address" property="address"></result>
</resultMap>
<select id="queryAllStudents" resultMap="studentMap">
select <include refid="student_column_list"></include>
from student
</select>
</mapper>
package com.szh.service;
import com.szh.dto.StudentExcelDTO;
import java.util.List;
/**
*
*/
public interface StudentService {
List<StudentExcelDTO> queryAllStudents();
}
package com.szh.service.impl;
import com.szh.dto.StudentExcelDTO;
import com.szh.mapper.StudentMapper;
import com.szh.model.Student;
import com.szh.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
@Slf4j
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public List<StudentExcelDTO> queryAllStudents() {
List<Student> studentList = studentMapper.queryAllStudents();
List<StudentExcelDTO> students = new ArrayList<>();
studentList.forEach(item -> {
StudentExcelDTO student = new StudentExcelDTO();
BeanUtils.copyProperties(item, student);
students.add(student);
});
return students;
}
}
这里的controller和上面的Excel上传是同一个controller,代码可以合并。
package com.szh.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.fastjson.JSON;
import com.szh.dto.GoodsExcelDTO;
import com.szh.dto.StudentExcelDTO;
import com.szh.listener.GoodsListener;
import com.szh.service.GoodsService;
import com.szh.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
/**
*
*/
@RestController
@RequestMapping(value = "/excel")
public class EasyExcelController {
@Autowired
private StudentService studentService;
/**
* Excel文件下载
*/
@GetMapping(value = "/download")
public void download(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
//这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("学生信息表", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
List<StudentExcelDTO> studentList = studentService.queryAllStudents();
EasyExcel.write(response.getOutputStream(), StudentExcelDTO.class).sheet("sheet1").doWrite(studentList);
}
/**
* Excel文件下载并且失败的时候返回json
*/
@GetMapping(value = "/downloadFailedUsingJson")
public void downloadFailedUsingJson(HttpServletResponse response) throws IOException {
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
//这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode("学生信息表", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
List<StudentExcelDTO> studentList = studentService.queryAllStudents();
EasyExcel.write(response.getOutputStream(), StudentExcelDTO.class).autoCloseStream(Boolean.FALSE).sheet("sheet1").doWrite(studentList);
} catch (Exception e) {
//重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = MapUtils.newHashMap();
map.put("status", "failure");
map.put("message", "Excel文件下载失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
}
核心配置文件、主启动类这些都是和上面的案例一样的,就不再给出了。
下面直接启动项目,在postman中进行测试。
这里大家注意一下,有的小伙伴可能直接点了Send,然后postman反馈了一堆乱码,但是此时把接口地址拿到浏览器中是可以直接访问的。
解决办法就是:点击下图中的 Send and Download。
保存之后,我们的Excel下载接口就可以正常跑通,下面就是下载成功的Excel了。😄😄😄
4.结语
上面的两个案例就是SpringBoot整合EasyExcel,实现Excel的读写(上传下载)的操作。
我这里所有的整合步骤均来自阿里的EasyExcel官方文档,所以大家有什么疑问都可以去官方文档中查找,地址在文章开头已经给出了。
或者大家也可以在文章下面评论,我都会一一回复的,和大家一起讨论技术话题。😄😄😄
版权归原作者 宋宋_浩浩_Java工程师 所有, 如有侵权,请联系我们删除。