根据用户ID导出用户
This commit is contained in:
@@ -10,7 +10,10 @@ import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@@ -140,4 +143,14 @@ public class LbDepartmentUserController {
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package com.rj.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.rj.entity.LbDepartmentUser;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface ILbDepartmentUserService extends IService<LbDepartmentUser> {
|
||||
@@ -41,4 +44,9 @@ public interface ILbDepartmentUserService extends IService<LbDepartmentUser> {
|
||||
* 按租户查询部门用户并构建父子树(parent_id 对应 lb_user.pid)。
|
||||
*/
|
||||
Map<String, Object> getDepartmentUserTree(String tenantId, Integer isActive, LocalDate joinDate, LocalDate lastTradeDate);
|
||||
|
||||
/**
|
||||
* 按租户与用户 ID 列表导出部门用户及订单汇总 Excel。
|
||||
*/
|
||||
void exportUsersExcel(String tenantId, List<String> userIds, HttpServletResponse response);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,32 @@ import com.rj.mapper.LbOrderRowMapper;
|
||||
import com.rj.mapper.LbPurchaseApplyMapper;
|
||||
import com.rj.mapper.LbUserMapper;
|
||||
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.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Service;
|
||||
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.Comparator;
|
||||
import java.util.HashSet;
|
||||
@@ -32,6 +54,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMapper, LbDepartmentUser>
|
||||
implements ILbDepartmentUserService {
|
||||
@@ -892,4 +915,260 @@ public class LbDepartmentUserServiceImpl extends ServiceImpl<LbDepartmentUserMap
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user