v1.0.3 #7

Merged
timi merged 7 commits from dev into master 2026-07-31 17:13:38 +00:00
13 changed files with 436 additions and 69 deletions
Showing only changes of commit 844c5381c4 - Show all commits
+7 -2
View File
@@ -11,7 +11,7 @@
<groupId>com.imyeyu.timiserverapi</groupId> <groupId>com.imyeyu.timiserverapi</groupId>
<artifactId>TimiServerAPI</artifactId> <artifactId>TimiServerAPI</artifactId>
<version>1.0.1</version> <version>1.0.3</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TimiServerAPI</name> <name>TimiServerAPI</name>
<description>imyeyu.com API</description> <description>imyeyu.com API</description>
@@ -178,7 +178,7 @@
<dependency> <dependency>
<groupId>com.imyeyu.network</groupId> <groupId>com.imyeyu.network</groupId>
<artifactId>timi-network</artifactId> <artifactId>timi-network</artifactId>
<version>0.0.12</version> <version>0.0.13</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.imyeyu.lang</groupId> <groupId>com.imyeyu.lang</groupId>
@@ -323,6 +323,11 @@
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>
<version>1.18.1</version> <version>1.18.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.4.1</version>
</dependency>
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
@@ -17,9 +17,11 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq;
import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq; import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq;
import com.imyeyu.api.modules.gao.vo.GaoQuickRegistrationEventRecordReq; import com.imyeyu.api.modules.gao.vo.GaoQuickRegistrationEventRecordReq;
import com.imyeyu.spring.annotation.AOPLog; import com.imyeyu.spring.annotation.AOPLog;
import com.imyeyu.spring.annotation.IgnoreGlobalReturn;
import com.imyeyu.spring.annotation.RequestRateLimit; import com.imyeyu.spring.annotation.RequestRateLimit;
import com.imyeyu.spring.bean.Page; import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult; import com.imyeyu.spring.bean.PageResult;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -27,6 +29,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@@ -67,6 +71,26 @@ public class GaoCustomerController {
return service.search(req); return service.search(req);
} }
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ)
@PostMapping("/search/page")
public PageResult<GaoCustomer> searchPage(@RequestBody Page<GaoCustomerSearchReq> page) {
return service.searchPage(page);
}
@AOPLog
@RequestRateLimit
@IgnoreGlobalReturn
@RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ)
@PostMapping("/export")
public void export(@RequestBody GaoCustomerSearchReq req, HttpServletResponse resp) throws IOException {
String fileName = URLEncoder.encode("客户列表.xlsx", "UTF-8").replace("+", "%20");
resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
resp.setHeader("Content-Disposition", "attachment; filename=\"gao-customer.xlsx\"; filename*=UTF-8''" + fileName);
resp.getOutputStream().write(service.exportSearchExcel(req));
}
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ) @RequireGaoPermission(GaoPermissionCode.CUSTOMER_READ)
@@ -95,8 +119,9 @@ public class GaoCustomerController {
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.CUSTOMER_CREATE) @RequireGaoPermission(GaoPermissionCode.CUSTOMER_CREATE)
@PostMapping("/create") @PostMapping("/create")
public void create(@RequestBody GaoCustomer customer) { public GaoCustomer create(@RequestBody GaoCustomer customer) {
service.create(customer); service.create(customer);
return customer;
} }
@AOPLog @AOPLog
@@ -29,7 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.net.URLEncoder;
import java.util.List; import java.util.List;
/// ///
@@ -53,6 +53,14 @@ public class GaoRegistrationEventRecordController {
return service.page(page); return service.page(page);
} }
@AOPLog
@RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ)
@PostMapping("/range/page")
public PageResult<GaoRegistrationEventRecord> rangePage(@RequestBody Page<GaoRegistrationEventRecordRangeReq> page) {
return service.pageByRange(page);
}
@AOPLog @AOPLog
@RequestRateLimit @RequestRateLimit
@RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ)
@@ -106,10 +114,10 @@ public class GaoRegistrationEventRecordController {
@RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ) @RequireGaoPermission(GaoPermissionCode.REGISTRATION_EVENT_RECORD_READ)
@PostMapping("/range/export") @PostMapping("/range/export")
public void rangeExport(@RequestBody GaoRegistrationEventRecordRangeReq req, HttpServletResponse resp) throws IOException { public void rangeExport(@RequestBody GaoRegistrationEventRecordRangeReq req, HttpServletResponse resp) throws IOException {
List<GaoRegistrationEventRecord> list = service.listByRange(req); String fileName = URLEncoder.encode("登记事件记录.xlsx", "UTF-8").replace("+", "%20");
resp.setContentType("application/vnd.ms-excel;charset=UTF-8"); resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
resp.setHeader("Content-Disposition", "attachment; filename=\"gao-registration-event-record.csv\""); resp.setHeader("Content-Disposition", "attachment; filename=\"gao-registration-event-record.xlsx\"; filename*=UTF-8''" + fileName);
resp.getOutputStream().write(buildCsv(list).getBytes(StandardCharsets.UTF_8)); resp.getOutputStream().write(service.exportRangeExcel(req));
} }
@AOPLog @AOPLog
@@ -163,30 +171,4 @@ public class GaoRegistrationEventRecordController {
public List<GaoRegistrationEventRecordDailyStatView> allCustomerRegistrationEventDailyStat(@RequestBody GaoRegistrationEventRecordTrendStatReq req) { public List<GaoRegistrationEventRecordDailyStatView> allCustomerRegistrationEventDailyStat(@RequestBody GaoRegistrationEventRecordTrendStatReq req) {
return service.statDailyByRegistrationEvent(req); return service.statDailyByRegistrationEvent(req);
} }
private String buildCsv(List<GaoRegistrationEventRecord> list) {
StringBuilder builder = new StringBuilder();
builder.append((char) 65279);
builder.append("客户编码,客户姓名,登记事件,登记时间,登记人用户 ID,备注\n");
for (GaoRegistrationEventRecord record : list) {
builder.append(csv(record.getCustomer() == null ? null : record.getCustomer().getCode())).append(',');
builder.append(csv(record.getCustomer() == null ? null : record.getCustomer().getName())).append(',');
builder.append(csv(record.getRegistrationEvent() == null ? null : record.getRegistrationEvent().getName())).append(',');
builder.append(csv(record.getRegisteredAt())).append(',');
builder.append(csv(record.getOperatorUserId())).append(',');
builder.append(csv(record.getRemark())).append('\n');
}
return builder.toString();
}
private String csv(Object value) {
if (value == null) {
return "";
}
String text = value.toString();
if (!text.contains(",") && !text.contains("\"") && !text.contains("\n") && !text.contains("\r")) {
return text;
}
return "\"%s\"".formatted(text.replace("\"", "\"\""));
}
} }
@@ -20,6 +20,10 @@ public interface GaoCustomerMapper extends BaseMapper<GaoCustomer, String> {
List<GaoCustomer> search(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId); List<GaoCustomer> search(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId);
Long countSearch(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId);
List<GaoCustomer> searchPage(@Param("keyword") String keyword, @Param("introducerCustomerId") String introducerCustomerId, @Param("offset") Long offset, @Param("size") Long size);
List<GaoCustomer> selectInvitedList(@Param("introducerCustomerId") String introducerCustomerId); List<GaoCustomer> selectInvitedList(@Param("introducerCustomerId") String introducerCustomerId);
Long countInvited(@Param("introducerCustomerId") String introducerCustomerId); Long countInvited(@Param("introducerCustomerId") String introducerCustomerId);
@@ -103,4 +103,18 @@ public interface GaoRegistrationEventRecordMapper extends BaseMapper<GaoRegistra
/// @param req 范围查询请求 /// @param req 范围查询请求
/// @return 登记事件记录列表 /// @return 登记事件记录列表
List<GaoRegistrationEventRecord> selectByRange(@Param("req") GaoRegistrationEventRecordRangeReq req); List<GaoRegistrationEventRecord> selectByRange(@Param("req") GaoRegistrationEventRecordRangeReq req);
/// 按任意时间范围统计登记事件记录
///
/// @param req 范围查询请求
/// @return 登记事件记录数
Long countByRange(@Param("req") GaoRegistrationEventRecordRangeReq req);
/// 按任意时间范围分页查询登记事件记录
///
/// @param req 范围查询请求
/// @param offset 偏移量
/// @param size 每页条数
/// @return 登记事件记录列表
List<GaoRegistrationEventRecord> selectPageByRange(@Param("req") GaoRegistrationEventRecordRangeReq req, @Param("offset") Long offset, @Param("size") Long size);
} }
@@ -5,8 +5,11 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStatView;
import com.imyeyu.api.modules.gao.vo.GaoCustomerInviteUpdateReq; import com.imyeyu.api.modules.gao.vo.GaoCustomerInviteUpdateReq;
import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq; import com.imyeyu.api.modules.gao.vo.GaoCustomerSearchReq;
import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq; import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService; import com.imyeyu.spring.service.BaseService;
import java.io.IOException;
import java.util.List; import java.util.List;
/// ///
@@ -40,6 +43,18 @@ public interface GaoCustomerService extends BaseService<GaoCustomer, String> {
/// @return 客户列表 /// @return 客户列表
List<GaoCustomer> search(GaoCustomerSearchReq req); List<GaoCustomer> search(GaoCustomerSearchReq req);
/// 分页搜索客户
///
/// @param page 分页搜索请求
/// @return 客户分页结果
PageResult<GaoCustomer> searchPage(Page<GaoCustomerSearchReq> page);
/// 按搜索条件导出客户 Excel
///
/// @param req 搜索请求
/// @return xlsx 文件字节
byte[] exportSearchExcel(GaoCustomerSearchReq req) throws IOException;
/// 查询邀请的客户 /// 查询邀请的客户
/// ///
/// @param introducerCustomerId 介绍人客户 ID /// @param introducerCustomerId 介绍人客户 ID
@@ -10,8 +10,11 @@ import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordPeriodReq;
import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq; import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordRangeReq;
import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordStatReq; import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordStatReq;
import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordTrendStatReq; import com.imyeyu.api.modules.gao.vo.GaoRegistrationEventRecordTrendStatReq;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.service.BaseService; import com.imyeyu.spring.service.BaseService;
import java.io.IOException;
import java.util.List; import java.util.List;
/// ///
@@ -39,6 +42,18 @@ public interface GaoRegistrationEventRecordService extends BaseService<GaoRegist
/// @return 登记事件记录列表 /// @return 登记事件记录列表
List<GaoRegistrationEventRecord> listByRange(GaoRegistrationEventRecordRangeReq req); List<GaoRegistrationEventRecord> listByRange(GaoRegistrationEventRecordRangeReq req);
/// 按任意时间范围分页查询登记事件记录
///
/// @param page 分页参数
/// @return 登记事件记录分页结果
PageResult<GaoRegistrationEventRecord> pageByRange(Page<GaoRegistrationEventRecordRangeReq> page);
/// 按任意时间范围导出登记事件记录 Excel
///
/// @param req 范围查询请求
/// @return xlsx 文件字节
byte[] exportRangeExcel(GaoRegistrationEventRecordRangeReq req) throws IOException;
/// 按周期查询登记事件记录 /// 按周期查询登记事件记录
/// ///
/// @param req 周期请求 /// @param req 周期请求
@@ -14,12 +14,22 @@ import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStatReq;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode; import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
@@ -40,6 +50,7 @@ import java.util.Map;
public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustomer, String> implements GaoCustomerService { public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustomer, String> implements GaoCustomerService {
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final DateTimeFormatter EXPORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
private final GaoCustomerMapper mapper; private final GaoCustomerMapper mapper;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
@@ -104,14 +115,75 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
@Override @Override
public List<GaoCustomer> search(GaoCustomerSearchReq req) { public List<GaoCustomer> search(GaoCustomerSearchReq req) {
if (req == null) { req = resolveSearchReq(req);
req = new GaoCustomerSearchReq();
}
List<GaoCustomer> list = mapper.search(req.getKeyword(), req.getIntroducerCustomerId()); List<GaoCustomer> list = mapper.search(req.getKeyword(), req.getIntroducerCustomerId());
list.forEach(this::loadTransient); list.forEach(this::loadTransient);
return list; return list;
} }
@Override
public PageResult<GaoCustomer> searchPage(Page<GaoCustomerSearchReq> page) {
TimiException.required(page, "not found page");
TimiException.required(page.getIndex(), "not found page.index");
TimiException.required(page.getSize(), "not found page.size");
TimiException.requiredTrue(0 <= page.getIndex(), "page.index lt 0");
TimiException.requiredTrue(0 < page.getSize(), "page.size lte 0");
GaoCustomerSearchReq req = resolveSearchReq(page.getEqualsExample());
Long total = TimiJava.defaultIfNull(mapper.countSearch(req.getKeyword(), req.getIntroducerCustomerId()), 0L);
List<GaoCustomer> list = mapper.searchPage(req.getKeyword(), req.getIntroducerCustomerId(), page.getIndex() * page.getSize(), page.getSize());
list.forEach(this::loadTransient);
PageResult<GaoCustomer> result = new PageResult<>();
result.setTotal(total);
result.setList(list);
return result;
}
@Override
public byte[] exportSearchExcel(GaoCustomerSearchReq req) throws IOException {
List<GaoCustomer> list = search(req);
try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("客户列表");
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 14);
titleStyle.setFont(titleFont);
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
appendRow(sheet, 0, titleStyle, "客户列表导出");
appendRow(sheet, 1, null, "生成时间", EXPORT_TIME_FORMATTER.format(Instant.now()));
appendRow(sheet, 2, null, "关键词", req == null || req.getKeyword() == null || req.getKeyword().isBlank() ? "全部" : req.getKeyword().trim());
appendRow(sheet, 3, null, "记录总数", String.valueOf(list.size()));
appendRow(sheet, 5, headerStyle, "客户编码", "姓名", "性别", "年龄", "电话", "地址", "病症", "备注", "邀请人", "邀请客户数", "创建时间");
int rowIndex = 6;
for (GaoCustomer customer : list) {
appendRow(sheet, rowIndex, null,
text(customer.getCode()),
text(customer.getName()),
formatGender(customer),
customer.getAge() == null ? "" : String.valueOf(customer.getAge()),
text(customer.getTelephone()),
text(customer.getAddress()),
text(customer.getDisease()),
text(customer.getRemark()),
customer.getIntroducerCustomer() == null ? "" : text(customer.getIntroducerCustomer().getName()),
customer.getInvitedCount() == null ? "0" : String.valueOf(customer.getInvitedCount()),
formatExportTime(customer.getCreatedAt())
);
rowIndex++;
}
for (int index = 0; index < 11; index++) {
sheet.autoSizeColumn(index);
sheet.setColumnWidth(index, Math.min(Math.max(sheet.getColumnWidth(index), 10 * 256), 36 * 256));
}
workbook.write(output);
return output.toByteArray();
}
}
@Override @Override
public List<GaoCustomer> listInvited(String introducerCustomerId) { public List<GaoCustomer> listInvited(String introducerCustomerId) {
TimiException.required(introducerCustomerId, "not found introducerCustomerId"); TimiException.required(introducerCustomerId, "not found introducerCustomerId");
@@ -176,16 +248,21 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
TimiException.requiredTrue(days <= 366, "daily stat range too large"); TimiException.requiredTrue(days <= 366, "daily stat range too large");
} }
private GaoCustomerSearchReq resolveSearchReq(GaoCustomerSearchReq req) {
return req == null ? new GaoCustomerSearchReq() : req;
}
private void checkEntity(GaoCustomer entity, boolean requireId) { private void checkEntity(GaoCustomer entity, boolean requireId) {
TimiException.required(entity, "not found customer"); TimiException.required(entity, "not found customer");
if (requireId) { if (requireId) {
TimiException.required(entity.getId(), "not found customer.id"); TimiException.required(entity.getId(), "not found customer.id");
} }
TimiException.required(entity.getCode(), "not found customer.code");
TimiException.required(entity.getName(), "not found customer.name"); TimiException.required(entity.getName(), "not found customer.name");
GaoCustomer dbCustomer = mapper.selectByCode(entity.getCode()); if (TimiJava.isNotEmpty(entity.getCode())) {
if (dbCustomer != null && !dbCustomer.getId().equals(entity.getId())) { GaoCustomer dbCustomer = mapper.selectByCode(entity.getCode());
throw new TimiException(TimiCode.ARG_BAD).msgKey("客户编码已存在"); if (dbCustomer != null && !dbCustomer.getId().equals(entity.getId())) {
throw new TimiException(TimiCode.ARG_BAD).msgKey("客户编码已存在");
}
} }
if (TimiJava.isNotEmpty(entity.getIntroducerCustomerId())) { if (TimiJava.isNotEmpty(entity.getIntroducerCustomerId())) {
TimiException.requiredTrue(!entity.getIntroducerCustomerId().equals(entity.getId()), "customer.introducerCustomerId invalid"); TimiException.requiredTrue(!entity.getIntroducerCustomerId().equals(entity.getId()), "customer.introducerCustomerId invalid");
@@ -240,4 +317,35 @@ public class GaoCustomerServiceImplement extends AbstractEntityService<GaoCustom
} }
return list; return list;
} }
private void appendRow(Sheet sheet, int rowIndex, CellStyle style, String... values) {
Row row = sheet.createRow(rowIndex);
for (int index = 0; index < values.length; index++) {
row.createCell(index).setCellValue(values[index]);
if (style != null) {
row.getCell(index).setCellStyle(style);
}
}
}
private String formatGender(GaoCustomer customer) {
if (customer.getGender() == null) {
return "";
}
return switch (customer.getGender()) {
case MALE -> "";
case FEMALE -> "";
};
}
private String formatExportTime(Long value) {
if (value == null) {
return "";
}
return EXPORT_TIME_FORMATTER.format(Instant.ofEpochMilli(value));
}
private String text(Object value) {
return value == null ? "" : value.toString();
}
} }
@@ -26,13 +26,23 @@ import com.imyeyu.api.modules.user.service.UserLoginService;
import com.imyeyu.api.modules.user.service.UserService; import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.java.TimiJava; import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiException; import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.spring.bean.Page;
import com.imyeyu.spring.bean.PageResult;
import com.imyeyu.spring.mapper.BaseMapper; import com.imyeyu.spring.mapper.BaseMapper;
import com.imyeyu.spring.service.AbstractEntityService; import com.imyeyu.spring.service.AbstractEntityService;
import com.imyeyu.utils.Time; import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
@@ -43,6 +53,8 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
/// ///
/// 登记事件记录服务实现 /// 登记事件记录服务实现
@@ -54,6 +66,7 @@ import java.util.Map;
public class GaoRegistrationEventRecordServiceImplement extends AbstractEntityService<GaoRegistrationEventRecord, String> implements GaoRegistrationEventRecordService { public class GaoRegistrationEventRecordServiceImplement extends AbstractEntityService<GaoRegistrationEventRecord, String> implements GaoRegistrationEventRecordService {
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final DateTimeFormatter EXPORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
private final GaoRegistrationEventRecordMapper mapper; private final GaoRegistrationEventRecordMapper mapper;
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
@@ -148,6 +161,67 @@ public class GaoRegistrationEventRecordServiceImplement extends AbstractEntitySe
return list; return list;
} }
@Override
public PageResult<GaoRegistrationEventRecord> pageByRange(Page<GaoRegistrationEventRecordRangeReq> page) {
TimiException.required(page, "not found page");
TimiException.required(page.getIndex(), "not found page.index");
TimiException.required(page.getSize(), "not found page.size");
TimiException.requiredTrue(0 <= page.getIndex(), "page.index lt 0");
TimiException.requiredTrue(0 < page.getSize(), "page.size lte 0");
GaoRegistrationEventRecordRangeReq req = TimiJava.defaultIfNull(page.getEqualsExample(), new GaoRegistrationEventRecordRangeReq());
checkStatRange(req.getBeginRegisteredAt(), req.getEndRegisteredAt());
Long total = TimiJava.defaultIfNull(mapper.countByRange(req), 0L);
List<GaoRegistrationEventRecord> list = mapper.selectPageByRange(req, page.getIndex() * page.getSize(), page.getSize());
list.forEach(this::loadTransient);
PageResult<GaoRegistrationEventRecord> result = new PageResult<>();
result.setTotal(total);
result.setList(list);
return result;
}
@Override
public byte[] exportRangeExcel(GaoRegistrationEventRecordRangeReq req) throws IOException {
List<GaoRegistrationEventRecord> list = listByRange(req);
try (Workbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
Sheet sheet = workbook.createSheet("登记事件记录");
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 14);
titleStyle.setFont(titleFont);
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
appendRow(sheet, 0, titleStyle, "登记事件记录导出");
appendRow(sheet, 1, null, "生成时间", EXPORT_TIME_FORMATTER.format(Instant.now()));
appendRow(sheet, 2, null, "时间范围", "%s ~ %s".formatted(formatExportTime(req.getBeginRegisteredAt()), formatExportTime(req.getEndRegisteredAt())));
appendRow(sheet, 3, null, "事件筛选", buildEventFilterText(req));
appendRow(sheet, 4, null, "关键词", req.getKeyword() == null || req.getKeyword().isBlank() ? "全部" : req.getKeyword().trim());
appendRow(sheet, 5, null, "记录总数", String.valueOf(list.size()));
appendRow(sheet, 7, headerStyle, "客户编码", "客户姓名", "登记事件", "登记时间", "登记人", "备注");
int rowIndex = 8;
for (GaoRegistrationEventRecord record : list) {
appendRow(sheet, rowIndex, null,
record.getCustomer() == null ? "" : text(record.getCustomer().getCode()),
record.getCustomer() == null ? "" : text(record.getCustomer().getName()),
record.getRegistrationEvent() == null ? "" : text(record.getRegistrationEvent().getName()),
formatExportTime(record.getRegisteredAt()),
getOperatorName(record),
text(record.getRemark())
);
rowIndex++;
}
for (int index = 0; index < 6; index++) {
sheet.autoSizeColumn(index);
sheet.setColumnWidth(index, Math.min(Math.max(sheet.getColumnWidth(index), 12 * 256), 40 * 256));
}
workbook.write(output);
return output.toByteArray();
}
}
@Override @Override
public List<GaoRegistrationEventPeriodStatView> statByRegistrationEventAndPeriodType(String registrationEventId, GaoRegistrationEventRecordPeriodReq.PeriodType periodType) { public List<GaoRegistrationEventPeriodStatView> statByRegistrationEventAndPeriodType(String registrationEventId, GaoRegistrationEventRecordPeriodReq.PeriodType periodType) {
TimiException.required(registrationEventId, "not found registrationEventId"); TimiException.required(registrationEventId, "not found registrationEventId");
@@ -322,4 +396,53 @@ public class GaoRegistrationEventRecordServiceImplement extends AbstractEntitySe
} }
return list; return list;
} }
private void appendRow(Sheet sheet, int rowIndex, CellStyle style, String... values) {
Row row = sheet.createRow(rowIndex);
for (int index = 0; index < values.length; index++) {
row.createCell(index).setCellValue(values[index]);
if (style != null) {
row.getCell(index).setCellStyle(style);
}
}
}
private String buildEventFilterText(GaoRegistrationEventRecordRangeReq req) {
if ((req.getRegistrationEventId() == null || req.getRegistrationEventId().isBlank()) && (req.getRegistrationEventIdList() == null || req.getRegistrationEventIdList().isEmpty())) {
return "全部事件";
}
if (req.getRegistrationEventId() != null && !req.getRegistrationEventId().isBlank()) {
return "事件 ID" + req.getRegistrationEventId();
}
StringJoiner joiner = new StringJoiner("");
req.getRegistrationEventIdList().stream()
.filter(Objects::nonNull)
.filter(item -> !item.isBlank())
.forEach(joiner::add);
return "事件 ID" + joiner;
}
private String formatExportTime(Long value) {
if (value == null) {
return "";
}
return EXPORT_TIME_FORMATTER.format(Instant.ofEpochMilli(value));
}
private String getOperatorName(GaoRegistrationEventRecord record) {
if (record.getOperator() == null) {
return text(record.getOperatorUserId());
}
if (record.getOperator().getNick() != null && !record.getOperator().getNick().isBlank()) {
return record.getOperator().getNick();
}
if (record.getOperator().getName() != null && !record.getOperator().getName().isBlank()) {
return record.getOperator().getName();
}
return text(record.getOperatorUserId());
}
private String text(Object value) {
return value == null ? "" : value.toString();
}
} }
@@ -2,6 +2,8 @@ package com.imyeyu.api.modules.gao.vo;
import lombok.Data; import lombok.Data;
import java.util.List;
/// ///
/// 登记事件记录范围查询请求 /// 登记事件记录范围查询请求
/// ///
@@ -16,6 +18,12 @@ public class GaoRegistrationEventRecordRangeReq {
/// 登记事件 ID,为空查询全部登记事件 /// 登记事件 ID,为空查询全部登记事件
private String registrationEventId; private String registrationEventId;
/// 登记事件 ID 列表,为空查询全部登记事件
private List<String> registrationEventIdList;
/// 关键词,匹配客户、登记事件、备注、登记人
private String keyword;
/// 开始登记时间 /// 开始登记时间
private Long beginRegisteredAt; private Long beginRegisteredAt;
@@ -1,6 +1,6 @@
CREATE TABLE IF NOT EXISTS `gao_customer` ( CREATE TABLE IF NOT EXISTS `gao_customer` (
`id` VARCHAR(36) NOT NULL, `id` VARCHAR(36) NOT NULL,
`code` VARCHAR(64) NOT NULL COMMENT '客户编码', `code` VARCHAR(64) NULL COMMENT '客户编码',
`name` VARCHAR(64) NOT NULL COMMENT '姓名', `name` VARCHAR(64) NOT NULL COMMENT '姓名',
`gender` VARCHAR(16) NULL COMMENT '性别代码,MALE 男,FEMALE 女', `gender` VARCHAR(16) NULL COMMENT '性别代码,MALE 男,FEMALE 女',
`age` INT NULL COMMENT '年龄', `age` INT NULL COMMENT '年龄',
@@ -1,29 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?> <?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" > <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoCustomerMapper"> <mapper namespace="com.imyeyu.api.modules.gao.mapper.GaoCustomerMapper">
<sql id="searchWhere">
1 = 1
AND `deleted_at` IS NULL
<if test="keyword != null and keyword != ''">
AND (
`code` LIKE CONCAT('%', #{keyword}, '%')
OR `name` LIKE CONCAT('%', #{keyword}, '%')
OR `telephone` LIKE CONCAT('%', #{keyword}, '%')
OR `address` LIKE CONCAT('%', #{keyword}, '%')
OR `disease` LIKE CONCAT('%', #{keyword}, '%')
OR CAST(`conditioning_part` AS CHAR) LIKE CONCAT('%', #{keyword}, '%')
)
</if>
<if test="introducerCustomerId != null and introducerCustomerId != ''">
AND `introducer_customer_id` = #{introducerCustomerId}
</if>
</sql>
<select id="search" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomer"> <select id="search" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomer">
SELECT SELECT
* *
FROM `gao_customer` FROM `gao_customer`
WHERE WHERE
1 = 1 <include refid="searchWhere"/>
AND `deleted_at` IS NULL
<if test="keyword != null and keyword != ''">
AND (
`code` LIKE CONCAT('%', #{keyword}, '%')
OR `name` LIKE CONCAT('%', #{keyword}, '%')
OR `telephone` LIKE CONCAT('%', #{keyword}, '%')
OR `address` LIKE CONCAT('%', #{keyword}, '%')
OR `disease` LIKE CONCAT('%', #{keyword}, '%')
OR CAST(`conditioning_part` AS CHAR) LIKE CONCAT('%', #{keyword}, '%')
)
</if>
<if test="introducerCustomerId != null and introducerCustomerId != ''">
AND `introducer_customer_id` = #{introducerCustomerId}
</if>
ORDER BY `created_at` DESC ORDER BY `created_at` DESC
</select> </select>
<select id="countSearch" resultType="java.lang.Long">
SELECT
COUNT(1)
FROM `gao_customer`
WHERE
<include refid="searchWhere"/>
</select>
<select id="searchPage" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomer">
SELECT
*
FROM `gao_customer`
WHERE
<include refid="searchWhere"/>
ORDER BY `created_at` DESC
LIMIT #{offset}, #{size}
</select>
<select id="selectInvitedList" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomer"> <select id="selectInvitedList" resultType="com.imyeyu.api.modules.gao.entity.GaoCustomer">
SELECT SELECT
* *
@@ -214,20 +214,66 @@
ORDER BY r.`registered_at` DESC, r.`created_at` DESC ORDER BY r.`registered_at` DESC, r.`created_at` DESC
</select> </select>
<sql id="rangeWhere">
r.`registered_at` &gt;= #{req.beginRegisteredAt}
AND r.`registered_at` &lt;= #{req.endRegisteredAt}
AND r.`deleted_at` IS NULL
<if test="req.customerId != null and req.customerId != ''">
AND r.`customer_id` = #{req.customerId}
</if>
<if test="req.registrationEventId != null and req.registrationEventId != ''">
AND r.`registration_event_id` = #{req.registrationEventId}
</if>
<if test="req.registrationEventIdList != null and req.registrationEventIdList.size() > 0">
AND r.`registration_event_id` IN
<foreach collection="req.registrationEventIdList" item="registrationEventId" open="(" separator="," close=")">
#{registrationEventId}
</foreach>
</if>
<if test="req.keyword != null and req.keyword != ''">
AND (
c.`code` LIKE CONCAT('%', #{req.keyword}, '%')
OR c.`name` LIKE CONCAT('%', #{req.keyword}, '%')
OR e.`name` LIKE CONCAT('%', #{req.keyword}, '%')
OR r.`remark` LIKE CONCAT('%', #{req.keyword}, '%')
OR u.`name` LIKE CONCAT('%', #{req.keyword}, '%')
OR u.`nick` LIKE CONCAT('%', #{req.keyword}, '%')
OR r.`operator_user_id` LIKE CONCAT('%', #{req.keyword}, '%')
)
</if>
</sql>
<sql id="rangeFrom">
FROM `gao_registration_event_record` r
LEFT JOIN `gao_customer` c ON r.`customer_id` = c.`id`
LEFT JOIN `gao_registration_event` e ON r.`registration_event_id` = e.`id`
LEFT JOIN `user` u ON r.`operator_user_id` = u.`id`
</sql>
<select id="selectByRange" resultType="com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord"> <select id="selectByRange" resultType="com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord">
SELECT SELECT
* r.*
FROM `gao_registration_event_record` <include refid="rangeFrom"/>
WHERE WHERE
`registered_at` &gt;= #{req.beginRegisteredAt} <include refid="rangeWhere"/>
AND `registered_at` &lt;= #{req.endRegisteredAt} ORDER BY r.`registered_at` DESC, r.`created_at` DESC
AND `deleted_at` IS NULL </select>
<if test="req.customerId != null and req.customerId != ''">
AND `customer_id` = #{req.customerId} <select id="countByRange" resultType="java.lang.Long">
</if> SELECT
<if test="req.registrationEventId != null and req.registrationEventId != ''"> COUNT(1)
AND `registration_event_id` = #{req.registrationEventId} <include refid="rangeFrom"/>
</if> WHERE
ORDER BY `registered_at` DESC, `created_at` DESC <include refid="rangeWhere"/>
</select>
<select id="selectPageByRange" resultType="com.imyeyu.api.modules.gao.entity.GaoRegistrationEventRecord">
SELECT
r.*
<include refid="rangeFrom"/>
WHERE
<include refid="rangeWhere"/>
ORDER BY r.`registered_at` DESC, r.`created_at` DESC
LIMIT #{offset}, #{size}
</select> </select>
</mapper> </mapper>