diff --git a/Dockerfile b/Dockerfile
index d9477d3..42bbb8a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,6 +10,8 @@ COPY tenant-service ./tenant-service
COPY im-service ./im-service
COPY push-service ./push-service
COPY update-service ./update-service
+COPY demo-service ./demo-service
+COPY file-service ./file-service
RUN mvn -pl ${SERVICE_MODULE} -am -DskipTests package
diff --git a/Jenkinsfile b/Jenkinsfile
index 7c69bf2..628391d 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -2,7 +2,7 @@ pipeline {
agent any
parameters {
- choice(name: 'SERVICE', choices: ['tenant-service', 'im-service', 'push-service', 'update-service'], description: '要构建的服务模块')
+ choice(name: 'SERVICE', choices: ['tenant-service', 'im-service', 'push-service', 'update-service', 'demo-service', 'file-service'], description: '要构建的服务模块')
string(name: 'IMAGE_TAG', defaultValue: 'latest', description: '镜像 Tag(如 v1.2.3 或 latest)')
booleanParam(name: 'DEPLOY', defaultValue: true, description: '构建后是否自动部署到生产服务器')
}
diff --git a/demo-service/pom.xml b/demo-service/pom.xml
new file mode 100644
index 0000000..533bf79
--- /dev/null
+++ b/demo-service/pom.xml
@@ -0,0 +1,77 @@
+
+
+ 4.0.0
+
+
+ com.xuqm
+ xuqmgroup-server-parent
+ 0.1.0-SNAPSHOT
+ ../pom.xml
+
+
+ demo-service
+ demo-service
+ Demo tenant service — user auth, file upload/dedup, IM account bridge
+
+
+
+ com.xuqm
+ common
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ io.jsonwebtoken
+ jjwt-api
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ runtime
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+ com.mysql
+ mysql-connector-j
+ runtime
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/demo-service/src/main/java/com/xuqm/demo/DemoServiceApplication.java b/demo-service/src/main/java/com/xuqm/demo/DemoServiceApplication.java
new file mode 100644
index 0000000..0eaa444
--- /dev/null
+++ b/demo-service/src/main/java/com/xuqm/demo/DemoServiceApplication.java
@@ -0,0 +1,14 @@
+package com.xuqm.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@SpringBootApplication(scanBasePackages = {"com.xuqm.demo", "com.xuqm.common"})
+@EnableScheduling
+public class DemoServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DemoServiceApplication.class, args);
+ }
+}
diff --git a/demo-service/src/main/java/com/xuqm/demo/config/GlobalExceptionHandler.java b/demo-service/src/main/java/com/xuqm/demo/config/GlobalExceptionHandler.java
new file mode 100644
index 0000000..feb2745
--- /dev/null
+++ b/demo-service/src/main/java/com/xuqm/demo/config/GlobalExceptionHandler.java
@@ -0,0 +1,35 @@
+package com.xuqm.demo.config;
+
+import com.xuqm.common.exception.BusinessException;
+import com.xuqm.common.model.ApiResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.multipart.MaxUploadSizeExceededException;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+ @ExceptionHandler(BusinessException.class)
+ public ApiResponse handleBusiness(BusinessException ex) {
+ return ApiResponse.error(ex.getCode(), ex.getMessage());
+ }
+
+ @ExceptionHandler(MaxUploadSizeExceededException.class)
+ @ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)
+ public ApiResponse handleMaxUploadSize(MaxUploadSizeExceededException ex) {
+ return ApiResponse.error(413, "File size exceeds the maximum allowed limit");
+ }
+
+ @ExceptionHandler(Exception.class)
+ @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+ public ApiResponse handleGeneric(Exception ex) {
+ log.error("Unhandled exception", ex);
+ return ApiResponse.error(500, "Internal server error");
+ }
+}
diff --git a/demo-service/src/main/java/com/xuqm/demo/config/SecurityConfig.java b/demo-service/src/main/java/com/xuqm/demo/config/SecurityConfig.java
new file mode 100644
index 0000000..6b3d2e7
--- /dev/null
+++ b/demo-service/src/main/java/com/xuqm/demo/config/SecurityConfig.java
@@ -0,0 +1,52 @@
+package com.xuqm.demo.config;
+
+import com.xuqm.common.security.JwtAuthFilter;
+import com.xuqm.common.security.JwtUtil;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+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 org.springframework.web.client.RestTemplate;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+ private final JwtUtil jwtUtil;
+
+ public SecurityConfig(JwtUtil jwtUtil) {
+ this.jwtUtil = jwtUtil;
+ }
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(AbstractHttpConfigurer::disable)
+ .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers(
+ "/api/demo/auth/**",
+ "/actuator/**"
+ ).permitAll()
+ .anyRequest().authenticated()
+ )
+ .addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
+ return http.build();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public RestTemplate restTemplate() {
+ return new RestTemplate();
+ }
+}
diff --git a/demo-service/src/main/java/com/xuqm/demo/controller/DemoAuthController.java b/demo-service/src/main/java/com/xuqm/demo/controller/DemoAuthController.java
new file mode 100644
index 0000000..4a342e6
--- /dev/null
+++ b/demo-service/src/main/java/com/xuqm/demo/controller/DemoAuthController.java
@@ -0,0 +1,65 @@
+package com.xuqm.demo.controller;
+
+import com.xuqm.common.model.ApiResponse;
+import com.xuqm.demo.service.DemoAuthService;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/demo/auth")
+public class DemoAuthController {
+
+ private final DemoAuthService authService;
+
+ public DemoAuthController(DemoAuthService authService) {
+ this.authService = authService;
+ }
+
+ @PostMapping("/register")
+ public ApiResponse