企业级Java项目金融应用领域——保险系统(补充)

发布于:2025-08-18 ⋅ 阅读:(18) ⋅ 点赞:(0)

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();
}

网站公告

今日签到

点亮在社区的每一天
去签到