Files
workspace/code/fms/CODEBUDDY.md
T
2026-07-27 17:37:03 +08:00

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