Commit 199a33816394bf0dd2dac3682380df3d05c239d9

Authored by zichun
1 parent 62aeb80f

chore(backend): init Spring Boot 3 project skeleton REQ-USR-004

- pom.xml: Spring Boot 3.3.5, MyBatis-Plus 3.5.7, JJWT 0.12.6, Flyway 10.x
- application.yml: DB/JWT from env vars, Flyway baseline-on-migrate=true
- V1 migration copied to classpath:db/migration
- ApplicationContextTest: @SpringBootTest context loads + Flyway baseline OK
backend/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0"
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  5 + <modelVersion>4.0.0</modelVersion>
  6 +
  7 + <parent>
  8 + <groupId>org.springframework.boot</groupId>
  9 + <artifactId>spring-boot-starter-parent</artifactId>
  10 + <version>3.3.5</version>
  11 + <relativePath/>
  12 + </parent>
  13 +
  14 + <groupId>com.example</groupId>
  15 + <artifactId>erp</artifactId>
  16 + <version>0.0.1-SNAPSHOT</version>
  17 + <name>erp</name>
  18 + <description>小羚羊 ERP 后端</description>
  19 + <packaging>jar</packaging>
  20 +
  21 + <properties>
  22 + <java.version>21</java.version>
  23 + <mybatis-plus.version>3.5.7</mybatis-plus.version>
  24 + <jjwt.version>0.12.6</jjwt.version>
  25 + <hutool.version>5.8.28</hutool.version>
  26 + <flyway.version>10.17.0</flyway.version>
  27 + </properties>
  28 +
  29 + <dependencies>
  30 + <!-- Web -->
  31 + <dependency>
  32 + <groupId>org.springframework.boot</groupId>
  33 + <artifactId>spring-boot-starter-web</artifactId>
  34 + </dependency>
  35 +
  36 + <!-- Security -->
  37 + <dependency>
  38 + <groupId>org.springframework.boot</groupId>
  39 + <artifactId>spring-boot-starter-security</artifactId>
  40 + </dependency>
  41 +
  42 + <!-- Validation -->
  43 + <dependency>
  44 + <groupId>org.springframework.boot</groupId>
  45 + <artifactId>spring-boot-starter-validation</artifactId>
  46 + </dependency>
  47 +
  48 + <!-- MyBatis-Plus (Spring Boot 3) -->
  49 + <dependency>
  50 + <groupId>com.baomidou</groupId>
  51 + <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
  52 + <version>${mybatis-plus.version}</version>
  53 + </dependency>
  54 +
  55 + <!-- MySQL -->
  56 + <dependency>
  57 + <groupId>com.mysql</groupId>
  58 + <artifactId>mysql-connector-j</artifactId>
  59 + <scope>runtime</scope>
  60 + </dependency>
  61 +
  62 + <!-- Flyway -->
  63 + <dependency>
  64 + <groupId>org.flywaydb</groupId>
  65 + <artifactId>flyway-core</artifactId>
  66 + <version>${flyway.version}</version>
  67 + </dependency>
  68 + <dependency>
  69 + <groupId>org.flywaydb</groupId>
  70 + <artifactId>flyway-mysql</artifactId>
  71 + <version>${flyway.version}</version>
  72 + </dependency>
  73 +
  74 + <!-- JWT (JJWT 0.12.x) -->
  75 + <dependency>
  76 + <groupId>io.jsonwebtoken</groupId>
  77 + <artifactId>jjwt-api</artifactId>
  78 + <version>${jjwt.version}</version>
  79 + </dependency>
  80 + <dependency>
  81 + <groupId>io.jsonwebtoken</groupId>
  82 + <artifactId>jjwt-impl</artifactId>
  83 + <version>${jjwt.version}</version>
  84 + <scope>runtime</scope>
  85 + </dependency>
  86 + <dependency>
  87 + <groupId>io.jsonwebtoken</groupId>
  88 + <artifactId>jjwt-jackson</artifactId>
  89 + <version>${jjwt.version}</version>
  90 + <scope>runtime</scope>
  91 + </dependency>
  92 +
  93 + <!-- Lombok -->
  94 + <dependency>
  95 + <groupId>org.projectlombok</groupId>
  96 + <artifactId>lombok</artifactId>
  97 + <optional>true</optional>
  98 + </dependency>
  99 +
  100 + <!-- Hutool -->
  101 + <dependency>
  102 + <groupId>cn.hutool</groupId>
  103 + <artifactId>hutool-all</artifactId>
  104 + <version>${hutool.version}</version>
  105 + </dependency>
  106 +
  107 + <!-- Test -->
  108 + <dependency>
  109 + <groupId>org.springframework.boot</groupId>
  110 + <artifactId>spring-boot-starter-test</artifactId>
  111 + <scope>test</scope>
  112 + </dependency>
  113 + <dependency>
  114 + <groupId>org.springframework.security</groupId>
  115 + <artifactId>spring-security-test</artifactId>
  116 + <scope>test</scope>
  117 + </dependency>
  118 + </dependencies>
  119 +
  120 + <build>
  121 + <plugins>
  122 + <plugin>
  123 + <groupId>org.springframework.boot</groupId>
  124 + <artifactId>spring-boot-maven-plugin</artifactId>
  125 + <configuration>
  126 + <excludes>
  127 + <exclude>
  128 + <groupId>org.projectlombok</groupId>
  129 + <artifactId>lombok</artifactId>
  130 + </exclude>
  131 + </excludes>
  132 + </configuration>
  133 + </plugin>
  134 + </plugins>
  135 + </build>
  136 +</project>
