diff --git a/CLAUDE.md b/CLAUDE.md
index cbd9440..f8d9566 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -132,7 +132,7 @@ B 阶段分两段,**全部固化到 skills**。入口:`/erp-workflow:coding-
### 你禁止做的 🚫
-1. **主会话直接 `mysql -e` 跑业务 DDL**(只读查询 / 临时本地调试除外)——业务 schema 必须走 `sql/migrations/V_n__*.sql`,详见下方 Schema 演化规约
+1. **主会话直接 `mysql -e` 跑业务 DDL**(只读查询 / 临时本地调试 / A4 `db-init` 首次 apply V1 验证除外)——业务 schema 必须走 `sql/migrations/V_n__*.sql`,详见下方 Schema 演化规约。**A4 例外**:`db-init` 在 A 阶段 setup-test-db 后会一次性手工 `mysql < V1__initial_schema.sql` 把 V1 灌入测试库,并校验 `SHOW TABLES` 行数 = docs/03 表数量,用于 DDL 自检;B 阶段(Spring Boot 启动后)Flyway 会重建 schema 并 apply 全部 migration(包括 V1),手工 apply 不会污染 Flyway 历史。
2. **手动 Edit `docs/08 § 二/§ 三` 的 `MR:` / `整体 MR:` 字段**,必须要由 `mr-create` 自动回写
### Schema 演化规约(Flyway migration)
diff --git a/backend/pom.xml b/backend/pom.xml
new file mode 100644
index 0000000..a19dcac
--- /dev/null
+++ b/backend/pom.xml
@@ -0,0 +1,141 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.3.4
+
+
+
+ com.xly.erp
+ xly-erp-backend
+ 0.0.1-SNAPSHOT
+ jar
+ xly-erp-backend
+ 小羚羊 ERP 后端
+
+
+ 17
+ 17
+ 17
+ UTF-8
+ 3.5.7
+ 0.12.5
+ 1.18.40
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.security
+ spring-security-crypto
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+ ${mybatis-plus.version}
+
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.flywaydb
+ flyway-mysql
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ ${jjwt.version}
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ ${jjwt.version}
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ ${jjwt.version}
+ runtime
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ ${env.DB_HOST}
+ ${env.DB_PORT}
+ ${env.DB_USER}
+ ${env.DB_PASSWORD}
+ ${env.DB_SCHEMA}
+ ${env.JWT_SECRET}
+
+
+
+
+
+
diff --git a/backend/src/main/java/com/xly/erp/Application.java b/backend/src/main/java/com/xly/erp/Application.java
new file mode 100644
index 0000000..87b3550
--- /dev/null
+++ b/backend/src/main/java/com/xly/erp/Application.java
@@ -0,0 +1,13 @@
+package com.xly.erp;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@MapperScan("com.xly.erp.module.*.mapper")
+public class Application {
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/backend/src/main/java/com/xly/erp/common/config/PasswordEncoderConfig.java b/backend/src/main/java/com/xly/erp/common/config/PasswordEncoderConfig.java
new file mode 100644
index 0000000..26131c8
--- /dev/null
+++ b/backend/src/main/java/com/xly/erp/common/config/PasswordEncoderConfig.java
@@ -0,0 +1,18 @@
+package com.xly.erp.common.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+/**
+ * BCrypt 密码编码器 Bean。strength=10(Spring Security 默认)。
+ * docs/03 sys_user.sPasswordHash + docs/04 § 1.6。
+ */
+@Configuration
+public class PasswordEncoderConfig {
+
+ @Bean
+ public BCryptPasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder(10);
+ }
+}
diff --git a/backend/src/main/java/com/xly/erp/common/config/WebMvcConfig.java b/backend/src/main/java/com/xly/erp/common/config/WebMvcConfig.java
new file mode 100644
index 0000000..98fee6d
--- /dev/null
+++ b/backend/src/main/java/com/xly/erp/common/config/WebMvcConfig.java
@@ -0,0 +1,21 @@
+package com.xly.erp.common.config;
+
+import com.xly.erp.common.security.JwtHandlerInterceptor;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+@RequiredArgsConstructor
+public class WebMvcConfig implements WebMvcConfigurer {
+
+ private final JwtHandlerInterceptor jwtInterceptor;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(jwtInterceptor)
+ .addPathPatterns("/api/v1/**")
+ .excludePathPatterns("/api/v1/auth/login");
+ }
+}
diff --git a/backend/src/main/java/com/xly/erp/common/exception/BizException.java b/backend/src/main/java/com/xly/erp/common/exception/BizException.java
new file mode 100644
index 0000000..cf7fa22
--- /dev/null
+++ b/backend/src/main/java/com/xly/erp/common/exception/BizException.java
@@ -0,0 +1,30 @@
+package com.xly.erp.common.exception;
+
+import lombok.Getter;
+
+/**
+ * 业务异常 — 由 service 层抛出,由 GlobalExceptionHandler 统一转 Result.fail。
+ * docs/04 § 1.4。
+ */
+@Getter
+public class BizException extends RuntimeException {
+ private final int code;
+ /** 可选附带的响应数据(例如 42301 锁定返 lockUntil)。null 表示无 data 字段。 */
+ private final Object data;
+
+ public BizException(int code, String message) {
+ this(code, message, (Object) null);
+ }
+
+ public BizException(int code, String message, Object data) {
+ super(message);
+ this.code = code;
+ this.data = data;
+ }
+
+ public BizException(int code, String message, Throwable cause) {
+ super(message, cause);
+ this.code = code;
+ this.data = null;
+ }
+}
diff --git a/backend/src/main/java/com/xly/erp/common/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/xly/erp/common/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..7523765
--- /dev/null
+++ b/backend/src/main/java/com/xly/erp/common/exception/GlobalExceptionHandler.java
@@ -0,0 +1,66 @@
+package com.xly.erp.common.exception;
+
+import com.xly.erp.common.response.ErrorCode;
+import com.xly.erp.common.response.Result;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * 全局异常处理器。
+ * 把 BizException / 参数校验异常 / 兜底异常转 Result.fail 统一响应。
+ * docs/04 § 1.4。
+ */
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(BizException.class)
+ public ResponseEntity> handleBiz(BizException e) {
+ log.warn("[BizException] code={} message={} hasData={}", e.getCode(), e.getMessage(), e.getData() != null);
+ Result