序文
Java で書かれた強力な週報エクスポート ツールを紹介します。週報データを Excel にエクスポートして、管理や参照が容易になります。このツールはApache POI
ライブラリを使用して Excel ファイルを処理し、結果として得られる Excel ファイルの書式設定とスタイルが高品質になるようにします。
バックグラウンド
多くの企業は、日常業務において、プロジェクトの進捗状況やチーム メンバーの作業をより適切に追跡するために、定期的に週次レポートに記入して提出する必要があります。従来の週次レポートの手動入力には、時間がかかり、間違いが発生しやすいなど、いくつかの問題があります。効率と精度を向上させるために、Java を使用して週次レポート エクスポート ツールを作成します。このツールは、指定した時間範囲に従ってデータベースからデータを抽出し、週次レポートを Excel ファイルとしてエクスポートできます。
1.ツール機能
当社の週次レポート ツールには次の主要な機能があります。
- 指定した時間範囲内の週報データをエクスポートします。
- 週報データをプロジェクトリーダーごとにグループ化し、対応するExcelテーブルを生成します。
- 各プロジェクト リーダーの月曜日から日曜日までのプロジェクトの勤務時間を要約します。
- 週次レポートの内容をより美しくするために、エクスポートされたデータに特定の形式とスタイルの制御を提供します。
- 合計時間とプロジェクトの概要情報を自動的に計算します。
2. 実装の詳細
1.パラメータの検証
まず、受信した日付パラメータを検証して、日付範囲の正当性を確認します。
2. データの取得と処理
データベースクエリを通じてすべてのプロジェクト情報とメンバー情報を取得し、指定された時間範囲に従って各プロジェクトの作業時間の詳細を取得します。次に、後で Excel テーブルを生成できるように、データをグループ化し、プロジェクト リーダーに従ってデータをグループ化します。
3. Excelテーブルの生成
次に、Apache POI ライブラリを使用して Excel テーブルを生成します。表の読みやすさと美しさを確保するために、ヘッダー行、スタイル、結合セルなどを設定します。
4. データ入力
データベースからクエリしたデータを Excel シートに入力します。各プロジェクト リーダーが担当するプロジェクトの数に応じて、セルが動的に結合され、プロジェクト情報の表示が容易になります。
5. 総労働時間の計算
月曜日から日曜日までの労働時間データをテーブルに追加した後、各プロジェクト リーダーが担当するすべてのプロジェクトの月曜日から日曜日までの合計労働時間を計算して入力します。
6. 概要概要
最後に、各プロジェクトリーダーが担当する全プロジェクトの概要情報を集計し、Excelシートに追加しました。
7. ファイルのエクスポート
最後のステップでは、生成された Excel ファイルを HTTP 応答を通じてダウンロードできるようにユーザーに提供します。
3. 実践開発
1.メイブン
<!--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 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. 週の使用量
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結果表示
要約する
この週次レポート レポート ツールを使用すると、週次レポートの入力と管理がより効率的かつ便利になります。手動操作の負担が軽減されるだけでなく、データ処理やエクスポートのエラー率も大幅に削減されます。このツールがあなたのプロジェクト管理とチームコラボレーションに役立つことを願っています。
結論:学校では孔子や孟子の仁・義・礼・知・信を学び、社会では老子から悪人の道を学べ