**weather-data** is a full-stack meteorological data analysis platform: Java 17 / Spring Boot 3.5 multi-module backend + Vue 3 / Vite 5 / TypeScript frontend. Forked from [renren-security](https://gitee.com/renrenio/renren-security).
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.
| **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 includes `testTask` (paused by default). |
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`).
### Logging
- Logback config differs per module. Logger names use `io.renren` (fork legacy), **not**`com.weather`.
- Admin dev profile enables MyBatis SQL stdout logging (`StdOutImpl`); API does not.
- When adding `@Slf4j` to `com.weather.*` classes, add a `com.weather` level override or change the existing `io.renren` logger scope.
- Admin: `project-options.redis.open: true` in dev YAML. API: inherits `RedisAspect` default of `false` (no override in its YAML).
- Knife4j: disabled by default in admin (`knife4j.enable: false`), **enabled** in API (`knife4j.enable: true`). Docs at `/doc.html`.
-`RedisAspect` wraps `@RedisCache` annotations with channel publish for cache invalidation.
### MyBatis-Plus gotchas
- **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.
- Admin `typeAliasesPackage: com.weather.modules.*.entity`; API still uses `io.renren.entity` (legacy).
4. Runs on `CompletableFuture` with manual `UserContextHolder` propagation for security context.
5. On completion, clears Redis summary cache (`weather:summarize:*`).
#### Weather summarize cache
`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.
#### Station priority ordering
`WeatherDailyDataServiceImpl.page()` uses custom `CASE WHEN` SQL to order stations belonging to the user's department + sub-departments first.
-`src/router/index.ts`: `beforeEach` guard — auth check, dynamic route registration from backend menus, tab management. Routes are dynamically added via `addRoute` with **flattened nested routes** (keep-alive limitation). View components resolved via `import.meta.glob("/src/views/**/*.vue")`.
-`src/store/index.ts` (`useAppStore`): monolithic store — all state nested in `state.state` (double nesting, e.g. `store.state.appIsLogin`). `initApp` fetches menus/permissions/user/dicts in 4 parallel requests.
-`src/store/importTasks.ts`: separate store for import task tracking (computed getters: `activeTasks`, `hasActiveTasks`, `recentTasks`).
-`src/utils/router.ts`: converts backend menu records → Vue router records, supports iframe/external links with `openStyle` flags.
Layout is event-driven: `src/layout/` shell + `mitt` event bus (`src/utils/emits.ts`). The `EMitt` enum defines 13 events for sidebar, theme, tabs, layout changes. **Trace both the Pinia store and mitt events** when changing navigation/sidebar/tabs/theme.
### Common page pattern: `useView` hook
Admin CRUD pages use `src/hooks/useView.ts` for shared list-page workflow.
Key behaviors to know before refactoring:
-`closeCurrentTab()`: if tabs enabled, emits `OnCloseCurrTab` mitt event; otherwise navigates to `/home`.
-`exportHandle()`: uses `window.location.href` with token as query param (NOT Axios).
-`createdIsNeed: true` / `activatedIsNeed: false` by default. Pages needing refresh on tab activation must set `activatedIsNeed: true`.
- Includes workflow helpers (`handleFlowRoute`, `flowDetailRoute`) hardcoded to `/flow/task-form`.
### Cache utility
All cache keys prefixed with `v1@` to avoid collisions. Supports `localStorage` and `sessionStorage` (token uses sessionStorage). JSON serialization is automatic. `getCache` supports auto-delete-after-read (`isDelete` flag).
#### 1. Null ≠ zero — missing data MUST be preserved as null
When mapping backend API responses to frontend models, **never** default missing numeric values to `0`. Rainfall of `0mm` means "no rain that day" (valid measurement); `null` means "no data available" (missing record). Use `: null` not `: 0` in data mapping, and display `"—"` for null values via `fmtVal()`.
`html2canvas`, `jspdf`, and `echarts` are NOT imported at module level. Load them via `await import()` only when triggered by user action. This saves ~600KB from the initial bundle.
Never use `@import url("https://fonts.googleapis.com/...")` inside Vue scoped styles. Use `<link rel="preconnect">` + `<link rel="stylesheet">` in `index.html`.
Never use `watch(filters, callback, { deep: true })`. Derive a precise computed trigger key (e.g. `dataHash`, `filteredHash`, `extremesVersion`) and watch that instead.
Six services in `docker-compose.yml`: mysql (8.0), redis (7 Alpine + AOF), admin JAR, API JAR, UI (Nginx + built frontend), gateway (Nginx reverse proxy on port 80).
**Important**: The `deploy/` directory referenced by Docker Compose (`deploy/mysql/init/`, `deploy/nginx/`) **does not exist locally** — it must be created for deployment.
Environment variables from `.env` at project root. Two frontend Dockerfiles: standard multi-stage (`Dockerfile`) and pre-built (`Dockerfile.offline`).