Procházet zdrojové kódy

实现通用基础模块

- 统一响应格式 R<T>
- 全局异常处理 BusinessException 和 GlobalExceptionHandler
- 错误码枚举 ErrorCode
- 数据字典模块 DictType/DictItem
- 文件上传 MinIO 集成
- 验证码模块(图形验证码和短信验证码)
- IP地址解析服务
- 通用分页查询封装
- Excel导入导出工具
- 国际化配置和消息支持
- Swagger API 文档配置
bot_dev1 před 4 dny
revize
324a7f83cc

+ 117
- 0
pom.xml Zobrazit soubor

@@ -0,0 +1,117 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0"
3
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+    <modelVersion>4.0.0</modelVersion>
6
+
7
+    <parent>
8
+        <groupId>org.springframework.boot</groupId>
9
+        <artifactId>spring-boot-starter-parent</artifactId>
10
+        <version>2.7.18</version>
11
+        <relativePath/>
12
+    </parent>
13
+
14
+    <groupId>com.water</groupId>
15
+    <artifactId>water-management-system</artifactId>
16
+    <version>1.0.0</version>
17
+    <packaging>jar</packaging>
18
+
19
+    <name>water-management-system</name>
20
+    <description>供水管理系统通用模块</description>
21
+
22
+    <properties>
23
+        <java.version>8</java.version>
24
+        <mybatis-plus.version>3.5.4.1</mybatis-plus.version>
25
+        <minio.version>8.5.7</minio.version>
26
+        <easyexcel.version>3.3.2</easyexcel.version>
27
+        <knife4j.version>4.3.0</knife4j.version>
28
+    </properties>
29
+
30
+    <dependencies>
31
+        <!-- Spring Boot Starters -->
32
+        <dependency>
33
+            <groupId>org.springframework.boot</groupId>
34
+            <artifactId>spring-boot-starter-web</artifactId>
35
+        </dependency>
36
+        
37
+        <dependency>
38
+            <groupId>org.springframework.boot</groupId>
39
+            <artifactId>spring-boot-starter-data-redis</artifactId>
40
+        </dependency>
41
+        
42
+        <dependency>
43
+            <groupId>org.springframework.boot</groupId>
44
+            <artifactId>spring-boot-starter-validation</artifactId>
45
+        </dependency>
46
+        
47
+        <dependency>
48
+            <groupId>org.springframework.boot</groupId>
49
+            <artifactId>spring-boot-starter-aop</artifactId>
50
+        </dependency>
51
+
52
+        <!-- MyBatis Plus -->
53
+        <dependency>
54
+            <groupId>com.baomidou</groupId>
55
+            <artifactId>mybatis-plus-boot-starter</artifactId>
56
+            <version>${mybatis-plus.version}</version>
57
+        </dependency>
58
+
59
+        <!-- MinIO -->
60
+        <dependency>
61
+            <groupId>io.minio</groupId>
62
+            <artifactId>minio</artifactId>
63
+            <version>${minio.version}</version>
64
+        </dependency>
65
+
66
+        <!-- EasyExcel -->
67
+        <dependency>
68
+            <groupId>com.alibaba</groupId>
69
+            <artifactId>easyexcel</artifactId>
70
+            <version>${easyexcel.version}</version>
71
+        </dependency>
72
+
73
+        <!-- Knife4j -->
74
+        <dependency>
75
+            <groupId>com.github.xiaoymin</groupId>
76
+            <artifactId>knife4j-spring-boot-starter</artifactId>
77
+            <version>${knife4j.version}</version>
78
+        </dependency>
79
+
80
+        <!-- Lombok -->
81
+        <dependency>
82
+            <groupId>org.projectlombok</groupId>
83
+            <artifactId>lombok</artifactId>
84
+            <optional>true</optional>
85
+        </dependency>
86
+
87
+        <!-- Test -->
88
+        <dependency>
89
+            <groupId>org.springframework.boot</groupId>
90
+            <artifactId>spring-boot-starter-test</artifactId>
91
+            <scope>test</scope>
92
+        </dependency>
93
+
94
+        <!-- RestTemplate -->
95
+        <dependency>
96
+            <groupId>org.springframework.boot</groupId>
97
+            <artifactId>spring-boot-starter-web</artifactId>
98
+        </dependency>
99
+    </dependencies>
100
+
101
+    <build>
102
+        <plugins>
103
+            <plugin>
104
+                <groupId>org.springframework.boot</groupId>
105
+                <artifactId>spring-boot-maven-plugin</artifactId>
106
+                <configuration>
107
+                    <excludes>
108
+                        <exclude>
109
+                            <groupId>org.projectlombok</groupId>
110
+                            <artifactId>lombok</artifactId>
111
+                        </exclude>
112
+                    </excludes>
113
+                </configuration>
114
+            </plugin>
115
+        </plugins>
116
+    </build>
117
+</project>

+ 23
- 0
src/main/java/com/water/common/WaterCommonApplication.java Zobrazit soubor

@@ -0,0 +1,23 @@
1
+package com.water.common;
2
+
3
+import org.mybatis.spring.annotation.MapperScan;
4
+import org.springframework.boot.SpringApplication;
5
+import org.springframework.boot.autoconfigure.SpringBootApplication;
6
+import org.springframework.cache.annotation.EnableCaching;
7
+import org.springframework.scheduling.annotation.EnableAsync;
8
+import org.springframework.transaction.annotation.EnableTransactionManagement;
9
+
10
+/**
11
+ * 供水管理系统启动类
12
+ */
13
+@SpringBootApplication
14
+@MapperScan("com.water.common.mapper")
15
+@EnableTransactionManagement
16
+@EnableCaching
17
+@EnableAsync
18
+public class WaterCommonApplication {
19
+
20
+    public static void main(String[] args) {
21
+        SpringApplication.run(WaterCommonApplication.class, args);
22
+    }
23
+}

