根据用户ID导出用户

This commit is contained in:
2026-05-20 18:14:50 +08:00
parent 81b577ee19
commit 6d0cfcdd84
3 changed files with 300 additions and 0 deletions

View File

@@ -10,7 +10,10 @@ import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
import java.util.Map; import java.util.Map;
@RestController @RestController
@@ -140,4 +143,14 @@ public class LbDepartmentUserController {
} }
return ResponseEntity.badRequest().body(result); return ResponseEntity.badRequest().body(result);
} }
@PostMapping("/exportUsers")
@Operation(summary = "导出指定用户Excel",
description = "按租户ID与用户ID列表导出用户ID、用户名、手机号、加入时间、最早/最晚buy_time、累计交易金额")
public void exportUsers(
@Parameter(description = "租户ID", required = true) @RequestParam String tenantId,
@Parameter(description = "用户ID列表", required = true) @RequestBody List<String> userIds,
HttpServletResponse response) {
lbDepartmentUserService.exportUsersExcel(tenantId, userIds, response);
}
} }

View File

@@ -3,7 +3,10 @@ package com.rj.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.rj.entity.LbDepartmentUser; import com.rj.entity.LbDepartmentUser;
import jakarta.servlet.http.HttpServletResponse;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
import java.util.Map; import java.util.Map;
public interface ILbDepartmentUserService extends IService<LbDepartmentUser> { public interface ILbDepartmentUserService extends IService<LbDepartmentUser> {
@@ -41,4 +44,9 @@ public interface ILbDepartmentUserService extends IService<LbDepartmentUser> {
* 按租户查询部门用户并构建父子树parent_id 对应 lb_user.pid * 按租户查询部门用户并构建父子树parent_id 对应 lb_user.pid
*/ */
Map<String, Object> getDepartmentUserTree(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate); Map<String, Object> getDepartmentUserTree(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate);
/**
* 按租户与用户 ID 列表导出部门用户及订单汇总 Excel。
*/
void exportUsersExcel(String tenantId, List<String> userIds, HttpServletResponse response);
} }

View File

@@ -14,10 +14,32 @@ import com.rj.mapper.LbOrderRowMapper;
import com.rj.mapper.LbPurchaseApplyMapper; import com.rj.mapper.LbPurchaseApplyMapper;
import com.rj.mapper.LbUserMapper; import com.rj.mapper.LbUserMapper;
import com.rj.service.ILbDepartmentUserService; import com.rj.service.ILbDepartmentUserService;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.awt.Color;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
@@ -32,6 +54,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@Slf4j
@Service @Service
public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMapper, LbDepartmentUser> public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMapper, LbDepartmentUser>
implements ILbDepartmentUserService { implements ILbDepartmentUserService {
@@ -892,4 +915,260 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMap
entity.setLastBuyAmt(trade.getDailyBuyAmt()); entity.setLastBuyAmt(trade.getDailyBuyAmt());
} }
} }
@Override
public void exportUsersExcel(String tenantId, List<String> userIds, HttpServletResponse response) {
try {
if (tenantId == null || tenantId.trim().isEmpty()) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "tenantId不能为空");
return;
}
if (userIds == null || userIds.isEmpty()) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "用户ID列表不能为空");
return;
}
String tenantIdTrim = tenantId.trim();
List<String> orderedUserIds = new ArrayList<>();
Set<String> seen = new HashSet<>();
for (String rawId : userIds) {
if (rawId == null) {
continue;
}
String userId = rawId.trim();
if (userId.isEmpty() || !seen.add(userId)) {
continue;
}
orderedUserIds.add(userId);
}
if (orderedUserIds.isEmpty()) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "用户ID列表不能为空");
return;
}
Map<String, LbDepartmentUser> deptUserByUserId = new HashMap<>();
List<LbDepartmentUser> deptUsers = this.list(
new LambdaQueryWrapper<LbDepartmentUser>()
.eq(LbDepartmentUser::getTenantId, tenantIdTrim)
.in(LbDepartmentUser::getUserId, orderedUserIds));
if (deptUsers != null) {
for (LbDepartmentUser deptUser : deptUsers) {
if (deptUser.getUserId() != null && !deptUser.getUserId().trim().isEmpty()) {
deptUserByUserId.put(deptUser.getUserId().trim(), deptUser);
}
}
}
List<Long> buyerIds = new ArrayList<>();
Map<String, Long> buyerIdByUserId = new HashMap<>();
for (String userId : orderedUserIds) {
try {
Long buyerId = Long.parseLong(userId);
buyerIds.add(buyerId);
buyerIdByUserId.put(userId, buyerId);
} catch (NumberFormatException ignored) {
// 非数字 userId 无法关联订单,仍导出用户基础信息
}
}
Map<String, OrderBuyStats> statsByUserId = new HashMap<>();
if (!buyerIds.isEmpty()) {
List<LbOrderRow> orderRows = lbOrderRowMapper.selectList(
new LambdaQueryWrapper<LbOrderRow>()
.eq(LbOrderRow::getTenantId, tenantIdTrim)
.eq(LbOrderRow::getDataType, ORDER_DATA_TYPE_DETAIL)
.in(LbOrderRow::getBuyerId, buyerIds)
.isNotNull(LbOrderRow::getPayTime));
if (orderRows != null) {
Map<Long, String> userIdByBuyerId = new HashMap<>();
for (Map.Entry<String, Long> entry : buyerIdByUserId.entrySet()) {
userIdByBuyerId.put(entry.getValue(), entry.getKey());
}
for (LbOrderRow row : orderRows) {
if (!isPaidOrderRow(row) || row.getBuyerId() == null) {
continue;
}
String userId = userIdByBuyerId.get(row.getBuyerId());
if (userId == null) {
continue;
}
statsByUserId
.computeIfAbsent(userId, k -> new OrderBuyStats())
.accumulate(row);
}
}
}
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("部门用户");
CellStyle headerStyle = createExportHeaderStyle(workbook);
CellStyle dataStyle = createExportDataStyle(workbook);
String[] headers = {
"用户ID", "用户名", "手机号", "加入时间",
"最早buy_time", "最晚buy_time", "累计交易金额buy_amt"
};
Row headerRow = sheet.createRow(0);
headerRow.setHeightInPoints(22);
for (int i = 0; i < headers.length; i++) {
createExportCell(headerRow, i, headers[i], headerStyle);
}
int rowIndex = 1;
for (String userId : orderedUserIds) {
LbDepartmentUser deptUser = deptUserByUserId.get(userId);
OrderBuyStats stats = statsByUserId.get(userId);
Row row = sheet.createRow(rowIndex++);
row.setHeightInPoints(20);
createExportCell(row, 0, userId, dataStyle);
createExportCell(row, 1, deptUser == null ? "" : nullToEmpty(deptUser.getName()), dataStyle);
createExportCell(row, 2, deptUser == null ? "" : nullToEmpty(deptUser.getPhone()), dataStyle);
createExportCell(row, 3, formatJoinDate(deptUser == null ? null : deptUser.getJoinDate()), dataStyle);
createExportCell(row, 4, stats == null ? "" : nullToEmpty(stats.earliestBuyTime), dataStyle);
createExportCell(row, 5, stats == null ? "" : nullToEmpty(stats.latestBuyTime), dataStyle);
createExportCell(row, 6, stats == null ? "" : formatAmount(stats.totalBuyAmt), dataStyle);
}
adjustExportColumnWidths(sheet, headers);
String filename = "部门用户导出_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".xlsx";
String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded);
try (ServletOutputStream os = response.getOutputStream()) {
workbook.write(os);
os.flush();
}
}
} catch (Exception e) {
log.error("部门用户导出异常", e);
try {
response.reset();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType("text/plain;charset=UTF-8");
response.getWriter().write("导出异常:" + e.getMessage());
} catch (Exception ignored) {
// ignore
}
}
}
private static final class OrderBuyStats {
private String earliestBuyTime;
private LocalDateTime earliestBuyDateTime;
private String latestBuyTime;
private LocalDateTime latestBuyDateTime;
private BigDecimal totalBuyAmt = BigDecimal.ZERO;
void accumulate(LbOrderRow row) {
if (row.getTotalMoney() != null) {
totalBuyAmt = totalBuyAmt.add(row.getTotalMoney());
}
String buyTimeText = row.getBuyTime() == null ? null : row.getBuyTime().trim();
if (buyTimeText == null || buyTimeText.isEmpty()) {
return;
}
LocalDateTime buyDateTime = parseOrderTime(buyTimeText);
if (buyDateTime == null) {
return;
}
if (earliestBuyDateTime == null || buyDateTime.isBefore(earliestBuyDateTime)) {
earliestBuyDateTime = buyDateTime;
earliestBuyTime = buyTimeText;
}
if (latestBuyDateTime == null || buyDateTime.isAfter(latestBuyDateTime)) {
latestBuyDateTime = buyDateTime;
latestBuyTime = buyTimeText;
}
}
}
private static String formatJoinDate(LocalDate joinDate) {
return joinDate == null ? "" : joinDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
}
private static String formatAmount(BigDecimal amount) {
if (amount == null) {
return "";
}
return amount.stripTrailingZeros().toPlainString();
}
private static String nullToEmpty(String value) {
return value == null ? "" : value;
}
private static CellStyle createExportHeaderStyle(Workbook workbook) {
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
((XSSFCellStyle) headerStyle).setFillForegroundColor(new XSSFColor(new Color(255, 217, 102), null));
headerStyle.setBorderTop(BorderStyle.THIN);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
return headerStyle;
}
private static CellStyle createExportDataStyle(Workbook workbook) {
CellStyle dataStyle = workbook.createCellStyle();
dataStyle.setAlignment(HorizontalAlignment.CENTER);
dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
dataStyle.setBorderTop(BorderStyle.THIN);
dataStyle.setBorderBottom(BorderStyle.THIN);
dataStyle.setBorderLeft(BorderStyle.THIN);
dataStyle.setBorderRight(BorderStyle.THIN);
return dataStyle;
}
private static void createExportCell(Row row, int col, String text, CellStyle style) {
Cell cell = row.createCell(col);
cell.setCellValue(text);
cell.setCellStyle(style);
}
private static void adjustExportColumnWidths(Sheet sheet, String[] headers) {
int columnCount = headers.length;
int[] maxWidths = new int[columnCount];
for (int i = 0; i < columnCount; i++) {
maxWidths[i] = calcExportDisplayWidth(headers[i]);
}
for (int r = 1; r <= sheet.getLastRowNum(); r++) {
Row row = sheet.getRow(r);
if (row == null) {
continue;
}
for (int c = 0; c < columnCount; c++) {
Cell cell = row.getCell(c);
if (cell != null && cell.getCellType() == CellType.STRING) {
maxWidths[c] = Math.max(maxWidths[c], calcExportDisplayWidth(cell.getStringCellValue()));
}
}
}
for (int c = 0; c < columnCount; c++) {
int width = (maxWidths[c] + 2) * 256;
sheet.setColumnWidth(c, Math.min(width, 255 * 256));
}
}
private static int calcExportDisplayWidth(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
int width = 0;
for (char ch : text.toCharArray()) {
width += ch > 127 ? 2 : 1;
}
return width;
}
} }