Files
workspace/code/fms/CODEBUDDY.md
T
2026-08-04 22:31:52 +08:00

105 lines
8.2 KiB
Markdown

# CODEBUDDY.md
This file provides guidance to CodeBuddy Code when working with code in this repository.
## Repository layout
This is a two-part application (FMS — 模块/数据管理平台):
- `fms-api/` — Spring Boot 4.1 (Java 21, Maven) backend. Serves on port **8088** with context-path **`/api`**.
- `fms-vue/` — Vue 3 + Vite 8 (pnpm) frontend. Dev server on port **5180**, proxies `/api` → `http://127.0.0.1:8088`.
Supporting docs in the repo root (Chinese):
- `开发规范.md` — binding development rules (read it; summarized below).
- `新数据库结构.md` — the module-management table design (authoritative schema).
- `旧数据库结构.md` — legacy schema, kept for reference only.
- `模块管理实施计划.md` — phased plan for the module-management feature.
- `fms-api/config/migrations/` — SQL Server schema migration scripts.
## Commands
### Backend (`fms-api/`)
```bash
./mvnw clean package # build jar -> target/fms-api-0.0.1-SNAPSHOT.jar
./mvnw test # run all tests (8 test classes under src/test)
./mvnw test -Dtest=ClassName # run a single test class
./mvnw test -Dtest=ClassName#methodName # run a single test method
./mvnw spring-boot:run # run the dev server (or use start.cmd)
java -jar target/fms-api-0.0.1-SNAPSHOT.jar # run built jar
```
JWT secret is read from env `FMS_JWT_SECRET` (defaults to a dev value in `application.yaml`).
### Frontend (`fms-vue/`)
```bash
pnpm install
pnpm dev # vite dev server on :5180
pnpm build # production build
pnpm preview # preview built output
pnpm fmt # format with oxfmt
pnpm fmt:check # check formatting
```
There is **no lint or test script** in the frontend.
### Build/test discipline (from `开发规范.md`)
Do **not** run build or start services yourself after finishing a change — only when the user explicitly asks. Running tests to verify changes is allowed (e.g. backend `./mvnw test`, frontend `pnpm fmt:check`). When delivering, state whether tests were run (and their results) and that no build was executed.
## Backend architecture
The defining characteristic of this backend is that it has **almost no business-specific endpoints**. Almost all data access goes through a small set of generic endpoints in `DataController`, implemented by `DataService` / `DataSaveService`. There are no entity/DAO/mapper layers — `utils/DbUtils` does raw JDBC against SQL Server.
Generic data endpoints (full path includes the `/api` context-path):
- `POST /api/data/loaddata` — load rows from a `view_name` with optional search/order (`DataService.loadData`).
- `POST /api/data/page` — paginated load (`page_no`, `page_size`, `view_name`, `order_by`).
- `POST /api/data/saveobjt` — batch upsert across multiple tables in one transaction (`DataSaveService.save`). Request body is a JSON array of `{ table, key_field, inserts[], updates[], deletes[] }`; the whole request rolls back if any table fails.
- `GET /api/data/nextid` — Snowflake ID from `utils/snowflake/IdGenerator`.
- `POST /api/data/loaddatabysql` — runs an arbitrary SQL string. **Avoid this**; it exists but new feature work should prefer the structured endpoints.
- `POST /api/auth/login` — `AuthController` / `AuthService`.
Key rules when working on the backend:
- Prefer the generic endpoints for query/paging/save/ID-generation. Do **not** modify their URL, parameters, response shape, or logic, and do **not** add new business-specific endpoints, unless you first explain why the generic API cannot do it and get user confirmation.
- `b_id` values that are `bigint` snowflake IDs must be serialized to the frontend as **strings**.
### Multi-organization database routing
Connections are per-organization SQL Server pools (Druid). The active org is selected at request time:
- `config/dbconfigs/<ORG_ID>.properties` holds `url`/`username`/`password`/`driver` for one org (e.g. `g3hd.properties` for org `G3HD`). **These files are git-ignored** and must not be committed. The `config-dir` is set by `fms.database.config-dir` in `application.yaml` (default `./config/dbconfigs`).
- `database/OrgDatabaseConfigLoader` loads a properties file by org id; `OrgDataSourceManager` caches one `DruidDataSource` per org.
- `config/JwtAuthFilter` parses the JWT, then `database/OrgContext` holds the current `orgId` so `DbUtils` targets the right datasource (`OrgRoutingDataSource`).
- New features operate on the `g3hd.properties` database (`fms`); do not touch the legacy database.
### Auth / JWT
- `config/JwtAuthFilter` (skips `/auth/login`) validates `Authorization: Bearer`, checks `ActiveSessionRegistry` (single-device enforcement), and sets `OrgContext.orgId`.
- `utils/JwtUtils` (jjwt) puts `orgid` / `sessionId` claims. `config/AuthProperties` reads `fms.auth.jwt-secret` / `fms.auth.jwt-expiration`.
- Login response returns `id`, `account`, `name`; the JWT session identifier stays the account.
## Frontend architecture
Vue 3 `<script setup>` SFCs, Pinia for state, **antdv-next** components (auto-imported via `@antdv-next/auto-import-resolver` in `vite.config.js` — do not manually import antdv-next components that the resolver handles). There is **no vue-i18n**; i18n is custom and DB-driven.
### API service layer
`src/services/api.js` (built on axios wrapper `src/services/http.js`, baseURL `/api`) exposes:
- `loadDataApi` → `POST /data/loaddata`
- `pageDataApi` → `POST /data/page`
- `saveObjectApi` → `POST /data/saveobjt`
- `nextIdApi` → `GET /data/nextid`
- `loadDataBySqlApi` → `POST /data/loaddatabysql` (avoid)
- `loginApi` → `POST /auth/login`
Feature code should call these, or wrap them in a dedicated service like `src/services/moduleManagement.js` (e.g. `loadModuleConfiguration`, `nextIds`, `saveChanges`). Do not introduce new backend URLs from the frontend — reuse the generic endpoints.
### Routing & permissions
- `src/router/index.js` registers view components via **static explicit `import()`**, not a dynamic whitelist. Data-backed routes carry `meta.dataCode` (e.g. `d_login_log`), plus `menuKey`, `title`, `requiresAuth`; legacy system pages without data nodes may temporarily use `meta.moduleCode`.
- A `beforeEach` guard checks auth, then resolves `meta.dataCode` to its parent page and checks that page's `b_user_module` grant. `src/stores/permissions.js` loads `s_module` / `b_user_module` / `b_user_power` / `s_module_power` and gates page entry and button enable/disable. Currently permission enforcement is **frontend-only**; backend interception is not yet implemented.
- `src/layouts/components/AppSidebar.vue` still uses a hardcoded `fallbackMenuItems` array. It should fall back to this static menu when the generic-interface module load fails.
### State & i18n
- Pinia stores: `stores/auth.js` (token/user, persisted), `stores/app.js` (locale, sidebar collapse, visited tabs), `stores/permissions.js` (module/power grants).
- i18n: `app` store holds `locale` (default `zh-CN`); translation text comes from the DB `b_i18n` table (loaded via `loadModuleConfiguration`), not vue-i18n.
### Module management feature
`src/views/module-management/` holds `ModuleManagementView.vue` plus `components/` panels (`ModuleTreePanel`, `ModuleBasicInfoPanel`, `ModuleFieldDefinitionPanel`, `ModulePowerPanel`, `ModuleTranslationsPanel`). Demo data lives in `src/data/moduleDemo.js` — the real page must not depend on it. The table design (six tables: `s_module`, `s_module_field`, `s_module_power`, `b_user_module`, `b_user_power`, `b_i18n`) is in `新数据库结构.md`; follow that schema exactly. `s_module.b_id` is an immutable snowflake ID; `s_module.b_code` is a mutable, unique lowercase code (e.g. `m_biz`). Bigint IDs are returned to the frontend as strings.
## Cross-cutting conventions
- Reuse existing components and antdv-next; extract shared logic into components/composables/services only when it genuinely reduces duplication. Keep new CSS scoped to the component.
- Changes should stay scoped to the current task — no unrelated refactors or formatting. Confirm impact before touching generic endpoints, shared components, or global styles. Preserve existing functionality and the user's uncommitted work.
- Boolean columns are `int` `0/1`; `b_xh` is display order (smaller = first). Table/column names must be backend-validated identifiers — never concatenated into SQL.