From 06ed168bbe9bd100c460ae21a499dbaee04e5abb Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 22 五月 2026 10:52:04 +0800
Subject: [PATCH] feat(lims): 新增数据分析与展示功能

---
 src/main/java/com/ruoyi/lims/dto/LimsAnalysisOverviewDto.java              |   34 ++
 src/main/java/com/ruoyi/lims/dto/LimsTrendPointDto.java                    |   28 +
 src/main/java/com/ruoyi/lims/controller/LimsDataAnalysisController.java    |   51 +++
 src/main/java/com/ruoyi/lims/service/LimsDataAnalysisService.java          |   19 +
 src/main/java/com/ruoyi/lims/dto/LimsComparisonItemDto.java                |   28 +
 src/main/java/com/ruoyi/lims/dto/LimsDataAnalysisQueryDto.java             |   37 ++
 src/main/java/com/ruoyi/lims/dto/LimsDataAnalysisDashboardDto.java         |   38 ++
 src/main/resources/application-dev.yml                                     |  222 +++++++++++++
 doc/lims-data-analysis-front-integration.md                                |  150 ++++++++
 src/main/java/com/ruoyi/lims/dto/LimsQualityDistributionItemDto.java       |   22 +
 src/main/java/com/ruoyi/lims/service/impl/LimsDataAnalysisServiceImpl.java |  387 ++++++++++++++++++++++
 11 files changed, 1,016 insertions(+), 0 deletions(-)

