From 199a33816394bf0dd2dac3682380df3d05c239d9 Mon Sep 17 00:00:00 2001 From: zichun Date: Fri, 8 May 2026 09:46:15 +0800 Subject: [PATCH] chore(backend): init Spring Boot 3 project skeleton REQ-USR-004 --- backend/pom.xml | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ backend/src/main/java/com/example/erp/Application.java | 12 ++++++++++++ backend/src/main/resources/application-dev.yml | 4 ++++ backend/src/main/resources/application.yml | 27 +++++++++++++++++++++++++++ backend/src/main/resources/db/migration/V1__initial_schema.sql | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ backend/src/test/java/com/example/erp/ApplicationContextTest.java | 14 ++++++++++++++ backend/src/test/resources/application-test.yml | 19 +++++++++++++++++++ 7 files changed, 363 insertions(+), 0 deletions(-) create mode 100644 backend/pom.xml create mode 100644 backend/src/main/java/com/example/erp/Application.java create mode 100644 backend/src/main/resources/application-dev.yml create mode 100644 backend/src/main/resources/application.yml create mode 100644 backend/src/main/resources/db/migration/V1__initial_schema.sql create mode 100644 backend/src/test/java/com/example/erp/ApplicationContextTest.java create mode 100644 backend/src/test/resources/application-test.yml diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..ea867cc --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,136 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.3.5 + + + + com.example + erp + 0.0.1-SNAPSHOT + erp + 小羚羊 ERP 后端 + jar + + + 21 + 3.5.7 + 0.12.6 + 5.8.28 + 10.17.0 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + + com.mysql + mysql-connector-j + runtime + + + + + org.flywaydb + flyway-core + ${flyway.version} + + + org.flywaydb + flyway-mysql + ${flyway.version} + + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + + + + + org.projectlombok + lombok + true + + + + + cn.hutool + hutool-all + ${hutool.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/backend/src/main/java/com/example/erp/Application.java b/backend/src/main/java/com/example/erp/Application.java new file mode 100644 index 0000000..5748201 --- /dev/null +++ b/backend/src/main/java/com/example/erp/Application.java @@ -0,0 +1,12 @@ +package com.example.erp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml new file mode 100644 index 0000000..a58b22c --- /dev/null +++ b/backend/src/main/resources/application-dev.yml @@ -0,0 +1,4 @@ +logging: + level: + com.example.erp: DEBUG + org.springframework.security: DEBUG diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 0000000..dd5f5b6 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,27 @@ +spring: + datasource: + url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_SCHEMA}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&characterEncoding=UTF-8 + username: ${DB_USER} + password: ${DB_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + flyway: + locations: classpath:db/migration + baseline-on-migrate: true + baseline-version: 1 + validate-on-migrate: false + out-of-order: false + +server: + port: 8080 + +jwt: + secret: ${JWT_SECRET} + access-token-expiry: 86400 + refresh-token-expiry: 604800 + +mybatis-plus: + configuration: + map-underscore-to-camel-case: false + global-config: + db-config: + id-type: auto diff --git a/backend/src/main/resources/db/migration/V1__initial_schema.sql b/backend/src/main/resources/db/migration/V1__initial_schema.sql new file mode 100644 index 0000000..6f7ef83 --- /dev/null +++ b/backend/src/main/resources/db/migration/V1__initial_schema.sql @@ -0,0 +1,151 @@ +-- Flyway migration V1 — initial schema for 小羚羊 +-- Generated: 2026-05-08T01:01:55Z +-- Source: 由 A4 db-init 从 docs/03-数据库设计文档.md 翻译生成(schema SSoT 是 docs/03) +-- This is the FIRST migration; subsequent schema changes must be written as new files sql/migrations/V2__.sql, V3__... etc. +-- Apply: Flyway runs this automatically at Spring Boot startup. +-- Do not hand-edit this file after it is committed; write a new migration instead. + +SET NAMES utf8mb4; +SET CHARACTER_SET_CLIENT = utf8mb4; + +-- ============================================================ +-- Table: usr_user +-- ============================================================ +CREATE TABLE `usr_user` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)', + `sUserCode` VARCHAR(50) NOT NULL COMMENT '用户号(业务编号,人类可读唯一标识)', + `sUsername` VARCHAR(100) NOT NULL COMMENT '用户名(登录标识,全局唯一,不可修改)', + `sPasswordHash` VARCHAR(255) NOT NULL COMMENT 'BCrypt 哈希密码,禁止存储明文', + `sUserType` VARCHAR(20) NOT NULL DEFAULT '普通用户' COMMENT '用户类型:普通用户 / 超级管理员', + `sLanguage` VARCHAR(20) NOT NULL DEFAULT '中文' COMMENT '界面语言:中文 / 英文 / 繁体', + `bCanEditDoc` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '单据修改权限:0=否,1=是', + `bIsDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否作废/禁用:0=正常,1=禁用', + `sEmployeeId` VARCHAR(100) NULL COMMENT '关联职员 ID(跨模块引用,职员未关联时为 NULL)', + `sCreatorUsername` VARCHAR(100) NULL COMMENT '制单人用户名(冗余字段,便于列表展示)', + `tLastLoginDate` DATETIME NULL COMMENT '最后登录时间', + `iLoginFailCount` INT NOT NULL DEFAULT 0 COMMENT '连续登录失败次数,用于防暴力破解', + `tLockUntil` DATETIME NULL COMMENT '账号锁定截止时间,NULL 表示未锁定', + PRIMARY KEY (`iIncrement`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='用户账户主表,存储登录信息、类型、语言偏好及安全控制字段'; + +-- ============================================================ +-- Table: usr_permission_group +-- ============================================================ +CREATE TABLE `usr_permission_group` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)', + `sGroupCode` VARCHAR(100) NOT NULL COMMENT '权限代码(如 usr:create、usr:edit),全局唯一', + `sGroupName` VARCHAR(200) NOT NULL COMMENT '权限显示名称(如"新增用户"、"修改用户")', + `sCategory` VARCHAR(100) NULL COMMENT '权限分类标签,用于前端权限分组展示', + PRIMARY KEY (`iIncrement`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='权限分类/权限组定义表,每行对应一个可分配给用户的权限项'; + +-- ============================================================ +-- Table: usr_user_permission +-- ============================================================ +CREATE TABLE `usr_user_permission` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)', + `sUserId` VARCHAR(100) NOT NULL COMMENT '关联 usr_user.sId', + `sPermGroupId` VARCHAR(100) NOT NULL COMMENT '关联 usr_permission_group.sId', + PRIMARY KEY (`iIncrement`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='用户与权限组的多对多关联表'; + +-- ============================================================ +-- Table: tStaff +-- ============================================================ +CREATE TABLE `tStaff` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)', + `sStaffNo` VARCHAR(50) NULL COMMENT '职员编号;系统内唯一', + `sStaffName` VARCHAR(50) NOT NULL COMMENT '职员姓名', + `sDepartment` VARCHAR(100) NULL DEFAULT NULL COMMENT '所属部门(本期暂用字符串,未来如需独立 tDepartment 字典表再另行重构)', + `sCreatedBy` VARCHAR(50) NULL COMMENT '制单人', + `bDeleted` BIT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记', + `tDeletedDate` DATETIME NULL DEFAULT NULL COMMENT '软删除时间', + `sDeletedBy` VARCHAR(50) NULL DEFAULT NULL COMMENT '软删除操作人', + PRIMARY KEY (`iIncrement`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='职员维度(员工名 / 部门 / 编号)'; + +-- ============================================================ +-- Table: brand +-- ============================================================ +CREATE TABLE `brand` ( + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)', + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)', + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)', + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)', + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)', + `sName` VARCHAR(100) NULL COMMENT '公司名称', + `sShortName` VARCHAR(100) NULL COMMENT '公司简称', + `sNo` VARCHAR(100) NULL COMMENT '单位编号(登录账号根据单位编号作为前缀)', + PRIMARY KEY (`iIncrement`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + COMMENT='公司表'; + +-- ============================================================ +-- Indexes: usr_user +-- ============================================================ +CREATE UNIQUE INDEX `uk_usr_user_sid` ON `usr_user` (`sId`); +CREATE UNIQUE INDEX `uk_usr_user_username` ON `usr_user` (`sUsername`); +CREATE UNIQUE INDEX `uk_usr_user_usercode` ON `usr_user` (`sUserCode`); +CREATE INDEX `idx_usr_user_tenant` ON `usr_user` (`sBrandsId`, `sSubsidiaryId`); +CREATE INDEX `idx_usr_user_type` ON `usr_user` (`sUserType`); +CREATE INDEX `idx_usr_user_disabled` ON `usr_user` (`bIsDisabled`); + +-- ============================================================ +-- Indexes: usr_permission_group +-- ============================================================ +CREATE UNIQUE INDEX `uk_usr_perm_group_sid` ON `usr_permission_group` (`sId`); +CREATE UNIQUE INDEX `uk_usr_perm_group_code` ON `usr_permission_group` (`sGroupCode`); +CREATE INDEX `idx_usr_perm_group_tenant` ON `usr_permission_group` (`sBrandsId`, `sSubsidiaryId`); + +-- ============================================================ +-- Indexes: usr_user_permission +-- ============================================================ +CREATE UNIQUE INDEX `uk_usr_user_perm` ON `usr_user_permission` (`sUserId`, `sPermGroupId`); +CREATE INDEX `idx_usr_user_perm_user` ON `usr_user_permission` (`sUserId`); +CREATE INDEX `idx_usr_user_perm_group` ON `usr_user_permission` (`sPermGroupId`); + +-- ============================================================ +-- Indexes: tStaff +-- ============================================================ +CREATE UNIQUE INDEX `uk_staff_no` ON `tStaff` (`sStaffNo`); +CREATE INDEX `idx_staff_name` ON `tStaff` (`sStaffName`); +CREATE INDEX `idx_department` ON `tStaff` (`sDepartment`); + +-- ============================================================ +-- Indexes: brand +-- ============================================================ +CREATE UNIQUE INDEX `uk_brand_no` ON `brand` (`sNo`); +CREATE INDEX `idx_brand_name` ON `brand` (`sName`); + +-- ============================================================ +-- Foreign Keys +-- ============================================================ +ALTER TABLE `usr_user_permission` + ADD CONSTRAINT `fk_usr_user_perm_user` + FOREIGN KEY (`sUserId`) REFERENCES `usr_user` (`sId`) + ON DELETE CASCADE ON UPDATE CASCADE; + +ALTER TABLE `usr_user_permission` + ADD CONSTRAINT `fk_usr_user_perm_group` + FOREIGN KEY (`sPermGroupId`) REFERENCES `usr_permission_group` (`sId`) + ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/backend/src/test/java/com/example/erp/ApplicationContextTest.java b/backend/src/test/java/com/example/erp/ApplicationContextTest.java new file mode 100644 index 0000000..88fabbd --- /dev/null +++ b/backend/src/test/java/com/example/erp/ApplicationContextTest.java @@ -0,0 +1,14 @@ +package com.example.erp; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@ActiveProfiles("test") +class ApplicationContextTest { + + @Test + void contextLoads() { + } +} diff --git a/backend/src/test/resources/application-test.yml b/backend/src/test/resources/application-test.yml new file mode 100644 index 0000000..b301682 --- /dev/null +++ b/backend/src/test/resources/application-test.yml @@ -0,0 +1,19 @@ +spring: + datasource: + url: jdbc:mysql://${DB_HOST:118.178.19.35}:${DB_PORT:3318}/${DB_SCHEMA:xlyweberp_vibe_erp_test}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&characterEncoding=UTF-8 + username: ${DB_USER:xlyprint} + password: ${DB_PASSWORD:xlyXLYprint2016} + driver-class-name: com.mysql.cj.jdbc.Driver + flyway: + baseline-on-migrate: true + baseline-version: 1 + validate-on-migrate: false + +jwt: + secret: ${JWT_SECRET:a3b7e8f1c4d6029e5b8f37a1c9d2e4068b5f1d3a7c0e9b2f48d6a1c5e7f9b3d2} + access-token-expiry: 86400 + refresh-token-expiry: 604800 + +logging: + level: + com.example.erp: DEBUG -- libgit2 0.22.2