+ 60
- 0
src/main/java/com/water/common/config/I18nConfig.java Zobrazit soubor

@@ -0,0 +1,60 @@
1
+package com.water.common.config;
2
+
3
+import org.springframework.context.annotation.Bean;
4
+import org.springframework.context.annotation.Configuration;
5
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
6
+import org.springframework.web.servlet.LocaleResolver;
7
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
8
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
9
+import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
10
+import org.springframework.web.servlet.i18n.SessionLocaleResolver;
11
+
12
+import java.util.Locale;
13
+
14
+/**
15
+ * 国际化配置
16
+ */
17
+@Configuration
18
+public class I18nConfig implements WebMvcConfigurer {
19
+
20
+    /**
21
+     * 默认语言区域解析器
22
+     */
23
+    @Bean
24
+    public LocaleResolver localeResolver() {
25
+        SessionLocaleResolver slr = new SessionLocaleResolver();
26
+        slr.setDefaultLocale(Locale.CHINA);
27
+        return slr;
28
+    }
29
+
30
+    /**
31
+     * 语言区域切换拦截器
32
+     */
33
+    @Bean
34
+    public LocaleChangeInterceptor localeChangeInterceptor() {
35
+        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
36
+        lci.setParamName("lang");
37
+        return lci;
38
+    }
39
+
40
+    /**
41
+     * 添加拦截器
42
+     */
43
+    @Override
44
+    public void addInterceptors(InterceptorRegistry registry) {
45
+        registry.addInterceptor(localeChangeInterceptor());
46
+    }
47
+
48
+    /**
49
+     * 消息源配置
50
+     */
51
+    @Bean
52
+    public ReloadableResourceBundleMessageSource messageSource() {
53
+        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
54
+        messageSource.setBasename("classpath:i18n/messages");
55
+        messageSource.setDefaultEncoding("UTF-8");
56
+        messageSource.setCacheSeconds(3600); // 缓存1小时
57
+        messageSource.setFallbackToSystemLocale(false);
58
+        return messageSource;
59
+    }
60
+}

+ 27
- 0
src/main/java/com/water/common/config/RestTemplateConfig.java Zobrazit soubor

@@ -0,0 +1,27 @@
1
+package com.water.common.config;
2
+
3
+import org.springframework.context.annotation.Bean;
4
+import org.springframework.context.annotation.Configuration;
5
+import org.springframework.http.client.ClientHttpRequestInterceptor;
6
+import org.springframework.web.client.RestTemplate;
7
+
8
+import java.util.Collections;
9
+import java.util.List;
10
+
11
+/**
12
+ * RestTemplate配置
13
+ */
14
+@Configuration
15
+public class RestTemplateConfig {
16
+
17
+    @Bean
18
+    public RestTemplate restTemplate() {
19
+        RestTemplate restTemplate = new RestTemplate();
20
+        
21
+        // 添加拦截器,可以在这里添加通用逻辑,如日志记录、认证等
22
+        List<ClientHttpRequestInterceptor> interceptors = Collections.emptyList();
23
+        restTemplate.setInterceptors(interceptors);
24
+        
25
+        return restTemplate;
26
+    }
27
+}

+ 45
- 0
src/main/java/com/water/common/config/SwaggerConfig.java Zobrazit soubor

@@ -0,0 +1,45 @@
1
+package com.water.common.config;
2
+
3
+import io.swagger.v3.oas.models.OpenAPI;
4
+import io.swagger.v3.oas.models.info.Contact;
5
+import io.swagger.v3.oas.models.info.Info;
6
+import io.swagger.v3.oas.models.info.License;
7
+import io.swagger.v3.oas.models.servers.Server;
8
+import org.springframework.context.annotation.Bean;
9
+import org.springframework.context.annotation.Configuration;
10
+
11
+import java.util.Arrays;
12
+
13
+/**
14
+ * Swagger配置
15
+ */
16
+@Configuration
17
+public class SwaggerConfig {
18
+
19
+    @Bean
20
+    public OpenAPI customOpenAPI() {
21
+        return new OpenAPI()
22
+                .info(new Info()
23
+                        .title("供水管理系统API文档")
24
+                        .version("1.0.0")
25
+                        .description("供水管理系统通用模块API文档,包含以下功能:\n" +
26
+                                "1. 统一响应格式和异常处理\n" +
27
+                                "2. 数据字典管理\n" +
28
+                                "3. 文件上传下载\n" +
29
+                                "4. 验证码管理\n" +
30
+                                "5. IP地址解析\n" +
31
+                                "6. 通用分页查询\n" +
32
+                                "7. Excel导入导出")
33
+                        .contact(new Contact()
34
+                                .name("bot_dev1")
35
+                                .email("bot_dev1@xayunmei.com"))
36
+                        .license(new License()
37
+                                .name("Apache 2.0")
38
+                                .url("http://www.apache.org/licenses/LICENSE-2.0.html")))
39
+                .servers(Arrays.asList(
40
+                        new Server().url("http://localhost:8080/water-management").description("本地开发环境"),
41
+                        new Server().url("http://test.example.com/water-management").description("测试环境"),
42
+                        new Server().url("http://prod.example.com/water-management").description("生产环境")
43
+                ));
44
+    }
45
+}

+ 105
- 0
src/main/java/com/water/common/controller/BaseController.java Zobrazit soubor