backend/src/main/java/com/example/erp/Application.java 0 → 100644
  1 +package com.example.erp;
  2 +
  3 +import org.springframework.boot.SpringApplication;
  4 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  5 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  6 +
  7 +@SpringBootApplication
  8 +public class Application {
  9 + public static void main(String[] args) {
  10 + SpringApplication.run(Application.class, args);
  11 + }
  12 +}
backend/src/main/resources/application-dev.yml 0 → 100644
  1 +logging:
  2 + level:
  3 + com.example.erp: DEBUG
  4 + org.springframework.security: DEBUG
backend/src/main/resources/application.yml 0 → 100644
  1 +spring:
  2 + datasource:
  3 + url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_SCHEMA}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&characterEncoding=UTF-8
  4 + username: ${DB_USER}
  5 + password: ${DB_PASSWORD}
  6 + driver-class-name: com.mysql.cj.jdbc.Driver
  7 + flyway:
  8 + locations: classpath:db/migration
  9 + baseline-on-migrate: true
  10 + baseline-version: 1
  11 + validate-on-migrate: false
  12 + out-of-order: false
  13 +
  14 +server:
  15 + port: 8080
  16 +
  17 +jwt:
  18 + secret: ${JWT_SECRET}
  19 + access-token-expiry: 86400
  20 + refresh-token-expiry: 604800
  21 +
  22 +mybatis-plus:
  23 + configuration:
  24 + map-underscore-to-camel-case: false
  25 + global-config:
  26 + db-config:
  27 + id-type: auto
