First backend commit //main focus on Auth and JWT service not available for now
Change-Id: Ifccbc53798588f91244e095d6072990ac9e0b9fa
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