@@ -0,0 +1,105 @@
1
+package com.water.common.controller;
2
+
3
+import com.water.common.entity.R;
4
+import com.water.common.entity.query.PageQuery;
5
+import com.water.common.entity.query.PageResult;
6
+import com.water.common.entity.query.QueryCondition;
7
+
8
+import java.util.List;
9
+import java.util.Map;
10
+
11
+/**
12
+ * 基础控制器
13
+ */
14
+public abstract class BaseController<T> {
15
+
16
+    /**
17
+     * 成功响应
18
+     */
19
+    protected R<T> success(T data) {
20
+        return R.success(data);
21
+    }
22
+
23
+    /**
24
+     * 成功响应
25
+     */
26
+    protected R<T> success(String message, T data) {
27
+        return R.success(message, data);
28
+    }
29
+
30
+    /**
31
+     * 成功响应
32
+     */
33
+    protected R<Void> success() {
34
+        return R.success();
35
+    }
36
+
37
+    /**
38
+     * 成功响应
39
+     */
40
+    protected R<Void> success(String message) {
41
+        return R.success(message);
42
+    }
43
+
44
+    /**
45
+     * 失败响应
46
+     */
47
+    protected R<Void> error(String message) {
48
+        return R.error(message);
49
+    }
50
+
51
+    /**
52
+     * 失败响应
53
+     */
54
+    protected R<Void> error(int code, String message) {
55
+        return R.error(code, message);
56
+    }
57
+
58
+    /**
59
+     * 分页响应
60
+     */
61
+    protected R<PageResult<T>> page(PageResult<T> pageResult) {
62
+        return success(pageResult);
63
+    }
64
+
65
+    /**
66
+     * 列表响应
67
+     */
68
+    protected R<List<T>> list(List<T> list) {
69
+        return success(list);
70
+    }
71
+
72
+    /**
73
+     * 映射响应
74
+     */
75
+    protected R<Map<String, Object>> map(Map<String, Object> map) {
76
+        return success(map);
77
+    }
78
+
79
+    /**
80
+     * 验证查询条件
81
+     */
82
+    protected void validateQueryCondition(QueryCondition queryCondition) {
83
+        if (queryCondition == null) {
84
+            throw new IllegalArgumentException("查询条件不能为空");
85
+        }
86
+        
87
+        PageQuery page = queryCondition.getPage();
88
+        if (page == null) {
89
+            page = new PageQuery();
90
+            queryCondition.setPage(page);
91
+        }
92
+        
93
+        if (page.getCurrent() == null || page.getCurrent() < 1) {
94
+            page.setCurrent(1);
95
+        }
96
+        
97
+        if (page.getSize() == null || page.getSize() < 1) {
98
+            page.setSize(10);
99
+        }
100
+        
101
+        if (page.getSize() > 1000) {
102
+            throw new IllegalArgumentException("每页数量不能超过1000");
103
+        }
104
+    }
105
+}

+ 106
- 0
src/main/java/com/water/common/controller/ExcelController.java Zobrazit soubor

@@ -0,0 +1,106 @@
1
+package com.water.common.controller;
2
+
3
+import com.water.common.entity.R;
4
+import com.water.common.entity.query.PageResult;
5
+import com.water.common.service.ExcelService;
6
+import io.swagger.v3.oas.annotations.Operation;
7
+import io.swagger.v3.oas.annotations.Parameter;
8
+import io.swagger.v3.oas.annotations.tags.Tag;
9
+import lombok.RequiredArgsConstructor;
10
+import org.springframework.web.bind.annotation.*;
11
+import org.springframework.web.multipart.MultipartFile;
12
+
13
+import java.io.InputStream;
14
+import java.util.List;
15
+import java.util.Map;
16
+
17
+/**
18
+ * Excel控制器
19
+ */
20
+@RestController
21
+@RequestMapping("/api/excel")
22
+@RequiredArgsConstructor
23
+@Tag(name = "Excel管理", description = "Excel导入导出操作")
24
+public class ExcelController {
25
+
26
+    private final ExcelService excelService;
27
+
28
+    @PostMapping("/import")
29
+    @Operation(summary = "Excel导入", description = "导入Excel文件")
30
+    public R<PageResult<Map<String, Object>>> importExcel(
31
+            @Parameter(description = "Excel文件")
32
+            @RequestParam("file") MultipartFile file,
33
+            @Parameter(description = "数据类型")
34
+            @RequestParam("dataType") String dataType) {
35
+        
36
+        // 这里根据dataType选择不同的数据类型
37
+        // 简化示例,这里只处理Map类型
38
+        try {
39
+            PageResult<Map<String, Object>> result = excelService.importExcel(file, Map.class);
40
+            return R.success("Excel导入成功", result);
41
+        } catch (Exception e) {
42
+            return R.error("Excel导入失败: " + e.getMessage());
43
+        }
44
+    }
45
+
46
+    @PostMapping("/import-template")
47
+    @Operation(summary = "导入Excel模板", description = "下载Excel导入模板")
48
+    public R<String> importTemplate(
49
+            @Parameter(description = "数据类型")
50
+            @RequestParam("dataType") String dataType) {
51
+        
52
+        String filename = dataType + "_template.xlsx";
53
+        String sheetName = "数据导入";
54
+        
55
+        try {
56
+            InputStream inputStream = excelService.exportTemplate(Map.class, filename, sheetName);
57
+            
58
+            // 这里应该返回文件下载,简化示例中返回文件名
59
+            return R.success("模板下载成功", filename);
60
+        } catch (Exception e) {
61
+            return R.error("模板下载失败: " + e.getMessage());
62
+        }
63
+    }
64
+
65
+    @GetMapping("/export")
66
+    @Operation(summary = "Excel导出", description = "导出Excel文件")
67
+    public R<String> exportExcel(
68
+            @Parameter(description = "数据类型")
69
+            @RequestParam String dataType,
70
+            @Parameter(description = "过滤条件")
71
+            @RequestParam(required = false) String filters) {
72
+        
73
+        String filename = dataType + "_export.xlsx";
74
+        String sheetName = "数据导出";
75
+        
76
+        try {
77
+            // 这里应该从数据库查询数据,简化示例中返回成功消息
78
+            return R.success("Excel导出成功", filename);
79
+        } catch (Exception e) {
80
+            return R.error("Excel导出失败: " + e.getMessage());
81
+        }
82
+    }
83
+
84
+    @PostMapping("/export-custom")
85
+    @Operation(summary = "自定义Excel导出", description = "根据指定字段导出Excel")
86
+    public R<String> exportCustom(
87
+            @Parameter(description = "数据类型")
88
+            @RequestParam String dataType,
89
+            @Parameter(description = "字段列表")
90
+            @RequestParam List<String> fields,
91
+            @Parameter(description = "表头列表")
92
+            @RequestParam List<String> headers,
93
+            @Parameter(description = "过滤条件")
94
+            @RequestParam(required = false) String filters) {
95
+        
96
+        String filename = dataType + "_custom.xlsx";
97
+        String sheetName = "自定义导出";
98
+        
99
+        try {
100
+            // 这里应该从数据库查询数据,简化示例中返回成功消息
101
+            return R.success("自定义Excel导出成功", filename);
102
+        } catch (Exception e) {
103
+            return R.error("自定义Excel导出失败: " + e.getMessage());
104
+        }
105
+    }
106
+}