backend/src/main/resources/db/migration/V1__initial_schema.sql 0 → 100644
  1 +-- Flyway migration V1 — initial schema for 小羚羊
  2 +-- Generated: 2026-05-08T01:01:55Z
  3 +-- Source: 由 A4 db-init 从 docs/03-数据库设计文档.md 翻译生成(schema SSoT 是 docs/03)
  4 +-- This is the FIRST migration; subsequent schema changes must be written as new files sql/migrations/V2__<desc>.sql, V3__... etc.
  5 +-- Apply: Flyway runs this automatically at Spring Boot startup.
  6 +-- Do not hand-edit this file after it is committed; write a new migration instead.
  7 +
  8 +SET NAMES utf8mb4;
  9 +SET CHARACTER_SET_CLIENT = utf8mb4;
  10 +
  11 +-- ============================================================
  12 +-- Table: usr_user
  13 +-- ============================================================
  14 +CREATE TABLE `usr_user` (
  15 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  16 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  17 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  18 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  19 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  20 + `sUserCode` VARCHAR(50) NOT NULL COMMENT '用户号(业务编号,人类可读唯一标识)',
  21 + `sUsername` VARCHAR(100) NOT NULL COMMENT '用户名(登录标识,全局唯一,不可修改)',
  22 + `sPasswordHash` VARCHAR(255) NOT NULL COMMENT 'BCrypt 哈希密码,禁止存储明文',
  23 + `sUserType` VARCHAR(20) NOT NULL DEFAULT '普通用户' COMMENT '用户类型:普通用户 / 超级管理员',
  24 + `sLanguage` VARCHAR(20) NOT NULL DEFAULT '中文' COMMENT '界面语言:中文 / 英文 / 繁体',
  25 + `bCanEditDoc` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '单据修改权限:0=否,1=是',
  26 + `bIsDisabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否作废/禁用:0=正常,1=禁用',
  27 + `sEmployeeId` VARCHAR(100) NULL COMMENT '关联职员 ID(跨模块引用,职员未关联时为 NULL)',
  28 + `sCreatorUsername` VARCHAR(100) NULL COMMENT '制单人用户名(冗余字段,便于列表展示)',
  29 + `tLastLoginDate` DATETIME NULL COMMENT '最后登录时间',
  30 + `iLoginFailCount` INT NOT NULL DEFAULT 0 COMMENT '连续登录失败次数,用于防暴力破解',
  31 + `tLockUntil` DATETIME NULL COMMENT '账号锁定截止时间,NULL 表示未锁定',
  32 + PRIMARY KEY (`iIncrement`)
  33 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  34 + COMMENT='用户账户主表,存储登录信息、类型、语言偏好及安全控制字段';
  35 +
  36 +-- ============================================================
  37 +-- Table: usr_permission_group
  38 +-- ============================================================
  39 +CREATE TABLE `usr_permission_group` (
  40 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  41 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  42 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  43 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  44 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  45 + `sGroupCode` VARCHAR(100) NOT NULL COMMENT '权限代码(如 usr:create、usr:edit),全局唯一',
  46 + `sGroupName` VARCHAR(200) NOT NULL COMMENT '权限显示名称(如"新增用户"、"修改用户")',
  47 + `sCategory` VARCHAR(100) NULL COMMENT '权限分类标签,用于前端权限分组展示',
  48 + PRIMARY KEY (`iIncrement`)
  49 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  50 + COMMENT='权限分类/权限组定义表,每行对应一个可分配给用户的权限项';
  51 +
  52 +-- ============================================================
  53 +-- Table: usr_user_permission
  54 +-- ============================================================
  55 +CREATE TABLE `usr_user_permission` (
  56 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  57 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  58 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  59 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  60 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  61 + `sUserId` VARCHAR(100) NOT NULL COMMENT '关联 usr_user.sId',
  62 + `sPermGroupId` VARCHAR(100) NOT NULL COMMENT '关联 usr_permission_group.sId',
  63 + PRIMARY KEY (`iIncrement`)
  64 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  65 + COMMENT='用户与权限组的多对多关联表';
  66 +
  67 +-- ============================================================
  68 +-- Table: tStaff
  69 +-- ============================================================
  70 +CREATE TABLE `tStaff` (
  71 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  72 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  73 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  74 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  75 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  76 + `sStaffNo` VARCHAR(50) NULL COMMENT '职员编号;系统内唯一',
  77 + `sStaffName` VARCHAR(50) NOT NULL COMMENT '职员姓名',
  78 + `sDepartment` VARCHAR(100) NULL DEFAULT NULL COMMENT '所属部门(本期暂用字符串,未来如需独立 tDepartment 字典表再另行重构)',
  79 + `sCreatedBy` VARCHAR(50) NULL COMMENT '制单人',
  80 + `bDeleted` BIT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
  81 + `tDeletedDate` DATETIME NULL DEFAULT NULL COMMENT '软删除时间',
  82 + `sDeletedBy` VARCHAR(50) NULL DEFAULT NULL COMMENT '软删除操作人',
  83 + PRIMARY KEY (`iIncrement`)
  84 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  85 + COMMENT='职员维度(员工名 / 部门 / 编号)';
  86 +
  87 +-- ============================================================
  88 +-- Table: brand
  89 +-- ============================================================
  90 +CREATE TABLE `brand` (
  91 + `iIncrement` INT NOT NULL AUTO_INCREMENT COMMENT '整数主键 ID(标准列)',
  92 + `sId` VARCHAR(100) NULL COMMENT '业务 ID(标准列)',
  93 + `sBrandsId` VARCHAR(100) NULL COMMENT '品牌 ID(多租户隔离,标准列)',
  94 + `sSubsidiaryId` VARCHAR(100) NULL COMMENT '子公司 ID(组织层级隔离,标准列)',
  95 + `tCreateDate` DATETIME NOT NULL COMMENT '创建时间(标准列)',
  96 + `sName` VARCHAR(100) NULL COMMENT '公司名称',
  97 + `sShortName` VARCHAR(100) NULL COMMENT '公司简称',
  98 + `sNo` VARCHAR(100) NULL COMMENT '单位编号(登录账号根据单位编号作为前缀)',
  99 + PRIMARY KEY (`iIncrement`)
  100 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
  101 + COMMENT='公司表';
  102 +
  103 +-- ============================================================
  104 +-- Indexes: usr_user
  105 +-- ============================================================
  106 +CREATE UNIQUE INDEX `uk_usr_user_sid` ON `usr_user` (`sId`);
  107 +CREATE UNIQUE INDEX `uk_usr_user_username` ON `usr_user` (`sUsername`);
  108 +CREATE UNIQUE INDEX `uk_usr_user_usercode` ON `usr_user` (`sUserCode`);
  109 +CREATE INDEX `idx_usr_user_tenant` ON `usr_user` (`sBrandsId`, `sSubsidiaryId`);
  110 +CREATE INDEX `idx_usr_user_type` ON `usr_user` (`sUserType`);
  111 +CREATE INDEX `idx_usr_user_disabled` ON `usr_user` (`bIsDisabled`);
  112 +
  113 +-- ============================================================
  114 +-- Indexes: usr_permission_group
  115 +-- ============================================================
  116 +CREATE UNIQUE INDEX `uk_usr_perm_group_sid` ON `usr_permission_group` (`sId`);
  117 +CREATE UNIQUE INDEX `uk_usr_perm_group_code` ON `usr_permission_group` (`sGroupCode`);
  118 +CREATE INDEX `idx_usr_perm_group_tenant` ON `usr_permission_group` (`sBrandsId`, `sSubsidiaryId`);
  119 +
  120 +-- ============================================================
  121 +-- Indexes: usr_user_permission
  122 +-- ============================================================
  123 +CREATE UNIQUE INDEX `uk_usr_user_perm` ON `usr_user_permission` (`sUserId`, `sPermGroupId`);
  124 +CREATE INDEX `idx_usr_user_perm_user` ON `usr_user_permission` (`sUserId`);
  125 +CREATE INDEX `idx_usr_user_perm_group` ON `usr_user_permission` (`sPermGroupId`);
  126 +
  127 +-- ============================================================
  128 +-- Indexes: tStaff
  129 +-- ============================================================
  130 +CREATE UNIQUE INDEX `uk_staff_no` ON `tStaff` (`sStaffNo`);
  131 +CREATE INDEX `idx_staff_name` ON `tStaff` (`sStaffName`);
  132 +CREATE INDEX `idx_department` ON `tStaff` (`sDepartment`);
  133 +
  134 +-- ============================================================
  135 +-- Indexes: brand
  136 +-- ============================================================
  137 +CREATE UNIQUE INDEX `uk_brand_no` ON `brand` (`sNo`);
  138 +CREATE INDEX `idx_brand_name` ON `brand` (`sName`);
  139 +
  140 +-- ============================================================
  141 +-- Foreign Keys
  142 +-- ============================================================
  143 +ALTER TABLE `usr_user_permission`
  144 + ADD CONSTRAINT `fk_usr_user_perm_user`
  145 + FOREIGN KEY (`sUserId`) REFERENCES `usr_user` (`sId`)
  146 + ON DELETE CASCADE ON UPDATE CASCADE;
  147 +
  148 +ALTER TABLE `usr_user_permission`
  149 + ADD CONSTRAINT `fk_usr_user_perm_group`
  150 + FOREIGN KEY (`sPermGroupId`) REFERENCES `usr_permission_group` (`sId`)
  151 + ON DELETE CASCADE ON UPDATE CASCADE;
backend/src/test/java/com/example/erp/ApplicationContextTest.java 0 → 100644
  1 +package com.example.erp;
  2 +
  3 +import org.junit.jupiter.api.Test;
  4 +import org.springframework.boot.test.context.SpringBootTest;
  5 +import org.springframework.test.context.ActiveProfiles;
  6 +
  7 +@SpringBootTest
  8 +@ActiveProfiles("test")
  9 +class ApplicationContextTest {
  10 +
  11 + @Test
  12 + void contextLoads() {
  13 + }
  14 +}
backend/src/test/resources/application-test.yml 0 → 100644
  1 +spring:
  2 + datasource:
  3 + 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
  4 + username: ${DB_USER:xlyprint}
  5 + password: ${DB_PASSWORD:xlyXLYprint2016}
  6 + driver-class-name: com.mysql.cj.jdbc.Driver
  7 + flyway:
  8 + baseline-on-migrate: true
  9 + baseline-version: 1
  10 + validate-on-migrate: false
  11 +
  12 +jwt:
  13 + secret: ${JWT_SECRET:a3b7e8f1c4d6029e5b8f37a1c9d2e4068b5f1d3a7c0e9b2f48d6a1c5e7f9b3d2}
  14 + access-token-expiry: 86400
  15 + refresh-token-expiry: 604800
  16 +
  17 +logging:
  18 + level:
  19 + com.example.erp: DEBUG