前言
为大家介绍一个使用Java编写的功能强大的周报导出工具,它可以将周报数据导出到Excel中,方便管理和查阅。这个工具使用了Apache POI
库来处理Excel文件,确保生成的Excel文件具有高质量的格式和样式。
背景
在日常工作中,很多公司都需要定期填写和提交周报,以便更好地追踪项目进展和团队成员的工作情况。传统的手工填写周报存在一些问题,比如耗时、易出错等。为了提高效率和准确性,我们使用Java编写了一个周报导出工具,它可以根据指定的时间范围,从数据库中提取数据,并将周报导出为Excel文件。
一、工具功能
我们的周报导出工具具有以下主要功能:
- 导出指定时间范围内的周报数据。
- 根据项目负责人将周报数据分组,并生成相应的Excel表格。
- 汇总每个项目负责人负责的项目每周一到周日的工时。
- 提供对导出数据的一定格式和样式控制,使周报内容更加美观。
- 自动计算合计工时和项目总结信息。
二、实现细节
1. 参数校验
首先,我们对传入的日期参数进行校验,确保日期范围的合法性。
2. 数据获取与处理
我们通过数据库查询获得所有的项目信息和成员信息,并根据指定的时间范围获取每个项目的工时明细。然后,我们对数据进行分组,将数据按照项目负责人分组,以便后续生成Excel表格。
3. Excel表格生成
接下来,我们使用Apache POI库来生成Excel表格。我们设置标题行、样式、合并单元格等,以确保表格的可读性和美观性。
4. 数据填充
我们将从数据库中查询的数据填充到Excel表格中。根据每个项目负责人负责的项目数,动态合并单元格,方便展示项目信息。
5. 合计工时计算
在表格中添加了每周一到周日的工时数据后,我们计算并填充每个项目负责人负责的所有项目每周一到周日的合计工时。
6. 总结汇总
最后,我们将每个项目负责人负责的所有项目的总结信息汇总,并添加到Excel表格中。
7. 文件导出
最后一步是将生成的Excel文件通过HTTP响应的方式提供给用户进行下载。
三、实战开发
1. Maven
<!--POI解析excel-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<!-- Excel Xlsx格式解析 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
2. 核心导包import
import javafx.util.Pair;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
3. 业务代码
public void exportWeekReportToExcel(HttpServletResponse response, LocalDate startDatetime, LocalDate endDatetime) {
{
//校验参数
validateDateRange(startDatetime,endDatetime);
//分割时间一周
List<Pair<LocalDate, LocalDate>> pairs = DayOfWeekUtil.splitTimeByWeek(startDatetime, endDatetime);
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("项目周报");
//获取所有的项目信息和成员信息
List<ProjectMembersEntry> projectMembersEntries = stWorkLogMapper.selectProjectMembers();
int rowNumber = 0;
for (Pair<LocalDate, LocalDate> pair : pairs) {
LocalDate weekStartDate = pair.getKey();
LocalDate weekEndDate = pair.getValue();
//获取所有用户的的所有项目的工时明细
List<WorkTimesEntry> workTimesEntries = stWorkLogMapper.selectWorkTimes(weekStartDate.toString(),weekEndDate.toString());
if(workTimesEntries.size()<1){
continue;
}
//获取周报总结内容
String weekData = "";
// 创建标题行样式
CellStyle titleCellStyle = workbook.createCellStyle();
cellStyleSet(workbook, titleCellStyle);
// 创建工作内容行样式
CellStyle contentCellStyle = workbook.createCellStyle();
workContentStyle(workbook, contentCellStyle);
// 创建空行样式
CellStyle blankCellStyle = workbook.createCellStyle();
blankStyle(workbook, blankCellStyle);
//筛选项目负责人
List<String> projectManagerList = projectMembersEntries.stream().map(ProjectMembersEntry::getCreateBy).distinct().collect(Collectors.toList());
for (String createBy : projectManagerList) {
List<ProjectMembersEntry> collect = projectMembersEntries.stream().filter(entry -> entry.getCreateBy().equals(createBy)).collect(Collectors.toList());
//对每个项目负责人负责的项目填充excel的标题
ProjectMembersEntry projectMembersEntry = collect.get(0);
String dept = projectMembersEntry.getDept();
String projectManager = projectMembersEntry.getProjectManager();
// 创建标题行
Row titleRow = sheet.createRow(rowNumber);
Cell titleRowCell1 = titleRow.createCell(0);
titleRowCell1.setCellValue("姓名:");
titleRowCell1.setCellStyle(titleCellStyle);
Cell titleRowCell2 = titleRow.createCell(1);
titleRowCell2.setCellValue(projectManager);
titleRowCell2.setCellStyle(contentCellStyle);
Cell titleRowCell3 = titleRow.createCell(2);
titleRowCell3.setCellValue("部门:");
titleRowCell3.setCellStyle(titleCellStyle);
CellRangeAddress mergedRegion = new CellRangeAddress(rowNumber, rowNumber, 3, 4);
sheet.addMergedRegion(mergedRegion);
Cell titleRowCell4 = titleRow.createCell(3);
titleRowCell4.setCellValue(dept);
titleRowCell4.setCellStyle(contentCellStyle);
Cell titleRowCell41 = titleRow.createCell(4);
titleRowCell41.setCellStyle(titleCellStyle);
Cell titleRowCell5 = titleRow.createCell(5);
titleRowCell5.setCellValue("期间:");
titleRowCell5.setCellStyle(titleCellStyle);
// 合并startDatetime和endDatetime所在的三列
CellRangeAddress mergedRegion2 = new CellRangeAddress(rowNumber, rowNumber, 6, 8);
sheet.addMergedRegion(mergedRegion2);
Cell titleRowCell6 = titleRow.createCell(6);
titleRowCell6.setCellValue(weekStartDate + "至" + weekEndDate);
titleRowCell6.setCellStyle(contentCellStyle);
Cell titleRowCell7 = titleRow.createCell(7);
titleRowCell7.setCellStyle(contentCellStyle);
Cell titleRowCell8 = titleRow.createCell(8);
titleRowCell8.setCellStyle(contentCellStyle);
rowNumber++;
// 创建第二层表头行
// 设置列宽
// 第一列宽度
sheet.setColumnWidth(0, 25 * 256);
sheet.setColumnWidth(1, 15 * 256);
// 第二列宽度
for (int i = 2; i <= 8; i++) {
// 剩下七列宽度
sheet.setColumnWidth(i, 10 * 256);
}
// 创建第二行标题
Row titleRow1 = sheet.createRow(rowNumber);
Cell titleCell = titleRow1.createCell(0);
titleCell.setCellValue("所负责研发项目名称\n及项目编号");
titleCell.setCellStyle(titleCellStyle);
// 合并单元格,两行一列
sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber+1, 0, 0));
titleCell = titleRow1.createCell(1);
titleCell.setCellValue("项目组成员");
titleCell.setCellStyle(titleCellStyle);
// 合并单元格,两行一列
sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber+1, 1, 1));
// 创建第三行标题
titleCell = titleRow1.createCell(2);
titleCell.setCellValue("工时明细");
titleCell.setCellStyle(titleCellStyle);
titleCell = titleRow1.createCell(8);
titleCell.setCellStyle(titleCellStyle);
// 合并单元格,一行七列
sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber, 2, 8));
rowNumber++;
// 创建第三行子标题
Row titleRow2 = sheet.createRow(rowNumber);
String[] weekdays = {
"周一", "周二", "周三", "周四", "周五", "周六", "周日"};
Cell dayCell1 = titleRow2.createCell(0);
dayCell1.setCellStyle(titleCellStyle);
Cell dayCell2 = titleRow2.createCell(1);
dayCell2.setCellStyle(titleCellStyle);
for (int i = 2; i <= 8; i++) {
Cell dayCell = titleRow2.createCell(i);
dayCell.setCellValue(weekdays[i - 2]);
dayCell.setCellStyle(titleCellStyle);
}
rowNumber++;
// 用于存储每周一到周日的工时合计
String[] weekdayTotal = new String[7];
for (int i = 0; i < 7; i++) {
weekdayTotal[i] = "0";
}
//项目Id的set集合
Set<String> set = new HashSet<>();
// 创建第三行内容
for (ProjectMembersEntry membersEntry : collect) {
String projectId = membersEntry.getProjectId();
set.add(projectId);
String projectCode = membersEntry.getProjectCode();
String projectName = membersEntry.getProjectName();
String projectUserIds = membersEntry.getProjectUserIds();
String projectMembers = membersEntry.getProjectMembers();
if(StringUtils.isEmpty(projectUserIds)||StringUtils.isEmpty(projectMembers)){
continue;
}
String[] userIdSplit = projectUserIds.split(",");
String[] memberSplit = projectMembers.split(",");
int length = userIdSplit.length;
Row titleRow3 = sheet.createRow(rowNumber);
Cell titleCell3 = titleRow3.createCell(0);
titleCell3.setCellValue(projectName+"\n("+projectCode+")");
titleCell3.setCellStyle(contentCellStyle);
// 合并单元格,多行一列
if(length>1){
sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber+length-1, 0, 0));
}
int a=0,b=7;
//填充考勤工时
for (int i = 0; i < userIdSplit.length; i++){
String userId = userIdSplit[i];
String username = memberSplit[i];
List<WorkTimesEntry> workTimesEntryList = workTimesEntries.stream().filter(workTimesEntry -> workTimesEntry.getProjectId().equals(projectId))
.filter(workTimesEntry -> workTimesEntry.getUserId().equals(userId)).collect(Collectors.toList());
Row titleRows;
Cell cell;
if(i==0){
titleRows = titleRow3;
}else{
titleRows = sheet.createRow(rowNumber);
cell = titleRows.createCell(0);
cell.setCellStyle(contentCellStyle);
}
cell = titleRows.createCell(1);
cell.setCellValue(username);
cell.setCellStyle(contentCellStyle);
// 创建一个Map来跟踪每个星期几对应的已创建单元格的索引
Map<DayOfWeek, Integer> columnIndexMap = new HashMap<>();
for (DayOfWeek dayOfWeek : DayOfWeek.values()) {
columnIndexMap.put(dayOfWeek, -1);
}
//先设置该行的单元格格式
for (int j = a; j < b; j++) {
Cell timeCell = titleRows.createCell(j + 2);
timeCell.setCellStyle(contentCellStyle);
}
//填充工时内容
for (int j = a; j < b; j++) {
String actualityDuration = workTimesEntryList.size() < j + 1 ? "" : workTimesEntryList.get(j).getActualityDuration();
Date logDateTime = workTimesEntryList.size() < j + 1 ? null : workTimesEntryList.get(j).getLogDate();
// 默认列号,假设是周一
int columnIndex = j + 2;
LocalDate logDate = null;
try {
if (!Objects.isNull(logDateTime)) {
// 解析logDate为LocalDate对象
logDate = DateUtils.convertToLocalDate(logDateTime);
// 获取logDate对应的星期几
DayOfWeek dayOfWeek = logDate.getDayOfWeek();
// 根据星期几来设置列号
switch (dayOfWeek) {
case MONDAY:
columnIndex = j + 2;
break;
case TUESDAY:
columnIndex = j + 3;
break;
case WEDNESDAY:
columnIndex = j + 4;
break;
case THURSDAY:
columnIndex = j + 5;
break;
case FRIDAY:
columnIndex = j + 6;
break;
case SATURDAY:
columnIndex = j + 7;
break;
case SUNDAY:
columnIndex = j + 8;
break;
}
// 累加每周一到周日的工时
weekdayTotal[columnIndex - 2] = parseDuration(weekdayTotal[columnIndex - 2], actualityDuration);
// 检查是否已经创建过该列对应的单元格,如果未创建,则创建新的单元格并存储索引
int columnIndexMark = columnIndexMap.get(dayOfWeek);
if (columnIndexMark == -1) {
columnIndexMap.put(dayOfWeek, columnIndex);
}
Cell timeCell = titleRows.createCell(columnIndex);
timeCell.setCellValue(actualityDuration);
timeCell.setCellStyle(contentCellStyle);
}
} catch (DateTimeParseException e) {
// 异常处理:日期字符串格式不正确,可以抛出自定义异常或输出错误日志
throw new BusinessException("日期字符串格式不正确: " + logDate);
}
}
rowNumber++;
}
}
//添加合计行,计算项目负责人负责的所有项目每周一到周日的合计工时
Row sumRow = sheet.createRow(rowNumber);
Cell sumCell = sumRow.createCell(0);
sumCell.setCellValue("合计");
sumCell.setCellStyle(titleCellStyle);
sumCell = sumRow.createCell(1);
sumCell.setCellStyle(titleCellStyle);
sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber, 0, 1));
// 创建合计结果单元格
for (int i = 2; i <= 8; i++) {
Cell dayCell = sumRow.createCell(i);
dayCell.setCellValue(weekdayTotal[i - 2]);
dayCell.setCellStyle(titleCellStyle);
}
//周报总结汇总
LambdaQueryWrapper<StWeekLog> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.between(StWeekLog::getLogStartTime,weekStartDate,weekEndDate)
.in(StWeekLog::getProjectId,set);
List<StWeekLog> stWeekLogList = stWeekLogMapper.selectList(queryWrapper);
if(stWeekLogList.size()>0){
for (StWeekLog stWeekLog : stWeekLogList) {
if(StringUtils.isEmpty(weekData)){
weekData = stWeekLog.getWeekDesc();
}else{
weekData += "。\n"+stWeekLog.getWeekDesc();
}
}
}
rowNumber++;
//添加本周项目执行情况总结行
Row weekExecRow = sheet.createRow(rowNumber);
Cell weekExecCell = weekExecRow.createCell(0);
weekExecCell.setCellValue("本周项目执行情况总结");
weekExecCell.setCellStyle(titleCellStyle);
weekExecCell = weekExecRow.createCell(1);
weekExecCell.setCellValue(weekData);
weekExecCell.setCellStyle(contentCellStyle);
sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber + 2, 0, 0));
// 合并三行八列的单元格
sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber + 2, 1, 8));
// 创建执行情况总结内容(三行八列合并)
for (int n = 2; n <= 8; n++) {
Cell timeCell = weekExecRow.createCell(n);
timeCell.setCellStyle(contentCellStyle);
}
rowNumber++;
Row weekExecRow1 = sheet.createRow(rowNumber);
for (int n = 0; n <= 8; n++) {
Cell timeCell = weekExecRow1.createCell(n);
timeCell.setCellStyle(contentCellStyle);
}
rowNumber++;
Row weekExecRow2 = sheet.createRow(rowNumber);
for (int n = 0; n <= 8; n++) {
Cell timeCell = weekExecRow2.createCell(n);
timeCell.setCellStyle(contentCellStyle);
}
rowNumber++;
//添加两个空行
for (int i = 0; i < 2; i++) {
Row blankRow = sheet.createRow(rowNumber);
blankRow.createCell(0).setCellValue("");
// 合并空行的9列单元格
sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber, 0, 8));
rowNumber++; // 增加行号
}
}
}
try {
// 保存Excel文件
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment;filename=" +
new String(("周报_"+startDatetime+"-"+endDatetime).getBytes(), "ISO8859-1") + ".xlsx");
workbook.write(response.getOutputStream());
workbook.close();
response.getOutputStream().close();
} catch (IOException e) {
throw new BusinessException(e.getMessage());
}
}
4. 封装的函数
/**
* 空行风格
*
* @param workbook 工作簿
* @param blankCellStyle 空白单元格样式
* @author yangz
* @date 2023/07/20
*/
private static void blankStyle(Workbook workbook, CellStyle blankCellStyle) {
Font blankFont = workbook.createFont();
blankCellStyle.setFont(blankFont);
blankCellStyle.setBorderTop(BorderStyle.THICK);
blankCellStyle.setBorderBottom(BorderStyle.THICK);
}
/**
* 工作内容样式
*
* @param workbook 工作簿
* @param contentCellStyle 内容单元格样式
* @author yangz
* @date 2023/07/20
*/
private static void workContentStyle(Workbook workbook, CellStyle contentCellStyle) {
Font contentFont = workbook.createFont();
contentFont.setBold(false);
contentCellStyle.setFont(contentFont);
contentCellStyle.setWrapText(true);
contentCellStyle.setAlignment(HorizontalAlignment.CENTER);
contentCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
contentCellStyle.setBorderTop(BorderStyle.THICK);
contentCellStyle.setBorderBottom(BorderStyle.THICK);
contentCellStyle.setBorderLeft(BorderStyle.THICK);
contentCellStyle.setBorderRight(BorderStyle.THICK);
}
/**
* 标题单元格样式设置
*
* @param workbook 工作簿
* @param projectCellStyle 项目单元格样式
* @return {@link CellStyle }
* @author yangz
* @date 2023/07/19
*/
private static CellStyle cellStyleSet(Workbook workbook, CellStyle projectCellStyle) {
Font projectFont = workbook.createFont();
projectFont.setBold(true);
projectCellStyle.setFont(projectFont);
projectCellStyle.setWrapText(true);
projectCellStyle.setAlignment(HorizontalAlignment.CENTER);
projectCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
projectCellStyle.setBorderTop(BorderStyle.THICK);
projectCellStyle.setBorderBottom(BorderStyle.THICK);
projectCellStyle.setBorderLeft(BorderStyle.THICK);
projectCellStyle.setBorderRight(BorderStyle.THICK);
return projectCellStyle;
}
/**
* 校验时间
*
* @param startDate 开始日期时间
* @param endDate 结束日期时间
* @author yangz
* @date 2023/07/21
*/
public void validateDateRange(LocalDate startDate, LocalDate endDate) {
if (startDate == null || endDate == null || startDate.isAfter(endDate)) {
throw new IllegalArgumentException("开始日期和结束日期不能为空,且开始日期必须早于或等于结束日期");
}
long weekSpan = ChronoUnit.DAYS.between(startDate, endDate);
if (weekSpan < MIN_WEEK_SPAN) {
throw new IllegalArgumentException("开始日期和结束日期的时间跨度必须大于一周");
}
}
5.星期工具类Utils
import javafx.util.Pair;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
/**
* 星期实效
*
* @author yangz
* @date 2023/07/21
*/
public class DayOfWeekUtil {
/**
* 得到一天星期
*
* @param date 日期
* @return {@link String }
* @author yangz
* @date 2023/07/21
*/
public static String getDayOfWeek(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
switch (dayOfWeek) {
case MONDAY:
return "星期一";
case TUESDAY:
return "星期二";
case WEDNESDAY:
return "星期三";
case THURSDAY:
return "星期四";
case FRIDAY:
return "星期五";
case SATURDAY:
return "星期六";
case SUNDAY:
return "星期日";
default:
return "未知";
}
}
/**
* 周得到整数一天
*
* @param date 日期
* @return {@link Integer }
* @author yangz
* @date 2023/07/21
*/
public static Integer getIntDayOfWeek(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
switch (dayOfWeek) {
case MONDAY:
return 1;
case TUESDAY:
return 2;
case WEDNESDAY:
return 3;
case THURSDAY:
return 4;
case FRIDAY:
return 5;
case SATURDAY:
return 6;
case SUNDAY:
return 7;
default:
return null;
}
}
/**
* 分割时间一周
*
* @param startDatetime 开始日期时间
* @param endDatetime 结束日期时间
* @author yangz
* @date 2023/07/21
*/
public static List<Pair<LocalDate, LocalDate>> splitTimeByWeek(LocalDate startDatetime, LocalDate endDatetime) {
List<Pair<LocalDate, LocalDate>> weeks = new ArrayList<>();
LocalDate currentStart = startDatetime;
LocalDate currentEnd = startDatetime.plusDays(6);
while (!currentEnd.isAfter(endDatetime)) {
weeks.add(new Pair<>(currentStart, currentEnd));
currentStart = currentEnd.plusDays(1);
currentEnd = currentStart.plusDays(6);
}
if (!currentStart.isAfter(endDatetime)) {
weeks.add(new Pair<>(currentStart, endDatetime));
}
return weeks;
}
public static void main(String[] args) {
LocalDate date = LocalDate.of(2023, 7, 20);
String dayOfWeek = getDayOfWeek(date);
System.out.println("2023-07-20 是:" + dayOfWeek);
System.out.println("2023-07-20 是:" + getIntDayOfWeek(date));
}
}
6. 使用这个周报导出工具非常简单:
- 在Java代码中调用exportWeekReportToExcel方法,并传入正确的参数:HttpServletResponse、startDatetime和endDatetime。
- 完成数据的查询和生成Excel表格操作后,工具会自动将生成的Excel文件提供给用户下载。
7. excel结果展示
总结
这个周报导出工具使得周报的填写和管理变得更加高效和方便。它不仅减少了手动操作的工作量,还大幅度降低了数据处理和导出的错误率。希望这个工具能对您的项目管理和团队协作有所帮助!
结束语:学校里学孔孟的仁义礼智信,社会里学老子的厚黑学小人之道