8.1 KiB
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/)
./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/)
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 aview_namewith 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 fromutils/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_idvalues that arebigintsnowflake 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>.propertiesholdsurl/username/password/driverfor one org (e.g.g3hd.propertiesfor orgG3HD). These files are git-ignored and must not be committed. Theconfig-diris set byfms.database.config-dirinapplication.yaml(default./config/dbconfigs).database/OrgDatabaseConfigLoaderloads a properties file by org id;OrgDataSourceManagercaches oneDruidDataSourceper org.config/JwtAuthFilterparses the JWT, thendatabase/OrgContextholds the currentorgIdsoDbUtilstargets the right datasource (OrgRoutingDataSource).- New features operate on the
g3hd.propertiesdatabase (fms); do not touch the legacy database.
Auth / JWT
config/JwtAuthFilter(skips/auth/login) validatesAuthorization: Bearer, checksActiveSessionRegistry(single-device enforcement), and setsOrgContext.orgId.utils/JwtUtils(jjwt) putsorgid/sessionIdclaims.config/AuthPropertiesreadsfms.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/loaddatapageDataApi→POST /data/pagesaveObjectApi→POST /data/saveobjtnextIdApi→GET /data/nextidloadDataBySqlApi→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.jsregisters view components via static explicitimport(), not a dynamic whitelist. Each route carriesmeta.moduleCode(e.g.m_module), plusmenuKey,title,requiresAuth.- A
beforeEachguard checks auth, thenpermissionStore.canModule(to.meta.moduleCode).src/stores/permissions.jsloadsb_user_module/b_user_power/b_module_powerand gates page entry and button enable/disable. Currently permission enforcement is frontend-only; backend interception is not yet implemented. src/layouts/components/AppSidebar.vuestill uses a hardcodedfallbackMenuItemsarray. 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:
appstore holdslocale(defaultzh-CN); translation text comes from the DBb_i18ntable (loaded vialoadModuleConfiguration), 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: b_module, b_module_field, b_module_power, b_user_module, b_user_power, b_i18n) is in 新数据库结构.md; follow that schema exactly. b_module.b_id is an immutable snowflake ID; b_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
int0/1;b_xhis display order (smaller = first). Table/column names must be backend-validated identifiers — never concatenated into SQL.