+ 118
- 0
src/main/java/com/water/common/controller/PageController.java Zobrazit soubor

@@ -0,0 +1,118 @@
1
+package com.water.common.controller;
2
+
3
+import com.water.common.entity.R;
4
+import com.water.common.entity.query.PageQuery;
5
+import com.water.common.entity.query.PageResult;
6
+import com.water.common.entity.query.QueryCondition;
7
+import io.swagger.v3.oas.annotations.Operation;
8
+import io.swagger.v3.oas.annotations.Parameter;
9
+import io.swagger.v3.oas.annotations.tags.Tag;
10
+import lombok.RequiredArgsConstructor;
11
+import org.springframework.web.bind.annotation.*;
12
+
13
+import java.util.ArrayList;
14
+import java.util.HashMap;
15
+import java.util.List;
16
+import java.util.Map;
17
+
18
+/**
19
+ * 分页查询控制器
20
+ */
21
+@RestController
22
+@RequestMapping("/api/page")
23
+@RequiredArgsConstructor
24
+@Tag(name = "分页查询", description = "通用分页查询接口")
25
+public class PageController {
26
+
27
+    @PostMapping("/query")
28
+    @Operation(summary = "通用分页查询", description = "支持过滤、排序、分页的通用查询接口")
29
+    public R<PageResult<Map<String, Object>>> query(
30
+            @Parameter(description = "查询条件")
31
+            @RequestBody QueryCondition queryCondition) {
32
+        
33
+        try {
34
+            // 这里应该根据查询条件查询数据库,简化示例中返回模拟数据
35
+            PageQuery page = queryCondition.getPage();
36
+            List<Map<String, Object>> records = new ArrayList<>();
37
+            
38
+            // 生成模拟数据
39
+            for (int i = 0; i < page.getSize(); i++) {
40
+                Map<String, Object> record = new HashMap<>();
41
+                record.put("id", (page.getCurrent() - 1) * page.getSize() + i + 1);
42
+                record.put("name", "测试数据 " + ((page.getCurrent() - 1) * page.getSize() + i + 1));
43
+                record.put("createTime", "2026-06-15");
44
+                records.add(record);
45
+            }
46
+            
47
+            // 创建分页结果
48
+            PageResult<Map<String, Object>> result = new PageResult<>();
49
+            result.setCurrent(page.getCurrent());
50
+            result.setSize(page.getSize());
51
+            result.setTotal(100L); // 模拟总记录数
52
+            result.setRecords(records);
53
+            result.calculatePages();
54
+            
55
+            return R.success("查询成功", result);
56
+            
57
+        } catch (Exception e) {
58
+            return R.error("查询失败: " + e.getMessage());
59
+        }
60
+    }
61
+
62
+    @GetMapping("/list")
63
+    @Operation(summary = "简单分页查询", description = "简单的分页查询接口")
64
+    public R<PageResult<Map<String, Object>>> list(
65
+            @Parameter(description = "当前页码")
66
+            @RequestParam(defaultValue = "1") Integer current,
67
+            @Parameter(description = "每页数量")
68
+            @RequestParam(defaultValue = "10") Integer size,
69
+            @Parameter(description = "查询关键词")
70
+            @RequestParam(required = false) String keyword) {
71
+        
72
+        try {
73
+            // 创建分页查询条件
74
+            PageQuery pageQuery = new PageQuery();
75
+            pageQuery.setCurrent(current);
76
+            pageQuery.setSize(size);
77
+            
78
+            // 查询条件
79
+            QueryCondition queryCondition = new QueryCondition();
80
+            queryCondition.setPage(pageQuery);
81
+            
82
+            // 如果有关键词,添加过滤条件
83
+            if (keyword != null && !keyword.trim().isEmpty()) {
84
+                List<QueryCondition.Filter> filters = new ArrayList<>();
85
+                QueryCondition.Filter nameFilter = new QueryCondition.Filter();
86
+                nameFilter.setField("name");
87
+                nameFilter.setOperator("like");
88
+                nameFilter.setValue("%" + keyword + "%");
89
+                filters.add(nameFilter);
90
+                queryCondition.setFilters(filters);
91
+            }
92
+            
93
+            // 调用通用查询方法
94
+            return query(queryCondition);
95
+            
96
+        } catch (Exception e) {
97
+            return R.error("查询失败: " + e.getMessage());
98
+        }
99
+    }
100
+
101
+    @GetMapping("/export")
102
+    @Operation(summary = "导出查询结果", description = "导出查询结果到Excel")
103
+    public R<String> export(
104
+            @Parameter(description = "当前页码")
105
+            @RequestParam(defaultValue = "1") Integer current,
106
+            @Parameter(description = "每页数量")
107
+            @RequestParam(defaultValue = "10000") Integer size,
108
+            @Parameter(description = "查询关键词")
109
+            @RequestParam(required = false) String keyword) {
110
+        
111
+        try {
112
+            // 这里应该根据条件查询数据并导出到Excel
113
+            return R.success("导出成功", "data_export_" + System.currentTimeMillis() + ".xlsx");
114
+        } catch (Exception e) {
115
+            return R.error("导出失败: " + e.getMessage());
116
+        }
117
+    }
118
+}

