项目优化,构建文件Lombok异常问题修复

This commit is contained in:
2026-06-29 13:55:42 +08:00
parent 53b3b2c989
commit 0799f9cb4d
47 changed files with 1034 additions and 865 deletions
+111 -109
View File
@@ -1,144 +1,146 @@
# CLAUDE.md system-admin
# CLAUDE.md - system-admin
Backend module: Spring Boot 3.5 admin application (port 8080, context `/system-admin`).
This file provides guidance to Claude Code when working in the system-admin module.
---
## Purpose
## Build & Run
Main Spring Boot application module. Entry point: `com.weather.AdminApplication`. Depends on `system-common` and `system-dynamic-datasource`.
```bash
mvn clean install -DskipTests # full build (tests skipped by default)
mvn clean install -DskipTests=false # build with tests
mvn -pl system-admin -DskipTests=false -Dtest=YourTestClass test # single test
## Module Structure
```
com.weather/
├── AdminApplication.java # @SpringBootApplication, @EnableAsync
├── common/ # Module-level shared code
│ ├── annotation/ # @DataFilter, @LogOperation
│ ├── aspect/ # DataFilterAspect, LogOperationAspect
│ ├── config/ # JacksonConfig, MybatisPlusConfig, SwaggerConfig, FileServerConfig
│ ├── exception/ # CustomExceptionHandler
│ ├── handler/ # FieldMetaObjectHandler (MyBatis-Plus auto-fill)
│ ├── interceptor/ # DataFilterInterceptor, DataScope
│ ├── utils/ # ExcelUtils, TimeUtils
│ └── validator/group/ # Cloud storage validator groups
└── modules/
├── job/ # Quartz scheduling
├── log/ # Audit logs (operation, login, error)
├── oss/ # Cloud file storage (Alibaba/Qiniu/Tencent)
├── region/ # Geographic region tree
├── security/ # Shiro auth, OAuth2 filter, login
├── sys/ # System management (users, roles, menus, depts, dicts, params, alerts)
└── weather/ # Weather domain
├── dailydata/ # Daily weather data CRUD, import, caching
├── filescan/ # WatchService file scanning pipeline
└── station/ # Weather station management
```
Tests use **JUnit 4** (`@RunWith(SpringRunner.class)`), not JUnit 5. Only 2 test files exist (Redis, dynamic datasource). No Maven wrapper (`mvnw`). Maven remote repository: Aliyun mirror.
## Service Layer Pattern
Launch from IntelliJ:
- `AdminApplication` (`system-admin/`) → port 8080, context `/system-admin`
- `GeneratorApplication` (`renren-generator/`) — commented out of build
All business modules follow this structure under `modules/<domain>/`:
## Service layer pattern
Two base classes in `system-common`:
| Base | Purpose |
|---|---|
| `CrudService<Dao, Entity, DTO>` | Generic CRUD: `page()`, `get()`, `save()`, `update()`, `delete()` |
| `BaseService<Dao>` | Lighter base without DTO generic |
Module convention:
```
modules/<name>/
├── controller/ → @RestController, returns Result
├── dao/ extends BaseMapper<Entity> (MyBatis-Plus)
├── dto/ → request/query DTOs (often extends BaseEntity)
├── entity/ → @TableName JPA entity
├── service/ → interface extends CrudService/BaseService
│ └── impl/ → @Service implementation
├── excel/ → EasyExcel VO classes (optional)
└── vo/ → response VO classes (optional)
controller/ → @RestController, delegates to service interface
service/ → Interface extends CrudService<Entity, Dto>
service/impl/Extends CrudServiceImpl<Dao, Entity, Dto>, implements getWrapper()
dao/ Interface extends BaseDao / BaseMapper
entity/ Extends BaseEntity, maps to DB table
dto/ → Request/response DTOs
excel/ → @ExcelProperty-annotated beans for EasyExcel import/export
```
Mapper XMLs: `src/main/resources/mapper/<domain>/**/*.xml`
Entity-to-DTO conversion uses `ConvertUtils.sourceToTarget()`.
### Conventions
## Security / Auth
- Lombok used throughout: `@Data`, `@AllArgsConstructor`, `@Slf4j` are standard on entity/service classes.
- DTO/Entity/VO separation per module — request DTOs often extend `BaseEntity`.
- **Framework**: Apache Shiro with Jakarta-compatible artifacts (classifier: `jakarta`)
- **Token flow**: Client sends `token` header/param -> `Oauth2Filter` extracts it -> `Oauth2Realm` validates against `sys_user_token` table -> returns `UserDetail` (user + dept scope + permissions)
- **Public paths** (no auth): `/login`, `/captcha`, `/druid/**`, `/doc.html`, `/swagger/**`, `/v3/api-docs/**`, `/favicon.ico`
- **Password hashing**: Custom `BCryptPasswordEncoder` with `PasswordUtils` (configurable rounds via sys_params)
- **Async context**: `UserContextHolder` (ThreadLocal) preserves user context across async task boundaries (used in file import, scheduled tasks). `SecurityUser.getUser()` falls back to this when Shiro Subject is unavailable.
## PK & Auth
## Data Scoping (Dept-Based Row-Level Security)
- PK: `ASSIGN_ID` (Snowflake) via `IdUtil.getSnowflakeNextId()`. All entities extend `BaseEntity`. Exception: `WeatherStationEntity` uses `AUTO_INCREMENT`.
- Auth: Apache Shiro 1.12 (**Jakarta classifier**) + OAuth2 token. Login → `token` header.
- **Do not introduce Spring Security** — the project uses Shiro exclusively.
`DataFilterInterceptor` is a MyBatis-Plus `InnerInterceptor`. When a service method is annotated with `@DataFilter`, the `DataFilterAspect` injects a `DataScope` object containing a SQL filter clause into the query parameters. The interceptor then appends this WHERE clause to the SQL, restricting results to the user's authorized departments.
## Key cross-cutting mechanisms
Example service usage:
```java
@DataFilter
public PageData<Dto> page(Map<String, Object> params) {
// SQL automatically filtered by dept scope
}
```
| Mechanism | How |
|---|---|
| **Data permissions** | `@DataFilter` on controller → `DataFilterAspect` → MyBatis interceptor injects dept-based SQL |
| **Auto-fill** | `FieldMetaObjectHandler` fills creator/date via MyBatis-Plus. **Only works with `insert()`/`updateById()`** — batch inserts (e.g. `insertBatchMultiRow`) bypass auto-fill; fields must be set manually. |
| **Scheduled jobs** | Quartz. `schedule_job` table, implements `ITask`, `@Component("beanName")`. Jobs auto-register at startup via `JobCommandLineRunner`. Seed data: `testTask` (paused, every 30 min), `fileScanTask` (paused, every 5 min), `weatherSummarizeCacheTask` (**active**, daily 1 AM). |
| **File scanning** | `WatchService` (primary, background thread) + Quartz fallback (`FileScanTask`) + startup runner (`FileScanStartupRunner`). Files identified by MD5 hash. |
| **Excel import** | EasyExcel + async dual-pass via `WeatherDataImportManager`. Progress tracked in-memory (`ConcurrentHashMap`), lost on restart. |
| **API responses** | Always wrapped in `Result`. Frontend expects `code === 0` for success. |
| **Validation** | Hibernate Validator. XSS filter via `XssFilter`. i18n messages in `system-common/src/main/resources/i18n/validation.properties`. |
## File Scanning Pipeline
## Exception handling
`FileWatchServiceManager` (`modules/weather/filescan/`) monitors `{FILE_SCAN_ROOT_PATH}/receive/` using `java.nio.file.WatchService`:
Single `@RestControllerAdvice` handler in the admin module:
1. **Directory layout**: `receive/<deptName>/`, `display/<deptName>/`, `archive/<deptName>/`
2. **Detection**: WatchService detects `ENTRY_CREATE` and `ENTRY_MODIFY` events
3. **Wait**: `waitForFileReady()` polls file size stability + file lock to ensure write completion
4. **Dedup**: MD5 hash check against `weather_file_scan_record` table
5. **Parse**: `FileNameParser` extracts region, category, period from filename
6. **Store**: Record inserted; file copied to `display/`; old version moved to `archive/`
7. **Root files**: Files in `receive/` root (no dept subdir) are classified as "model forecast" with `deptId = null`
| Handler | Catches | Persists errors? |
|---|---|---|
| `CustomExceptionHandler` | `CommonException`, `DuplicateKeyException`, `UnauthorizedException`, generic `Exception` | **Yes** — saves to `SysLogErrorService` (IP, user-agent, URI, params, stack trace) |
`FileScanStartupRunner` (ApplicationRunner) triggers full directory scan + WatchService registration on startup.
Both return a generic error for caught `Exception` (not the exception message). `CommonException` uses i18n message lookup via `MessageUtils.getMessage(code)`. Error codes follow `int` scheme: 5 digits, first 2 = module, last 3 = business (e.g. `10001`-`10029`).
## Weather Daily Data
## Logging
Core table: `weather_daily_data` (entity: `WeatherDailyDataEntity`). Fields: stationId, observeDate, avgTemp, maxTemp/minTemp (with time), rainfall (20-20 and 08-08), relativeHumidity, atmospheres, wind (avg/max/extreme speed + direction + time), deptId.
- Logback-spring config in `system-admin/src/main/resources/logback-spring.xml`. Logger names use `io.renren` (fork legacy), **not** `com.weather`.
- Admin dev profile enables MyBatis SQL stdout logging (`StdOutImpl`).
- When adding `@Slf4j` to `com.weather.*` classes, add a `com.weather` level override or change the existing `io.renren` logger scope.
### Import Flow
## Redis & Docs
`WeatherDataImportManager` handles Excel uploads:
1. Save MultipartFile to temp file
2. Submit async task (`CompletableFuture`), returns taskId immediately
3. First pass: `EasyExcel.read()` with count listener to get total rows
4. Second pass: `WeatherDataListener` reads rows in batches, calls `insertBatch()`
5. `insertBatch()` resolves station-to-dept mapping, fills audit fields, uses `insertBatchMultiRow()` (custom MySQL multi-row INSERT)
6. Clears weather summarize Redis cache on completion
7. Progress tracked in-memory (`ConcurrentHashMap<String, ImportProgress>`)
- Redis: `project-options.redis.open: true` in dev YAML controls whether `RedisAspect` intercepts Redis calls (default `false` — Redis operations silently skipped when disabled).
- Knife4j: disabled by default (`knife4j.enable: false`). Docs at `/doc.html` when enabled.
- `RedisAspect` wraps `@RedisCache` annotations with channel publish for cache invalidation.
### Summarize Caching
## MyBatis-Plus gotchas
`WeatherSummarizeCacheTask` (Quartz job) precomputes "same month-day across years" summary data per station into Redis. Cache key: `weather:summarize:{month}:{day}`. On cache hit, `getCachedSummarize()` filters by station + year range. On miss, falls back to `selectSummarizeByMonthDay()` DB query.
- **Batch inserts bypass auto-fill** — `FieldMetaObjectHandler` only fires on `insert()`/`updateById()`. Custom batch methods must manually set `creator`, `createDate`, `updater`, `updateDate`, `deptId`.
- Column names with special characters (e.g. `rain_20_20`) require explicit `@TableField` annotations — MyBatis-Plus cannot auto-map them from camelCase.
- `typeAliasesPackage: com.weather.modules.*.entity` — all entity classes must reside under a `modules` sub-package.
## Alert / Notification System
## Weather domain
- **Storage**: `SysAlertEntity` in `sys_alert` table
- **Real-time push**: `SseAlertService` manages active SSE connections (`CopyOnWriteArraySet<SseEmitter>`), broadcasts on Spring events:
- `AlertCreatedEvent` -> SSE event `alert`
- `AlertWithdrawnEvent` -> SSE event `alert-withdrawn`
- `AlertDeletedEvent` -> SSE event `alert-deleted`
- **External sources**: `AlertSourcePollingTask` (Quartz) polls external services; `AlertSourceCollector` gathers results
Three sub-modules under `system-admin/.../modules/weather/`:
## Job Scheduling (Quartz)
| Module | Purpose |
|---|---|
| `dailydata/` | Daily observations, Excel batch import (async dual-pass), EasyExcel listener, summary export |
| `station/` | Weather station CRUD, linked to dept via `dept_id` |
| `filescan/` | File monitoring + serving. Format: `<地区>地区-<指标>.png` / `<地区>地区631信息.txt` |
Tables: `schedule_job`, `schedule_job_log`, plus standard `qrtz_*` tables.
### Weather data import flow
- `ScheduleConfig`: Quartz `SchedulerFactoryBean` configuration
- `ScheduleJob` entity: bean class, cron expression, params, status (PAUSE/NORMAL)
- `ScheduleUtils`: Create/update/delete/pause/resume Quartz triggers
- `JobCommandLineRunner`: On startup, restores all NORMAL-status jobs from DB
- `ITask` interface: `run(String params)` method -- all job classes implement this
- Concurrency control via `@DisallowConcurrentExecution`
1. **First pass**: `AnalysisEventListener` counts total rows.
2. **Second pass**: `WeatherDataListener` processes with batch insert (2000 records/batch).
3. Progress tracked in-memory via `ConcurrentHashMap<String, ImportProgress>` (`volatile` fields + `AtomicInteger`).
4. Runs on `CompletableFuture` with manual `UserContextHolder` propagation for security context. (Note: `UserContextHolder` is in `system-admin/.../security/user/`, not the empty `system-common/.../holder/` package.)
5. On completion, clears Redis summary cache (`weather:summarize:*`).
## Database
### Weather summarize cache
Schema: `system-admin/db/weather_data_system.sql`
`WeatherSummarizeCacheTask` (Quartz job) pre-computes daily historical summaries into Redis. Uses **MySQL-specific** SQL functions (`MONTH()`, `DAY()` on `observe_date`). Cache key: `weather:summarize:{month}:{day}`, non-expiring. Service checks cache first for queries spanning same month/day across years.
### Core Tables
### Station priority ordering
`WeatherDailyDataServiceImpl.page()` uses custom `CASE WHEN` SQL to order stations belonging to the user's department + sub-departments first.
## System alert module
CRUD + polling notification system under `modules/sys/`:
| Layer | File |
|---|---|
| Entity | `entity/SysAlertEntity.java``sys_alert` table, Snowflake PK |
| DAO | `dao/SysAlertDao.java` + `mapper/sys/SysAlertDao.xml``selectActiveAlerts`, `selectActiveAlertsSince`, `countBySource` |
| DTO | `dto/SysAlertDTO.java``publishTime`/`createDate`/`updateDate` read-only |
| VO | `vo/SysAlertVO.java``id` as String (mapped from Long) |
| Service | `service/SysAlertService.java` + `impl/``publishIfNotExists` (dedup by source), `withdraw` (soft), CRUD |
| Controller | `controller/SysAlertController.java` — polling: `GET active`, `GET active/since` (no auth); CRUD: `page`/`get`/`save`/`update`/`delete`/`withdraw` (Shiro permissions) |
| Enums | `enums/AlertLevelEnum.java` (info/warning/danger), `enums/AlertSourceTypeEnum.java` (manual/import/schedule/datasource) |
### Data source extension
| File | Role |
|---|---|
| `alert/AlertSource.java` | SPI interface: `getName()`, `check()` returning `AlertMessage` records |
| `alert/AlertSourceCollector.java` | `@Autowired(required=false) List<AlertSource>` auto-discovery, calls `publishIfNotExists` for each |
| `alert/task/AlertSourcePollingTask.java` | Quartz job (`alertSourcePollingTask`), default paused in `schedule_job` |
To add a new alert source: implement `AlertSource`, register as `@Component`, enable the polling task.
| Table | Purpose |
|-------|---------|
| `sys_user`, `sys_role`, `sys_menu`, `sys_dept` | RBAC |
| `sys_role_user`, `sys_role_menu`, `sys_role_data_scope` | RBAC associations |
| `sys_user_token` | Shiro auth tokens |
| `sys_dict_type`, `sys_dict_data` | Dictionary system |
| `sys_params` | Key-value system parameters |
| `sys_alert` | Alert/notification records |
| `weather_station` | Weather station registry |
| `weather_daily_data` | Daily meteorological observations |
| `weather_file_scan_record` | File tracking (receive -> display -> archive) |
| `sys_region` | Geographic region tree (province/city/county) |
| `schedule_job`, `schedule_job_log` | Quartz job definitions and execution history |
| `qrtz_*` | Quartz internal scheduler tables |
| `sys_log_operation`, `sys_log_login`, `sys_log_error` | Audit logs |
| `sys_oss` | Cloud storage object records |
+45 -11
View File
@@ -11,7 +11,7 @@
Target Server Version : 80044 (8.0.44)
File Encoding : 65001
Date: 24/06/2026 13:44:11
Date: 27/06/2026 22:38:33
*/
SET NAMES utf8mb4;
@@ -276,9 +276,9 @@ CREATE TABLE `schedule_job` (
-- ----------------------------
-- Records of schedule_job
-- ----------------------------
INSERT INTO `schedule_job` VALUES (1067246875800000076, 'testTask', '123456', '0 0/30 * * * ?', 0, '有参测试,多个参数使用json', 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-06-16 19:18:58');
INSERT INTO `schedule_job` VALUES (2029504525156528130, 'fileScanTask', '', '0 0/5 * * * ?', 0, '文件扫描定时任务', 1067246875800000001, '2026-03-05 18:29:20', 1067246875800000001, '2026-06-24 12:14:59');
INSERT INTO `schedule_job` VALUES (2069614094374367234, 'weatherSummarizeCacheTask', '', '0 0 1 * * ?', 1, '天气汇总缓存', 1067246875800000001, '2026-06-24 10:50:26', 1067246875800000001, '2026-06-24 12:14:51');
INSERT INTO `schedule_job` VALUES (2029504525156528130, 'fileScanTask', '', '0 0/1 * * * ?', 1, '文件扫描定时任务,每分钟', 1067246875800000001, '2026-03-05 18:29:20', 1067246875800000001, '2026-06-27 19:02:10');
INSERT INTO `schedule_job` VALUES (2069614094374367234, 'weatherSummarizeCacheTask', '', '0 0 1 * * ?', 1, '天气汇总缓存,每天凌晨1点', 1067246875800000001, '2026-06-24 10:50:26', 1067246875800000001, '2026-06-27 19:02:02');
INSERT INTO `schedule_job` VALUES (2070458228203012097, 'alertSourcePollingTask', '', '0 */10 * * * ?', 1, '通知数据源轮询,每10分钟', 1067246875800000001, '2026-06-26 18:44:44', 1067246875800000001, '2026-06-26 18:44:44');
-- ----------------------------
-- Table structure for schedule_job_log
@@ -302,6 +302,33 @@ CREATE TABLE `schedule_job_log` (
-- Records of schedule_job_log
-- ----------------------------
-- ----------------------------
-- Table structure for sys_alert
-- ----------------------------
DROP TABLE IF EXISTS `sys_alert`;
CREATE TABLE `sys_alert` (
`id` bigint NOT NULL COMMENT 'Snowflake ID',
`level` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'info' COMMENT '级别: info|warning|danger',
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '通知标题',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '通知正文',
`source_type` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'manual' COMMENT '来源: manual|import|schedule|datasource',
`source_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '业务关联ID',
`is_active` tinyint(1) NULL DEFAULT 1 COMMENT '1=有效 0=已撤回',
`publish_time` datetime NOT NULL COMMENT '发布时间',
`expire_time` datetime NULL DEFAULT NULL COMMENT '过期时间(null=永不过期)',
`creator` bigint NULL DEFAULT NULL COMMENT '创建人ID',
`create_date` datetime NULL DEFAULT NULL COMMENT '创建时间',
`updater` bigint NULL DEFAULT NULL COMMENT '更新人ID',
`update_date` datetime NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_active_time`(`is_active` ASC, `publish_time` ASC) USING BTREE,
INDEX `idx_source`(`source_type` ASC, `source_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统通知表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of sys_alert
-- ----------------------------
-- ----------------------------
-- Table structure for sys_dept
-- ----------------------------
@@ -516,7 +543,7 @@ INSERT INTO `sys_menu` VALUES (1067246875800000014, 1067246875800000012, '查看
INSERT INTO `sys_menu` VALUES (1067246875800000015, 1067246875800000012, '新增', NULL, 'sys:dept:save', 1, NULL, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000016, 1067246875800000012, '修改', NULL, 'sys:dept:update', 1, NULL, 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000017, 1067246875800000012, '删除', NULL, 'sys:dept:delete', 1, NULL, 3, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000025, 1067246875800000035, '菜单管理', 'sys/menu', NULL, 0, 'icon-unorderedlist', 0, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000025, 1067246875800000035, '菜单管理', 'sys/menu', NULL, 0, 'icon-unorderedlist', 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-06-26 18:49:40');
INSERT INTO `sys_menu` VALUES (1067246875800000026, 1067246875800000025, '查看', NULL, 'sys:menu:list,sys:menu:info', 1, NULL, 0, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000027, 1067246875800000025, '新增', NULL, 'sys:menu:save', 1, NULL, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000028, 1067246875800000025, '修改', NULL, 'sys:menu:update', 1, NULL, 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
@@ -531,14 +558,14 @@ INSERT INTO `sys_menu` VALUES (1067246875800000036, 1067246875800000030, '暂停
INSERT INTO `sys_menu` VALUES (1067246875800000037, 1067246875800000030, '恢复', NULL, 'sys:schedule:resume', 1, NULL, 5, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000038, 1067246875800000030, '立即执行', NULL, 'sys:schedule:run', 1, NULL, 6, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000039, 1067246875800000030, '日志列表', NULL, 'sys:schedule:log', 1, NULL, 7, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000040, 1067246875800000035, '参数管理', 'sys/params', '', 0, 'icon-fileprotect', 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000040, 1067246875800000035, '参数管理', 'sys/params', '', 0, 'icon-fileprotect', 4, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-06-26 18:49:56');
INSERT INTO `sys_menu` VALUES (1067246875800000041, 1067246875800000035, '字典管理', 'sys/dict-type', NULL, 0, 'icon-golden-fill', 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000042, 1067246875800000041, '查看', NULL, 'sys:dict:page,sys:dict:info', 1, NULL, 0, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000043, 1067246875800000041, '新增', NULL, 'sys:dict:save', 1, NULL, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000044, 1067246875800000041, '修改', NULL, 'sys:dict:update', 1, NULL, 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000045, 1067246875800000041, '删除', NULL, 'sys:dict:delete', 1, NULL, 3, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000046, 0, '日志管理', NULL, NULL, 0, 'icon-container', 5, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 17:28:42');
INSERT INTO `sys_menu` VALUES (1067246875800000047, 1067246875800000035, '文件上传', 'oss/oss', 'sys:oss:all', 0, 'icon-upload', 4, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000047, 1067246875800000035, '文件上传', 'oss/oss', 'sys:oss:all', 0, 'icon-upload', 5, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-06-26 18:50:12');
INSERT INTO `sys_menu` VALUES (1067246875800000048, 1067246875800000046, '登录日志', 'sys/log-login', 'sys:log:login', 0, 'icon-filedone', 0, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000049, 1067246875800000046, '操作日志', 'sys/log-operation', 'sys:log:operation', 0, 'icon-solution', 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_menu` VALUES (1067246875800000050, 1067246875800000046, '异常日志', 'sys/log-error', 'sys:log:error', 0, 'icon-file-exception', 2, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
@@ -573,12 +600,19 @@ INSERT INTO `sys_menu` VALUES (2029484183805616134, 2029484183805616129, '导出
INSERT INTO `sys_menu` VALUES (2063582074925912065, 2029484183805616129, '汇总', '', 'dailyweather:weatherdailydata:query', 1, '', 5, 1067246875800000001, '2026-06-07 19:21:21', 1067246875800000001, '2026-06-07 19:21:29');
INSERT INTO `sys_menu` VALUES (2064677221264756738, 2029421667205255169, '列表', '', 'station:weatherstation:list', 1, '', 5, 1067246875800000001, '2026-06-10 19:53:04', 1067246875800000001, '2026-06-10 19:53:04');
INSERT INTO `sys_menu` VALUES (2065416928948871170, 0, '气象模块', '', '', 0, 'icon-ungroup', 0, 1067246875800000001, '2026-06-12 20:52:24', 1067246875800000001, '2026-06-12 21:14:12');
INSERT INTO `sys_menu` VALUES (2065422543658115073, 2065416928948871170, '实时监测', '/weather/realtime-monitoring', '', 0, 'icon-time-circle-fill', 0, 1067246875800000001, '2026-06-12 21:14:43', 1067246875800000001, '2026-06-17 19:14:06');
INSERT INTO `sys_menu` VALUES (2065422543658115073, 2065416928948871170, '实时监测', 'weather/realtime-monitoring', '', 0, 'icon-time-circle-fill', 0, 1067246875800000001, '2026-06-12 21:14:43', 1067246875800000001, '2026-06-26 18:52:32');
INSERT INTO `sys_menu` VALUES (2065422609408024577, 2065416928948871170, '回波预测', 'weather/prediction', '', 0, 'icon-earth', 1, 1067246875800000001, '2026-06-12 21:14:58', 1067246875800000001, '2026-06-17 20:17:17');
INSERT INTO `sys_menu` VALUES (2065422708557176834, 2065416928948871170, '631气象信息', 'weather/631weather-data', '', 0, 'icon-pic-left', 2, 1067246875800000001, '2026-06-12 21:15:22', 1067246875800000001, '2026-06-17 22:07:37');
INSERT INTO `sys_menu` VALUES (2067153650086825985, 2065422543658115073, '获取实时监测', '', 'filescan:record:tree', 1, '', 0, 1067246875800000001, '2026-06-17 15:53:31', 1067246875800000001, '2026-06-17 15:53:31');
INSERT INTO `sys_menu` VALUES (2067153761810501634, 2065422609408024577, '获取模式预测', '', 'filescan:record:model:list', 1, '', 0, 1067246875800000001, '2026-06-17 15:53:57', 1067246875800000001, '2026-06-17 20:17:47');
INSERT INTO `sys_menu` VALUES (2067203040717520898, 2065416928948871170, '展示文件', '', 'filescan:record:display', 1, '', 0, 1067246875800000001, '2026-06-17 19:09:46', 1067246875800000001, '2026-06-17 19:09:46');
INSERT INTO `sys_menu` VALUES (2070459758339641346, 1067246875800000035, '通知管理', 'sys/system-alert', '', 0, 'icon-sound-fill', 0, 1067246875800000001, '2026-06-26 18:50:48', 1067246875800000001, '2026-06-26 18:52:17');
INSERT INTO `sys_menu` VALUES (2070462059406118913, 2070459758339641346, '查询', '', 'sys:alert:page', 1, '', 0, 1067246875800000001, '2026-06-26 18:59:57', 1067246875800000001, '2026-06-26 18:59:57');
INSERT INTO `sys_menu` VALUES (2070462181372284929, 2070459758339641346, '详情', '', 'sys:alert:info', 1, '', 1, 1067246875800000001, '2026-06-26 19:00:26', 1067246875800000001, '2026-06-26 19:00:26');
INSERT INTO `sys_menu` VALUES (2070462282153021442, 2070459758339641346, '新增', '', 'sys:alert:save', 1, '', 2, 1067246875800000001, '2026-06-26 19:00:50', 1067246875800000001, '2026-06-26 19:00:50');
INSERT INTO `sys_menu` VALUES (2070462368132059138, 2070459758339641346, '修改', '', 'sys:alert:update', 1, '', 3, 1067246875800000001, '2026-06-26 19:01:11', 1067246875800000001, '2026-06-26 19:01:11');
INSERT INTO `sys_menu` VALUES (2070462442044084226, 2070459758339641346, '删除', '', 'sys:alert:delete', 1, '', 4, 1067246875800000001, '2026-06-26 19:01:28', 1067246875800000001, '2026-06-26 19:01:28');
INSERT INTO `sys_menu` VALUES (2070462786237059073, 2070459758339641346, '撤回', '', 'sys:alert:retract', 1, '', 5, 1067246875800000001, '2026-06-26 19:02:50', 1067246875800000001, '2026-06-26 19:02:50');
-- ----------------------------
-- Table structure for sys_oss
@@ -619,8 +653,8 @@ CREATE TABLE `sys_params` (
-- ----------------------------
-- Records of sys_params
-- ----------------------------
INSERT INTO `sys_params` VALUES (1067246875800000073, 'CLOUD_STORAGE_CONFIG_KEY', '{\"type\":1,\"qiniuDomain\":\"http://test.oss.renren.io\",\"qiniuPrefix\":\"upload\",\"qiniuAccessKey\":\"NrgMfABZxWLo5B-YYSjoE8-AZ1EISdi1Z3ubLOeZ\",\"qiniuSecretKey\":\"uIwJHevMRWU0VLxFvgy0tAcOdGqasdtVlJkdy6vV\",\"qiniuBucketName\":\"renren-oss\",\"aliyunDomain\":\"\",\"aliyunPrefix\":\"\",\"aliyunEndPoint\":\"\",\"aliyunAccessKeyId\":\"\",\"aliyunAccessKeySecret\":\"\",\"aliyunBucketName\":\"\",\"qcloudDomain\":\"\",\"qcloudPrefix\":\"\",\"qcloudSecretId\":\"\",\"qcloudSecretKey\":\"\",\"qcloudBucketName\":\"\"}', 0, '云存储配置信息', 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_params` VALUES (2066834685586268161, 'FILE_SCAN_ROOT_PATH', 'D:/weather-data-files', 1, '气象文件扫描根路径', 1067246875800000001, '2026-06-16 18:46:04', 1067246875800000001, '2026-06-24 12:33:37');
INSERT INTO `sys_params` VALUES (1067246875800000073, 'CLOUD_STORAGE_CONFIG_KEY', '{\"type\":1,\"qiniuDomain\":\"http://test.oss.com\",\"qiniuPrefix\":\"upload\",\"qiniuAccessKey\":\"AccessKey\",\"qiniuSecretKey\":\"SecretKey\",\"qiniuBucketName\":\"test-oss\",\"aliyunDomain\":\"\",\"aliyunPrefix\":\"\",\"aliyunEndPoint\":\"\",\"aliyunAccessKeyId\":\"\",\"aliyunAccessKeySecret\":\"\",\"aliyunBucketName\":\"\",\"qcloudDomain\":\"\",\"qcloudPrefix\":\"\",\"qcloudSecretId\":\"\",\"qcloudSecretKey\":\"\",\"qcloudBucketName\":\"\"}', 0, '云存储配置信息', 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_params` VALUES (2066834685586268161, 'FILE_SCAN_ROOT_PATH', 0, 1, '气象文件扫描根路径', 1067246875800000001, '2026-06-16 18:46:04', 1067246875800000001, '2026-06-27 18:59:36');
-- ----------------------------
-- Table structure for sys_region
@@ -4106,7 +4140,7 @@ CREATE TABLE `sys_user` (
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1067246875800000001, 'admin', '$2a$10$012Kx2ba5jzqr9gLlG4MX.bnQJTD9UWqF57XDo2N3.fPtLne02u/m', '管理员', NULL, 0, 'root@renren.io', '13612345678', 1067246875800000066, 1, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
INSERT INTO `sys_user` VALUES (1067246875800000001, 'admin', '$2a$10$012Kx2ba5jzqr9gLlG4MX.bnQJTD9UWqF57XDo2N3.fPtLne02u/m', '管理员', NULL, 0, 'root@weather.com', '13111111111', 1067246875800000066, 1, 1, 1067246875800000001, '2026-03-04 11:24:04', 1067246875800000001, '2026-03-04 11:24:04');
-- ----------------------------
-- Table structure for sys_user_token
@@ -1,66 +0,0 @@
///**
// * Copyright (c) 2018 人人开源 All rights reserved.
// *
// * https://www.renren.io
// *
// * 版权所有,侵权必究!
// */
//
//package io.weather.modules.job.config;
//
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.scheduling.quartz.SchedulerFactoryBean;
//
//import javax.sql.DataSource;
//import java.util.Properties;
//
///**
// * 定时任务配置(备注:集群需要打开注释)
// *
// * @author 123
// */
//@Configuration
//public class ScheduleConfig {
//
// @Bean
// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
// SchedulerFactoryBean factory = new SchedulerFactoryBean();
// factory.setDataSource(dataSource);
//
// //quartz参数
// Properties prop = new Properties();
// prop.put("org.quartz.scheduler.instanceName", "RenrenScheduler");
// prop.put("org.quartz.scheduler.instanceId", "AUTO");
// //线程池配置
// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
// prop.put("org.quartz.threadPool.threadCount", "20");
// prop.put("org.quartz.threadPool.threadPriority", "5");
// //JobStore配置
// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
// //集群配置
// prop.put("org.quartz.jobStore.isClustered", "true");
// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
//
// prop.put("org.quartz.jobStore.misfireThreshold", "12000");
// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
// prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
//
// //PostgreSQL数据库,需要打开此注释
// //prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
//
// factory.setQuartzProperties(prop);
//
// factory.setSchedulerName("RenrenScheduler");
// //延时启动
// factory.setStartupDelay(30);
// factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// //可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
// factory.setOverwriteExistingJobs(true);
// //设置自动启动,默认为true
// factory.setAutoStartup(true);
//
// return factory;
// }
//}
@@ -8,6 +8,7 @@ import com.weather.common.utils.SpringContextUtils;
import com.weather.modules.job.entity.ScheduleJobEntity;
import com.weather.modules.job.entity.ScheduleJobLogEntity;
import com.weather.modules.job.service.ScheduleJobLogService;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,6 +23,7 @@ import java.util.Date;
*
* @author 123
*/
@DisallowConcurrentExecution
public class ScheduleJob extends QuartzJobBean {
private Logger logger = LoggerFactory.getLogger(getClass());
@@ -1,20 +1,3 @@
/**
* Copyright 2018 人人开源 https://www.renren.io
* <p>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.weather.modules.security.password;
/**
@@ -15,8 +15,9 @@ import jakarta.validation.constraints.Null;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 部门管理
@@ -25,9 +26,9 @@ import java.util.Date;
* @since 1.0.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@EqualsAndHashCode
@Schema(title = "部门管理")
public class SysDeptDTO extends TreeNode implements Serializable {
public class SysDeptDTO implements TreeNode<SysDeptDTO> {
private static final long serialVersionUID = 1L;
@Schema(title = "id")
@@ -39,6 +40,9 @@ public class SysDeptDTO extends TreeNode implements Serializable {
@NotNull(message="{sysdept.pid.require}", groups = DefaultGroup.class)
private Long pid;
@Schema(title = "子部门列表")
private List<SysDeptDTO> children = new ArrayList<>();
@Schema(title = "部门名称")
@NotBlank(message="{sysdept.name.require}", groups = DefaultGroup.class)
private String name;
@@ -16,8 +16,9 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Range;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 菜单管理
@@ -26,9 +27,9 @@ import java.util.Date;
* @since 1.0.0
*/
@Data
@EqualsAndHashCode(callSuper = true)
@EqualsAndHashCode
@Schema(title = "菜单管理")
public class SysMenuDTO extends TreeNode<SysMenuDTO> implements Serializable {
public class SysMenuDTO implements TreeNode<SysMenuDTO> {
private static final long serialVersionUID = 1L;
@Schema(title = "id")
@@ -40,6 +41,9 @@ public class SysMenuDTO extends TreeNode<SysMenuDTO> implements Serializable {
@NotNull(message="{sysmenu.pid.require}", groups = DefaultGroup.class)
private Long pid;
@Schema(title = "子菜单列表")
private List<SysMenuDTO> children = new ArrayList<>();
@Schema(title = "菜单名称")
@NotBlank(message="sysmenu.name.require", groups = DefaultGroup.class)
private String name;
@@ -1,11 +1,3 @@
/**
* Copyright (c) 2019 人人开源 All rights reserved.
* <p>
* https://www.renren.io
* <p>
* 版权所有,侵权必究!
*/
package com.weather.modules.sys.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -1,11 +1,3 @@
/**
* Copyright (c) 2019 人人开源 All rights reserved.
* <p>
* https://www.renren.io
* <p>
* 版权所有,侵权必究!
*/
package com.weather.modules.sys.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -39,7 +39,7 @@ public class WeatherDataListener extends AnalysisEventListener<WeatherExcelVO> {
try {
LocalDate localDate = LocalDate.of(data.getYear(), data.getMonth(), data.getDay());
entity.setObserveDate(Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()));
entity.setObserveDate(Date.from(localDate.atStartOfDay(ZoneId.of("GMT+8")).toInstant()));
} catch (Exception e) {
log.warn("第{}行日期转换失败: {}年{}月{}日,已跳过", context.readRowHolder().getRowIndex(),
data.getYear(), data.getMonth(), data.getDay());
@@ -55,7 +55,7 @@ public class WeatherSummarizeCacheTask implements ITask {
.collect(Collectors.groupingBy(dto -> String.valueOf(dto.getStationId())));
String key = RedisKeys.getWeatherSummarizeKey(month, day);
redisUtils.set(key, grouped, RedisUtils.NOT_EXPIRE);
redisUtils.set(key, grouped, RedisUtils.HOUR_SIX_EXPIRE);
log.info("天气汇总缓存刷新完成,共 {} 条记录,{} 个站点", list.size(), grouped.size());
} catch (Exception e) {
@@ -61,10 +61,10 @@ public class WeatherExcelVO {
@ExcelProperty("最大风速出现时间")
private Integer maxWindTime;
@ExcelProperty("极大风速的风向(角度)")
@ExcelProperty("极大风速")
private BigDecimal extremeWindSpeed;
@ExcelProperty("极大风速")
@ExcelProperty("极大风速的风向(角度)")
private Integer extremeWindDirection;
@ExcelProperty("极大风速出现时间")
@@ -2,7 +2,7 @@ spring:
data:
redis:
database: 0
host: 127.0.0.1
host: 192.168.2.186
port: 6379
password: # 密码(默认为空)
timeout: 6000ms # 连接超时时长(毫秒)
@@ -16,7 +16,7 @@ spring:
druid:
#MySQL
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
url: jdbc:mysql://192.168.2.186:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
username: root
password: root
initial-size: 10
@@ -51,17 +51,3 @@ spring:
project-options:
redis:
open: true
##多数据源的配置,需要引用renren-dynamic-datasource
#dynamic:
# datasource:
# slave1:
# driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
# url: jdbc:sqlserver://123456:1433;DatabaseName=renren_security
# username: sa
# password: 123456
# slave2:
# driver-class-name: org.postgresql.Driver
# url: jdbc:postgresql://123456:5432/renren_security
# username: postgres
# password: 123456
@@ -2,7 +2,7 @@ spring:
data:
redis:
database: 0
host: 127.0.0.1
host: 192.168.2.186
port: 6379
password: # 生产环境请设置密码
timeout: 6000ms # 连接超时时长(毫秒)
@@ -16,7 +16,7 @@ spring:
druid:
#MySQL
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
url: jdbc:mysql://192.168.2.186:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
username: weather
password: 123456
initial-size: 10
@@ -2,7 +2,7 @@ spring:
data:
redis:
database: 0
host: 127.0.0.1
host: 192.168.2.186
port: 6379
password: # 密码(默认为空)
timeout: 6000ms # 连接超时时长(毫秒)
@@ -16,7 +16,7 @@ spring:
druid:
#MySQL
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
url: jdbc:mysql://192.168.2.186:3306/weather_data_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
username: weather
password: 123456
initial-size: 10
@@ -5,7 +5,7 @@ server:
threads:
max: 1000
min-spare: 30
port: 8080
port: 48080
servlet:
context-path: /system-admin
session:
@@ -8,14 +8,14 @@
<springProfile name="dev,test">
<logger name="org.springframework.web" level="TRACE"/>
<logger name="org.springboot.sample" level="TRACE" />
<logger name="io.renren" level="DEBUG" />
<logger name="com.weather" level="DEBUG" />
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<logger name="org.springframework.web" level="ERROR"/>
<logger name="org.springboot.sample" level="ERROR" />
<logger name="io.renren" level="ERROR" />
<logger name="com.weather" level="ERROR" />
</springProfile>
</configuration>