7. 渠道管理
代理人/经纪人管理
电商平台对接
移动端支持
第三方渠道接入
# 渠道API配置
channel:
api:
key-expiry-days: 90 # API密钥有效期
rate-limit: 100 # 每分钟请求限制
# 缓存配置
cache:
channel:
ttl: 3600 # 1小时
@Component
public class ApiKeyGenerator {
public String generateApiKey(String channelCode) {
String random = RandomStringUtils.randomAlphanumeric(16);
return channelCode + "_" + random;
}
public String generateApiSecret() {
return RandomStringUtils.randomAlphanumeric(32);
}
public String encodeBasicAuth(String apiKey, String apiSecret) {
String auth = apiKey + ":" + apiSecret;
return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes());
}
}
public class ChannelAuthenticationFilter extends OncePerRequestFilter {
private final ChannelService channelService;
public ChannelAuthenticationFilter(ChannelService channelService) {
this.channelService = channelService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Basic ")) {
try {
String base64Credentials = authHeader.substring("Basic ".length());
String credentials = new String(Base64.getDecoder().decode(base64Credentials));
String[] parts = credentials.split(":", 2);
ChannelAuthDTO authDTO = new ChannelAuthDTO();
authDTO.setChannelCode(parts[0]);
authDTO.setApiKey(parts[0]);
authDTO.setApiSecret(parts[1]);
Channel channel = channelService.authenticateChannel(authDTO);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(channel, null, null);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
return;
}
}
filterChain.doFilter(request, response);
}
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ChannelSecurityConfig extends WebSecurityConfigurerAdapter {
private final ChannelService channelService;
public ChannelSecurityConfig(ChannelService channelService) {
this.channelService = channelService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/channel/**")
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new ChannelAuthenticationFilter(channelService),
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().authenticated();
}
}
Controller层
@RestController
@RequestMapping("/api/channels")
@Api(tags = "渠道管理")
public class ChannelController {
private final ChannelService channelService;
public ChannelController(ChannelService channelService) {
this.channelService = channelService;
}
@PostMapping
@ApiOperation("创建渠道")
@PreAuthorize("hasRole('CHANNEL_MANAGER') or hasRole('ADMIN')")
public ResponseEntity<Channel> createChannel(@RequestBody ChannelDTO channelDTO) {
Channel channel = channelService.createChannel(channelDTO);
return ResponseEntity.ok(channel);
}
@PutMapping("/{id}")
@ApiOperation("更新渠道")
@PreAuthorize("hasRole('CHANNEL_MANAGER') or hasRole('ADMIN')")
public ResponseEntity<Channel> updateChannel(@PathVariable Long id,
@RequestBody ChannelDTO channelDTO) {
Channel channel = channelService.updateChannel(id, channelDTO);
return ResponseEntity.ok(channel);
}
@DeleteMapping("/{id}")
@ApiOperation("删除渠道")
@PreAuthorize("hasRole('CHANNEL_MANAGER') or hasRole('ADMIN')")
public ResponseEntity<Void> deleteChannel(@PathVariable Long id) {
channelService.deleteChannel(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/{id}")
@ApiOperation("获取渠道详情")
@PreAuthorize("hasAnyRole('CHANNEL_MANAGER', 'AGENT', 'ADMIN')")
public ResponseEntity<Channel> getChannelById(@PathVariable Long id) {
Channel channel = channelService.getChannelById(id);
return ResponseEntity.ok(channel);
}
@GetMapping("/code/{code}")
@ApiOperation("通过渠道代码获取渠道")
@PreAuthorize("hasAnyRole('CHANNEL_MANAGER', 'AGENT', 'ADMIN')")
public ResponseEntity<Channel> getChannelByCode(@PathVariable String code) {
Channel channel = channelService.getChannelByCode(code);
return ResponseEntity.ok(channel);
}
@GetMapping("/search")
@ApiOperation("搜索渠道")
@PreAuthorize("hasAnyRole('CHANNEL_MANAGER', 'AGENT', 'ADMIN')")
public ResponseEntity<Page<Channel>> searchChannels(ChannelSearchDTO searchDTO,
Pageable pageable) {
Page<Channel> channels = channelService.searchChannels(searchDTO, pageable);
return ResponseEntity.ok(channels);
}
@PutMapping("/{id}/status/{status}")
@ApiOperation("变更渠道状态")
@PreAuthorize("hasRole('CHANNEL_MANAGER') or hasRole('ADMIN')")
public ResponseEntity<Channel> changeChannelStatus(@PathVariable Long id,
@PathVariable String status) {
Channel channel = channelService.changeChannelStatus(id, status);
return ResponseEntity.ok(channel);
}
@PostMapping("/authenticate")
@ApiOperation("渠道认证")
public ResponseEntity<Channel> authenticateChannel(@RequestBody ChannelAuthDTO authDTO) {
Channel channel = channelService.authenticateChannel(authDTO);
return ResponseEntity.ok(channel);
}
@PostMapping("/{channelId}/regenerate-api-key")
@ApiOperation("重新生成API密钥")
@PreAuthorize("hasRole('CHANNEL_MANAGER') or hasRole('ADMIN')")
public ResponseEntity<Channel> generateNewApiKey(@PathVariable Long channelId) {
Channel channel = channelService.generateNewApiKey(channelId);
return ResponseEntity.ok(channel);
}
@GetMapping("/active")
@ApiOperation("获取活跃渠道列表")
@PreAuthorize("hasAnyRole('CHANNEL_MANAGER', 'AGENT', 'ADMIN')")
public ResponseEntity<List<Channel>> getActiveChannels() {
List<Channel> channels = channelService.getActiveChannels();
return ResponseEntity.ok(channels);
}
@GetMapping("/{channelId}/statistics")
@ApiOperation("获取渠道统计")
@PreAuthorize("hasRole('CHANNEL_MANAGER') or hasRole('ADMIN')")
public ResponseEntity<ChannelStatisticsDTO> getChannelStatistics(@PathVariable Long channelId) {
ChannelStatisticsDTO statistics = channelService.getChannelStatistics(channelId);
return ResponseEntity.ok(statistics);
}
}
Service层
@Slf4j
@Service
@Transactional
public class ChannelServiceImpl implements ChannelService {
private final ChannelRepository channelRepository;
private final ChannelProductRepository channelProductRepository;
private final ProductRepository productRepository;
private final ModelMapper modelMapper;
private final ApiKeyGenerator apiKeyGenerator;
private final PasswordEncoder passwordEncoder;
private final RabbitTemplate rabbitTemplate;
private final AuditLogService auditLogService;
public ChannelServiceImpl(ChannelRepository channelRepository,
ChannelProductRepository channelProductRepository,
ProductRepository productRepository,
ModelMapper modelMapper,
ApiKeyGenerator apiKeyGenerator,
PasswordEncoder passwordEncoder,
RabbitTemplate rabbitTemplate,
AuditLogService auditLogService) {
this.channelRepository = channelRepository;
this.channelProductRepository = channelProductRepository;
this.productRepository = productRepository;
this.modelMapper = modelMapper;
this.apiKeyGenerator = apiKeyGenerator;
this.passwordEncoder = passwordEncoder;
this.rabbitTemplate = rabbitTemplate;
this.auditLogService = auditLogService;
}
@Override
@CacheEvict(value = "channels", allEntries = true)
public Channel createChannel(ChannelDTO channelDTO) {
// 验证渠道代码是否已存在
if (channelRepository.existsByCode(channelDTO.getCode())) {
throw new BusinessException("渠道代码已存在");
}
// 创建渠道
Channel channel = modelMapper.map(channelDTO, Channel.class);
// 生成API密钥
generateApiKeys(channel);
channel.setStatus(ChannelStatus.PENDING);
channel.setCreatedAt(new Date());
channel.setUpdatedAt(new Date());
Channel savedChannel = channelRepository.save(channel);
// 保存渠道产品
saveChannelProducts(channelDTO, savedChannel);
// 记录审计日志
auditLogService.logChannelCreation(savedChannel);
// 异步发送渠道创建通知
sendChannelNotification(savedChannel, "channel.created");
return savedChannel;
}
@Override
@CacheEvict(value = "channels", key = "#id")
public Channel updateChannel(Long id, ChannelDTO channelDTO) {
Channel existingChannel = channelRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("渠道不存在"));
// 验证渠道代码是否已被其他渠道使用
if (!existingChannel.getCode().equals(channelDTO.getCode())) {
if (channelRepository.existsByCode(channelDTO.getCode())) {
throw new BusinessException("渠道代码已被其他渠道使用");
}
}
modelMapper.map(channelDTO, existingChannel);
existingChannel.setUpdatedAt(new Date());
// 更新渠道产品
updateChannelProducts(channelDTO, existingChannel);
Channel updatedChannel = channelRepository.save(existingChannel);
// 异步发送渠道更新通知
sendChannelNotification(updatedChannel, "channel.updated");
return updatedChannel;
}
@Override
@CacheEvict(value = "channels", key = "#id")
public void deleteChannel(Long id) {
Channel channel = channelRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("渠道不存在"));
// 检查是否有关联代理人
if (!channel.getAgents().isEmpty()) {
throw new BusinessException("该渠道有关联代理人,无法删除");
}
// 删除渠道产品
channelProductRepository.deleteByChannelId(id);
channelRepository.delete(channel);
// 异步发送渠道删除通知
sendChannelNotification(channel, "channel.deleted");
}
@Override
@Cacheable(value = "channels", key = "#id")
public Channel getChannelById(Long id) {
return channelRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("渠道不存在"));
}
@Override
@Cacheable(value = "channels", key = "#code")
public Channel getChannelByCode(String code) {
return channelRepository.findByCode(code)
.orElseThrow(() -> new ResourceNotFoundException("渠道不存在"));
}
@Override
public Page<Channel> searchChannels(ChannelSearchDTO searchDTO, Pageable pageable) {
return channelRepository.searchChannels(
searchDTO.getKeyword(),
searchDTO.getType(),
searchDTO.getStatus(),
searchDTO.getEffectiveDateFrom(),
searchDTO.getEffectiveDateTo(),
pageable);
}
@Override
@CacheEvict(value = "channels", key = "#id")
public Channel changeChannelStatus(Long id, String status) {
Channel channel = channelRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("渠道不存在"));
ChannelStatus newStatus = ChannelStatus.valueOf(status.toUpperCase());
// 验证状态转换是否合法
validateStatusTransition(channel.getStatus(), newStatus);
channel.setStatus(newStatus);
channel.setUpdatedAt(new Date());
Channel updatedChannel = channelRepository.save(channel);
// 异步发送渠道状态变更通知
sendChannelNotification(updatedChannel, "channel.status.changed");
return updatedChannel;
}
@Override
public Channel authenticateChannel(ChannelAuthDTO authDTO) {
Channel channel = channelRepository.findByApiKey(authDTO.getChannelCode())
.orElseThrow(() -> new ResourceNotFoundException("渠道不存在或未激活"));
// 验证API密钥
if (!passwordEncoder.matches(authDTO.getApiSecret(), channel.getApiSecret())) {
throw new BusinessException("API密钥验证失败");
}
return channel;
}
@Override
@CacheEvict(value = "channels", key = "#channelId")
public Channel generateNewApiKey(Long channelId) {
Channel channel = channelRepository.findById(channelId)
.orElseThrow(() -> new ResourceNotFoundException("渠道不存在"));
// 生成新的API密钥
generateApiKeys(channel);
channel.setUpdatedAt(new Date());
Channel updatedChannel = channelRepository.save(channel);
// 异步发送API密钥更新通知
sendChannelNotification(updatedChannel, "channel.api.regenerated");
return updatedChannel;
}
@Override
public List<Channel> getActiveChannels() {
return channelRepository.findByStatus(ChannelStatus.ACTIVE);
}
@Override
public ChannelStatisticsDTO getChannelStatistics(Long channelId) {
Channel channel = channelRepository.findById(channelId)
.orElseThrow(() -> new ResourceNotFoundException("渠道不存在"));
ChannelStatisticsDTO statistics = new ChannelStatisticsDTO();
// 获取活跃代理人数量
long activeAgents = channel.getAgents().stream()
.filter(a -> a.getStatus() == AgentStatus.ACTIVE)
.count();
statistics.setActiveAgents((int) activeAgents);
// 其他统计指标...
return statistics;
}
private void generateApiKeys(Channel channel) {
String apiKey = apiKeyGenerator.generateApiKey(channel.getCode());
String apiSecret = apiKeyGenerator.generateApiSecret();
channel.setApiKey(apiKey);
channel.setApiSecret(passwordEncoder.encode(apiSecret));
}
private void saveChannelProducts(ChannelDTO channelDTO, Channel channel) {
for (ChannelDTO.ChannelProductDTO productDTO : channelDTO.getProducts()) {
Product product = productRepository.findById(productDTO.getProductId())
.orElseThrow(() -> new ResourceNotFoundException("产品不存在"));
ChannelProduct channelProduct = new ChannelProduct();
channelProduct.setChannel(channel);
channelProduct.setProduct(product);
channelProduct.setCommissionRate(productDTO.getCommissionRate());
channelProduct.setIsActive(productDTO.getIsActive());
channelProductRepository.save(channelProduct);
}
}
private void updateChannelProducts(ChannelDTO channelDTO, Channel channel) {
// 删除旧渠道产品
channelProductRepository.deleteByChannelId(channel.getId());
// 添加新渠道产品
saveChannelProducts(channelDTO, channel);
}
private void validateStatusTransition(ChannelStatus currentStatus, ChannelStatus newStatus) {
// 这里应该有完整的业务规则来验证状态转换是否合法
// 简化版示例
if (currentStatus == ChannelStatus.BLACKLIST && newStatus != ChannelStatus.BLACKLIST) {
throw new BusinessException("黑名单渠道必须先解除黑名单");
}
}
@Async
protected void sendChannelNotification(Channel channel, String routingKey) {
try {
rabbitTemplate.convertAndSend(routingKey, channel.getId());
} catch (Exception e) {
log.error("发送渠道通知失败: {}", channel.getId(), e);
}
}
}
Repository层
public interface AgentRepository extends JpaRepository<Agent, Long> {
Agent findByAgentNo(String agentNo);
List<Agent> findByChannelId(Long channelId);
List<Agent> findByStatus(AgentStatus status);
@Query("SELECT a FROM Agent a WHERE " +
"(:keyword IS NULL OR a.name LIKE %:keyword% OR a.agentNo LIKE %:keyword%) AND " +
"(:channelId IS NULL OR a.channel.id = :channelId) AND " +
"(:status IS NULL OR a.status = :status) AND " +
"(:joinDateFrom IS NULL OR a.joinDate >= :joinDateFrom) AND " +
"(:joinDateTo IS NULL OR a.joinDate <= :joinDateTo)")
Page<Agent> searchAgents(@Param("keyword") String keyword,
@Param("channelId") Long channelId,
@Param("status") AgentStatus status,
@Param("joinDateFrom") Date joinDateFrom,
@Param("joinDateTo") Date joinDateTo,
Pageable pageable);
}
public interface ChannelProductRepository extends JpaRepository<ChannelProduct, Long> {
List<ChannelProduct> findByChannelId(Long channelId);
List<ChannelProduct> findByChannelIdAndIsActiveTrue(Long channelId);
List<ChannelProduct> findByProductId(Long productId);
}
public interface ChannelRepository extends JpaRepository<Channel, Long> {
Optional<Channel> findByCode(String code);
List<Channel> findByType(ChannelType type);
List<Channel> findByStatus(ChannelStatus status);
@Query("SELECT c FROM Channel c WHERE " +
"(:keyword IS NULL OR c.name LIKE %:keyword% OR c.code LIKE %:keyword%) AND " +
"(:type IS NULL OR c.type = :type) AND " +
"(:status IS NULL OR c.status = :status) AND " +
"(:effectiveDateFrom IS NULL OR c.effectiveDate >= :effectiveDateFrom) AND " +
"(:effectiveDateTo IS NULL OR c.effectiveDate <= :effectiveDateTo)")
Page<Channel> searchChannels(@Param("keyword") String keyword,
@Param("type") ChannelType type,
@Param("status") ChannelStatus status,
@Param("effectiveDateFrom") Date effectiveDateFrom,
@Param("effectiveDateTo") Date effectiveDateTo,
Pageable pageable);
@Query("SELECT c FROM Channel c WHERE c.apiKey = :apiKey AND c.status = 'ACTIVE'")
Optional<Channel> findByApiKey(@Param("apiKey") String apiKey);
}
8. 报表与分析
业务报表
财务分析
风险分析
大数据分析
# 报表配置
report:
cache:
ttl: 3600 # 1小时缓存时间
excel:
max-rows: 100000 # Excel最大行数限制
pdf:
page-size: A4 # PDF页面大小
margin: 10 # 页边距(mm)
# iText PDF配置
itext:
license: classpath:/itext-license.xml
# 缓存配置
spring:
cache:
type: redis
redis:
time-to-live: 3600000 # 1小时
package com.insurance.controller;
import com.insurance.dto.*;
import com.insurance.entity.*;
import com.insurance.service.ReportService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/reports")
@Api(tags = "报表与分析")
public class ReportController {
private final ReportService reportService;
public ReportController(ReportService reportService) {
this.reportService = reportService;
}
@PostMapping("/generate")
@ApiOperation("生成报表")
@PreAuthorize("hasAnyRole('REPORT', 'ADMIN')")
public ResponseEntity<byte[]> generateReport(@RequestBody ReportRequestDTO requestDTO) {
byte[] reportContent = reportService.generateReport(requestDTO);
String contentType;
String fileExtension;
switch (requestDTO.getFormat().toUpperCase()) {
case "PDF":
contentType = MediaType.APPLICATION_PDF_VALUE;
fileExtension = "pdf";
break;
case "EXCEL":
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
fileExtension = "xlsx";
break;
default:
throw new IllegalArgumentException("不支持的报表格式");
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + requestDTO.getReportCode() + "." + fileExtension + "\"")
.body(reportContent);
}
@PostMapping("/data")
@ApiOperation("获取报表数据")
@PreAuthorize("hasAnyRole('REPORT', 'ADMIN')")
public ResponseEntity<Map<String, Object>> getReportData(@RequestBody ReportRequestDTO requestDTO) {
Map<String, Object> reportData = reportService.getReportData(requestDTO);
return ResponseEntity.ok(reportData);
}
@PostMapping("/definitions")
@ApiOperation("创建报表定义")
@PreAuthorize("hasRole('REPORT_ADMIN') or hasRole('ADMIN')")
public ResponseEntity<ReportDefinition> createReportDefinition(@RequestBody ReportDefinitionDTO definitionDTO) {
ReportDefinition report = reportService.createReportDefinition(definitionDTO);
return ResponseEntity.ok(report);
}
@PutMapping("/definitions/{id}")
@ApiOperation("更新报表定义")
@PreAuthorize("hasRole('REPORT_ADMIN') or hasRole('ADMIN')")
public ResponseEntity<ReportDefinition> updateReportDefinition(@PathVariable Long id,
@RequestBody ReportDefinitionDTO definitionDTO) {
ReportDefinition report = reportService.updateReportDefinition(id, definitionDTO);
return ResponseEntity.ok(report);
}
@DeleteMapping("/definitions/{id}")
@ApiOperation("删除报表定义")
@PreAuthorize("hasRole('REPORT_ADMIN') or hasRole('ADMIN')")
public ResponseEntity<Void> deleteReportDefinition(@PathVariable Long id) {
reportService.deleteReportDefinition(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/definitions")
@ApiOperation("获取所有报表定义")
@PreAuthorize("hasAnyRole('REPORT', 'ADMIN')")
public ResponseEntity<List<ReportDefinition>> getAllReportDefinitions() {
List<ReportDefinition> reports = reportService.getAllReportDefinitions();
return ResponseEntity.ok(reports);
}
@PostMapping("/schedules")
@ApiOperation("创建报表调度")
@PreAuthorize("hasRole('REPORT_ADMIN') or hasRole('ADMIN')")
public ResponseEntity<ReportSchedule> createReportSchedule(@RequestBody ReportScheduleDTO scheduleDTO) {
ReportSchedule schedule = reportService.createReportSchedule(scheduleDTO);
return ResponseEntity.ok(schedule);
}
@PutMapping("/schedules/{id}")
@ApiOperation("更新报表调度")
@PreAuthorize("hasRole('REPORT_ADMIN') or hasRole('ADMIN')")
public ResponseEntity<ReportSchedule> updateReportSchedule(@PathVariable Long id,
@RequestBody ReportScheduleDTO scheduleDTO) {
ReportSchedule schedule = reportService.updateReportSchedule(id, scheduleDTO);
return ResponseEntity.ok(schedule);
}
@DeleteMapping("/schedules/{id}")
@ApiOperation("删除报表调度")
@PreAuthorize("hasRole('REPORT_ADMIN') or hasRole('ADMIN')")
public ResponseEntity<Void> deleteReportSchedule(@PathVariable Long id) {
reportService.deleteReportSchedule(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/schedules/report/{reportId}")
@ApiOperation("获取报表的调度任务")
@PreAuthorize("hasAnyRole('REPORT', 'ADMIN')")
public ResponseEntity<List<ReportSchedule>> getSchedulesByReport(@PathVariable Long reportId) {
List<ReportSchedule> schedules = reportService.getSchedulesByReport(reportId);
return ResponseEntity.ok(schedules);
}
@PostMapping("/dashboards")
@ApiOperation("创建仪表盘")
@PreAuthorize("hasAnyRole('REPORT', 'ADMIN')")
public ResponseEntity<Dashboard> createDashboard(@RequestBody DashboardDTO dashboardDTO) {
Dashboard dashboard = reportService.createDashboard(dashboardDTO);
return ResponseEntity.ok(dashboard);
}
@PutMapping("/dashboards/{id}")
@ApiOperation("更新仪表盘")
@PreAuthorize("hasAnyRole('REPORT', 'ADMIN')")
public ResponseEntity<Dashboard> updateDashboard(@PathVariable Long id,
@RequestBody DashboardDTO dashboardDTO) {
Dashboard dashboard = reportService.updateDashboard(id, dashboardDTO);
return ResponseEntity.ok(dashboard);
}
@DeleteMapping("/dashboards/{id}")
@ApiOperation("删除仪表盘")
@PreAuthorize("hasAnyRole('REPORT', 'ADMIN')")
public ResponseEntity<Void> deleteDashboard(@PathVariable Long id) {
reportService.deleteDashboard(id);
return ResponseEntity.noContent().build();
}
@GetMapping("/dashboards/user/{userId}")
@ApiOperation("获取用户的仪表盘")
@PreAuthorize("hasAnyRole('REPORT', 'ADMIN')")
public ResponseEntity<List<Dashboard>> getUserDashboards(@PathVariable Long userId) {
List<Dashboard> dashboards = reportService.getUserDashboards(userId);
return ResponseEntity.ok(dashboards);
}
@GetMapping("/dashboards/shared")
@ApiOperation("获取共享仪表盘")
@PreAuthorize("hasAnyRole('REPORT', 'ADMIN')")
public ResponseEntity<List<Dashboard>> getSharedDashboards() {
List<Dashboard> dashboards = reportService.getSharedDashboards();
return ResponseEntity.ok(dashboards);
}
}
报表生成工具类
@Component
public class PdfReportGenerator {
public void generatePdfReport(Map<String, Object> reportData, OutputStream outputStream) throws DocumentException {
Document document = new Document();
PdfWriter.getInstance(document, outputStream);
document.open();
// 添加标题
Font titleFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 18);
Paragraph title = new Paragraph((String) reportData.get("reportName"), titleFont);
title.setAlignment(Element.ALIGN_CENTER);
title.setSpacingAfter(20f);
document.add(title);
// 添加日期范围
Font dateFont = FontFactory.getFont(FontFactory.HELVETICA, 12);
Paragraph dateRange = new Paragraph(
"日期范围: " + reportData.get("startDate") + " 至 " + reportData.get("endDate"),
dateFont);
dateRange.setSpacingAfter(15f);
document.add(dateRange);
// 添加表格
@SuppressWarnings("unchecked")
List<Map<String, Object>> data = (List<Map<String, Object>>) reportData.get("data");
if (!data.isEmpty()) {
PdfPTable table = new PdfPTable(data.get(0).size());
table.setWidthPercentage(100);
// 表头
Font headerFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 12);
for (String key : data.get(0).keySet()) {
PdfPCell cell = new PdfPCell(new Phrase(key, headerFont));
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
table.addCell(cell);
}
// 表格数据
Font dataFont = FontFactory.getFont(FontFactory.HELVETICA, 10);
for (Map<String, Object> rowData : data) {
for (Object value : rowData.values()) {
PdfPCell cell = new PdfPCell(new Phrase(
value != null ? value.toString() : "",
dataFont));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(cell);
}
}
document.add(table);
}
document.close();
}
}
Service层
@Slf4j
@Service
@Transactional
public class ReportServiceImpl implements ReportService {
private final ReportDefinitionRepository reportDefinitionRepository;
private final ReportScheduleRepository reportScheduleRepository;
private final DashboardRepository dashboardRepository;
private final ModelMapper modelMapper;
private final ExcelReportGenerator excelReportGenerator;
private final PdfReportGenerator pdfReportGenerator;
private final EmailService emailService;
public ReportServiceImpl(ReportDefinitionRepository reportDefinitionRepository,
ReportScheduleRepository reportScheduleRepository,
DashboardRepository dashboardRepository,
ModelMapper modelMapper,
ExcelReportGenerator excelReportGenerator,
PdfReportGenerator pdfReportGenerator,
EmailService emailService) {
this.reportDefinitionRepository = reportDefinitionRepository;
this.reportScheduleRepository = reportScheduleRepository;
this.dashboardRepository = dashboardRepository;
this.modelMapper = modelMapper;
this.excelReportGenerator = excelReportGenerator;
this.pdfReportGenerator = pdfReportGenerator;
this.emailService = emailService;
}
@Override
public byte[] generateReport(ReportRequestDTO requestDTO) {
ReportDefinition report = reportDefinitionRepository.findByCode(requestDTO.getReportCode())
.orElseThrow(() -> new ResourceNotFoundException("报表定义不存在"));
// 获取报表数据
Map<String, Object> reportData = getReportData(requestDTO);
// 根据格式生成报表
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
switch (requestDTO.getFormat().toUpperCase()) {
case "EXCEL":
excelReportGenerator.generateExcelReport(reportData, outputStream);
break;
case "PDF":
pdfReportGenerator.generatePdfReport(reportData, outputStream);
break;
default:
throw new BusinessException("不支持的报表格式");
}
return outputStream.toByteArray();
}
@Override
public ReportDefinition createReportDefinition(ReportDefinitionDTO definitionDTO) {
// 验证报表代码是否已存在
if (reportDefinitionRepository.existsByCode(definitionDTO.getCode())) {
throw new BusinessException("报表代码已存在");
}
ReportDefinition report = modelMapper.map(definitionDTO, ReportDefinition.class);
report.setCreatedAt(new Date());
report.setUpdatedAt(new Date());
return reportDefinitionRepository.save(report);
}
@Override
public ReportDefinition updateReportDefinition(Long id, ReportDefinitionDTO definitionDTO) {
ReportDefinition existingReport = reportDefinitionRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("报表定义不存在"));
// 验证报表代码是否已被其他报表使用
if (!existingReport.getCode().equals(definitionDTO.getCode())) {
if (reportDefinitionRepository.existsByCode(definitionDTO.getCode())) {
throw new BusinessException("报表代码已被其他报表使用");
}
}
modelMapper.map(definitionDTO, existingReport);
existingReport.setUpdatedAt(new Date());
return reportDefinitionRepository.save(existingReport);
}
@Override
public void deleteReportDefinition(Long id) {
ReportDefinition report = reportDefinitionRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("报表定义不存在"));
// 检查是否有关联的调度任务
if (!reportScheduleRepository.findByReportId(id).isEmpty()) {
throw new BusinessException("该报表有关联的调度任务,无法删除");
}
reportDefinitionRepository.delete(report);
}
@Override
public List<ReportDefinition> getAllReportDefinitions() {
return reportDefinitionRepository.findAll();
}
@Override
public ReportSchedule createReportSchedule(ReportScheduleDTO scheduleDTO) {
ReportDefinition report = reportDefinitionRepository.findById(scheduleDTO.getReportId())
.orElseThrow(() -> new ResourceNotFoundException("报表定义不存在"));
ReportSchedule schedule = modelMapper.map(scheduleDTO, ReportSchedule.class);
schedule.setReport(report);
schedule.setCreatedAt(new Date());
schedule.setUpdatedAt(new Date());
return reportScheduleRepository.save(schedule);
}
@Override
public ReportSchedule updateReportSchedule(Long id, ReportScheduleDTO scheduleDTO) {
ReportSchedule existingSchedule = reportScheduleRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("报表调度不存在"));
modelMapper.map(scheduleDTO, existingSchedule);
existingSchedule.setUpdatedAt(new Date());
return reportScheduleRepository.save(existingSchedule);
}
@Override
public void deleteReportSchedule(Long id) {
reportScheduleRepository.deleteById(id);
}
@Override
public List<ReportSchedule> getSchedulesByReport(Long reportId) {
return reportScheduleRepository.findByReportId(reportId);
}
@Override
public Dashboard createDashboard(DashboardDTO dashboardDTO) {
Dashboard dashboard = modelMapper.map(dashboardDTO, Dashboard.class);
// 设置关联的报表
List<ReportDefinition> reports = reportDefinitionRepository.findAllById(dashboardDTO.getReportIds());
dashboard.setReports(reports);
dashboard.setCreatedAt(new Date());
dashboard.setUpdatedAt(new Date());
return dashboardRepository.save(dashboard);
}
@Override
public Dashboard updateDashboard(Long id, DashboardDTO dashboardDTO) {
Dashboard existingDashboard = dashboardRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("仪表盘不存在"));
modelMapper.map(dashboardDTO, existingDashboard);
// 更新关联的报表
List<ReportDefinition> reports = reportDefinitionRepository.findAllById(dashboardDTO.getReportIds());
existingDashboard.setReports(reports);
existingDashboard.setUpdatedAt(new Date());
return dashboardRepository.save(existingDashboard);
}
@Override
public void deleteDashboard(Long id) {
dashboardRepository.deleteById(id);
}
@Override
public List<Dashboard> getUserDashboards(Long userId) {
return dashboardRepository.findByCreatedBy(userId);
}
@Override
public List<Dashboard> getSharedDashboards() {
return dashboardRepository.findByIsSharedTrue();
}
@Override
@Cacheable(value = "reportData", key = "#requestDTO.reportCode + #requestDTO.startDate + #requestDTO.endDate")
public Map<String, Object> getReportData(ReportRequestDTO requestDTO) {
ReportDefinition report = reportDefinitionRepository.findByCode(requestDTO.getReportCode())
.orElseThrow(() -> new ResourceNotFoundException("报表定义不存在"));
// 这里应该有实际的数据查询逻辑
// 简化版示例:模拟数据
Map<String, Object> data = new HashMap<>();
data.put("reportName", report.getName());
data.put("startDate", requestDTO.getStartDate());
data.put("endDate", requestDTO.getEndDate());
// 根据报表类型返回不同的模拟数据
switch (report.getType()) {
case POLICY:
data.put("data", generatePolicyReportData());
break;
case CLAIM:
data.put("data", generateClaimReportData());
break;
case FINANCIAL:
data.put("data", generateFinancialReportData());
break;
default:
data.put("data", Collections.emptyList());
}
return data;
}
@Override
@Scheduled(cron = "0 0 6 * * ?") // 每天6点执行
public void executeScheduledReports() {
log.info("开始执行定时报表任务...");
// 处理每日报表
List<ReportSchedule> dailySchedules = reportScheduleRepository.findDailySchedules();
processSchedules(dailySchedules);
// 处理每周报表 (当前星期几)
String dayOfWeek = String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_WEEK));
List<ReportSchedule> weeklySchedules = reportScheduleRepository.findWeeklySchedules(dayOfWeek);
processSchedules(weeklySchedules);
// 处理每月报表 (当前日期)
int dayOfMonth = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
List<ReportSchedule> monthlySchedules = reportScheduleRepository.findMonthlySchedules(dayOfMonth);
processSchedules(monthlySchedules);
log.info("定时报表任务执行完成");
}
private void processSchedules(List<ReportSchedule> schedules) {
Date endDate = new Date();
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -1);
Date startDate = calendar.getTime();
for (ReportSchedule schedule : schedules) {
try {
ReportRequestDTO requestDTO = new ReportRequestDTO();
requestDTO.setReportCode(schedule.getReport().getCode());
requestDTO.setStartDate(startDate);
requestDTO.setEndDate(endDate);
requestDTO.setFormat(schedule.getFormat());
byte[] reportContent = generateReport(requestDTO);
// 发送邮件
emailService.sendEmailWithAttachment(
schedule.getRecipient(),
"报表: " + schedule.getReport().getName(),
"请查收附件中的报表",
schedule.getReport().getName() + "." + schedule.getFormat().toLowerCase(),
reportContent
);
log.info("成功发送报表: {} 给 {}", schedule.getReport().getName(), schedule.getRecipient());
} catch (Exception e) {
log.error("发送报表失败: " + schedule.getReport().getName(), e);
}
}
}
private List<Map<String, Object>> generatePolicyReportData() {
List<Map<String, Object>> data = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
Map<String, Object> row = new HashMap<>();
row.put("policyNo", "POL202300" + i);
row.put("productName", "保险产品" + i);
row.put("premium", 1000 * i);
row.put("status", i % 2 == 0 ? "有效" : "失效");
data.add(row);
}
return data;
}
private List<Map<String, Object>> generateClaimReportData() {
List<Map<String, Object>> data = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
Map<String, Object> row = new HashMap<>();
row.put("claimNo", "CLM202300" + i);
row.put("policyNo", "POL202300" + i);
row.put("claimAmount", 5000 * i);
row.put("status", i % 3 == 0 ? "已赔付" : "处理中");
data.add(row);
}
return data;
}
private List<Map<String, Object>> generateFinancialReportData() {
List<Map<String, Object>> data = new ArrayList<>();
String[] months = {"1月", "2月", "3月", "4月", "5月"};
for (int i = 0; i < months.length; i++) {
Map<String, Object> row = new HashMap<>();
row.put("month", months[i]);
row.put("premiumIncome", 100000 * (i + 1));
row.put("claimPayment", 50000 * (i + 1));
row.put("profit", 50000 * (i + 1));
data.add(row);
}
return data;
}
}
Repository层
public interface ReportDefinitionRepository extends JpaRepository<ReportDefinition, Long> {
ReportDefinition findByCode(String code);
List<ReportDefinition> findByType(ReportType type);
List<ReportDefinition> findByIsActiveTrue();
List<ReportDefinition> findByIsSystemTrue();
}
public interface ReportDefinitionRepository extends JpaRepository<ReportDefinition, Long> {
ReportDefinition findByCode(String code);
List<ReportDefinition> findByType(ReportType type);
List<ReportDefinition> findByIsActiveTrue();
List<ReportDefinition> findByIsSystemTrue();
}
public interface DashboardRepository extends JpaRepository<Dashboard, Long> {
List<Dashboard> findByCreatedBy(Long userId);
List<Dashboard> findByIsSharedTrue();
}