weather-data/CLAUDE.md

16 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.


Project overview

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.

weather-data/
├── system-common/            → shared Java lib
├── system-admin/             → admin backend (port 8080, /system-admin)
├── system-api/               → public API service (port 8081, /renren-api)
├── system-dynamic-datasource → multi-DS support (stub, not populated)
├── renren-generator/         → code generator (commented out of build)
└── weather-data-ui/          → Vue 3 SPA frontend

Database: weather_data_system (MySQL). Init from system-admin/db/weather_data_system.sql (includes schema + seed data). Default admin: admin / admin (BCrypt-encoded). Key custom tables: weather_daily_data, weather_station, weather_file_scan_record. No Flyway/Liquibase — all schema changes are manual SQL.

Port & context path reference

Service Port Context Path App Class
Admin 8080 /system-admin AdminApplication
API 8081 /renren-api ApiApplication
Frontend (dev) 8001 / Vite dev server
Frontend (prod) 80 / Nginx via gateway

Backend

Build & Run

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

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.

Launch from IntelliJ:

  • AdminApplication (system-admin/) → port 8080, context /system-admin
  • ApiApplication (system-api/) → port 8081, context /renren-api
  • GeneratorApplication (renren-generator/) — commented out of build

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)

Mapper XMLs: src/main/resources/mapper/<domain>/**/*.xml

Conventions

  • Lombok used throughout: @Data, @AllArgsConstructor, @Slf4j are standard on entity/service classes.
  • DTO/Entity/VO separation per module — request DTOs often extend BaseEntity.

PK & Auth

  • PK: ASSIGN_ID (Snowflake) via IdUtil.getSnowflakeNextId(). All entities extend BaseEntity.
  • Auth: Apache Shiro 1.12 (Jakarta classifier) + OAuth2 token. Login → token header.
  • API module: @Login annotation + AuthorizationInterceptor (token in header or param), backed by a token table.
  • Do not introduce Spring Security — the project uses Shiro exclusively.

Key cross-cutting mechanisms

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 includes testTask (paused by default).
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.

Exception handling

Two separate @RestControllerAdvice handlers, one per module:

Handler Catches Persists errors?
system-admin: CustomExceptionHandler CommonException, DuplicateKeyException, UnauthorizedException, generic Exception Yes — saves to SysLogErrorService (IP, user-agent, URI, params, stack trace)
system-api: RenExceptionHandler CommonException, DuplicateKeyException, generic Exception No — returns Result only

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.

Redis & Docs

  • 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-fillFieldMetaObjectHandler 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).

Weather domain (backend)

Three sub-modules under system-admin/.../modules/weather/:

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

Weather data import flow

  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.
  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.


Frontend (weather-data-ui/)

Commands

npm install                    # install dependencies
npm run dev                    # Vite dev server (port 8001, host 0.0.0.0)
npm run build / npm run build:prod  # production build
npm run serve                  # preview production build
npm run lint                   # lint with autofix (ESLint)
npx vue-tsc --noEmit           # type-check (not in pre-commit)

Pre-commit: lint-staged runs eslint --fix on *.ts/*.vue via yorkie git hooks (not husky). No test runner configured.

Stack

  • Vite 5 + Vue 3 + TypeScript SPA
  • Element Plus + Element Plus Icons (all icons registered globally)
  • vue-router with hash history (createWebHashHistory)
  • Pinia for state management
  • Axios via src/utils/http.ts + src/service/baseService.ts
  • API base URL: VITE_APP_API env var, overridable at runtime by window.SITE_CONFIG.apiURL

Environment config

  • Dev: VITE_APP_API=http://192.168.2.186:8080/system-admin (hardcoded IP — new devs must change)
  • Prod: VITE_APP_API=/system-admin (relative, proxied via Nginx)
  • Runtime override takes priority: window.SITE_CONFIG.apiURL

Vite config

  • base: "./" (relative paths), chunkSizeWarningLimit: 1024
  • Manual chunks: lodash and vlib (vue/vue-router/element-plus)
  • Dev: HMR overlay disabled, host: "0.0.0.0", port 8001

Axios HTTP pattern

  • Success check: response.data.code === 0 (not === 200)
  • Request interceptor: adds token header, X-Requested-With, request timing, cache-busting _t on GET
  • On code === 401: auto-redirects to /login
  • Response unwrapped: callers receive response.data
  • File exports: bypass Axios, use window.location.href with token as query param
  • Uploads: no Content-Type set (browser auto-sets for FormData)

Routing & state

  • src/router/base.ts: 7 base routes (/, /home, /login, /user/password, /iframe/:id?, /error, 404 catch-all)
  • 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).
  • dataListSortChangeHandle(): converts camelCase → snake_case for backend (e.g. stationIdstation_id).
  • 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).

Weather frontend module

The home dashboard (src/views/home.vue) uses a composable-based architecture:

Composable Responsibility
useWeatherConstants.ts Rain levels, temperature thresholds, filter field definitions, fmtVal(), level/class helpers
useWeatherFilter.ts Filter state, toggle/reset/match logic, matchOp()
useWeatherStats.ts computeStats(), buildStatCards(), buildSummary(), rainLevelDistribution, WeatherDataRow type
useWeatherChart.ts ECharts dynamic import, buildChartOption(), ResizeObserver, precise trigger key (not deep watch)
useWeatherExport.ts PNG/PDF export with dynamic html2canvas/jspdf imports, loading indicator

Supporting utils: src/utils/chartBuilder.ts, src/utils/exportReport.ts.

Critical rules (must follow)

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().

// ✅ Correct
rainfall: row.rain2020 != null ? +row.rain2020 : null,

// ❌ Wrong — confuses "no data" with "measured zero"
rainfall: row.rain2020 != null ? +row.rain2020 : 0,

All helper functions must accept number | null and return "—" or "" for null. Stats computations must skip null values.

2. Heavy libraries must use dynamic imports

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.

3. Google Fonts go in index.html, not scoped styles

Never use @import url("https://fonts.googleapis.com/...") inside Vue scoped styles. Use <link rel="preconnect"> + <link rel="stylesheet"> in index.html.

4. Export must show user feedback

Always show ElLoading.service fullscreen and ElMessage success/failure. Disable the export button during rendering.

5. Deep watchers on filter objects are banned

Never use watch(filters, callback, { deep: true }). Derive a precise computed trigger key (e.g. dataHash, filteredHash, extremesVersion) and watch that instead.

Import progress polling pattern

  1. Upload via baseService.upload() (FormData, no explicit Content-Type).
  2. On success, poll GET .../import/progress/{backendTaskId} every 3 seconds.
  3. Update Pinia store (useImportTaskStore) with percentage/state.
  4. Emit refreshDataList on completion. Header indicator (import-task-indicator.vue) shows active tasks with spinning badge + popover.

Docker deployment

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).

Repository notes

  • README.md does not contain substantive guidance — this file is the primary operational reference.
  • weather-data-ui/CLAUDE.md is superseded by this merged root file.
  • No CI configuration exists. Only pre-commit is frontend lint-staged via yarn git hooks.
  • renren-generator module exists but is commented out of the root POM build.
  • system-dynamic-datasource is a stub — multi-DS config in application-dev.yml is commented out.
  • .gitignore excludes .idea/ but .idea/ is tracked (committed IDE config; .idea/.gitignore only excludes local files like workspace.xml).