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-adminApiApplication(system-api/) → port 8081, context/renren-apiGeneratorApplication(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,@Slf4jare standard on entity/service classes. - DTO/Entity/VO separation per module — request DTOs often extend
BaseEntity.
PK & Auth
- PK:
ASSIGN_ID(Snowflake) viaIdUtil.getSnowflakeNextId(). All entities extendBaseEntity. - Auth: Apache Shiro 1.12 (Jakarta classifier) + OAuth2 token. Login →
tokenheader. - API module:
@Loginannotation +AuthorizationInterceptor(token in header or param), backed by atokentable. - 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), notcom.weather. - Admin dev profile enables MyBatis SQL stdout logging (
StdOutImpl); API does not. - When adding
@Slf4jtocom.weather.*classes, add acom.weatherlevel override or change the existingio.renrenlogger scope.
Redis & Docs
- Admin:
project-options.redis.open: truein dev YAML. API: inheritsRedisAspectdefault offalse(no override in its YAML). - Knife4j: disabled by default in admin (
knife4j.enable: false), enabled in API (knife4j.enable: true). Docs at/doc.html. RedisAspectwraps@RedisCacheannotations with channel publish for cache invalidation.
MyBatis-Plus gotchas
- Batch inserts bypass auto-fill —
FieldMetaObjectHandleronly fires oninsert()/updateById(). Custom batch methods must manually setcreator,createDate,updater,updateDate,deptId. - Column names with special characters (e.g.
rain_20_20) require explicit@TableFieldannotations — MyBatis-Plus cannot auto-map them from camelCase. - Admin
typeAliasesPackage: com.weather.modules.*.entity; API still usesio.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
- First pass:
AnalysisEventListenercounts total rows. - Second pass:
WeatherDataListenerprocesses with batch insert (2000 records/batch). - Progress tracked in-memory via
ConcurrentHashMap<String, ImportProgress>(volatilefields +AtomicInteger). - Runs on
CompletableFuturewith manualUserContextHolderpropagation for security context. - 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-routerwith hash history (createWebHashHistory)- Pinia for state management
- Axios via
src/utils/http.ts+src/service/baseService.ts - API base URL:
VITE_APP_APIenv var, overridable at runtime bywindow.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:
lodashandvlib(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
tokenheader,X-Requested-With, request timing, cache-busting_ton GET - On
code === 401: auto-redirects to/login - Response unwrapped: callers receive
response.data - File exports: bypass Axios, use
window.location.hrefwith token as query param - Uploads: no
Content-Typeset (browser auto-sets forFormData)
Routing & state
src/router/base.ts: 7 base routes (/,/home,/login,/user/password,/iframe/:id?,/error, 404 catch-all)src/router/index.ts:beforeEachguard — auth check, dynamic route registration from backend menus, tab management. Routes are dynamically added viaaddRoutewith flattened nested routes (keep-alive limitation). View components resolved viaimport.meta.glob("/src/views/**/*.vue").src/store/index.ts(useAppStore): monolithic store — all state nested instate.state(double nesting, e.g.store.state.appIsLogin).initAppfetches 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 withopenStyleflags.
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, emitsOnCloseCurrTabmitt event; otherwise navigates to/home.exportHandle(): useswindow.location.hrefwith token as query param (NOT Axios).dataListSortChangeHandle(): converts camelCase → snake_case for backend (e.g.stationId→station_id).createdIsNeed: true/activatedIsNeed: falseby default. Pages needing refresh on tab activation must setactivatedIsNeed: 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
- Upload via
baseService.upload()(FormData, no explicit Content-Type). - On success, poll
GET .../import/progress/{backendTaskId}every 3 seconds. - Update Pinia store (
useImportTaskStore) with percentage/state. - Emit
refreshDataListon 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.mddoes not contain substantive guidance — this file is the primary operational reference.weather-data-ui/CLAUDE.mdis superseded by this merged root file.- No CI configuration exists. Only pre-commit is frontend lint-staged via yarn git hooks.
renren-generatormodule exists but is commented out of the root POM build.system-dynamic-datasourceis a stub — multi-DS config inapplication-dev.ymlis commented out..gitignoreexcludes.idea/but.idea/is tracked (committed IDE config;.idea/.gitignoreonly excludes local files likeworkspace.xml).