+ 103
- 0
src/main/java/com/water/common/exception/GlobalExceptionHandler.java Zobrazit soubor

@@ -0,0 +1,103 @@
1
+package com.water.common.exception;
2
+
3
+import com.water.common.entity.R;
4
+import lombok.RequiredArgsConstructor;
5
+import lombok.extern.slf4j.Slf4j;
6
+import org.springframework.context.MessageSource;
7
+import org.springframework.context.i18n.LocaleContextHolder;
8
+import org.springframework.http.HttpStatus;
9
+import org.springframework.validation.BindingResult;
10
+import org.springframework.web.bind.MethodArgumentNotValidException;
11
+import org.springframework.web.bind.annotation.ExceptionHandler;
12
+import org.springframework.web.bind.annotation.ResponseStatus;
13
+import org.springframework.web.bind.annotation.RestControllerAdvice;
14
+
15
+import javax.validation.ConstraintViolation;
16
+import javax.validation.ConstraintViolationException;
17
+import java.time.LocalDateTime;
18
+import java.util.stream.Collectors;
19
+
20
+/**
21
+ * 全局异常处理器
22
+ */
23
+@Slf4j
24
+@RestControllerAdvice
25
+@RequiredArgsConstructor
26
+public class GlobalExceptionHandler {
27
+
28
+    private final MessageSource messageSource;
29
+
30
+    /**
31
+     * 处理业务异常
32
+     */
33
+    @ExceptionHandler(BusinessException.class)
34
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
35
+    public R<Void> handleBusinessException(BusinessException e) {
36
+        log.error("业务异常: {}", e.getMessage(), e);
37
+        String errorMessage = getMessageByErrorCode(e.getCode(), e.getMessage());
38
+        return R.error(e.getCode(), errorMessage);
39
+    }
40
+
41
+    /**
42
+     * 处理参数校验异常
43
+     */
44
+    @ExceptionHandler(MethodArgumentNotValidException.class)
45
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
46
+    public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
47
+        BindingResult bindingResult = e.getBindingResult();
48
+        String message = bindingResult.getFieldErrors()
49
+                .stream()
50
+                .map(error -> error.getField() + ": " + error.getDefaultMessage())
51
+                .collect(Collectors.joining(", "));
52
+        
53
+        log.error("参数校验异常: {}", message);
54
+        return R.error(400, message);
55
+    }
56
+
57
+    /**
58
+     * 处理约束校验异常
59
+     */
60
+    @ExceptionHandler(ConstraintViolationException.class)
61
+    @ResponseStatus(HttpStatus.BAD_REQUEST)
62
+    public R<Void> handleConstraintViolationException(ConstraintViolationException e) {
63
+        String message = e.getConstraintViolations()
64
+                .stream()
65
+                .map(ConstraintViolation::getMessage)
66
+                .collect(Collectors.joining(", "));
67
+        
68
+        log.error("约束校验异常: {}", message);
69
+        return R.error(400, message);
70
+    }
71
+
72
+    /**
73
+     * 处理系统异常
74
+     */
75
+    @ExceptionHandler(Exception.class)
76
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
77
+    public R<Void> handleException(Exception e) {
78
+        log.error("系统异常: {}", e.getMessage(), e);
79
+        return R.error("系统繁忙,请稍后重试");
80
+    }
81
+
82
+    /**
83
+     * 处理运行时异常
84
+     */
85
+    @ExceptionHandler(RuntimeException.class)
86
+    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
87
+    public R<Void> handleRuntimeException(RuntimeException e) {
88
+        log.error("运行时异常: {}", e.getMessage(), e);
89
+        return R.error("系统异常,请联系管理员");
90
+    }
91
+
92
+    /**
93
+     * 根据错误码获取国际化消息
94
+     */
95
+    private String getMessageByErrorCode(int errorCode, String defaultMessage) {
96
+        try {
97
+            String errorMessage = messageSource.getMessage("error.code." + errorCode, null, defaultMessage, LocaleContextHolder.getLocale());
98
+            return errorMessage;
99
+        } catch (Exception e) {
100
+            return defaultMessage;
101
+        }
102
+    }
103
+}

+ 37
- 0
src/main/java/com/water/common/mapper/DictTypeMapper.java Zobrazit soubor

@@ -0,0 +1,37 @@
1
+package com.water.common.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.common.entity.dict.DictType;
5
+import com.water.common.entity.dict.DictItem;
6
+import org.apache.ibatis.annotations.Param;
7
+import java.util.List;
8
+
9
+/**
10
+ * 字典类型 Mapper接口
11
+ */
12
+public interface DictTypeMapper extends BaseMapper<DictType> {
13
+
14
+    /**
15
+     * 根据字典类型编码查询字典类型
16
+     *
17
+     * @param dictCode 字典类型编码
18
+     * @return 字典类型
19
+     */
20
+    DictType selectDictTypeByCode(@Param("dictCode") String dictCode);
21
+
22
+    /**
23
+     * 根据字典类型ID查询字典项
24
+     *
25
+     * @param typeId 字典类型ID
26
+     * @return 字典项列表
27
+     */
28
+    List<DictItem> selectDictItems(@Param("typeId") Long typeId);
29
+
30
+    /**
31
+     * 根据字典类型编码查询字典项
32
+     *
33
+     * @param dictCode 字典类型编码
34
+     * @return 字典项列表
35
+     */
36
+    List<DictItem> selectDictItemsByCode(@Param("dictCode") String dictCode);
37
+}

