Files
workspace/code/fms/CODEBUDDY.md
T
2026-07-21 22:30:33 +08:00

7.9 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/test/start yourself after finishing a change. Only build, test, or start services when the user explicitly asks. When delivering, state that build/test were not 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. Each route carries meta.moduleCode (e.g. m_module), plus menuKey, title, requiresAuth.
  • A beforeEach guard checks auth, then permissionStore.canModule(to.meta.moduleCode). src/stores/permissions.js loads b_user_module / b_user_power / b_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: 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 int 0/1; b_xh is display order (smaller = first). Table/column names must be backend-validated identifiers — never concatenated into SQL.