diff --git a/doc/lims-data-analysis-front-integration.md b/doc/lims-data-analysis-front-integration.md
new file mode 100644
index 0000000..d87214e
--- /dev/null
+++ b/doc/lims-data-analysis-front-integration.md
@@ -0,0 +1,150 @@
+# LIMS 鏁版嵁鍒嗘瀽涓庡睍绀� - 鍓嶇鑱旇皟鏂囨。
+
+## 1. 妯″潡璇存槑
+
+鏈鍦� `lims` 妯″潡鏂板浜嗏�滄暟鎹垎鏋愪笌灞曠ず鈥濊兘鍔涳紝瑕嗙洊锛�
+
+- 鏁版嵁瓒嬪娍鍒嗘瀽锛堟寜澶�/鎸夊皬鏃讹級
+- 鏁版嵁姣旇緝鍒嗘瀽锛堟寜鏁版嵁绫诲瀷/璁惧锛�
+- 鏁版嵁璐ㄩ噺鍒嗗竷缁熻
+- 姒傝鎸囨爣鐪嬫澘锛堟�婚噺銆佸紓甯搞�佸悎鏍肩巼銆侀璀︾瓑锛�
+
+鎺ュ彛缁熶竴鍓嶇紑锛歚/lims/dataAnalysis`
+
+---
+
+## 2. 閴存潈涓庨�氱敤绾﹀畾
+
+1. 閴存潈鏂瑰紡锛氭部鐢ㄧ郴缁熺幇鏈� Token锛坄Authorization` 璇锋眰澶达級銆�
+2. 杩斿洖鏍煎紡锛歚AjaxResult`
+
+```json
+{
+  "code": 200,
+  "msg": "鎿嶄綔鎴愬姛",
+  "data": {}
+}
+```
+
+3. 鏃堕棿鍙傛暟鏍煎紡锛歚yyyy-MM-dd HH:mm:ss`
+4. 榛樿鏃堕棿鑼冨洿锛氳嫢 `startTime/endTime` 閮戒笉浼狅紝鍒欓粯璁ゆ渶杩� 7 澶┿��
+
+---
+
+## 3. 鏌ヨ鍙傛暟锛堥�氱敤锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|---|---|---|---|
+| startTime | string | 鍚� | 寮�濮嬫椂闂达紝鏍煎紡锛歚yyyy-MM-dd HH:mm:ss` |
+| endTime | string | 鍚� | 缁撴潫鏃堕棿锛屾牸寮忥細`yyyy-MM-dd HH:mm:ss` |
+| dataType | string | 鍚� | 鏁版嵁绫诲瀷锛歚temperature/humidity/pressure/flow/concentration` |
+| deviceCode | string | 鍚� | 璁惧缂栧彿 |
+| granularity | string | 鍚� | 瓒嬪娍绮掑害锛歚day/hour`锛岄粯璁� `day` |
+| dimension | string | 鍚� | 姣旇緝缁村害锛歚dataType/deviceName/deviceCode`锛岄粯璁� `dataType` |
+| topN | number | 鍚� | 姣旇緝鍒嗘瀽杩斿洖鏉℃暟锛岄粯璁� `10`锛屾渶澶� `50` |
+
+---
+
+## 4. 鎺ュ彛娓呭崟
+
+| 鎺ュ彛 | 鏂规硶 | 璇存槑 |
+|---|---|---|
+| `/lims/dataAnalysis/dashboard` | GET | 涓�娆¤繑鍥炵湅鏉垮叏閮ㄦ暟鎹紙鎺ㄨ崘锛� |
+| `/lims/dataAnalysis/overview` | GET | 浠呰繑鍥炴瑙堟寚鏍� |
+| `/lims/dataAnalysis/trend` | GET | 浠呰繑鍥炶秼鍔挎暟鎹� |
+| `/lims/dataAnalysis/comparison` | GET | 浠呰繑鍥炴瘮杈冨垎鏋愭暟鎹� |
+| `/lims/dataAnalysis/qualityDistribution` | GET | 浠呰繑鍥炶川閲忓垎甯冩暟鎹� |
+
+---
+
+## 5. 璇︾粏杩斿洖缁撴瀯
+
+### 5.1 `GET /lims/dataAnalysis/dashboard`
+
+`data` 缁撴瀯锛�
+
+```json
+{
+  "startTime": "2026-05-16 00:00:00",
+  "endTime": "2026-05-22 23:59:59",
+  "granularity": "day",
+  "dimension": "dataType",
+  "overview": {
+    "totalCollections": 1200,
+    "todayCollections": 210,
+    "abnormalCollections": 38,
+    "qualifiedRate": 94.17,
+    "inProgressExperiments": 6,
+    "warningMonitors": 3,
+    "inStockSamples": 168
+  },
+  "trend": [
+    {
+      "time": "2026-05-16",
+      "pointCount": 145,
+      "avgValue": 23.65,
+      "maxValue": 29.40,
+      "minValue": 20.10
+    }
+  ],
+  "comparison": [
+    {
+      "dimensionValue": "temperature",
+      "pointCount": 560,
+      "avgValue": 24.31,
+      "maxValue": 33.20,
+      "minValue": 18.90
+    }
+  ],
+  "qualityDistribution": [
+    {
+      "category": "qualified",
+      "pointCount": 1130,
+      "ratio": 94.17
+    },
+    {
+      "category": "abnormal",
+      "pointCount": 38,
+      "ratio": 3.17
+    },
+    {
+      "category": "pending",
+      "pointCount": 32,
+      "ratio": 2.67
+    }
+  ]
+}
+```
+
+瀛楁瑙i噴锛�
+
+- `overview.totalCollections`锛氭椂闂磋寖鍥村唴閲囬泦鎬绘潯鏁�  
+- `overview.todayCollections`锛氭渶杩� 24 灏忔椂閲囬泦鏉℃暟  
+- `overview.qualifiedRate`锛氬悎鏍肩巼锛堢櫨鍒嗘瘮锛�  
+- `trend`锛氭姌绾垮浘鏁版嵁婧�  
+- `comparison`锛氭煴鐘跺浘/妯悜鏉″舰鍥炬暟鎹簮  
+- `qualityDistribution`锛氶ゼ鍥炬暟鎹簮
+
+---
+
+## 6. 鍓嶇璋冪敤寤鸿
+
+1. 椤甸潰鍒濆鍖栦紭鍏堣皟鐢� `dashboard`锛屽噺灏戣姹傛鏁般��
+2. 鐢ㄦ埛鍒囨崲绛涢�夋潯浠讹紙鏃堕棿銆佽澶囥�佹暟鎹被鍨嬶級鍚庯紝閲嶆柊璇锋眰 `dashboard`銆�
+3. 鑻ラ〉闈㈠垎鍖哄姞杞斤紝鍙垎鍒皟 `overview/trend/comparison/qualityDistribution`銆�
+4. `trend` 宸茶ˉ榻愭椂闂磋酱绌烘。浣嶏紙鏃犳暟鎹繑鍥� 0锛夛紝鍙洿鎺ョ粯鍒惰繛缁姌绾裤��
+
+---
+
+## 7. 璋冪敤绀轰緥
+
+```bash
+curl -G 'http://localhost:8080/lims/dataAnalysis/dashboard' \
+  --data-urlencode 'startTime=2026-05-16 00:00:00' \
+  --data-urlencode 'endTime=2026-05-22 23:59:59' \
+  --data-urlencode 'granularity=day' \
+  --data-urlencode 'dimension=dataType' \
+  --data-urlencode 'topN=10' \
+  -H 'Authorization: Bearer <token>'
+```
+
diff --git a/src/main/java/com/ruoyi/lims/controller/LimsDataAnalysisController.java b/src/main/java/com/ruoyi/lims/controller/LimsDataAnalysisController.java
new file mode 100644
index 0000000..31575f3
--- /dev/null
+++ b/src/main/java/com/ruoyi/lims/controller/LimsDataAnalysisController.java
@@ -0,0 +1,51 @@
+package com.ruoyi.lims.controller;
+
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.lims.dto.LimsDataAnalysisQueryDto;
+import com.ruoyi.lims.service.LimsDataAnalysisService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/lims/dataAnalysis")
+@Api(value = "LimsDataAnalysis", tags = "鏁版嵁鍒嗘瀽涓庡睍绀�")
+public class LimsDataAnalysisController {
+
+    private final LimsDataAnalysisService limsDataAnalysisService;
+
+    @GetMapping("/dashboard")
+    @ApiOperation("鏁版嵁鍒嗘瀽鐪嬫澘")
+    public AjaxResult dashboard(LimsDataAnalysisQueryDto queryDto) {
+        return AjaxResult.success(limsDataAnalysisService.dashboard(queryDto));
+    }
+
+    @GetMapping("/overview")
+    @ApiOperation("鏁版嵁鍒嗘瀽姒傝")
+    public AjaxResult overview(LimsDataAnalysisQueryDto queryDto) {
+        return AjaxResult.success(limsDataAnalysisService.overview(queryDto));
+    }
+
+    @GetMapping("/trend")
+    @ApiOperation("鏁版嵁瓒嬪娍鍒嗘瀽")
+    public AjaxResult trend(LimsDataAnalysisQueryDto queryDto) {
+        return AjaxResult.success(limsDataAnalysisService.trend(queryDto));
+    }
+
+    @GetMapping("/comparison")
+    @ApiOperation("鏁版嵁姣旇緝鍒嗘瀽")
+    public AjaxResult comparison(LimsDataAnalysisQueryDto queryDto) {
+        return AjaxResult.success(limsDataAnalysisService.comparison(queryDto));
+    }
+
+    @GetMapping("/qualityDistribution")
+    @ApiOperation("鏁版嵁璐ㄩ噺鍒嗗竷")
+    public AjaxResult qualityDistribution(LimsDataAnalysisQueryDto queryDto) {
+        return AjaxResult.success(limsDataAnalysisService.qualityDistribution(queryDto));
+    }
+}
+
diff --git a/src/main/java/com/ruoyi/lims/dto/LimsAnalysisOverviewDto.java b/src/main/java/com/ruoyi/lims/dto/LimsAnalysisOverviewDto.java
new file mode 100644
index 0000000..ee7dff7
--- /dev/null
+++ b/src/main/java/com/ruoyi/lims/dto/LimsAnalysisOverviewDto.java
@@ -0,0 +1,34 @@
+package com.ruoyi.lims.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@ApiModel(description = "LIMS鏁版嵁鍒嗘瀽姒傝")
+public class LimsAnalysisOverviewDto {
+
+    @ApiModelProperty(value = "閲囬泦鎬绘潯鏁�")
+    private Long totalCollections;
+
+    @ApiModelProperty(value = "鏈�杩�24灏忔椂閲囬泦鏉℃暟")
+    private Long todayCollections;
+
+    @ApiModelProperty(value = "寮傚父鏁版嵁鏉℃暟")
+    private Long abnormalCollections;
+
+    @ApiModelProperty(value = "鏁版嵁鍚堟牸鐜�(%)")
+    private BigDecimal qualifiedRate;
+
+    @ApiModelProperty(value = "杩涜涓疄楠屾暟")
+    private Long inProgressExperiments;
+
+    @ApiModelProperty(value = "棰勮鐩戞帶鏁�")
+    private Long warningMonitors;
+
+    @ApiModelProperty(value = "鍦ㄥ簱鏍峰搧鏁�")
+    private Long inStockSamples;
+}
+
diff --git a/src/main/java/com/ruoyi/lims/dto/LimsComparisonItemDto.java b/src/main/java/com/ruoyi/lims/dto/LimsComparisonItemDto.java
new file mode 100644
index 0000000..9b357dc
--- /dev/null
+++ b/src/main/java/com/ruoyi/lims/dto/LimsComparisonItemDto.java
@@ -0,0 +1,28 @@
+package com.ruoyi.lims.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@ApiModel(description = "LIMS姣旇緝鍒嗘瀽椤�")
+public class LimsComparisonItemDto {
+
+    @ApiModelProperty(value = "缁村害鍊�")
+    private String dimensionValue;
+
+    @ApiModelProperty(value = "鏁版嵁鐐规暟閲�")
+    private Long pointCount;
+
+    @ApiModelProperty(value = "骞冲潎鍊�")
+    private BigDecimal avgValue;
+
+    @ApiModelProperty(value = "鏈�澶у��")
+    private BigDecimal maxValue;
+
+    @ApiModelProperty(value = "鏈�灏忓��")
+    private BigDecimal minValue;
+}
+
diff --git a/src/main/java/com/ruoyi/lims/dto/LimsDataAnalysisDashboardDto.java b/src/main/java/com/ruoyi/lims/dto/LimsDataAnalysisDashboardDto.java
new file mode 100644
index 0000000..59d9e32
--- /dev/null
+++ b/src/main/java/com/ruoyi/lims/dto/LimsDataAnalysisDashboardDto.java
@@ -0,0 +1,38 @@
+package com.ruoyi.lims.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@ApiModel(description = "LIMS鏁版嵁鍒嗘瀽鐪嬫澘")
+public class LimsDataAnalysisDashboardDto {
+
+    @ApiModelProperty(value = "鍒嗘瀽寮�濮嬫椂闂�")
+    private LocalDateTime startTime;
+
+    @ApiModelProperty(value = "鍒嗘瀽缁撴潫鏃堕棿")
+    private LocalDateTime endTime;
+
+    @ApiModelProperty(value = "瓒嬪娍绮掑害")
+    private String granularity;
+
+    @ApiModelProperty(value = "姣旇緝缁村害")
+    private String dimension;
+
+    @ApiModelProperty(value = "姒傝鏁版嵁")
+    private LimsAnalysisOverviewDto overview;
+
+    @ApiModelProperty(value = "瓒嬪娍鏁版嵁")
+    private List<LimsTrendPointDto> trend;
+
+    @ApiModelProperty(value = "姣旇緝鏁版嵁")
+    private List<LimsComparisonItemDto> comparison;
+
+    @ApiModelProperty(value = "璐ㄩ噺鍒嗗竷鏁版嵁")
+    private List<LimsQualityDistributionItemDto> qualityDistribution;
+}
+
diff --git a/src/main/java/com/ruoyi/lims/dto/LimsDataAnalysisQueryDto.java b/src/main/java/com/ruoyi/lims/dto/LimsDataAnalysisQueryDto.java
new file mode 100644
index 0000000..0e42185
--- /dev/null
+++ b/src/main/java/com/ruoyi/lims/dto/LimsDataAnalysisQueryDto.java
@@ -0,0 +1,37 @@
+package com.ruoyi.lims.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+@Data
+@ApiModel(description = "LIMS鏁版嵁鍒嗘瀽鏌ヨ鍙傛暟")
+public class LimsDataAnalysisQueryDto {
+
+    @ApiModelProperty(value = "寮�濮嬫椂闂达紝鏍煎紡锛歽yyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime startTime;
+
+    @ApiModelProperty(value = "缁撴潫鏃堕棿锛屾牸寮忥細yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime endTime;
+
+    @ApiModelProperty(value = "鏁版嵁绫诲瀷锛歵emperature/humidity/pressure/flow/concentration")
+    private String dataType;
+
+    @ApiModelProperty(value = "璁惧缂栧彿")
+    private String deviceCode;
+
+    @ApiModelProperty(value = "瓒嬪娍绮掑害锛歞ay/hour锛岄粯璁ay")
+    private String granularity;
+
+    @ApiModelProperty(value = "姣旇緝缁村害锛歞ataType/deviceName/deviceCode锛岄粯璁ataType")
+    private String dimension;
+
+    @ApiModelProperty(value = "姣旇緝鍒嗘瀽杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�50")
+    private Integer topN;
+}
+
diff --git a/src/main/java/com/ruoyi/lims/dto/LimsQualityDistributionItemDto.java b/src/main/java/com/ruoyi/lims/dto/LimsQualityDistributionItemDto.java
new file mode 100644
index 0000000..1afe724
--- /dev/null
+++ b/src/main/java/com/ruoyi/lims/dto/LimsQualityDistributionItemDto.java
@@ -0,0 +1,22 @@
+package com.ruoyi.lims.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@ApiModel(description = "LIMS璐ㄩ噺鍒嗗竷椤�")
+public class LimsQualityDistributionItemDto {
+
+    @ApiModelProperty(value = "鏁版嵁璐ㄩ噺鍒嗙被")
+    private String category;
+
+    @ApiModelProperty(value = "鏁伴噺")
+    private Long pointCount;
+
+    @ApiModelProperty(value = "鍗犳瘮(%)")
+    private BigDecimal ratio;
+}
+
diff --git a/src/main/java/com/ruoyi/lims/dto/LimsTrendPointDto.java b/src/main/java/com/ruoyi/lims/dto/LimsTrendPointDto.java
new file mode 100644
index 0000000..6cbab7b
--- /dev/null
+++ b/src/main/java/com/ruoyi/lims/dto/LimsTrendPointDto.java
@@ -0,0 +1,28 @@
+package com.ruoyi.lims.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@ApiModel(description = "LIMS瓒嬪娍鍒嗘瀽鐐�")
+public class LimsTrendPointDto {
+
+    @ApiModelProperty(value = "鏃堕棿缁村害鍊�")
+    private String time;
+
+    @ApiModelProperty(value = "鏁版嵁鐐规暟閲�")
+    private Long pointCount;
+
+    @ApiModelProperty(value = "骞冲潎鍊�")
+    private BigDecimal avgValue;
+
+    @ApiModelProperty(value = "鏈�澶у��")
+    private BigDecimal maxValue;
+
+    @ApiModelProperty(value = "鏈�灏忓��")
+    private BigDecimal minValue;
+}
+
diff --git a/src/main/java/com/ruoyi/lims/service/LimsDataAnalysisService.java b/src/main/java/com/ruoyi/lims/service/LimsDataAnalysisService.java
new file mode 100644
index 0000000..efa38ce
--- /dev/null
+++ b/src/main/java/com/ruoyi/lims/service/LimsDataAnalysisService.java
@@ -0,0 +1,19 @@
+package com.ruoyi.lims.service;
+
+import com.ruoyi.lims.dto.*;
+
+import java.util.List;
+
+public interface LimsDataAnalysisService {
+
+    LimsDataAnalysisDashboardDto dashboard(LimsDataAnalysisQueryDto queryDto);
+
+    LimsAnalysisOverviewDto overview(LimsDataAnalysisQueryDto queryDto);
+
+    List<LimsTrendPointDto> trend(LimsDataAnalysisQueryDto queryDto);
+
+    List<LimsComparisonItemDto> comparison(LimsDataAnalysisQueryDto queryDto);
+
+    List<LimsQualityDistributionItemDto> qualityDistribution(LimsDataAnalysisQueryDto queryDto);
+}
+
diff --git a/src/main/java/com/ruoyi/lims/service/impl/LimsDataAnalysisServiceImpl.java b/src/main/java/com/ruoyi/lims/service/impl/LimsDataAnalysisServiceImpl.java
new file mode 100644
index 0000000..6789689
--- /dev/null
+++ b/src/main/java/com/ruoyi/lims/service/impl/LimsDataAnalysisServiceImpl.java
@@ -0,0 +1,387 @@
+package com.ruoyi.lims.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.ruoyi.lims.dto.*;
+import com.ruoyi.lims.mapper.DataCollectionMapper;
+import com.ruoyi.lims.mapper.ExperimentMapper;
+import com.ruoyi.lims.mapper.RealtimeMonitorMapper;
+import com.ruoyi.lims.mapper.SampleMapper;
+import com.ruoyi.lims.pojo.DataCollection;
+import com.ruoyi.lims.pojo.Experiment;
+import com.ruoyi.lims.pojo.RealtimeMonitor;
+import com.ruoyi.lims.pojo.Sample;
+import com.ruoyi.lims.service.LimsDataAnalysisService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
+public class LimsDataAnalysisServiceImpl implements LimsDataAnalysisService {
+
+    private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter HOUR_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:00:00");
+
+    private final DataCollectionMapper dataCollectionMapper;
+    private final ExperimentMapper experimentMapper;
+    private final RealtimeMonitorMapper realtimeMonitorMapper;
+    private final SampleMapper sampleMapper;
+
+    @Override
+    public LimsDataAnalysisDashboardDto dashboard(LimsDataAnalysisQueryDto queryDto) {
+        LimsDataAnalysisQueryDto query = normalizeQuery(queryDto);
+        LimsDataAnalysisDashboardDto dashboardDto = new LimsDataAnalysisDashboardDto();
+        dashboardDto.setStartTime(query.getStartTime());
+        dashboardDto.setEndTime(query.getEndTime());
+        dashboardDto.setGranularity(query.getGranularity());
+        dashboardDto.setDimension(query.getDimension());
+        dashboardDto.setOverview(buildOverview(query));
+        dashboardDto.setTrend(buildTrend(query));
+        dashboardDto.setComparison(buildComparison(query));
+        dashboardDto.setQualityDistribution(buildQualityDistribution(query));
+        return dashboardDto;
+    }
+
+    @Override
+    public LimsAnalysisOverviewDto overview(LimsDataAnalysisQueryDto queryDto) {
+        return buildOverview(normalizeQuery(queryDto));
+    }
+
+    @Override
+    public List<LimsTrendPointDto> trend(LimsDataAnalysisQueryDto queryDto) {
+        return buildTrend(normalizeQuery(queryDto));
+    }
+
+    @Override
+    public List<LimsComparisonItemDto> comparison(LimsDataAnalysisQueryDto queryDto) {
+        return buildComparison(normalizeQuery(queryDto));
+    }
+
+    @Override
+    public List<LimsQualityDistributionItemDto> qualityDistribution(LimsDataAnalysisQueryDto queryDto) {
+        return buildQualityDistribution(normalizeQuery(queryDto));
+    }
+
+    private LimsAnalysisOverviewDto buildOverview(LimsDataAnalysisQueryDto query) {
+        QueryWrapper<DataCollection> totalWrapper = createDataCollectionFilter(query);
+        Long totalCollections = dataCollectionMapper.selectCount(totalWrapper);
+
+        QueryWrapper<DataCollection> abnormalWrapper = createDataCollectionFilter(query);
+        abnormalWrapper.eq("data_quality", "abnormal");
+        Long abnormalCollections = dataCollectionMapper.selectCount(abnormalWrapper);
+
+        QueryWrapper<DataCollection> qualifiedWrapper = createDataCollectionFilter(query);
+        qualifiedWrapper.eq("data_quality", "qualified");
+        Long qualifiedCollections = dataCollectionMapper.selectCount(qualifiedWrapper);
+
+        QueryWrapper<DataCollection> todayWrapper = new QueryWrapper<>();
+        todayWrapper.eq("del_flag", "0")
+                .ge("collection_time", LocalDateTime.now().minusHours(24))
+                .le("collection_time", LocalDateTime.now());
+        Long todayCollections = dataCollectionMapper.selectCount(todayWrapper);
+
+        Long inProgressExperiments = experimentMapper.selectCount(new QueryWrapper<Experiment>()
+                .eq("del_flag", "0")
+                .eq("experiment_status", "inProgress"));
+
+        Long warningMonitors = realtimeMonitorMapper.selectCount(new QueryWrapper<RealtimeMonitor>()
+                .eq("del_flag", "0")
+                .in("alert_status", Arrays.asList("warning", "alert")));
+
+        Long inStockSamples = sampleMapper.selectCount(new QueryWrapper<Sample>()
+                .eq("del_flag", "0")
+                .eq("sample_status", "inStock"));
+
+        LimsAnalysisOverviewDto dto = new LimsAnalysisOverviewDto();
+        dto.setTotalCollections(defaultLong(totalCollections));
+        dto.setTodayCollections(defaultLong(todayCollections));
+        dto.setAbnormalCollections(defaultLong(abnormalCollections));
+        dto.setInProgressExperiments(defaultLong(inProgressExperiments));
+        dto.setWarningMonitors(defaultLong(warningMonitors));
+        dto.setInStockSamples(defaultLong(inStockSamples));
+        dto.setQualifiedRate(calculateRate(qualifiedCollections, totalCollections));
+        return dto;
+    }
+
+    private List<LimsTrendPointDto> buildTrend(LimsDataAnalysisQueryDto query) {
+        String groupExpression = resolveTimeGroupExpression(query.getGranularity());
+        QueryWrapper<DataCollection> wrapper = createDataCollectionFilter(query);
+        wrapper.select(
+                        groupExpression + " AS time_key",
+                        "COUNT(1) AS point_count",
+                        "AVG(collection_value) AS avg_value",
+                        "MAX(collection_value) AS max_value",
+                        "MIN(collection_value) AS min_value"
+                )
+                .groupBy(groupExpression)
+                .orderByAsc("time_key");
+
+        List<Map<String, Object>> rawRows = dataCollectionMapper.selectMaps(wrapper);
+        Map<String, LimsTrendPointDto> trendMap = rawRows.stream().map(this::mapToTrendPoint)
+                .collect(Collectors.toMap(LimsTrendPointDto::getTime, item -> item, (a, b) -> b));
+
+        List<String> axis = buildTimeAxis(query);
+        List<LimsTrendPointDto> result = new ArrayList<>();
+        for (String key : axis) {
+            LimsTrendPointDto point = trendMap.get(key);
+            if (point == null) {
+                point = new LimsTrendPointDto();
+                point.setTime(key);
+                point.setPointCount(0L);
+                point.setAvgValue(BigDecimal.ZERO);
+                point.setMaxValue(BigDecimal.ZERO);
+                point.setMinValue(BigDecimal.ZERO);
+            }
+            result.add(point);
+        }
+        return result;
+    }
+
+    private List<LimsComparisonItemDto> buildComparison(LimsDataAnalysisQueryDto query) {
+        String dimensionColumn = resolveDimensionColumn(query.getDimension());
+
+        QueryWrapper<DataCollection> wrapper = createDataCollectionFilter(query);
+        wrapper.isNotNull(dimensionColumn)
+                .ne(dimensionColumn, "")
+                .select(
+                        dimensionColumn + " AS dimension_value",
+                        "COUNT(1) AS point_count",
+                        "AVG(collection_value) AS avg_value",
+                        "MAX(collection_value) AS max_value",
+                        "MIN(collection_value) AS min_value"
+                )
+                .groupBy(dimensionColumn);
+
+        List<LimsComparisonItemDto> result = dataCollectionMapper.selectMaps(wrapper).stream()
+                .map(this::mapToComparisonItem)
+                .sorted(Comparator.comparing(LimsComparisonItemDto::getPointCount, Comparator.nullsLast(Long::compareTo)).reversed())
+                .collect(Collectors.toList());
+
+        int limit = query.getTopN() == null ? 10 : query.getTopN();
+        if (result.size() > limit) {
+            return result.subList(0, limit);
+        }
+        return result;
+    }
+
+    private List<LimsQualityDistributionItemDto> buildQualityDistribution(LimsDataAnalysisQueryDto query) {
+        QueryWrapper<DataCollection> wrapper = createDataCollectionFilter(query);
+        wrapper.isNotNull("data_quality")
+                .ne("data_quality", "")
+                .select("data_quality AS category", "COUNT(1) AS point_count")
+                .groupBy("data_quality");
+
+        List<LimsQualityDistributionItemDto> result = dataCollectionMapper.selectMaps(wrapper).stream()
+                .map(this::mapToQualityDistributionItem)
+                .sorted(Comparator.comparing(LimsQualityDistributionItemDto::getPointCount, Comparator.nullsLast(Long::compareTo)).reversed())
+                .collect(Collectors.toList());
+
+        long total = result.stream().map(LimsQualityDistributionItemDto::getPointCount).filter(Objects::nonNull).mapToLong(Long::longValue).sum();
+        for (LimsQualityDistributionItemDto item : result) {
+            item.setRatio(calculateRate(item.getPointCount(), total));
+        }
+        return result;
+    }
+
+    private QueryWrapper<DataCollection> createDataCollectionFilter(LimsDataAnalysisQueryDto query) {
+        QueryWrapper<DataCollection> wrapper = new QueryWrapper<>();
+        wrapper.eq("del_flag", "0")
+                .ge("collection_time", query.getStartTime())
+                .le("collection_time", query.getEndTime());
+        if (StringUtils.hasText(query.getDataType())) {
+            wrapper.eq("data_type", query.getDataType().trim());
+        }
+        if (StringUtils.hasText(query.getDeviceCode())) {
+            wrapper.eq("device_code", query.getDeviceCode().trim());
+        }
+        return wrapper;
+    }
+
+    private String resolveTimeGroupExpression(String granularity) {
+        if ("hour".equalsIgnoreCase(granularity)) {
+            return "DATE_FORMAT(collection_time, '%Y-%m-%d %H:00:00')";
+        }
+        return "DATE_FORMAT(collection_time, '%Y-%m-%d')";
+    }
+
+    private String resolveDimensionColumn(String dimension) {
+        if ("deviceName".equalsIgnoreCase(dimension)) {
+            return "device_name";
+        }
+        if ("deviceCode".equalsIgnoreCase(dimension)) {
+            return "device_code";
+        }
+        return "data_type";
+    }
+
+    private List<String> buildTimeAxis(LimsDataAnalysisQueryDto query) {
+        List<String> axis = new ArrayList<>();
+        if ("hour".equalsIgnoreCase(query.getGranularity())) {
+            LocalDateTime cursor = query.getStartTime().withMinute(0).withSecond(0).withNano(0);
+            LocalDateTime end = query.getEndTime().withMinute(0).withSecond(0).withNano(0);
+            while (!cursor.isAfter(end)) {
+                axis.add(cursor.format(HOUR_FORMATTER));
+                cursor = cursor.plusHours(1);
+            }
+            return axis;
+        }
+        LocalDate cursor = query.getStartTime().toLocalDate();
+        LocalDate end = query.getEndTime().toLocalDate();
+        while (!cursor.isAfter(end)) {
+            axis.add(cursor.format(DAY_FORMATTER));
+            cursor = cursor.plusDays(1);
+        }
+        return axis;
+    }
+
+    private LimsTrendPointDto mapToTrendPoint(Map<String, Object> row) {
+        LimsTrendPointDto point = new LimsTrendPointDto();
+        point.setTime(asString(readIgnoreCase(row, "time_key")));
+        point.setPointCount(defaultLong(asLong(readIgnoreCase(row, "point_count"))));
+        point.setAvgValue(scale(asBigDecimal(readIgnoreCase(row, "avg_value"))));
+        point.setMaxValue(scale(asBigDecimal(readIgnoreCase(row, "max_value"))));
+        point.setMinValue(scale(asBigDecimal(readIgnoreCase(row, "min_value"))));
+        return point;
+    }
+
+    private LimsComparisonItemDto mapToComparisonItem(Map<String, Object> row) {
+        LimsComparisonItemDto item = new LimsComparisonItemDto();
+        item.setDimensionValue(asString(readIgnoreCase(row, "dimension_value")));
+        item.setPointCount(defaultLong(asLong(readIgnoreCase(row, "point_count"))));
+        item.setAvgValue(scale(asBigDecimal(readIgnoreCase(row, "avg_value"))));
+        item.setMaxValue(scale(asBigDecimal(readIgnoreCase(row, "max_value"))));
+        item.setMinValue(scale(asBigDecimal(readIgnoreCase(row, "min_value"))));
+        return item;
+    }
+
+    private LimsQualityDistributionItemDto mapToQualityDistributionItem(Map<String, Object> row) {
+        LimsQualityDistributionItemDto item = new LimsQualityDistributionItemDto();
+        item.setCategory(asString(readIgnoreCase(row, "category")));
+        item.setPointCount(defaultLong(asLong(readIgnoreCase(row, "point_count"))));
+        item.setRatio(BigDecimal.ZERO);
+        return item;
+    }
+
+    private Object readIgnoreCase(Map<String, Object> row, String key) {
+        if (row.containsKey(key)) {
+            return row.get(key);
+        }
+        for (Map.Entry<String, Object> entry : row.entrySet()) {
+            if (entry.getKey() != null && entry.getKey().equalsIgnoreCase(key)) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+
+    private BigDecimal calculateRate(Number part, Number total) {
+        if (part == null || total == null || total.longValue() == 0L) {
+            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+        }
+        return BigDecimal.valueOf(part.longValue())
+                .multiply(BigDecimal.valueOf(100))
+                .divide(BigDecimal.valueOf(total.longValue()), 2, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal scale(BigDecimal value) {
+        if (value == null) {
+            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+        }
+        return value.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private String asString(Object value) {
+        return value == null ? "" : String.valueOf(value);
+    }
+
+    private Long asLong(Object value) {
+        if (value == null) {
+            return 0L;
+        }
+        if (value instanceof Number) {
+            return ((Number) value).longValue();
+        }
+        return Long.parseLong(String.valueOf(value));
+    }
+
+    private BigDecimal asBigDecimal(Object value) {
+        if (value == null) {
+            return BigDecimal.ZERO;
+        }
+        if (value instanceof BigDecimal) {
+            return (BigDecimal) value;
+        }
+        if (value instanceof Number) {
+            return BigDecimal.valueOf(((Number) value).doubleValue());
+        }
+        return new BigDecimal(String.valueOf(value));
+    }
+
+    private Long defaultLong(Long value) {
+        return value == null ? 0L : value;
+    }
+
+    private LimsDataAnalysisQueryDto normalizeQuery(LimsDataAnalysisQueryDto source) {
+        LimsDataAnalysisQueryDto query = new LimsDataAnalysisQueryDto();
+        if (source != null) {
+            query.setStartTime(source.getStartTime());
+            query.setEndTime(source.getEndTime());
+            query.setDataType(source.getDataType());
+            query.setDeviceCode(source.getDeviceCode());
+            query.setGranularity(source.getGranularity());
+            query.setDimension(source.getDimension());
+            query.setTopN(source.getTopN());
+        }
+
+        LocalDateTime now = LocalDateTime.now().withSecond(0).withNano(0);
+        if (query.getStartTime() == null && query.getEndTime() == null) {
+            query.setEndTime(now);
+            query.setStartTime(now.minusDays(6).withHour(0).withMinute(0));
+        } else if (query.getStartTime() == null) {
+            query.setStartTime(query.getEndTime().minusDays(6).withHour(0).withMinute(0));
+        } else if (query.getEndTime() == null) {
+            query.setEndTime(query.getStartTime().plusDays(6).withHour(23).withMinute(59));
+        }
+
+        if (query.getStartTime().isAfter(query.getEndTime())) {
+            LocalDateTime temp = query.getStartTime();
+            query.setStartTime(query.getEndTime());
+            query.setEndTime(temp);
+        }
+
+        if (!"hour".equalsIgnoreCase(query.getGranularity()) && !"day".equalsIgnoreCase(query.getGranularity())) {
+            query.setGranularity("day");
+        } else {
+            query.setGranularity(query.getGranularity().toLowerCase(Locale.ROOT));
+        }
+
+        if (!"deviceName".equalsIgnoreCase(query.getDimension())
+                && !"deviceCode".equalsIgnoreCase(query.getDimension())
+                && !"dataType".equalsIgnoreCase(query.getDimension())) {
+            query.setDimension("dataType");
+        } else if ("devicename".equalsIgnoreCase(query.getDimension())) {
+            query.setDimension("deviceName");
+        } else if ("devicecode".equalsIgnoreCase(query.getDimension())) {
+            query.setDimension("deviceCode");
+        } else {
+            query.setDimension("dataType");
+        }
+
+        if (query.getTopN() == null || query.getTopN() <= 0) {
+            query.setTopN(10);
+        } else if (query.getTopN() > 50) {
+            query.setTopN(50);
+        }
+        return query;
+    }
+}
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
new file mode 100644
index 0000000..d2a0141
--- /dev/null
+++ b/src/main/resources/application-dev.yml
@@ -0,0 +1,222 @@
+# 鑺-浠撳偍鐗╂祦绯荤粺椤圭洰鐩稿叧閰嶇疆
+inspur:
+  appId: a3b0e3f1-7210-4ed0-8eed-c6c443e04e36
+  appSecret: 7aab6b10061962e861ab69596eec6c037daffd2b56cdf518df8554ff56daa9c8
+ruoyi:
+  # 鍚嶇О
+  name: RuoYi
+  # 鐗堟湰
+  version: 3.8.9
+  # 鐗堟潈骞翠唤
+  copyrightYear: 2025
+  # 鏂囦欢璺緞 绀轰緥锛� Windows閰嶇疆D:/ruoyi/uploadPath锛孡inux閰嶇疆 /home/ruoyi/uploadPath锛�
+  profile: /center-lims/mis/file
+
+  # 鑾峰彇ip鍦板潃寮�鍏�
+  addressEnabled: false
+  # 楠岃瘉鐮佺被鍨� math 鏁板瓧璁$畻 char 瀛楃楠岃瘉
+  captchaType: math
+
+# 寮�鍙戠幆澧冮厤缃�
+server:
+  # 鏈嶅姟鍣ㄧ殑HTTP绔彛锛岄粯璁や负8080
+  port: 8080
+  servlet:
+    # 搴旂敤鐨勮闂矾寰�
+    context-path: /
+  tomcat:
+    # tomcat鐨刄RI缂栫爜
+    uri-encoding: UTF-8
+    # 杩炴帴鏁版弧鍚庣殑鎺掗槦鏁帮紝榛樿涓�100
+    accept-count: 1000
+    threads:
+      # tomcat鏈�澶х嚎绋嬫暟锛岄粯璁や负200
+      max: 800
+      # Tomcat鍚姩鍒濆鍖栫殑绾跨▼鏁帮紝榛樿鍊�10
+      min-spare: 100
+
+# 鏃ュ織閰嶇疆
+logging:
+  level:
+    com.ruoyi: warn
+    org.springframework: warn
+
+minio:
+  endpoint: http://114.132.189.42/
+  port: 7019
+  secure: false
+  accessKey: admin
+  secretKey: 12345678
+  preview-expiry: 24 # 棰勮鍦板潃榛樿24灏忔椂
+  default-bucket: uploadPath
+# 鐢ㄦ埛閰嶇疆
+user:
+  password:
+    # 瀵嗙爜鏈�澶ч敊璇鏁�
+    maxRetryCount: 5
+    # 瀵嗙爜閿佸畾鏃堕棿锛堥粯璁�10鍒嗛挓锛�
+    lockTime: 10
+
+# Spring閰嶇疆
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 涓诲簱鏁版嵁婧�
+      master:
+        url: jdbc:mysql://127.0.0.1:3306/lims-ruoyi?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+        username: root
+        password: 123456
+      # 浠庡簱鏁版嵁婧�
+      slave:
+        # 浠庢暟鎹簮寮�鍏�/榛樿鍏抽棴
+        enabled: false
+        url:
+        username:
+        password:
+      # 鍒濆杩炴帴鏁�
+      initialSize: 5
+      # 鏈�灏忚繛鎺ユ睜鏁伴噺
+      minIdle: 10
+      # 鏈�澶ц繛鎺ユ睜鏁伴噺
+      maxActive: 20
+      # 閰嶇疆鑾峰彇杩炴帴绛夊緟瓒呮椂鐨勬椂闂�
+      maxWait: 60000
+      # 閰嶇疆杩炴帴瓒呮椂鏃堕棿
+      connectTimeout: 30000
+      # 閰嶇疆缃戠粶瓒呮椂鏃堕棿
+      socketTimeout: 60000
+      # 閰嶇疆闂撮殧澶氫箙鎵嶈繘琛屼竴娆℃娴嬶紝妫�娴嬮渶瑕佸叧闂殑绌洪棽杩炴帴锛屽崟浣嶆槸姣
+      timeBetweenEvictionRunsMillis: 60000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�灏忕敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      minEvictableIdleTimeMillis: 300000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�澶х敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      maxEvictableIdleTimeMillis: 900000
+      # 閰嶇疆妫�娴嬭繛鎺ユ槸鍚︽湁鏁�
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 璁剧疆鐧藉悕鍗曪紝涓嶅~鍒欏厑璁告墍鏈夎闂�
+        allow:
+        url-pattern: /druid/*
+        # 鎺у埗鍙扮鐞嗙敤鎴峰悕鍜屽瘑鐮�
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 鎱QL璁板綍
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+  # 璧勬簮淇℃伅
+  messages:
+    # 鍥介檯鍖栬祫婧愭枃浠惰矾寰�
+    basename: i18n/messages
+  # 鏂囦欢涓婁紶
+  servlet:
+    multipart:
+      # 鍗曚釜鏂囦欢澶у皬
+      max-file-size: 1GB
+      # 璁剧疆鎬讳笂浼犵殑鏂囦欢澶у皬
+      max-request-size: 2GB
+  # 鏈嶅姟妯″潡
+  devtools:
+    restart:
+      # 鐑儴缃插紑鍏�
+      enabled: false
+  # redis 閰嶇疆
+  redis:
+    # 鍦板潃
+    host: 127.0.0.1
+#    host: 172.17.0.1
+    # 绔彛锛岄粯璁や负6379
+    port: 6379
+    # 鏁版嵁搴撶储寮�
+    database: 0
+    # 瀵嗙爜
+    password:
+#    password: 123456
+
+    # 杩炴帴瓒呮椂鏃堕棿
+    timeout: 10s
+    lettuce:
+      pool:
+        # 杩炴帴姹犱腑鐨勬渶灏忕┖闂茶繛鎺�
+        min-idle: 0
+        # 杩炴帴姹犱腑鐨勬渶澶х┖闂茶繛鎺�
+        max-idle: 8
+        # 杩炴帴姹犵殑鏈�澶ф暟鎹簱杩炴帴鏁�
+        max-active: 8
+        # #杩炴帴姹犳渶澶ч樆濉炵瓑寰呮椂闂达紙浣跨敤璐熷�艰〃绀烘病鏈夐檺鍒讹級
+        max-wait: -1ms
+
+# token閰嶇疆
+token:
+  # 浠ょ墝鑷畾涔夋爣璇�
+  header: Authorization
+  # 浠ょ墝瀵嗛挜
+  secret: abcdefghijklmnopqrstuvwxyz
+  # 浠ょ墝鏈夋晥鏈燂紙榛樿30鍒嗛挓锛�
+  expireTime: 450
+  
+# MyBatis Plus閰嶇疆
+mybatis-plus:
+  # 鎼滅储鎸囧畾鍖呭埆鍚�   鏍规嵁鑷繁鐨勯」鐩潵
+  typeAliasesPackage: com.ruoyi.**.pojo
+  # 閰嶇疆mapper鐨勬壂鎻忥紝鎵惧埌鎵�鏈夌殑mapper.xml鏄犲皠鏂囦欢
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 鍔犺浇鍏ㄥ眬鐨勯厤缃枃浠�
+  configLocation: classpath:mybatis/mybatis-config.xml
+  global-config:
+    enable-sql-runner: true
+    db-config:
+      id-type: auto
+  
+# PageHelper鍒嗛〉鎻掍欢
+pagehelper:
+  helperDialect: mysql
+  supportMethodsArguments: true
+  params: count=countSql
+
+# Swagger閰嶇疆
+swagger:
+  # 鏄惁寮�鍚痵wagger
+  enabled: false
+  # 璇锋眰鍓嶇紑
+  pathMapping: /dev-api
+
+# 闃叉XSS鏀诲嚮
+xss:
+  # 杩囨护寮�鍏�
+  enabled: true
+  # 鎺掗櫎閾炬帴锛堝涓敤閫楀彿鍒嗛殧锛�
+  excludes: /system/notice
+  # 鍖归厤閾炬帴
+  urlPatterns: /system/*,/monitor/*,/tool/*
+  
+# 浠g爜鐢熸垚
+gen:
+  # 浣滆��
+  author: ruoyi
+  # 榛樿鐢熸垚鍖呰矾寰� system 闇�鏀规垚鑷繁鐨勬ā鍧楀悕绉� 濡� system monitor tool
+  packageName: com.ruoyi.project.system
+  # 鑷姩鍘婚櫎琛ㄥ墠缂�锛岄粯璁ゆ槸true
+  autoRemovePre: false
+  # 琛ㄥ墠缂�锛堢敓鎴愮被鍚嶄笉浼氬寘鍚〃鍓嶇紑锛屽涓敤閫楀彿鍒嗛殧锛�
+  tablePrefix: sys_
+  # 鏄惁鍏佽鐢熸垚鏂囦欢瑕嗙洊鍒版湰鍦帮紙鑷畾涔夎矾寰勶級锛岄粯璁や笉鍏佽
+  allowOverwrite: false
+
+file:
+  temp-dir: /center-lims/mis/file/temp/uploads
+  upload-dir: /center-lims/mis/file/prod/uploads
\ No newline at end of file

--
Gitblit v1.9.3