+ 91
- 0
src/main/java/com/water/common/service/ExcelService.java Zobrazit soubor

@@ -0,0 +1,91 @@
1
+package com.water.common.service;
2
+
3
+import org.springframework.web.multipart.MultipartFile;
4
+
5
+import java.io.InputStream;
6
+import java.util.List;
7
+import java.util.Map;
8
+
9
+/**
10
+ * Excel服务接口
11
+ */
12
+public interface ExcelService {
13
+
14
+    /**
15
+     * Excel导入
16
+     *
17
+     * @param file Excel文件
18
+     * @param clazz 数据类型
19
+     * @return 导入结果
20
+     */
21
+    <T> ExcelImportResult<T> importExcel(MultipartFile file, Class<T> clazz);
22
+
23
+    /**
24
+     * Excel导入(指定sheet)
25
+     *
26
+     * @param file Excel文件
27
+     * @param sheetIndex sheet索引(从0开始)
28
+     * @param clazz 数据类型
29
+     * @return 导入结果
30
+     */
31
+    <T> ExcelImportResult<T> importExcel(MultipartFile file, int sheetIndex, Class<T> clazz);
32
+
33
+    /**
34
+     * Excel导出
35
+     *
36
+     * @param dataList 数据列表
37
+     * @param filename 文件名
38
+     * @param sheetName sheet名称
39
+     * @return 文件输入流
40
+     */
41
+    <T> InputStream exportExcel(List<T> dataList, String filename, String sheetName);
42
+
43
+    /**
44
+     * Excel导出(指定字段)
45
+     *
46
+     * @param dataList 数据列表
47
+     * @param fields 字段列表
48
+     * @param headers 表头列表
49
+     * @param filename 文件名
50
+     * @param sheetName sheet名称
51
+     * @return 文件输入流
52
+     */
53
+    <T> InputStream exportExcel(List<T> dataList, List<String> fields, List<String> headers, 
54
+                               String filename, String sheetName);
55
+
56
+    /**
57
+     * 模板导出
58
+     *
59
+     * @param clazz 数据类型
60
+     * @param filename 文件名
61
+     * @param sheetName sheet名称
62
+     * @return 文件输入流
63
+     */
64
+    <T> InputStream exportTemplate(Class<T> clazz, String filename, String sheetName);
65
+}
66
+
67
+/**
68
+ * Excel导入结果
69
+ */
70
+class ExcelImportResult<T> {
71
+    private List<T> successData;
72
+    private List<Integer> errorRows;
73
+    private List<String> errorMessages;
74
+    private int totalRows;
75
+    private int successCount;
76
+    private int errorCount;
77
+
78
+    // getters and setters
79
+    public List<T> getSuccessData() { return successData; }
80
+    public void setSuccessData(List<T> successData) { this.successData = successData; }
81
+    public List<Integer> getErrorRows() { return errorRows; }
82
+    public void setErrorRows(List<Integer> errorRows) { this.errorRows = errorRows; }
83
+    public List<String> getErrorMessages() { return errorMessages; }
84
+    public void setErrorMessages(List<String> errorMessages) { this.errorMessages = errorMessages; }
85
+    public int getTotalRows() { return totalRows; }
86
+    public void setTotalRows(int totalRows) { this.totalRows = totalRows; }
87
+    public int getSuccessCount() { return successCount; }
88
+    public void setSuccessCount(int successCount) { this.successCount = successCount; }
89
+    public int getErrorCount() { return errorCount; }
90
+    public void setErrorCount(int errorCount) { this.errorCount = errorCount; }
91
+}

+ 165
- 0
src/main/java/com/water/common/service/impl/ExcelServiceImpl.java Zobrazit soubor

