yeg
2026-09-07 279e85aa564967fb187ae9687b3c81a5eee5e74d
实现拼接统计与报表保存
5个文件已添加
71 ■■■■■ 已修改文件
battery-business/src/main/java/com/battery/business/report/SpliceReportRepository.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
battery-business/src/main/java/com/battery/business/report/SpliceReportRequest.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
battery-business/src/main/java/com/battery/business/report/SpliceReportService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/battery/business/web/SpliceReportController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/db/migration-final/V17__splice_reports.sql 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
battery-business/src/main/java/com/battery/business/report/SpliceReportRepository.java
New file
@@ -0,0 +1,7 @@
package com.battery.business.report;
import javax.sql.DataSource;
import org.springframework.stereotype.Repository;
@Repository
public class SpliceReportRepository extends ReportVersionRepository {
    public SpliceReportRepository(DataSource dataSource) { super(dataSource, "splice", "拼接"); }
}
battery-business/src/main/java/com/battery/business/report/SpliceReportRequest.java
New file
@@ -0,0 +1,11 @@
package com.battery.business.report;
import java.util.List;
import java.util.Map;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
public record SpliceReportRequest(@NotBlank @Size(max=100) String name, @Size(max=2000) String description,
        @NotNull @Size(min=2) List<@NotNull @Valid Selection> selections, @NotNull Map<String,Object> settings) {
    public record Selection(@NotNull @Positive Long testId, @NotNull @Positive Long testBatteryId) {}
    public record Edit(@Positive int baseVersionNo, @NotBlank @Size(max=100) String name, String description,
            @NotNull Map<String,Object> settings, @NotBlank @Size(max=500) String changeSummary) {}
}
battery-business/src/main/java/com/battery/business/report/SpliceReportService.java
New file
@@ -0,0 +1,28 @@
package com.battery.business.report;
import java.math.BigDecimal; import java.math.MathContext; import java.util.*;
import com.alibaba.fastjson2.*; import com.battery.business.domain.*; import com.battery.business.service.*;
import com.ruoyi.common.exception.ServiceException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
/** 拼接报表按来源顺序平移循环号;统计只读取检测完成测试的当前有效循环记录。 */
@Service public class SpliceReportService {
 private final ITTestService tests; private final ITestDataQueryService data; private final SpliceReportRepository reports;
 public SpliceReportService(ITTestService t, ITestDataQueryService d, SpliceReportRepository r){tests=t;data=d;reports=r;}
 public record Candidate(SpliceReportRequest.Selection selection,String name,String type,long groupId,String group,String batteryNo,long startedAt){}
 public record Matrix(List<Map<String,Object>> segments,List<Integer> cycleNos){}
 public List<Candidate> sources(){ TTest q=new TTest(); q.setTestStatus("COMPLETED"); List<Candidate> out=new ArrayList<>(); for(TTest s:tests.selectTestList(q)){TTest t=tests.selectTestById(s.getTestId()); for(TTestBattery b:t.getBatteries()) out.add(candidate(t,b));} return out; }
 public Matrix preview(List<SpliceReportRequest.Selection> selections){
  if(selections==null||selections.size()<2||new HashSet<>(selections).size()!=selections.size()) throw new ServiceException("请选择至少两个不重复的检测完成测试电池");
  List<Map<String,Object>> segments=new ArrayList<>(); Set<Long> groups=new HashSet<>(); Set<String> batteries=new HashSet<>(); int offset=0; long group=0;
  for(var sel:selections){TTest t=tests.selectTestById(sel.testId()); if(t==null||!"COMPLETED".equals(t.getTestStatus())) throw new ServiceException("仅可选择检测完成测试"); TTestBattery b=t.getBatteries().stream().filter(x->sel.testBatteryId().equals(x.getTestBatteryId())).findFirst().orElseThrow(()->new ServiceException("电池不属于所选测试")); if(group==0)group=b.getGroupId(); if(group!=b.getGroupId()) throw new ServiceException("拼接测试必须属于同一电池组"); groups.add(b.getGroupId()); batteries.add(b.getBatteryNo());
   List<Map<String,Object>> rows=data.cycles(sel.testId()).stream().filter(r->Objects.equals(r.get("batteryNo"),b.getBatteryNo())).toList(); List<Integer> nos=rows.stream().map(this::cycle).filter(x->x>0).distinct().sorted().toList(); Map<String,Object> seg=new LinkedHashMap<>(); seg.put("source",candidate(t,b)); seg.put("offset",offset); seg.put("cycles",rows); seg.put("originalCycleNos",nos); seg.put("statistics",statistics(rows,nos)); segments.add(seg); offset += nos.size(); }
  if(batteries.size()==0) throw new ServiceException("未找到共同参与电池"); return new Matrix(segments,java.util.stream.IntStream.range(1,offset+1).boxed().toList()); }
 private Map<String,Object> statistics(List<Map<String,Object>> rows,List<Integer> nos){Map<String,Object> out=new LinkedHashMap<>(); for(int no:nos){var m=rows.stream().filter(r->cycle(r)==no).toList(); Map<String,Object> v=new LinkedHashMap<>(); if(m.size()==1){Map<String,Object> row=m.get(0); for(String k:List.of("chargeEnergy","dischargeEnergy","chargeCapacity","dischargeCapacity")) if(row.get(k)!=null)v.put(k,row.get(k)); Number c=num(v.get("chargeEnergy")),d=num(v.get("dischargeEnergy")); if(c!=null&&d!=null&&c.doubleValue()>0)v.put("energyEfficiency",BigDecimal.valueOf(d.doubleValue()).multiply(BigDecimal.valueOf(100)).divide(BigDecimal.valueOf(c.doubleValue()),MathContext.DECIMAL128));} out.put(Integer.toString(no),v);} return out;}
 private List<Map<String,Object>> dataStepRows(Map<String,Object> row){return List.of();}
 private Number num(Object x){return x instanceof Number?n((Number)x):null;} private Number n(Number x){return x;}
 @Transactional public Map<String,Object> create(SpliceReportRequest r,long uid,String user){Matrix m=preview(r.selections()); Map<String,Object> snap=new LinkedHashMap<>(); snap.put("name",r.name().trim());snap.put("description",r.description());snap.put("selections",r.selections());snap.put("segments",m.segments());snap.put("cycleNos",m.cycleNos());snap.put("settings",r.settings()); return get(reports.insert(r.name().trim(),JSON.toJSONString(snap),uid,user,System.currentTimeMillis()),null);}
 public Map<String,Object> get(long id,Integer version){var v=reports.get(id,version);Map<String,Object> x=JSON.parseObject(v.snapshotJson());x.put("id",id);x.put("versionNo",v.versionNo());x.put("creator",v.report().creator());x.put("createdAt",v.report().createdAt());x.put("operatorName",v.operatorName());x.put("versionCreatedAt",v.versionCreatedAt());x.put("changeSummary",v.changeSummary());return x;}
 @Transactional public Map<String,Object> edit(long id,SpliceReportRequest.Edit r,long uid,String user){reports.lock(id);var v=reports.get(id);if(v.versionNo()!=r.baseVersionNo())throw new ServiceException("报表已有新版本,请重新打开后编辑");var x=JSON.parseObject(v.snapshotJson());x.put("name",r.name().trim());x.put("description",r.description());x.put("settings",r.settings());reports.append(id,r.baseVersionNo()+1,r.name().trim(),x.toJSONString(),uid,user,r.changeSummary().trim());return get(id,null);}
 @Transactional public Map<String,Object> refresh(long id,int base,String summary,long uid,String user){reports.lock(id);var v=reports.get(id);if(v.versionNo()!=base)throw new ServiceException("报表已有新版本,请重新打开后编辑");var x=JSON.parseObject(v.snapshotJson());var selections=JSON.parseArray(x.getString("selections"),SpliceReportRequest.Selection.class);Matrix m=preview(selections);x.put("segments",m.segments());x.put("cycleNos",m.cycleNos());reports.append(id,base+1,x.getString("name"),x.toJSONString(),uid,user,"按最新来源生成:"+summary);return get(id,null);}
 public void delete(long id,long uid,String user){reports.lock(id);reports.delete(id,uid,user);} private Candidate candidate(TTest t,TTestBattery b){return new Candidate(new SpliceReportRequest.Selection(t.getTestId(),b.getTestBatteryId()),t.getTestName(),t.getTestTypeName(),b.getGroupId(),b.getGroupName(),b.getBatteryNo(),t.getStartedAtUtc()==null?0:t.getStartedAtUtc().getTime());} private int cycle(Map<String,Object> r){return r.get("cycleNo") instanceof Number n?n.intValue():0;}
}
ruoyi-admin/src/main/java/com/battery/business/web/SpliceReportController.java
New file
@@ -0,0 +1,18 @@
package com.battery.business.web;
import java.util.List; import com.battery.business.report.*; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*;
/** 拼接报表正式 API,所有来源与统计由后端校验并冻结。 */
@RestController @RequestMapping("/system/splice-report") public class SpliceReportController extends BaseController {
 private final SpliceReportService service; private final SpliceReportRepository reports; public SpliceReportController(SpliceReportService s,SpliceReportRepository r){service=s;reports=r;}
 @GetMapping("/sources") @PreAuthorize("@ss.hasPermi('system:splice-report:add')") public AjaxResult sources(){return success(service.sources());}
 @PostMapping("/preview") @PreAuthorize("@ss.hasPermi('system:splice-report:add')") public AjaxResult preview(@RequestBody List<SpliceReportRequest.Selection> s){return success(service.preview(s));}
 @GetMapping("/list") @PreAuthorize("@ss.hasPermi('system:splice-report:query')") public AjaxResult list(){return success().put("rows",reports.list());}
 @PostMapping @PreAuthorize("@ss.hasPermi('system:splice-report:add')") public AjaxResult create(@Valid @RequestBody SpliceReportRequest r){return success(service.create(r,getUserId(),getUsername()));}
 @GetMapping("/{id}") @PreAuthorize("@ss.hasPermi('system:splice-report:query')") public AjaxResult get(@PathVariable long id){return success(service.get(id,null));}
 @GetMapping("/{id}/editing") @PreAuthorize("@ss.hasPermi('system:splice-report:edit')") public AjaxResult editing(@PathVariable long id){return success(service.get(id,null));}
 @GetMapping("/{id}/versions") @PreAuthorize("@ss.hasPermi('system:splice-report:query')") public AjaxResult history(@PathVariable long id){return success(reports.history(id));}
 @GetMapping("/{id}/versions/{version}") @PreAuthorize("@ss.hasPermi('system:splice-report:query')") public AjaxResult version(@PathVariable long id,@PathVariable int version){return success(service.get(id,version));}
 @GetMapping("/{id}/versions/{version}/print") @PreAuthorize("@ss.hasPermi('system:splice-report:export')") public AjaxResult print(@PathVariable long id,@PathVariable int version){return success(service.get(id,version));}
 @PostMapping("/{id}/versions") @PreAuthorize("@ss.hasPermi('system:splice-report:edit')") public AjaxResult edit(@PathVariable long id,@Valid @RequestBody SpliceReportRequest.Edit r){return success(service.edit(id,r,getUserId(),getUsername()));}
 @PostMapping("/{id}/refresh-source") @PreAuthorize("@ss.hasPermi('system:splice-report:edit')") public AjaxResult refresh(@PathVariable long id,@RequestBody java.util.Map<String,Object> body){return success(service.refresh(id,((Number)body.get("baseVersionNo")).intValue(),String.valueOf(body.getOrDefault("changeSummary","")),getUserId(),getUsername()));}
 @DeleteMapping("/{id}") @PreAuthorize("@ss.hasPermi('system:splice-report:remove')") public AjaxResult delete(@PathVariable long id){service.delete(id,getUserId(),getUsername());return success();}
}
ruoyi-admin/src/main/resources/db/migration-final/V17__splice_reports.sql
New file
@@ -0,0 +1,7 @@
CREATE TABLE t_splice_report LIKE t_compare_report;
ALTER TABLE t_splice_report COMMENT='拼接报表身份';
CREATE TABLE t_splice_report_version LIKE t_compare_report_version;
ALTER TABLE t_splice_report_version COMMENT='拼接报表独立版本';
INSERT INTO sys_menu(menu_name,parent_id,order_num,path,component,route_name,menu_type,visible,status,perms,icon,create_by,create_time,remark)
SELECT '拼接报表导出', parent_id, 5, '#', 'F','0','0','0','analysis:splice-report:export','#','admin',UTC_TIMESTAMP(3),'拼接报表导出权限'
FROM sys_menu WHERE perms='analysis:splice-report:list' LIMIT 1;