First backend commit //main focus on Auth and JWT service not available for now
Change-Id: Ifccbc53798588f91244e095d6072990ac9e0b9fa
diff --git a/backend/demo/Dockerfile b/backend/demo/Dockerfile
new file mode 100644
index 0000000..5d92360
--- /dev/null
+++ b/backend/demo/Dockerfile
@@ -0,0 +1,25 @@
+# 构建阶段:使用 Maven 构建项目
+FROM maven:3.9.9-eclipse-temurin-24-alpine AS builder
+
+WORKDIR /app
+
+# 复制项目文件(忽略 target 目录)
+COPY pom.xml .
+COPY src ./src
+
+# 构建应用,跳过测试
+RUN mvn clean package -DskipTests
+
+# 运行阶段:使用较小的 JDK 镜像运行应用
+FROM openjdk:17-jdk-slim-buster
+
+WORKDIR /app
+
+# 从构建阶段复制打好的 jar 包
+COPY --from=builder /app/target/demo-0.0.1-SNAPSHOT.jar app.jar
+
+# 暴露 Spring Boot 默认端口
+EXPOSE 8080
+
+# 启动命令
+CMD ["java", "-jar", "app.jar"]
diff --git a/backend/demo/compose.yaml b/backend/demo/compose.yaml
index 4d2047e..61d6b9d 100644
--- a/backend/demo/compose.yaml
+++ b/backend/demo/compose.yaml
@@ -1,10 +1,29 @@
+
services:
mysql:
- image: 'mysql:latest'
+ image: mysql:latest
environment:
- - 'MYSQL_DATABASE=mydatabase'
- - 'MYSQL_PASSWORD=secret'
- - 'MYSQL_ROOT_PASSWORD=verysecret'
- - 'MYSQL_USER=myuser'
+ - MYSQL_DATABASE=mydatabase
+ - MYSQL_USER=myuser
+ - MYSQL_PASSWORD=secret
+ - MYSQL_ROOT_PASSWORD=verysecret
ports:
- - '3306'
+ - "3306:3306"
+ volumes:
+ - mysql-data:/var/lib/mysql
+
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ ports:
+ - "8080:8080"
+ environment:
+ SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mydatabase?serverTimezone=Asia/Shanghai
+ SPRING_DATASOURCE_USERNAME: myuser
+ SPRING_DATASOURCE_PASSWORD: secret
+ depends_on:
+ - mysql
+
+volumes:
+ mysql-data:
diff --git a/backend/demo/dependency_tree.txt b/backend/demo/dependency_tree.txt
new file mode 100644
index 0000000..7f66fd4
--- /dev/null
+++ b/backend/demo/dependency_tree.txt
@@ -0,0 +1,197 @@
+[INFO] Scanning for projects...
+[INFO]
+[INFO] --------------------------< com.example:demo >--------------------------
+[INFO] Building demo 0.0.1-SNAPSHOT
+[INFO] from pom.xml
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO]
+[INFO] --- dependency:3.8.1:tree (default-cli) @ demo ---
+[INFO] com.example:demo:jar:0.0.1-SNAPSHOT
+[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.4.4:compile
+[INFO] | +- org.springframework.boot:spring-boot-starter:jar:3.4.4:compile (version managed from 3.4.4; scope not updated to compile)
+[INFO] | | +- (org.springframework.boot:spring-boot:jar:3.4.4:compile - version managed from 3.4.4; omitted for duplicate)
+[INFO] | | +- (org.springframework.boot:spring-boot-autoconfigure:jar:3.4.4:compile - version managed from 3.4.4; omitted for duplicate)
+[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:3.4.4:compile (version managed from 3.4.4)
+[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.5.18:compile (version managed from 1.5.18)
+[INFO] | | | | +- ch.qos.logback:logback-core:jar:1.5.18:compile (version managed from 1.5.18)
+[INFO] | | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate)
+[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.24.3:compile (version managed from 2.24.3)
+[INFO] | | | | +- org.apache.logging.log4j:log4j-api:jar:2.24.3:compile (version managed from 2.24.3)
+[INFO] | | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.16; omitted for duplicate)
+[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:2.0.17:compile (version managed from 2.0.17)
+[INFO] | | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 2.0.17; omitted for duplicate)
+[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:2.1.1:compile (version managed from 2.1.1)
+[INFO] | | +- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | \- org.yaml:snakeyaml:jar:2.3:compile (version managed from 2.3)
+[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:3.4.4:compile (version managed from 3.4.4)
+[INFO] | | +- (org.springframework.boot:spring-boot-starter:jar:3.4.4:compile - version managed from 3.4.4; omitted for duplicate)
+[INFO] | | +- (org.springframework:spring-web:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | +- (com.fasterxml.jackson.core:jackson-databind:jar:2.18.3:compile - version managed from 2.18.3; omitted for duplicate)
+[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.18.3:compile (version managed from 2.18.3)
+[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.18.3:compile - version managed from 2.18.3; omitted for duplicate)
+[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.18.3:compile - version managed from 2.18.3; omitted for duplicate)
+[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.18.3:compile (version managed from 2.18.3)
+[INFO] | | | +- (com.fasterxml.jackson.core:jackson-annotations:jar:2.18.3:compile - version managed from 2.18.3; omitted for duplicate)
+[INFO] | | | +- (com.fasterxml.jackson.core:jackson-core:jar:2.18.3:compile - version managed from 2.18.3; omitted for duplicate)
+[INFO] | | | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.18.3:compile - version managed from 2.18.3; omitted for duplicate)
+[INFO] | | \- (com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.18.3:compile - version managed from 2.18.3; omitted for duplicate)
+[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:3.4.4:compile (version managed from 3.4.4)
+[INFO] | | +- (jakarta.annotation:jakarta.annotation-api:jar:2.1.1:compile - version managed from 2.1.1; omitted for duplicate)
+[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.39:compile (version managed from 10.1.39)
+[INFO] | | +- (org.apache.tomcat.embed:tomcat-embed-el:jar:10.1.39:compile - version managed from 10.1.39; omitted for duplicate)
+[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:10.1.39:compile (version managed from 10.1.39)
+[INFO] | | \- (org.apache.tomcat.embed:tomcat-embed-core:jar:10.1.39:compile - version managed from 10.1.39; omitted for duplicate)
+[INFO] | +- org.springframework:spring-web:jar:6.2.5:compile (version managed from 6.2.5)
+[INFO] | | +- org.springframework:spring-beans:jar:6.2.5:compile (version managed from 6.2.5)
+[INFO] | | | \- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | +- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | \- io.micrometer:micrometer-observation:jar:1.14.5:compile (version managed from 1.14.5)
+[INFO] | | \- io.micrometer:micrometer-commons:jar:1.14.5:compile (version managed from 1.14.5)
+[INFO] | \- org.springframework:spring-webmvc:jar:6.2.5:compile (version managed from 6.2.5)
+[INFO] | +- (org.springframework:spring-aop:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | +- (org.springframework:spring-beans:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | +- org.springframework:spring-context:jar:6.2.5:compile (version managed from 6.2.5)
+[INFO] | | +- (org.springframework:spring-aop:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | +- (org.springframework:spring-beans:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | +- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | +- (org.springframework:spring-expression:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | \- (io.micrometer:micrometer-observation:jar:1.14.5:compile - version managed from 1.14.5; omitted for duplicate)
+[INFO] | +- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | +- org.springframework:spring-expression:jar:6.2.5:compile (version managed from 6.2.5)
+[INFO] | | \- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | \- (org.springframework:spring-web:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] +- com.mysql:mysql-connector-j:jar:9.1.0:runtime
+[INFO] +- com.baomidou:mybatis-plus-spring-boot3-starter:jar:3.5.5:compile
+[INFO] | +- com.baomidou:mybatis-plus:jar:3.5.5:compile
+[INFO] | | +- com.baomidou:mybatis-plus-core:jar:3.5.5:compile
+[INFO] | | | +- (com.baomidou:mybatis-plus-annotation:jar:3.5.5:compile - omitted for duplicate)
+[INFO] | | | +- (com.github.jsqlparser:jsqlparser:jar:4.6:compile - omitted for duplicate)
+[INFO] | | | \- (org.mybatis:mybatis:jar:3.5.15:compile - omitted for duplicate)
+[INFO] | | +- com.baomidou:mybatis-plus-annotation:jar:3.5.5:compile
+[INFO] | | +- com.baomidou:mybatis-plus-extension:jar:3.5.5:compile
+[INFO] | | | \- (com.baomidou:mybatis-plus-core:jar:3.5.5:compile - omitted for duplicate)
+[INFO] | | +- org.mybatis:mybatis:jar:3.5.15:compile
+[INFO] | | \- com.github.jsqlparser:jsqlparser:jar:4.6:compile
+[INFO] | +- org.mybatis:mybatis-spring:jar:3.0.3:compile
+[INFO] | +- com.baomidou:mybatis-plus-spring-boot-autoconfigure:jar:3.5.5:compile
+[INFO] | +- org.springframework.boot:spring-boot-autoconfigure:jar:3.4.4:compile (version managed from 3.2.0; scope not updated to compile)
+[INFO] | | \- (org.springframework.boot:spring-boot:jar:3.4.4:compile - version managed from 3.4.4; omitted for duplicate)
+[INFO] | \- org.springframework.boot:spring-boot-starter-jdbc:jar:3.4.4:compile (version managed from 3.2.0)
+[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:3.4.4:compile - version managed from 3.4.4; omitted for duplicate)
+[INFO] | +- com.zaxxer:HikariCP:jar:5.1.0:compile (version managed from 5.1.0)
+[INFO] | | \- (org.slf4j:slf4j-api:jar:2.0.17:compile - version managed from 1.7.36; omitted for duplicate)
+[INFO] | \- org.springframework:spring-jdbc:jar:6.2.5:compile (version managed from 6.2.5)
+[INFO] | +- (org.springframework:spring-beans:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | +- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | \- org.springframework:spring-tx:jar:6.2.5:compile (version managed from 6.2.5)
+[INFO] | +- (org.springframework:spring-beans:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | \- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] +- org.springframework.boot:spring-boot-configuration-processor:jar:3.4.4:compile
+[INFO] +- org.springframework.boot:spring-boot-docker-compose:jar:3.4.4:runtime
+[INFO] | +- org.springframework.boot:spring-boot:jar:3.4.4:compile (version managed from 3.4.4; scope not updated to compile)
+[INFO] | | +- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | \- (org.springframework:spring-context:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.18.3:compile (version managed from 2.18.3; scope not updated to compile)
+[INFO] | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.18.3:compile (version managed from 2.18.3)
+[INFO] | | \- com.fasterxml.jackson.core:jackson-core:jar:2.18.3:compile (version managed from 2.18.3)
+[INFO] | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.18.3:compile (version managed from 2.18.3; scope not updated to compile)
+[INFO] | +- (com.fasterxml.jackson.core:jackson-core:jar:2.18.3:compile - version managed from 2.18.3; omitted for duplicate)
+[INFO] | \- (com.fasterxml.jackson.core:jackson-databind:jar:2.18.3:compile - version managed from 2.18.3; omitted for duplicate)
+[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:3.4.4:test
+[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:3.4.4:test - version managed from 3.4.4; omitted for duplicate)
+[INFO] | +- org.springframework.boot:spring-boot-test:jar:3.4.4:test (version managed from 3.4.4)
+[INFO] | | +- (org.springframework.boot:spring-boot:jar:3.4.4:test - version managed from 3.4.4; omitted for duplicate)
+[INFO] | | \- (org.springframework:spring-test:jar:6.2.5:test - version managed from 6.2.5; omitted for duplicate)
+[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:3.4.4:test (version managed from 3.4.4)
+[INFO] | | +- (org.springframework.boot:spring-boot:jar:3.4.4:test - version managed from 3.4.4; omitted for duplicate)
+[INFO] | | +- (org.springframework.boot:spring-boot-test:jar:3.4.4:test - version managed from 3.4.4; omitted for duplicate)
+[INFO] | | \- (org.springframework.boot:spring-boot-autoconfigure:jar:3.4.4:test - version managed from 3.4.4; omitted for duplicate)
+[INFO] | +- com.jayway.jsonpath:json-path:jar:2.9.0:test (version managed from 2.9.0)
+[INFO] | | +- (net.minidev:json-smart:jar:2.5.2:test - version managed from 2.5.0; omitted for duplicate)
+[INFO] | | \- org.slf4j:slf4j-api:jar:2.0.17:compile (version managed from 2.0.11)
+[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.2:test (version managed from 4.0.2)
+[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:2.1.3:test (version managed from 2.1.3)
+[INFO] | +- net.minidev:json-smart:jar:2.5.2:test (version managed from 2.5.2)
+[INFO] | | \- net.minidev:accessors-smart:jar:2.5.2:test
+[INFO] | | \- org.ow2.asm:asm:jar:9.7.1:test
+[INFO] | +- org.assertj:assertj-core:jar:3.26.3:test (version managed from 3.26.3)
+[INFO] | | \- net.bytebuddy:byte-buddy:jar:1.15.11:test (version managed from 1.14.18)
+[INFO] | +- org.awaitility:awaitility:jar:4.2.2:test (version managed from 4.2.2)
+[INFO] | | \- (org.hamcrest:hamcrest:jar:2.2:test - version managed from 2.1; omitted for duplicate)
+[INFO] | +- org.hamcrest:hamcrest:jar:2.2:test (version managed from 2.2)
+[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.11.4:test (version managed from 5.11.4)
+[INFO] | | +- org.junit.jupiter:junit-jupiter-api:jar:5.11.4:test (version managed from 5.11.4)
+[INFO] | | | +- org.opentest4j:opentest4j:jar:1.3.0:test
+[INFO] | | | +- org.junit.platform:junit-platform-commons:jar:1.11.4:test (version managed from 1.11.4)
+[INFO] | | | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate)
+[INFO] | | | \- org.apiguardian:apiguardian-api:jar:1.1.2:test
+[INFO] | | +- org.junit.jupiter:junit-jupiter-params:jar:5.11.4:test (version managed from 5.11.4)
+[INFO] | | | +- (org.junit.jupiter:junit-jupiter-api:jar:5.11.4:test - version managed from 5.11.4; omitted for duplicate)
+[INFO] | | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate)
+[INFO] | | \- org.junit.jupiter:junit-jupiter-engine:jar:5.11.4:test (version managed from 5.11.4)
+[INFO] | | +- org.junit.platform:junit-platform-engine:jar:1.11.4:test (version managed from 1.11.4)
+[INFO] | | | +- (org.opentest4j:opentest4j:jar:1.3.0:test - omitted for duplicate)
+[INFO] | | | +- (org.junit.platform:junit-platform-commons:jar:1.11.4:test - version managed from 1.11.4; omitted for duplicate)
+[INFO] | | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate)
+[INFO] | | +- (org.junit.jupiter:junit-jupiter-api:jar:5.11.4:test - version managed from 5.11.4; omitted for duplicate)
+[INFO] | | \- (org.apiguardian:apiguardian-api:jar:1.1.2:test - omitted for duplicate)
+[INFO] | +- org.mockito:mockito-core:jar:5.14.2:test (version managed from 5.14.2)
+[INFO] | | +- (net.bytebuddy:byte-buddy:jar:1.15.11:test - version managed from 1.15.4; omitted for duplicate)
+[INFO] | | +- net.bytebuddy:byte-buddy-agent:jar:1.15.11:test (version managed from 1.15.4)
+[INFO] | | \- org.objenesis:objenesis:jar:3.3:test
+[INFO] | +- org.mockito:mockito-junit-jupiter:jar:5.14.2:test (version managed from 5.14.2)
+[INFO] | | +- (org.mockito:mockito-core:jar:5.14.2:test - version managed from 5.14.2; omitted for duplicate)
+[INFO] | | \- (org.junit.jupiter:junit-jupiter-api:jar:5.11.4:test - version managed from 5.11.2; omitted for duplicate)
+[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.3:test (version managed from 1.5.3)
+[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
+[INFO] | +- org.springframework:spring-core:jar:6.2.5:compile (version managed from 6.2.5; scope not updated to compile)
+[INFO] | | \- org.springframework:spring-jcl:jar:6.2.5:compile (version managed from 6.2.5)
+[INFO] | +- org.springframework:spring-test:jar:6.2.5:test (version managed from 6.2.5)
+[INFO] | | \- (org.springframework:spring-core:jar:6.2.5:test - version managed from 6.2.5; omitted for duplicate)
+[INFO] | \- org.xmlunit:xmlunit-core:jar:2.10.0:test (version managed from 2.10.0)
+[INFO] | \- (jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.2:test - version managed from 2.3.3; omitted for duplicate)
+[INFO] +- org.springframework.boot:spring-boot-starter-validation:jar:3.4.4:compile
+[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:3.4.4:compile - version managed from 3.4.4; omitted for duplicate)
+[INFO] | +- org.apache.tomcat.embed:tomcat-embed-el:jar:10.1.39:compile (version managed from 10.1.39)
+[INFO] | \- org.hibernate.validator:hibernate-validator:jar:8.0.2.Final:compile (version managed from 8.0.2.Final)
+[INFO] | +- jakarta.validation:jakarta.validation-api:jar:3.0.2:compile (version managed from 3.0.2)
+[INFO] | +- org.jboss.logging:jboss-logging:jar:3.6.1.Final:compile (version managed from 3.4.3.Final)
+[INFO] | \- com.fasterxml:classmate:jar:1.7.0:compile (version managed from 1.5.1)
+[INFO] +- org.springframework.boot:spring-boot-starter-security:jar:3.4.4:compile
+[INFO] | +- (org.springframework.boot:spring-boot-starter:jar:3.4.4:compile - version managed from 3.4.4; omitted for duplicate)
+[INFO] | +- org.springframework:spring-aop:jar:6.2.5:compile (version managed from 6.2.5)
+[INFO] | | +- (org.springframework:spring-beans:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | | \- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.5; omitted for duplicate)
+[INFO] | +- org.springframework.security:spring-security-config:jar:6.4.4:compile (version managed from 6.4.4)
+[INFO] | | +- org.springframework.security:spring-security-core:jar:6.4.4:compile (version managed from 6.4.4)
+[INFO] | | | +- org.springframework.security:spring-security-crypto:jar:6.4.4:compile (version managed from 6.4.4)
+[INFO] | | | +- (org.springframework:spring-aop:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | | | +- (org.springframework:spring-beans:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | | | +- (org.springframework:spring-context:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | | | +- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | | | +- (org.springframework:spring-expression:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | | | \- (io.micrometer:micrometer-observation:jar:1.14.5:compile - version managed from 1.14.5; omitted for duplicate)
+[INFO] | | +- (org.springframework:spring-aop:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | | +- (org.springframework:spring-beans:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | | +- (org.springframework:spring-context:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | | \- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | \- org.springframework.security:spring-security-web:jar:6.4.4:compile (version managed from 6.4.4)
+[INFO] | +- (org.springframework.security:spring-security-core:jar:6.4.4:compile - version managed from 6.4.4; omitted for duplicate)
+[INFO] | +- (org.springframework:spring-core:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | +- (org.springframework:spring-aop:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | +- (org.springframework:spring-beans:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | +- (org.springframework:spring-context:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | +- (org.springframework:spring-expression:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] | \- (org.springframework:spring-web:jar:6.2.5:compile - version managed from 6.2.4; omitted for duplicate)
+[INFO] +- io.jsonwebtoken:jjwt-api:jar:0.11.5:compile
+[INFO] +- io.jsonwebtoken:jjwt-impl:jar:0.11.5:runtime
+[INFO] | \- (io.jsonwebtoken:jjwt-api:jar:0.11.5:runtime - omitted for duplicate)
+[INFO] \- io.jsonwebtoken:jjwt-jackson:jar:0.11.5:runtime
+[INFO] +- (io.jsonwebtoken:jjwt-api:jar:0.11.5:runtime - omitted for duplicate)
+[INFO] \- (com.fasterxml.jackson.core:jackson-databind:jar:2.18.3:runtime - version managed from 2.12.6.1; omitted for duplicate)
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time: 8.939 s
+[INFO] Finished at: 2025-05-10T00:10:04+08:00
+[INFO] ------------------------------------------------------------------------
diff --git a/backend/demo/pom.xml b/backend/demo/pom.xml
index fb51278..bac660b 100644
--- a/backend/demo/pom.xml
+++ b/backend/demo/pom.xml
@@ -1,74 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>3.4.4</version>
- <relativePath/>
- </parent>
- <groupId>com.example</groupId>
- <artifactId>demo</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>demo</name>
- <description>Demo project for Spring Boot</description>
- <url/>
- <licenses>
- <license/>
- </licenses>
- <developers>
- <developer/>
- </developers>
- <scm>
- <connection/>
- <developerConnection/>
- <tag/>
- <url/>
- </scm>
- <properties>
- <java.version>17</java.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-docker-compose</artifactId>
- <scope>runtime</scope>
- <optional>true</optional>
- </dependency>
- <dependency>
- <groupId>com.mysql</groupId>
- <artifactId>mysql-connector-j</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.h2database</groupId>
- <artifactId>h2</artifactId>
- <scope>runtime</scope>
- </dependency>
- </dependencies>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- </plugin>
- </plugins>
- </build>
+<modelVersion>4.0.0</modelVersion>
+
+<parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>3.4.4</version>
+ <relativePath/>
+</parent>
+
+<groupId>com.example</groupId>
+<artifactId>demo</artifactId>
+<version>0.0.1-SNAPSHOT</version>
+<name>demo</name>
+<description>Demo project for Spring Boot</description>
+
+<properties>
+ <java.version>17</java.version>
+</properties>
+
+<dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+
+
+ <!-- 数据库驱动:MySQL、H2 -->
+ <dependency>
+ <groupId>com.mysql</groupId>
+ <artifactId>mysql-connector-j</artifactId>
+ <scope>runtime</scope>
+ </dependency>
+
+
+ <!-- MyBatis-Plus -->
+ <dependency>
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+ <version>3.5.5</version>
+ </dependency>
+<!-- <dependency>
+ DO NOT DO THIS
+ <groupId>com.baomidou</groupId>
+ <artifactId>mybatis-plus-boot-starter</artifactId>
+ <version>3.5.5</version>
+ </dependency>
+-->
+ <!-- Spring Boot 配置处理器(可选) -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-configuration-processor</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <!-- Docker Compose 支持 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-docker-compose</artifactId>
+ <scope>runtime</scope>
+ <optional>true</optional>
+ </dependency>
+
+ <!-- 单元测试 -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+</dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+</dependency>
+ <dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt-api</artifactId>
+ <version>0.11.5</version>
+</dependency>
+<dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt-impl</artifactId>
+ <version>0.11.5</version>
+ <scope>runtime</scope>
+</dependency>
+<dependency>
+ <groupId>io.jsonwebtoken</groupId>
+ <artifactId>jjwt-jackson</artifactId>
+ <version>0.11.5</version>
+ <scope>runtime</scope>
+</dependency>
+
+</dependencies>
+
+<build>
+ <plugins>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+</build>
</project>
diff --git a/backend/demo/src/main/java/com/example/demo/DemoApplication.java b/backend/demo/src/main/java/com/example/demo/DemoApplication.java
index 1f94219..8575ec8 100644
--- a/backend/demo/src/main/java/com/example/demo/DemoApplication.java
+++ b/backend/demo/src/main/java/com/example/demo/DemoApplication.java
@@ -1,8 +1,10 @@
package com.example.demo;
+import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
+@MapperScan("com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
diff --git a/backend/demo/src/main/java/com/example/demo/config/JWTProperties.java b/backend/demo/src/main/java/com/example/demo/config/JWTProperties.java
new file mode 100644
index 0000000..e96ed56
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/config/JWTProperties.java
@@ -0,0 +1,91 @@
+package com.example.demo.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * JWT 配置属性,从 application.properties 中 jwt 开头的配置加载
+ */
+@Component
+@ConfigurationProperties(prefix = "jwt")
+public class JWTProperties {
+ /**
+ * 用于签名的密钥
+ */
+ private String secret;
+
+ /**
+ * Token 过期时长,单位毫秒
+ */
+ private long expirationMs;
+
+ /**
+ * HTTP Header 中放置 JWT 的字段名
+ */
+ private String header = "Authorization";
+
+ /**
+ * Header 中 Token 的前缀
+ */
+ private String tokenPrefix = "Bearer ";
+
+ /**
+ * 签发者信息(可选)
+ */
+ private String issuer;
+
+ /**
+ * 接收者信息(可选)
+ */
+ private String audience;
+
+ // --- getters & setters ---
+
+ public String getSecret() {
+ return secret;
+ }
+
+ public void setSecret(String secret) {
+ this.secret = secret;
+ }
+
+ public long getExpirationMs() {
+ return expirationMs;
+ }
+
+ public void setExpirationMs(long expirationMs) {
+ this.expirationMs = expirationMs;
+ }
+
+ public String getHeader() {
+ return header;
+ }
+
+ public void setHeader(String header) {
+ this.header = header;
+ }
+
+ public String getTokenPrefix() {
+ return tokenPrefix;
+ }
+
+ public void setTokenPrefix(String tokenPrefix) {
+ this.tokenPrefix = tokenPrefix;
+ }
+
+ public String getIssuer() {
+ return issuer;
+ }
+
+ public void setIssuer(String issuer) {
+ this.issuer = issuer;
+ }
+
+ public String getAudience() {
+ return audience;
+ }
+
+ public void setAudience(String audience) {
+ this.audience = audience;
+ }
+}
diff --git a/backend/demo/src/main/java/com/example/demo/config/SecurityConfig.java b/backend/demo/src/main/java/com/example/demo/config/SecurityConfig.java
new file mode 100644
index 0000000..bfd7b1b
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/config/SecurityConfig.java
@@ -0,0 +1,51 @@
+package com.example.demo.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import com.example.demo.security.JwtAuthenticationFilter;
+import com.example.demo.security.JwtTokenUtil;
+
+@Configuration
+public class SecurityConfig {
+
+ /**
+ * 密码加密器,用于注册用户时对密码加密、登录时校验
+ */
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ /**
+ * 将 Spring Security 的 AuthenticationManager 暴露为 Bean,
+ * 方便在 AuthController 或其它地方手动调用。
+ */
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+ return config.getAuthenticationManager();
+ }
+
+ /**
+ * 核心安全策略:禁用 CSRF、无状态 Session、开放登录接口、其余接口需认证。
+ */
+ @Bean
+public SecurityFilterChain filterChain(HttpSecurity http,
+ JwtTokenUtil tokenUtil,
+ JWTProperties props) throws Exception {
+ JwtAuthenticationFilter jwtFilter = new JwtAuthenticationFilter(tokenUtil, props);
+ http
+ // 省略其他配置…
+ .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
+ return http.build();
+}
+}
+
diff --git a/backend/demo/src/main/java/com/example/demo/controller/AuthController.java b/backend/demo/src/main/java/com/example/demo/controller/AuthController.java
new file mode 100644
index 0000000..08411d7
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/controller/AuthController.java
@@ -0,0 +1,74 @@
+package com.example.demo.controller;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.example.demo.dto.LoginRequestDTO;
+import com.example.demo.dto.LoginResponseDTO;
+import com.example.demo.entity.User;
+import com.example.demo.exception.AuthException;
+import com.example.demo.security.JwtTokenUtil;
+import com.example.demo.service.UserService;
+
+import jakarta.validation.Valid;
+
+@RestController
+@RequestMapping("/api/auth")
+public class AuthController {
+
+ private final UserService userService;
+ private final PasswordEncoder passwordEncoder;
+ private final JwtTokenUtil jwtTokenUtil;
+
+ @Autowired
+ public AuthController(UserService userService,
+ PasswordEncoder passwordEncoder,
+ JwtTokenUtil jwtTokenUtil) {
+ this.userService = userService;
+ this.passwordEncoder = passwordEncoder;
+ this.jwtTokenUtil = jwtTokenUtil;
+ }
+
+ @PostMapping("/login")
+ public ResponseEntity<?> login(
+ @Valid @RequestBody LoginRequestDTO loginRequest,
+ BindingResult bindingResult) {
+
+ if (bindingResult.hasErrors()) {
+ String errMsg = bindingResult.getFieldErrors().stream()
+ .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
+ .reduce((a, b) -> a + "; " + b)
+ .orElse("Invalid parameters");
+ return ResponseEntity.badRequest().body(Map.of("error", errMsg));
+ }
+
+ User user = userService.lambdaQuery()
+ .eq(User::getUsername, loginRequest.getUsername())
+ .one();
+
+ if (user == null || !passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
+ throw new AuthException("用户名或密码错误");
+ }
+
+ String token = jwtTokenUtil.generateToken(user.getId(), user.getUsername(), user.getRole());
+
+ LoginResponseDTO response = new LoginResponseDTO();
+ response.setToken(token);
+ response.setExpiresAt(Instant.now().plusMillis(jwtTokenUtil.getExpiration()));
+ response.setUserId(user.getId());
+ response.setUsername(user.getUsername());
+ response.setRoles(List.of(user.getRole()));
+
+ return ResponseEntity.ok(response);
+ }
+}
diff --git a/backend/demo/src/main/java/com/example/demo/dto/LoginRequestDTO.java b/backend/demo/src/main/java/com/example/demo/dto/LoginRequestDTO.java
new file mode 100644
index 0000000..8c85fec
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/dto/LoginRequestDTO.java
@@ -0,0 +1,32 @@
+package com.example.demo.dto;
+
+import java.io.Serializable;
+
+import jakarta.validation.constraints.NotBlank;
+
+
+public class LoginRequestDTO implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @NotBlank(message = "用户名不能为空")
+ private String username;
+
+ @NotBlank(message = "密码不能为空")
+ private String password;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+}
\ No newline at end of file
diff --git a/backend/demo/src/main/java/com/example/demo/dto/LoginResponseDTO.java b/backend/demo/src/main/java/com/example/demo/dto/LoginResponseDTO.java
new file mode 100644
index 0000000..2dd6241
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/dto/LoginResponseDTO.java
@@ -0,0 +1,64 @@
+package com.example.demo.dto;
+
+import java.io.Serializable;
+import java.time.Instant;
+import java.util.List;
+
+public class LoginResponseDTO implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /** 登录成功后返回的 JWT 或会话标识 */
+ private String token;
+
+ /** Token 过期时间戳 */
+ private Instant expiresAt;
+
+ /** 用户 ID,可选 */
+ private Long userId;
+
+ /** 用户名,可选 */
+ private String username;
+
+ /** 用户角色列表,可选 */
+ private List<String> roles;
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public Instant getExpiresAt() {
+ return expiresAt;
+ }
+
+ public void setExpiresAt(Instant expiresAt) {
+ this.expiresAt = expiresAt;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Long userId) {
+ this.userId = userId;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public List<String> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List<String> roles) {
+ this.roles = roles;
+ }
+}
diff --git a/backend/demo/src/main/java/com/example/demo/entity/User.java b/backend/demo/src/main/java/com/example/demo/entity/User.java
new file mode 100644
index 0000000..bcbe6de
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/entity/User.java
@@ -0,0 +1,105 @@
+package com.example.demo.entity;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+
+@TableName("user")
+public class User implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ @TableId(type = IdType.AUTO)
+ private Long id;
+
+ private String username;
+
+ private String password;
+
+ private String email;
+
+ private Integer status;
+
+ private Integer score;
+
+ private String role;
+
+ private LocalDateTime createTime;
+
+ private LocalDateTime updateTime;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public void setStatus(Integer status) {
+ this.status = status;
+ }
+
+ public Integer getScore() {
+ return score;
+ }
+
+ public void setScore(Integer score) {
+ this.score = score;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ public LocalDateTime getCreateTime() {
+ return createTime;
+ }
+
+ public void setCreateTime(LocalDateTime createTime) {
+ this.createTime = createTime;
+ }
+
+ public LocalDateTime getUpdateTime() {
+ return updateTime;
+ }
+
+ public void setUpdateTime(LocalDateTime updateTime) {
+ this.updateTime = updateTime;
+ }
+}
diff --git a/backend/demo/src/main/java/com/example/demo/exception/AuthException.java b/backend/demo/src/main/java/com/example/demo/exception/AuthException.java
new file mode 100644
index 0000000..526f1da
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/exception/AuthException.java
@@ -0,0 +1,14 @@
+package com.example.demo.exception;
+
+/**
+ * 自定义认证/授权异常
+ */
+public class AuthException extends RuntimeException {
+ public AuthException(String message) {
+ super(message);
+ }
+
+ public AuthException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/backend/demo/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java b/backend/demo/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..bc560e6
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java
@@ -0,0 +1,39 @@
+package com.example.demo.exception;
+
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+ /** 处理自定义认证异常 */
+ @ExceptionHandler(AuthException.class)
+ public ResponseEntity<Map<String, String>> handleAuthException(AuthException ex) {
+ Map<String, String> body = Map.of("error", ex.getMessage());
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body);
+ }
+
+ /** 处理请求参数校验失败 */
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
+ Map<String, String> errors = new HashMap<>();
+ for (FieldError fe : ex.getBindingResult().getFieldErrors()) {
+ errors.put(fe.getField(), fe.getDefaultMessage());
+ }
+ return ResponseEntity.badRequest().body(errors);
+ }
+
+ /** 处理其它未捕获的异常 */
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity<Map<String, String>> handleAllExceptions(Exception ex) {
+ Map<String, String> body = Map.of("error", "Internal server error");
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
+ }
+}
diff --git a/backend/demo/src/main/java/com/example/demo/mapper/UserMapper.java b/backend/demo/src/main/java/com/example/demo/mapper/UserMapper.java
new file mode 100644
index 0000000..94ba7a8
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/mapper/UserMapper.java
@@ -0,0 +1,17 @@
+package com.example.demo.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.example.demo.entity.User;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * UserMapper 接口
+ *
+ * 继承 MyBatis-Plus 提供的 BaseMapper,实现基本的 CRUD 操作
+ */
+@Mapper
+public interface UserMapper extends BaseMapper<User> {
+
+ // List<User> selectByStatus(@Param("status") Integer status);
+}
+
diff --git a/backend/demo/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java b/backend/demo/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..108e966
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/security/JwtAuthenticationFilter.java
@@ -0,0 +1,64 @@
+package com.example.demo.security;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import com.example.demo.config.JWTProperties;
+import com.example.demo.exception.AuthException;
+
+import io.jsonwebtoken.Claims;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ private final JwtTokenUtil tokenUtil;
+ private final JWTProperties props;
+
+ public JwtAuthenticationFilter(JwtTokenUtil tokenUtil, JWTProperties props) {
+ this.tokenUtil = tokenUtil;
+ this.props = props;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain chain)
+ throws ServletException, IOException {
+
+ String headerValue = request.getHeader(props.getHeader());
+ if (!StringUtils.hasText(headerValue) || !headerValue.startsWith(props.getTokenPrefix())) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ String token = headerValue.substring(props.getTokenPrefix().length());
+ if (!tokenUtil.validateToken(token)) {
+ throw new AuthException("Invalid or expired JWT token");
+ }
+
+ Claims claims = tokenUtil.parseClaims(token);
+ String username = claims.getSubject();
+ Long userId = tokenUtil.getUserId(token);
+ String role = tokenUtil.getRole(token);
+
+ UsernamePasswordAuthenticationToken auth =
+ new UsernamePasswordAuthenticationToken(
+ userId,
+ null,
+ List.of(new SimpleGrantedAuthority(role))
+ );
+ SecurityContextHolder.getContext().setAuthentication(auth);
+
+ chain.doFilter(request, response);
+ }
+}
diff --git a/backend/demo/src/main/java/com/example/demo/security/JwtTokenUtil.java b/backend/demo/src/main/java/com/example/demo/security/JwtTokenUtil.java
new file mode 100644
index 0000000..3f44d8a
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/security/JwtTokenUtil.java
@@ -0,0 +1,81 @@
+package com.example.demo.security;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import com.example.demo.config.JWTProperties;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.security.Keys;
+@Component
+public class JwtTokenUtil {
+
+ private final JWTProperties props;
+ private final byte[] secretBytes;
+
+ public JwtTokenUtil(JWTProperties props) {
+ this.props = props;
+ this.secretBytes = props.getSecret().getBytes(StandardCharsets.UTF_8);
+ }
+
+ /** 生成 JWT */
+ public String generateToken(Long userId, String username, String role) {
+ Date now = new Date();
+ Date expiry = new Date(now.getTime() + props.getExpirationMs());
+
+ return Jwts.builder()
+ .setSubject(username)
+ .setIssuer(props.getIssuer())
+ .setAudience(props.getAudience())
+ .setIssuedAt(now)
+ .setExpiration(expiry)
+ .addClaims(Map.of("userId", userId, "role", role))
+ .signWith(Keys.hmacShaKeyFor(secretBytes), SignatureAlgorithm.HS256)
+ .compact();
+ }
+
+ /** 验证并解析 Token,抛出异常则验证失败 */
+ public Claims parseClaims(String token) {
+ return Jwts.parserBuilder()
+ .setSigningKey(secretBytes)
+ .build()
+ .parseClaimsJws(token)
+ .getBody();
+ }
+
+ /** 校验 Token 是否有效 */
+ public boolean validateToken(String token) {
+ try {
+ parseClaims(token);
+ return true;
+ } catch (JwtException e) {
+ return false;
+ }
+ }
+
+ /** 从 Token 获取用户名(Subject) */
+ public String getUsername(String token) {
+ return parseClaims(token).getSubject();
+ }
+
+ /** 从 Token 获取用户 ID */
+ public Long getUserId(String token) {
+ Object id = parseClaims(token).get("userId");
+ return id == null ? null : Long.valueOf(id.toString());
+ }
+
+ /** 从 Token 获取角色 */
+ public String getRole(String token) {
+ return parseClaims(token).get("role", String.class);
+ }
+
+ public long getExpiration() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+}
diff --git a/backend/demo/src/main/java/com/example/demo/service/UserService.java b/backend/demo/src/main/java/com/example/demo/service/UserService.java
new file mode 100644
index 0000000..60e8aef
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/service/UserService.java
@@ -0,0 +1,13 @@
+package com.example.demo.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.example.demo.entity.User;
+
+/**
+ * User 业务层接口
+ */
+public interface UserService extends IService<User> {
+ // 如果需要自定义方法,可以在这里再添加,例如:
+ // User findByUsername(String username);
+}
diff --git a/backend/demo/src/main/java/com/example/demo/service/impl/UserServiceImpl.java b/backend/demo/src/main/java/com/example/demo/service/impl/UserServiceImpl.java
new file mode 100644
index 0000000..5bd1693
--- /dev/null
+++ b/backend/demo/src/main/java/com/example/demo/service/impl/UserServiceImpl.java
@@ -0,0 +1,23 @@
+package com.example.demo.service.impl;
+
+// UserServiceImpl.java
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.example.demo.entity.User;
+import com.example.demo.mapper.UserMapper;
+import com.example.demo.service.UserService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * User 业务层默认实现
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
+ // 如果你在接口中声明了自定义方法,这里可以重写并实现:
+ // @Override
+ // public User findByUsername(String username) {
+ // return lambdaQuery().eq(User::getUsername, username).one();
+ // }
+}
diff --git a/backend/demo/src/main/resources/application.properties b/backend/demo/src/main/resources/application.properties
index 90968f5..4472028 100644
--- a/backend/demo/src/main/resources/application.properties
+++ b/backend/demo/src/main/resources/application.properties
@@ -1,10 +1,12 @@
-# H2 Configuration (Ensure these are uncommented)
-spring.datasource.url=jdbc:h2:mem:testdb
-spring.datasource.driver-class-name=org.h2.Driver
-spring.datasource.username=sa
-spring.datasource.password=
-spring.h2.console.enabled=true
+# ========== 数据源 ==========
+spring.datasource.url=jdbc:mysql://mysql:3306/mydatabase?serverTimezone=Asia/Shanghai
+spring.datasource.username=myuser
+spring.datasource.password=secret
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-# Required for JPA/Hibernate
-spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
-spring.jpa.hibernate.ddl-auto=update
\ No newline at end of file
+# ========== JWT 配置 ==========
+jwt.secret=YourJWTSecretKeyHere1234567890
+jwt.expirationMs=3600000
+
+# ========== 日志输出(可选) ==========
+logging.level.org.springframework.security=DEBUG