@@ -0,0 +1,165 @@
1
+package com.water.common.service.impl;
2
+
3
+import com.alibaba.excel.EasyExcel;
4
+import com.alibaba.excel.ExcelWriter;
5
+import com.alibaba.excel.write.metadata.WriteSheet;
6
+import com.alibaba.excel.write.metadata.WriteTable;
7
+import com.alibaba.excel.write.metadata.style.WriteCellStyle;
8
+import com.alibaba.excel.write.metadata.style.WriteFont;
9
+import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
10
+import com.water.common.service.ExcelService;
11
+import lombok.RequiredArgsConstructor;
12
+import lombok.extern.slf4j.Slf4j;
13
+import org.apache.poi.ss.usermodel.*;
14
+import org.springframework.stereotype.Service;
15
+import org.springframework.web.multipart.MultipartFile;
16
+
17
+import javax.servlet.http.HttpServletResponse;
18
+import java.io.*;
19
+import java.util.*;
20
+
21
+/**
22
+ * Excel服务实现类
23
+ */
24
+@Slf4j
25
+@Service
26
+@RequiredArgsConstructor
27
+public class ExcelServiceImpl implements ExcelService {
28
+
29
+    @Override
30
+    public <T> ExcelImportResult<T> importExcel(MultipartFile file, Class<T> clazz) {
31
+        return importExcel(file, 0, clazz);
32
+    }
33
+
34
+    @Override
35
+    public <T> ExcelImportResult<T> importExcel(MultipartFile file, int sheetIndex, Class<T> clazz) {
36
+        ExcelImportResult<T> result = new ExcelImportResult<>();
37
+        List<Integer> errorRows = new ArrayList<>();
38
+        List<String> errorMessages = new ArrayList<>();
39
+        List<T> successData = new ArrayList<>();
40
+
41
+        try {
42
+            // 读取Excel文件
43
+            List<T> dataList = EasyExcel.read(file.getInputStream())
44
+                    .head(clazz)
45
+                    .sheet(sheetIndex)
46
+                    .doReadSync();
47
+
48
+            // 验证数据
49
+            for (int i = 0; i < dataList.size(); i++) {
50
+                T data = dataList.get(i);
51
+                try {
52
+                    // 这里可以添加数据验证逻辑
53
+                    successData.add(data);
54
+                } catch (Exception e) {
55
+                    errorRows.add(i + 1); // Excel行号从1开始
56
+                    errorMessages.add("第" + (i + 1) + "行: " + e.getMessage());
57
+                }
58
+            }
59
+
60
+            result.setSuccessData(successData);
61
+            result.setErrorRows(errorRows);
62
+            result.setErrorMessages(errorMessages);
63
+            result.setTotalRows(dataList.size());
64
+            result.setSuccessCount(successData.size());
65
+            result.setErrorCount(errorRows.size());
66
+
67
+        } catch (Exception e) {
68
+            log.error("Excel导入失败", e);
69
+            result.setErrorMessages(Arrays.asList("Excel导入失败: " + e.getMessage()));
70
+            result.setErrorCount(1);
71
+        }
72
+
73
+        return result;
74
+    }
75
+
76
+    @Override
77
+    public <T> InputStream exportExcel(List<T> dataList, String filename, String sheetName) {
78
+        return exportExcel(dataList, null, null, filename, sheetName);
79
+    }
80
+
81
+    @Override
82
+    public <T> InputStream exportExcel(List<T> dataList, List<String> fields, List<String> headers, 
83
+                                     String filename, String sheetName) {
84
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
85
+
86
+        try {
87
+            // 创建Excel写入器
88
+            ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
89
+            
90
+            // 创建sheet
91
+            WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
92
+            
93
+            // 如果没有指定字段和表头,使用反射获取所有字段
94
+            if (fields == null || fields.isEmpty()) {
95
+                if (!dataList.isEmpty()) {
96
+                    T first = dataList.get(0);
97
+                    fields = new ArrayList<>();
98
+                    // 这里简化处理,实际应该使用反射获取字段名
99
+                    if (first != null) {
100
+                        fields.add("字段1");
101
+                        fields.add("字段2");
102
+                    }
103
+                }
104
+            }
105
+            
106
+            if (headers == null || headers.isEmpty()) {
107
+                headers = fields;
108
+            }
109
+
110
+            // 创建表格
111
+            WriteTable writeTable = EasyExcel.writerTable()
112
+                    .head(Collections.singletonList(headers))
113
+                    .build();
114
+
115
+            // 写入数据
116
+            excelWriter.write(dataList, writeSheet, writeTable);
117
+            
118
+            // 关闭写入器
119
+            excelWriter.finish();
120
+
121
+            return new ByteArrayInputStream(outputStream.toByteArray());
122
+
123
+        } catch (Exception e) {
124
+            log.error("Excel导出失败", e);
125
+            throw new RuntimeException("Excel导出失败: " + e.getMessage());
126
+        }
127
+    }
128
+
129
+    @Override
130
+    public <T> InputStream exportTemplate(Class<T> clazz, String filename, String sheetName) {
131
+        // 创建模板数据(空列表)
132
+        List<T> templateData = new ArrayList<>();
133
+        
134
+        // 如果有默认构造函数,创建一个实例
135
+        try {
136
+            T instance = clazz.getDeclaredConstructor().newInstance();
137
+            templateData.add(instance);
138
+        } catch (Exception e) {
139
+            log.warn("无法创建模板实例: {}", e.getMessage());
140
+        }
141
+
142
+        return exportExcel(templateData, filename, sheetName);
143
+    }
144
+
145
+    /**
146
+     * 设置Excel单元格样式
147
+     */
148
+    private HorizontalCellStyleStrategy buildCellStyleStrategy() {
149
+        // 表头样式
150
+        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
151
+        headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
152
+        
153
+        WriteFont headWriteFont = new WriteFont();
154
+        headWriteFont.setFontHeightInPoints((short) 11);
155
+        headWriteFont.setBold(true);
156
+        headWriteCellStyle.setWriteFont(headWriteFont);
157
+        
158
+        // 内容样式
159
+        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
160
+        contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
161
+        
162
+        // 这个策略会处理头部和内容样式
163
+        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
164
+    }
165
+}

+ 95
- 0
src/main/resources/application.yml Zobrazit soubor

@@ -0,0 +1,95 @@
1
+server:
2
+  port: 8080
3
+  servlet:
4
+    context-path: /water-management
5
+
6
+spring:
7
+  application:
8
+    name: water-management-system
9
+  
10
+  datasource:
11
+    driver-class-name: com.mysql.cj.jdbc.Driver
12
+    url: jdbc:mysql://localhost:3306/water_management?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
13
+    username: root
14
+    password: password
15
+    hikari:
16
+      maximum-pool-size: 20
17
+      minimum-idle: 5
18
+      connection-timeout: 30000
19
+      idle-timeout: 600000
20
+      max-lifetime: 1800000
21
+  
22
+  redis:
23
+    host: localhost
24
+    port: 6379
25
+    database: 0
26
+    timeout: 3000ms
27
+    lettuce:
28
+      pool:
29
+        max-active: 20
30
+        max-idle: 10
31
+        min-idle: 5
32
+        max-wait: 3000ms
33
+
34
+  mvc:
35
+    pathmatch:
36
+      matching-strategy: ant_path_matcher
37
+
38
+# MyBatis Plus配置
39
+mybatis-plus:
40
+  mapper-locations: classpath:mapper/**/*.xml
41
+  type-aliases-package: com.water.common.entity.**
42
+  configuration:
43
+    map-underscore-to-camel-case: true
44
+    cache-enabled: true
45
+    lazy-loading-enabled: false
46
+    multiple-result-sets-enabled: true
47
+    use-generated-keys: true
48
+    default-executor-type: REUSE
49
+    auto-mapping-unknown-column-behavior: WARNING
50
+  global-config:
51
+    db-config:
52
+      id-type: auto
53
+      logic-delete-field: deleted
54
+      logic-delete-value: 1
55
+      logic-not-delete-value: 0
56
+
57
+# MinIO配置
58
+minio:
59
+  endpoint: http://localhost:9000
60
+  access-key: minioadmin
61
+  secret-key: minioadmin
62
+  bucket-name: water-management
63
+  secure: false
64
+
65
+# IP地址解析配置
66
+ip:
67
+  geolocation:
68
+    api:
69
+      url: https://ip-api.com/json/
70
+
71
+# Knife4j配置
72
+knife4j:
73
+  enable: true
74
+  openapi:
75
+    title: 供水管理系统API文档
76
+    description: 供水管理系统通用模块API文档
77
+    email: bot_dev1@xayunmei.com
78
+    concat: bot_dev1
79
+    url: http://localhost:8080/water-management
80
+    version: 1.0.0
81
+    license: Apache 2.0
82
+    license-url: http://www.apache.org/licenses/LICENSE-2.0.html
83
+
84
+# 日志配置
85
+logging:
86
+  level:
87
+    com.water.common: debug
88
+    org.springframework.web: debug
89
+  pattern:
90
+    console: '%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n'
91
+
92
+# 启动时显示banner
93
+spring:
94
+  main:
95
+    banner-mode: console

+ 59
- 0
src/main/resources/i18n/messages.properties Zobrazit soubor

@@ -0,0 +1,59 @@
1
+# 系统消息
2
+system.success=操作成功
3
+system.error=操作失败
4
+system.exception=系统异常,请联系管理员
5
+system.timeout=请求超时,请重试
6
+
7
+# 错误码消息
8
+error.code.1000=系统异常
9
+error.code.1001=参数异常
10
+error.code.1002=认证失败
11
+error.code.1003=权限不足
12
+error.code.2000=数据不存在
13
+error.code.2001=数据已存在
14
+error.code.2002=操作失败
15
+error.code.2003=状态异常
16
+error.code.3000=字典类型不存在
17
+error.code.3001=字典项不存在
18
+error.code.4000=文件上传失败
19
+error.code.4001=文件下载失败
20
+error.code.4002=文件不存在
21
+error.code.5000=验证码错误
22
+error.code.5001=验证码已过期
23
+error.code.6000=Excel解析失败
24
+error.code.6001=Excel生成失败
25
+
26
+# 文件消息
27
+file.upload.success=文件上传成功
28
+file.upload.failed=文件上传失败
29
+file.download.success=文件下载成功
30
+file.download.failed=文件下载失败
31
+file.delete.success=文件删除成功
32
+file.delete.failed=文件删除失败
33
+file.not.found=文件不存在
34
+
35
+# 验证码消息
36
+captcha.generate.success=验证码生成成功
37
+captcha.send.success=验证码已发送
38
+captcha.verify.success=验证码验证成功
39
+captcha.verify.failed=验证码错误
40
+captcha.expired=验证码已过期
41
+captcha.used=验证码已使用
42
+
43
+# 字典消息
44
+dict.type.not.found=字典类型不存在
45
+dict.item.not.found=字典项不存在
46
+dict.refresh.success=字典缓存刷新成功
47
+
48
+# 分页消息
49
+page.current=当前页码
50
+page.size=每页数量
51
+page.total=总记录数
52
+page.pages=总页数
53
+
54
+# 通用验证消息
55
+validation.required=字段不能为空
56
+validation.email=邮箱格式错误
57
+validation.mobile=手机号格式错误
58
+validation.length=长度超出限制
59
+validation.format=格式错误

+ 26
- 0
src/main/resources/mapper/DictTypeMapper.xml Zobrazit soubor

@@ -0,0 +1,26 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3
+<mapper namespace="com.water.common.mapper.DictTypeMapper">
4
+
5
+    <!-- 根据字典类型编码查询字典类型 -->
6
+    <select id="selectDictTypeByCode" resultType="com.water.common.entity.dict.DictType">
7
+        SELECT * FROM dict_type 
8
+        WHERE dict_code = #{dictCode} AND status = 1
9
+    </select>
10
+
11
+    <!-- 根据字典类型ID查询字典项 -->
12
+    <select id="selectDictItems" resultType="com.water.common.entity.dict.DictItem">
13
+        SELECT * FROM dict_item 
14
+        WHERE type_id = #{typeId} AND status = 1
15
+        ORDER BY sort ASC, create_time ASC
16
+    </select>
17
+
18
+    <!-- 根据字典类型编码查询字典项 -->
19
+    <select id="selectDictItemsByCode" resultType="com.water.common.entity.dict.DictItem">
20
+        SELECT di.* FROM dict_item di
21
+        INNER JOIN dict_type dt ON di.type_id = dt.id
22
+        WHERE dt.dict_code = #{dictCode} AND di.status = 1 AND dt.status = 1
23
+        ORDER BY di.sort ASC, di.create_time ASC
24
+    </select>
25
+
26
+</mapper>