diff --git a/admin_website/src/views/Orders.vue b/admin_website/src/views/Orders.vue new file mode 100644 index 0000000..831d61f --- /dev/null +++ b/admin_website/src/views/Orders.vue @@ -0,0 +1,382 @@ + + + + + \ No newline at end of file diff --git a/admin_website/src/views/Statistics.vue b/admin_website/src/views/Statistics.vue new file mode 100644 index 0000000..a779420 --- /dev/null +++ b/admin_website/src/views/Statistics.vue @@ -0,0 +1,454 @@ + + + + + \ No newline at end of file diff --git a/backend/uploads/common/common_1756737289808-860828577.txt b/backend/uploads/common/common_1756737289808-860828577.txt new file mode 100644 index 0000000..d670460 --- /dev/null +++ b/backend/uploads/common/common_1756737289808-860828577.txt @@ -0,0 +1 @@ +test content diff --git a/backend/uploads/common/common_1756737304923-784117070.txt b/backend/uploads/common/common_1756737304923-784117070.txt new file mode 100644 index 0000000..d670460 --- /dev/null +++ b/backend/uploads/common/common_1756737304923-784117070.txt @@ -0,0 +1 @@ +test content diff --git a/docs/API接口文档.md b/docs/API接口文档.md index 6375cda..336544c 100644 --- a/docs/API接口文档.md +++ b/docs/API接口文档.md @@ -6,7 +6,9 @@ ## 基础信息 -- **Base URL**: `http://localhost:3200/api/v1` +- **Base URL**: + - Node.js后端: `http://localhost:3000/api/v1` + - Java后端: `http://localhost:3200` - **认证方式**: Bearer Token (JWT) - **响应格式**: JSON - **字符编码**: UTF-8 diff --git a/docs/README.md b/docs/README.md index da3f835..d900b16 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,6 +9,9 @@ - SQLite(开发环境) - MySQL(生产环境) - Redis +- Java +- Spring Boot +- Maven ## 文档结构 - 需求文档: 项目业务需求和功能说明 diff --git a/docs/项目总览.md b/docs/项目总览.md index 770c01a..9ad3008 100644 --- a/docs/项目总览.md +++ b/docs/项目总览.md @@ -1,10 +1,10 @@ # 爱鉴花项目总览 -## 项目概述 -爱鉴花是一款通过AI图片识别植物类型的微信小程序应用,为用户提供花卉相关信息、购买、配送等服务。 +## 项目简介 +爱鉴花是一个集花卉识别、植物知识科普、在线商城于一体的综合性微信小程序平台。用户可以通过拍照识别花卉,获取详细的植物信息,同时可以在商城中购买相关产品。 ## 项目组成 -1. **微信小程序 (uni-app)** - 用户端应用,提供植物识别、商城购物、配送服务等功能 +1. **微信小程序 (uni-app)** - 前端用户界面,提供植物识别、植物知识、商城购物、配送服务等功能 2. **后端接口 (Node.js)** - 提供RESTful API服务,包括植物识别、用户管理、商品管理、订单管理等 3. **后台管理系统 (Vue3)** - 管理后台,用于用户管理、商品管理、订单管理、数据统计等 4. **官方网站 (HTML5 Bootstrap)** - 公司展示网站,提供产品介绍、公司信息、联系方式等 @@ -13,8 +13,10 @@ ## 技术架构 - **前端技术栈**: uni-app、Vue3、Element Plus、Bootstrap -- **后端技术栈**: Node.js、Express.js、MySQL(生产环境)、SQLite(开发环境)、Redis -- **开发工具**: HBuilderX、VSCode、Git +- **后端技术栈**: + - Node.js、Express.js、MySQL(生产环境)、SQLite(开发环境)、Redis + - Java Spring Boot(新后端,用于替代部分Node.js功能) +- **开发工具**: HBuilderX、VSCode、Git、Maven - **部署环境**: Nginx、Docker、云服务器 ## 项目文档 diff --git a/java-backend/pom.xml b/java-backend/pom.xml new file mode 100644 index 0000000..b10090f --- /dev/null +++ b/java-backend/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + com.aijianhua + backend + 1.0.0 + jar + + 爱鉴花后端服务 + 爱鉴花小程序Java版后端服务 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.0 + + + + + 1.8 + UTF-8 + UTF-8 + + + + + + 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-validation + + + + + mysql + mysql-connector-java + 8.0.28 + + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + org.apache.commons + commons-lang3 + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.flywaydb + flyway-core + + + + org.flywaydb + flyway-mysql + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/Application.java b/java-backend/src/main/java/com/aijianhua/backend/Application.java new file mode 100644 index 0000000..28b354b --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/Application.java @@ -0,0 +1,11 @@ +package com.aijianhua.backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/config/SecurityConfig.java b/java-backend/src/main/java/com/aijianhua/backend/config/SecurityConfig.java new file mode 100644 index 0000000..0d2bde2 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/config/SecurityConfig.java @@ -0,0 +1,50 @@ +package com.aijianhua.backend.config; + +import com.aijianhua.backend.security.JwtAuthenticationEntryPoint; +import com.aijianhua.backend.security.JwtAuthenticationFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +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.configuration.WebSecurityConfigurerAdapter; +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.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Autowired + private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + + @Autowired + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable() + .authorizeRequests() + .antMatchers("/api/v1/auth/**").permitAll() + .antMatchers("/health").permitAll() + .anyRequest().authenticated() + .and() + .exceptionHandling() + .authenticationEntryPoint(jwtAuthenticationEntryPoint) + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + // 添加JWT过滤器 + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/config/SwaggerResourceConfig.java b/java-backend/src/main/java/com/aijianhua/backend/config/SwaggerResourceConfig.java new file mode 100644 index 0000000..e315d4b --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/config/SwaggerResourceConfig.java @@ -0,0 +1,18 @@ +package com.aijianhua.backend.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class SwaggerResourceConfig implements WebMvcConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("swagger-ui.html") + .addResourceLocations("classpath:/META-INF/resources/"); + + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/"); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/config/WebMvcConfig.java b/java-backend/src/main/java/com/aijianhua/backend/config/WebMvcConfig.java new file mode 100644 index 0000000..e355238 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/config/WebMvcConfig.java @@ -0,0 +1,22 @@ +package com.aijianhua.backend.config; + +import com.aijianhua.backend.interceptor.JwtInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Autowired + private JwtInterceptor jwtInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册JWT拦截器,排除认证相关接口 + registry.addInterceptor(jwtInterceptor) + .addPathPatterns("/api/v1/**") + .excludePathPatterns("/api/v1/auth/**"); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/controller/AuthController.java b/java-backend/src/main/java/com/aijianhua/backend/controller/AuthController.java new file mode 100644 index 0000000..7d8d3a1 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/controller/AuthController.java @@ -0,0 +1,75 @@ +package com.aijianhua.backend.controller; + +import com.aijianhua.backend.dto.ApiResponse; +import com.aijianhua.backend.dto.LoginRequest; +import com.aijianhua.backend.dto.RegisterRequest; +import com.aijianhua.backend.dto.UserResponse; +import com.aijianhua.backend.entity.User; +import com.aijianhua.backend.service.AuthService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/auth") +public class AuthController { + + @Autowired + private AuthService authService; + + /** + * 用户注册 + */ + @PostMapping("/register") + public ApiResponse> register(@Valid @RequestBody RegisterRequest registerRequest) { + User user = authService.register(registerRequest); + + // 生成JWT token + String token = authService.generateToken(user); + + // 构造响应数据 + Map data = new HashMap<>(); + data.put("user_id", user.getId()); + data.put("username", user.getUsername()); + data.put("phone", user.getPhone()); + data.put("email", user.getEmail()); + data.put("user_type", user.getUserType()); + data.put("token", token); + + return ApiResponse.created(data); + } + + /** + * 用户登录 + */ + @PostMapping("/login") + public ApiResponse> login(@Valid @RequestBody LoginRequest loginRequest) { + User user = authService.login(loginRequest); + + // 生成JWT token + String token = authService.generateToken(user); + + // 构造响应数据 + Map data = new HashMap<>(); + data.put("user_id", user.getId()); + data.put("username", user.getUsername()); + data.put("phone", user.getPhone()); + data.put("email", user.getEmail()); + data.put("user_type", user.getUserType()); + data.put("avatar_url", user.getAvatarUrl()); + data.put("token", token); + + return ApiResponse.success("登录成功", data); + } + + /** + * 获取当前用户信息 + */ + @GetMapping("/me") + public ApiResponse getCurrentUser(@RequestAttribute("userId") Long userId) { + UserResponse userResponse = authService.getUserInfo(userId); + return ApiResponse.success(userResponse); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/controller/HealthController.java b/java-backend/src/main/java/com/aijianhua/backend/controller/HealthController.java new file mode 100644 index 0000000..c02b61e --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/controller/HealthController.java @@ -0,0 +1,28 @@ +package com.aijianhua.backend.controller; + +import com.aijianhua.backend.dto.ApiResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 健康检查控制器 + */ +@RestController +@RequestMapping("/health") +public class HealthController { + + @GetMapping + public ApiResponse> healthCheck() { + Map healthInfo = new HashMap<>(); + healthInfo.put("status", "UP"); + healthInfo.put("timestamp", LocalDateTime.now()); + healthInfo.put("service", "aijianhua-backend"); + + return ApiResponse.success(healthInfo); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/controller/UploadController.java b/java-backend/src/main/java/com/aijianhua/backend/controller/UploadController.java new file mode 100644 index 0000000..dbcc512 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/controller/UploadController.java @@ -0,0 +1,88 @@ +package com.aijianhua.backend.controller; + +import com.aijianhua.backend.dto.ApiResponse; +import com.aijianhua.backend.dto.PageResponse; +import com.aijianhua.backend.dto.UploadResponse; +import com.aijianhua.backend.entity.Upload; +import com.aijianhua.backend.service.UploadService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/upload") +public class UploadController { + + @Autowired + private UploadService uploadService; + + /** + * 文件上传 + */ + @PostMapping + public ApiResponse uploadFile( + @RequestParam("file") MultipartFile file, + @RequestParam(value = "type", required = false) String type, + HttpServletRequest request) throws IOException { + + // 从请求中获取用户ID + Long userId = (Long) request.getAttribute("userId"); + + // 上传文件 + Upload upload = uploadService.uploadFile(file, type, userId); + + // 构造响应数据 + UploadResponse uploadResponse = new UploadResponse(); + uploadResponse.setUrl(upload.getFilePath()); + uploadResponse.setFilename(upload.getStoredName()); + uploadResponse.setOriginalName(upload.getOriginalName()); + uploadResponse.setSize(upload.getFileSize()); + uploadResponse.setMimeType(upload.getMimeType()); + uploadResponse.setUploadType(upload.getUploadType()); + + return ApiResponse.success("上传成功", uploadResponse); + } + + /** + * 获取上传文件列表 + */ + @GetMapping + public PageResponse getUploads( + @RequestParam(value = "page", defaultValue = "1") int page, + @RequestParam(value = "limit", defaultValue = "10") int limit, + @RequestParam(value = "type", required = false) String type, + HttpServletRequest request) { + + // 从请求中获取用户ID + Long userId = (Long) request.getAttribute("userId"); + + // 获取文件列表 + Page uploads = uploadService.getUploads(userId, type, page, limit); + + return PageResponse.success(uploads); + } + + /** + * 删除上传文件 + */ + @DeleteMapping("/{id}") + public ApiResponse> deleteUpload( + @PathVariable Long id, + HttpServletRequest request) throws IOException { + + // 从请求中获取用户ID + Long userId = (Long) request.getAttribute("userId"); + + // 删除文件 + uploadService.deleteUpload(id, userId); + + Map data = new HashMap<>(); + data.put("message", "删除成功"); + return ApiResponse.success(data); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/dto/ApiResponse.java b/java-backend/src/main/java/com/aijianhua/backend/dto/ApiResponse.java new file mode 100644 index 0000000..e131be9 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/dto/ApiResponse.java @@ -0,0 +1,38 @@ +package com.aijianhua.backend.dto; + +import lombok.Data; + +@Data +public class ApiResponse { + private Integer code; + private String message; + private T data; + + public ApiResponse() {} + + public ApiResponse(Integer code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + } + + public static ApiResponse success(T data) { + return new ApiResponse<>(200, "操作成功", data); + } + + public static ApiResponse success(String message, T data) { + return new ApiResponse<>(200, message, data); + } + + public static ApiResponse created(T data) { + return new ApiResponse<>(201, "创建成功", data); + } + + public static ApiResponse error(Integer code, String message) { + return new ApiResponse<>(code, message, null); + } + + public static ApiResponse error(String message) { + return new ApiResponse<>(500, message, null); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/dto/LoginRequest.java b/java-backend/src/main/java/com/aijianhua/backend/dto/LoginRequest.java new file mode 100644 index 0000000..272fd56 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/dto/LoginRequest.java @@ -0,0 +1,21 @@ +package com.aijianhua.backend.dto; + +import lombok.Data; +import javax.validation.constraints.NotBlank; + +@Data +public class LoginRequest { + @NotBlank(message = "登录账号不能为空") + private String login; + + @NotBlank(message = "密码不能为空") + private String password; + + public String getLogin() { + return login; + } + + public String getPassword() { + return password; + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/dto/PageResponse.java b/java-backend/src/main/java/com/aijianhua/backend/dto/PageResponse.java new file mode 100644 index 0000000..2ac4c33 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/dto/PageResponse.java @@ -0,0 +1,62 @@ +package com.aijianhua.backend.dto; + +import lombok.Data; +import org.springframework.data.domain.Page; + +@Data +public class PageResponse { + private Integer code; + private String message; + private PageData data; + + public PageResponse() {} + + public PageResponse(Integer code, String message, PageData data) { + this.code = code; + this.message = message; + this.data = data; + } + + public static PageResponse success(Page page) { + PageData pageData = new PageData<>(); + pageData.setItems(page.getContent()); + pageData.setPagination(new Pagination( + page.getNumber() + 1, + page.getSize(), + page.getTotalElements(), + page.getTotalPages() + )); + return new PageResponse<>(200, "获取成功", pageData); + } + + @Data + public static class PageData { + private Iterable items; + private Pagination pagination; + + public void setItems(Iterable items) { + this.items = items; + } + + public void setPagination(Pagination pagination) { + this.pagination = pagination; + } + } + + @Data + public static class Pagination { + private Integer page; + private Integer limit; + private Long total; + private Integer pages; + + public Pagination() {} + + public Pagination(Integer page, Integer limit, Long total, Integer pages) { + this.page = page; + this.limit = limit; + this.total = total; + this.pages = pages; + } + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/dto/RegisterRequest.java b/java-backend/src/main/java/com/aijianhua/backend/dto/RegisterRequest.java new file mode 100644 index 0000000..ba6e8dc --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/dto/RegisterRequest.java @@ -0,0 +1,42 @@ +package com.aijianhua.backend.dto; + +import lombok.Data; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +@Data +public class RegisterRequest { + @NotBlank(message = "用户名不能为空") + private String username; + + @NotBlank(message = "密码不能为空") + @Size(min = 6, message = "密码长度不能少于6位") + private String password; + + @NotBlank(message = "手机号不能为空") + private String phone; + + private String email; + + private String userType = "farmer"; + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getPhone() { + return phone; + } + + public String getEmail() { + return email; + } + + public String getUserType() { + return userType; + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/dto/UploadResponse.java b/java-backend/src/main/java/com/aijianhua/backend/dto/UploadResponse.java new file mode 100644 index 0000000..9fcbafb --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/dto/UploadResponse.java @@ -0,0 +1,58 @@ +package com.aijianhua.backend.dto; + +public class UploadResponse { + private String url; + private String filename; + private String originalName; + private Long size; + private String mimeType; + private String uploadType; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getOriginalName() { + return originalName; + } + + public void setOriginalName(String originalName) { + this.originalName = originalName; + } + + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public String getMimeType() { + return mimeType; + } + + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + public String getUploadType() { + return uploadType; + } + + public void setUploadType(String uploadType) { + this.uploadType = uploadType; + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/dto/UserResponse.java b/java-backend/src/main/java/com/aijianhua/backend/dto/UserResponse.java new file mode 100644 index 0000000..d33dfd9 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/dto/UserResponse.java @@ -0,0 +1,81 @@ +package com.aijianhua.backend.dto; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class UserResponse { + private Long id; + private String username; + private String phone; + private String email; + private String userType; + private String avatarUrl; + private LocalDateTime createdAt; + private LocalDateTime lastLogin; + + // Getters and Setters + 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 getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getUserType() { + return userType; + } + + public void setUserType(String userType) { + this.userType = userType; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getLastLogin() { + return lastLogin; + } + + public void setLastLogin(LocalDateTime lastLogin) { + this.lastLogin = lastLogin; + } +} diff --git a/java-backend/src/main/java/com/aijianhua/backend/entity/Upload.java b/java-backend/src/main/java/com/aijianhua/backend/entity/Upload.java new file mode 100644 index 0000000..ac002cf --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/entity/Upload.java @@ -0,0 +1,155 @@ +package com.aijianhua.backend.entity; + +import lombok.Data; +import javax.persistence.*; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "uploads") +public class Upload { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Column(name = "original_name", nullable = false) + private String originalName; + + @Column(name = "stored_name", nullable = false) + private String storedName; + + @Column(name = "file_path", nullable = false) + private String filePath; + + @Column(name = "file_size", nullable = false) + private Long fileSize; + + @Column(name = "mime_type", nullable = false) + private String mimeType; + + @Column(name = "file_type") + private String fileType; + + @Column(name = "upload_type", nullable = false) + private String uploadType; + + @Column(nullable = false) + private String status = "active"; + + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } + + // Getters and Setters + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getOriginalName() { + return originalName; + } + + public void setOriginalName(String originalName) { + this.originalName = originalName; + } + + public String getStoredName() { + return storedName; + } + + public void setStoredName(String storedName) { + this.storedName = storedName; + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public Long getFileSize() { + return fileSize; + } + + public void setFileSize(Long fileSize) { + this.fileSize = fileSize; + } + + public String getMimeType() { + return mimeType; + } + + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + public String getFileType() { + return fileType; + } + + public void setFileType(String fileType) { + this.fileType = fileType; + } + + public String getUploadType() { + return uploadType; + } + + public void setUploadType(String uploadType) { + this.uploadType = uploadType; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/entity/User.java b/java-backend/src/main/java/com/aijianhua/backend/entity/User.java new file mode 100644 index 0000000..3741ba9 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/entity/User.java @@ -0,0 +1,144 @@ +package com.aijianhua.backend.entity; + +import lombok.Data; +import javax.persistence.*; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String username; + + @Column(name = "password_hash", nullable = false) + private String password; + + @Column(nullable = false, unique = true) + private String phone; + + @Column(unique = true) + private String email; + + @Column(name = "user_type", nullable = false) + private String userType; + + @Column(name = "avatar_url") + private String avatarUrl; + + @Column(nullable = false) + private Integer status = 1; + + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @Column(name = "last_login") + private LocalDateTime lastLogin; + + @PrePersist + protected void onCreate() { + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } + + @PreUpdate + protected void onUpdate() { + updatedAt = LocalDateTime.now(); + } + + // Getters and Setters + 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 getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getUserType() { + return userType; + } + + public void setUserType(String userType) { + this.userType = userType; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + public LocalDateTime getLastLogin() { + return lastLogin; + } + + public void setLastLogin(LocalDateTime lastLogin) { + this.lastLogin = lastLogin; + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/exception/BusinessException.java b/java-backend/src/main/java/com/aijianhua/backend/exception/BusinessException.java new file mode 100644 index 0000000..6d96b39 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/exception/BusinessException.java @@ -0,0 +1,18 @@ +package com.aijianhua.backend.exception; + +public class BusinessException extends RuntimeException { + private int code = 400; + + public BusinessException(String message) { + super(message); + } + + public BusinessException(int code, String message) { + super(message); + this.code = code; + } + + public int getCode() { + return code; + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/exception/GlobalExceptionHandler.java b/java-backend/src/main/java/com/aijianhua/backend/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..cf2b34c --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/exception/GlobalExceptionHandler.java @@ -0,0 +1,72 @@ +package com.aijianhua.backend.exception; + +import com.aijianhua.backend.dto.ApiResponse; +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.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + /** + * 处理参数验证异常(RequestBody参数验证) + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity>> handleValidationExceptions(MethodArgumentNotValidException ex) { + Map errors = new HashMap<>(); + ex.getBindingResult().getAllErrors().forEach((error) -> { + String fieldName = ((FieldError) error).getField(); + String errorMessage = error.getDefaultMessage(); + errors.put(fieldName, errorMessage); + }); + return ResponseEntity.badRequest().body(new ApiResponse<>(400, "参数验证失败", errors)); + } + + /** + * 处理参数验证异常(方法调用时的验证) + */ + @ExceptionHandler(ConstraintViolationException.class) + public ResponseEntity>> handleConstraintViolationException(ConstraintViolationException ex) { + Map errors = new HashMap<>(); + for (ConstraintViolation violation : ex.getConstraintViolations()) { + String propertyPath = violation.getPropertyPath().toString(); + String message = violation.getMessage(); + errors.put(propertyPath, message); + } + return ResponseEntity.badRequest().body(new ApiResponse<>(400, "参数验证失败", errors)); + } + + /** + * 处理业务异常 + */ + @ExceptionHandler(BusinessException.class) + public ResponseEntity> handleBusinessException(BusinessException ex) { + return ResponseEntity.status(ex.getCode()).body(new ApiResponse<>(ex.getCode(), ex.getMessage(), null)); + } + + /** + * 处理未授权异常 + */ + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity> handleUnauthorizedException(UnauthorizedException ex) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ApiResponse<>(401, ex.getMessage(), null)); + } + + /** + * 处理其他所有未处理的异常 + */ + @ExceptionHandler(Exception.class) + public ResponseEntity> handleGenericException(Exception ex) { + // 记录日志 + ex.printStackTrace(); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ApiResponse<>(500, "系统内部错误", null)); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/exception/UnauthorizedException.java b/java-backend/src/main/java/com/aijianhua/backend/exception/UnauthorizedException.java new file mode 100644 index 0000000..29fbeb4 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/exception/UnauthorizedException.java @@ -0,0 +1,7 @@ +package com.aijianhua.backend.exception; + +public class UnauthorizedException extends RuntimeException { + public UnauthorizedException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/interceptor/JwtInterceptor.java b/java-backend/src/main/java/com/aijianhua/backend/interceptor/JwtInterceptor.java new file mode 100644 index 0000000..f6dbe08 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/interceptor/JwtInterceptor.java @@ -0,0 +1,36 @@ +package com.aijianhua.backend.interceptor; + +import com.aijianhua.backend.util.JwtUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Component +public class JwtInterceptor implements HandlerInterceptor { + + @Autowired + private JwtUtil jwtUtil; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + String authorizationHeader = request.getHeader("Authorization"); + + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + String token = authorizationHeader.substring(7); + + if (jwtUtil.validateToken(token) && !jwtUtil.isTokenExpired(token)) { + Long userId = jwtUtil.getUserIdFromToken(token); + request.setAttribute("userId", userId); + return true; + } + } + + // 未提供有效的认证token + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("{\"code\":401,\"message\":\"未提供有效的认证token\",\"data\":null}"); + return false; + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/repository/UploadRepository.java b/java-backend/src/main/java/com/aijianhua/backend/repository/UploadRepository.java new file mode 100644 index 0000000..d2d4e7b --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/repository/UploadRepository.java @@ -0,0 +1,13 @@ +package com.aijianhua.backend.repository; + +import com.aijianhua.backend.entity.Upload; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UploadRepository extends JpaRepository { + Page findByUserId(Long userId, Pageable pageable); + Page findByUserIdAndUploadType(Long userId, String uploadType, Pageable pageable); +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/repository/UserRepository.java b/java-backend/src/main/java/com/aijianhua/backend/repository/UserRepository.java new file mode 100644 index 0000000..256ffad --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/repository/UserRepository.java @@ -0,0 +1,17 @@ +package com.aijianhua.backend.repository; + +import com.aijianhua.backend.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByUsernameOrPhoneOrEmailAndStatus(String username, String phone, String email, Integer status); + Optional findByUsername(String username); + Optional findByPhone(String phone); + Optional findByEmail(String email); + Boolean existsByUsername(String username); + Boolean existsByPhone(String phone); + Boolean existsByEmail(String email); +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/security/JwtAuthenticationEntryPoint.java b/java-backend/src/main/java/com/aijianhua/backend/security/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..f0ac403 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/security/JwtAuthenticationEntryPoint.java @@ -0,0 +1,25 @@ +package com.aijianhua.backend.security; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + // 返回未授权的JSON响应 + response.getWriter().write("{\"code\":401,\"message\":\"未提供有效的认证token\",\"data\":null}"); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/security/JwtAuthenticationFilter.java b/java-backend/src/main/java/com/aijianhua/backend/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..2754c1c --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/security/JwtAuthenticationFilter.java @@ -0,0 +1,58 @@ +package com.aijianhua.backend.security; + +import com.aijianhua.backend.entity.User; +import com.aijianhua.backend.repository.UserRepository; +import com.aijianhua.backend.util.JwtUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; + +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + @Autowired + private JwtUtil jwtUtil; + + @Autowired + private UserRepository userRepository; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String authorizationHeader = request.getHeader("Authorization"); + + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + String token = authorizationHeader.substring(7); + + if (jwtUtil.validateToken(token) && !jwtUtil.isTokenExpired(token)) { + Long userId = jwtUtil.getUserIdFromToken(token); + + // 从数据库获取用户信息 + User user = userRepository.findById(userId).orElse(null); + + if (user != null && user.getStatus() == 1) { + // 创建认证对象 + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>()); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + + // 设置安全上下文 + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + } + + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/service/AuthService.java b/java-backend/src/main/java/com/aijianhua/backend/service/AuthService.java new file mode 100644 index 0000000..775c2f3 --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/service/AuthService.java @@ -0,0 +1,111 @@ +package com.aijianhua.backend.service; + +import com.aijianhua.backend.dto.LoginRequest; +import com.aijianhua.backend.dto.RegisterRequest; +import com.aijianhua.backend.dto.UserResponse; +import com.aijianhua.backend.entity.User; +import com.aijianhua.backend.exception.BusinessException; +import com.aijianhua.backend.repository.UserRepository; +import com.aijianhua.backend.util.JwtUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.util.Optional; + +@Service +public class AuthService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private JwtUtil jwtUtil; + + /** + * 用户注册 + */ + public User register(RegisterRequest registerRequest) { + // 检查用户是否已存在 + if (userRepository.existsByUsername(registerRequest.getUsername()) || + userRepository.existsByPhone(registerRequest.getPhone()) || + (registerRequest.getEmail() != null && userRepository.existsByEmail(registerRequest.getEmail()))) { + throw new BusinessException("用户名、手机号或邮箱已存在"); + } + + // 创建用户 + User user = new User(); + user.setUsername(registerRequest.getUsername()); + user.setPassword(passwordEncoder.encode(registerRequest.getPassword())); + user.setPhone(registerRequest.getPhone()); + user.setEmail(registerRequest.getEmail()); + user.setUserType(registerRequest.getUserType()); + user.setCreatedAt(LocalDateTime.now()); + user.setUpdatedAt(LocalDateTime.now()); + + return userRepository.save(user); + } + + /** + * 用户登录 + */ + public User login(LoginRequest loginRequest) { + // 查询用户(支持用户名、手机号、邮箱登录) + Optional userOptional = userRepository.findByUsernameOrPhoneOrEmailAndStatus( + loginRequest.getLogin(), + loginRequest.getLogin(), + loginRequest.getLogin(), + 1 + ); + + if (!userOptional.isPresent()) { + throw new BusinessException("用户不存在或已被禁用"); + } + + User user = userOptional.get(); + + // 验证密码 + if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { + throw new BusinessException("密码不正确"); + } + + // 更新最后登录时间 + user.setLastLogin(LocalDateTime.now()); + userRepository.save(user); + + return user; + } + + /** + * 生成JWT token + */ + public String generateToken(User user) { + return jwtUtil.generateToken(user.getId(), user.getUsername(), user.getUserType()); + } + + /** + * 获取用户信息 + */ + public UserResponse getUserInfo(Long userId) { + Optional userOptional = userRepository.findById(userId); + if (!userOptional.isPresent()) { + throw new BusinessException("用户不存在"); + } + + User user = userOptional.get(); + UserResponse userResponse = new UserResponse(); + userResponse.setId(user.getId()); + userResponse.setUsername(user.getUsername()); + userResponse.setPhone(user.getPhone()); + userResponse.setEmail(user.getEmail()); + userResponse.setUserType(user.getUserType()); + userResponse.setAvatarUrl(user.getAvatarUrl()); + userResponse.setCreatedAt(user.getCreatedAt()); + userResponse.setLastLogin(user.getLastLogin()); + + return userResponse; + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/service/UploadService.java b/java-backend/src/main/java/com/aijianhua/backend/service/UploadService.java new file mode 100644 index 0000000..b5cbb8b --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/service/UploadService.java @@ -0,0 +1,119 @@ +package com.aijianhua.backend.service; + +import com.aijianhua.backend.entity.Upload; +import com.aijianhua.backend.exception.BusinessException; +import com.aijianhua.backend.repository.UploadRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.UUID; + +@Service +public class UploadService { + + @Autowired + private UploadRepository uploadRepository; + + private static final String UPLOAD_DIR = "uploads/"; + + /** + * 文件上传 + */ + public Upload uploadFile(MultipartFile file, String uploadType, Long userId) throws IOException { + // 创建上传目录 + String typeDir = uploadType != null ? uploadType : "common"; + Path uploadPath = Paths.get(UPLOAD_DIR + typeDir); + if (!Files.exists(uploadPath)) { + Files.createDirectories(uploadPath); + } + + // 生成唯一文件名 + String originalFilename = file.getOriginalFilename(); + String fileExtension = ""; + if (originalFilename != null && originalFilename.contains(".")) { + fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + String storedName = typeDir + "_" + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8) + fileExtension; + + // 保存文件 + Path filePath = uploadPath.resolve(storedName); + Files.write(filePath, file.getBytes()); + + // 保存文件记录到数据库 + Upload upload = new Upload(); + upload.setUserId(userId); + upload.setOriginalName(originalFilename); + upload.setStoredName(storedName); + upload.setFilePath("/" + uploadPath.toString() + "/" + storedName); + upload.setFileSize(file.getSize()); + upload.setMimeType(file.getContentType()); + upload.setFileType(getFileType(file.getContentType())); + upload.setUploadType(typeDir); + upload.setCreatedAt(LocalDateTime.now()); + upload.setUpdatedAt(LocalDateTime.now()); + + return uploadRepository.save(upload); + } + + /** + * 获取文件类型 + */ + private String getFileType(String mimeType) { + if (mimeType == null) { + return "other"; + } + if (mimeType.startsWith("image/")) { + return "image"; + } + if (mimeType.startsWith("video/")) { + return "video"; + } + if (mimeType.startsWith("audio/")) { + return "audio"; + } + return "other"; + } + + /** + * 获取上传文件列表 + */ + public Page getUploads(Long userId, String type, int page, int limit) { + Pageable pageable = PageRequest.of(page - 1, limit, Sort.by(Sort.Direction.DESC, "createdAt")); + + if (type != null && !type.isEmpty()) { + return uploadRepository.findByUserIdAndUploadType(userId, type, pageable); + } + + return uploadRepository.findByUserId(userId, pageable); + } + + /** + * 删除上传文件 + */ + public void deleteUpload(Long id, Long userId) throws IOException { + // 查询文件信息 + Upload upload = uploadRepository.findById(id).orElse(null); + if (upload == null || !upload.getUserId().equals(userId)) { + throw new BusinessException("文件不存在"); + } + + // 删除物理文件 + Path filePath = Paths.get(upload.getFilePath().substring(1)); // 移除开头的 "/" + if (Files.exists(filePath)) { + Files.delete(filePath); + } + + // 删除数据库记录 + uploadRepository.deleteById(id); + } +} \ No newline at end of file diff --git a/java-backend/src/main/java/com/aijianhua/backend/util/JwtUtil.java b/java-backend/src/main/java/com/aijianhua/backend/util/JwtUtil.java new file mode 100644 index 0000000..587d70d --- /dev/null +++ b/java-backend/src/main/java/com/aijianhua/backend/util/JwtUtil.java @@ -0,0 +1,82 @@ +package com.aijianhua.backend.util; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Component +public class JwtUtil { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.expiration}") + private Long expiration; + + /** + * 生成JWT token + */ + public String generateToken(Long userId, String username, String userType) { + Map claims = new HashMap<>(); + claims.put("userId", userId); + claims.put("username", username); + claims.put("user_type", userType); + return Jwts.builder() + .setClaims(claims) + .setSubject(username) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(SignatureAlgorithm.HS512, secret) + .compact(); + } + + /** + * 验证JWT token + */ + public Boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(secret).parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * 从token中获取用户ID + */ + public Long getUserIdFromToken(String token) { + Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + return Long.parseLong(claims.get("userId").toString()); + } + + /** + * 从token中获取用户名 + */ + public String getUsernameFromToken(String token) { + Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + return claims.getSubject(); + } + + /** + * 从token中获取用户类型 + */ + public String getUserTypeFromToken(String token) { + Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + return claims.get("user_type").toString(); + } + + /** + * 检查token是否过期 + */ + public Boolean isTokenExpired(String token) { + Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); + Date expiration = claims.getExpiration(); + return expiration.before(new Date()); + } +} \ No newline at end of file diff --git a/java-backend/src/main/resources/api-docs.yaml b/java-backend/src/main/resources/api-docs.yaml new file mode 100644 index 0000000..b4a4681 --- /dev/null +++ b/java-backend/src/main/resources/api-docs.yaml @@ -0,0 +1,437 @@ +openapi: 3.0.0 +info: + title: 爱鉴花小程序API文档 + description: 爱鉴花小程序后端API接口文档 + version: 1.0.0 + +servers: + - url: http://localhost:8080/api/v1 + description: 本地开发服务器 + +paths: + /auth/register: + post: + summary: 用户注册 + description: 用户注册接口,支持用户名、手机号、邮箱注册 + operationId: register + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterRequest' + responses: + '200': + description: 注册成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseUser' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '409': + description: 用户已存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /auth/login: + post: + summary: 用户登录 + description: 用户登录接口,支持用户名、手机号、邮箱登录 + operationId: login + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequest' + responses: + '200': + description: 登录成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseUser' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '401': + description: 用户名或密码错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /auth/me: + get: + summary: 获取当前用户信息 + description: 获取当前登录用户信息 + operationId: getCurrentUser + security: + - bearerAuth: [] + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseUser' + '401': + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /upload: + post: + summary: 上传文件 + description: 上传文件接口,支持图片、文档等文件类型 + operationId: uploadFile + security: + - bearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + responses: + '200': + description: 上传成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseUpload' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '401': + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '500': + description: 服务器内部错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /upload/list: + get: + summary: 获取上传文件列表 + description: 获取当前用户上传的文件列表 + operationId: getUploads + security: + - bearerAuth: [] + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Upload' + '401': + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /upload/{id}: + delete: + summary: 删除上传文件 + description: 删除指定ID的上传文件 + operationId: deleteUpload + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: 删除成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseString' + '401': + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '404': + description: 文件不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /health: + get: + summary: 服务健康检查 + description: 检查服务运行状态 + operationId: healthCheck + responses: + '200': + description: 服务正常 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseHealth' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + RegisterRequest: + type: object + required: + - username + - password + - phone + properties: + username: + type: string + description: 用户名 + password: + type: string + minLength: 6 + description: 密码 + phone: + type: string + description: 手机号 + email: + type: string + format: email + description: 邮箱 + userType: + type: string + default: farmer + description: 用户类型 + + LoginRequest: + type: object + required: + - login + - password + properties: + login: + type: string + description: 登录凭证(用户名/手机号/邮箱) + password: + type: string + description: 密码 + + ApiResponseMap: + type: object + properties: + code: + type: integer + description: 响应码 + message: + type: string + description: 响应消息 + data: + type: object + description: 响应数据 + + ApiResponseUser: + type: object + properties: + code: + type: integer + description: 响应码 + message: + type: string + description: 响应消息 + data: + $ref: '#/components/schemas/UserResponse' + + ApiResponseUpload: + type: object + properties: + code: + type: integer + description: 响应码 + message: + type: string + description: 响应消息 + data: + $ref: '#/components/schemas/UploadResponse' + + ApiResponseError: + type: object + properties: + code: + type: integer + description: 错误码 + message: + type: string + description: 错误消息 + data: + type: object + nullable: true + description: 错误数据 + + UserResponse: + type: object + properties: + id: + type: integer + description: 用户ID + username: + type: string + description: 用户名 + phone: + type: string + description: 手机号 + email: + type: string + format: email + description: 邮箱 + userType: + type: string + description: 用户类型 + avatarUrl: + type: string + description: 头像URL + createdAt: + type: string + format: date-time + description: 创建时间 + lastLogin: + type: string + format: date-time + description: 最后登录时间 + + UploadResponse: + type: object + properties: + url: + type: string + description: 文件访问URL + filename: + type: string + description: 存储文件名 + originalName: + type: string + description: 原始文件名 + size: + type: integer + description: 文件大小 + mimeType: + type: string + description: MIME类型 + uploadType: + type: string + description: 上传类型 + + PageResponseUpload: + type: object + properties: + code: + type: integer + description: 响应码 + message: + type: string + description: 响应消息 + data: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/Upload' + pagination: + $ref: '#/components/schemas/Pagination' + + Upload: + type: object + properties: + id: + type: integer + description: 文件ID + userId: + type: integer + description: 用户ID + originalName: + type: string + description: 原始文件名 + storedName: + type: string + description: 存储文件名 + filePath: + type: string + description: 文件路径 + fileSize: + type: integer + description: 文件大小 + mimeType: + type: string + description: MIME类型 + fileType: + type: string + description: 文件类型 + uploadType: + type: string + description: 上传类型 + createdAt: + type: string + format: date-time + description: 创建时间 + updatedAt: + type: string + format: date-time + description: 更新时间 + + Pagination: + type: object + properties: + page: + type: integer + description: 当前页码 + limit: + type: integer + description: 每页数量 + total: + type: integer + description: 总记录数 + pages: + type: integer + description: 总页数 \ No newline at end of file diff --git a/java-backend/src/main/resources/application.yml b/java-backend/src/main/resources/application.yml new file mode 100644 index 0000000..8a1a861 --- /dev/null +++ b/java-backend/src/main/resources/application.yml @@ -0,0 +1,47 @@ +server: + port: 3200 + +spring: + application: + name: aijianhua-backend + datasource: + url: jdbc:mysql://129.211.213.226:9527/xlxumudata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai + username: root + password: aiotAiot123! + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 20 + minimum-idle: 5 + connection-timeout: 30000 + idle-timeout: 10000 + max-lifetime: 1800000 + connection-test-query: SELECT 1 + jpa: + hibernate: + ddl-auto: update + show-sql: false + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + format_sql: true + open-in-view: false + flyway: + enabled: false + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB + +# JWT配置 +jwt: + secret: xluMubackendSecretKey2024! + expiration: 604800000 # 7天 + +# 日志配置 +logging: + level: + com.aijianhua: INFO + org.springframework.web: INFO + org.hibernate.SQL: INFO + pattern: + console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" \ No newline at end of file diff --git a/java-backend/src/main/resources/db/migration/V1__Initial_Setup.sql b/java-backend/src/main/resources/db/migration/V1__Initial_Setup.sql new file mode 100644 index 0000000..e7824c7 --- /dev/null +++ b/java-backend/src/main/resources/db/migration/V1__Initial_Setup.sql @@ -0,0 +1,29 @@ +-- 创建用户表 +CREATE TABLE IF NOT EXISTS users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + phone VARCHAR(20) UNIQUE, + email VARCHAR(100) UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + is_active BOOLEAN DEFAULT TRUE +); + +-- 创建文件上传表 +CREATE TABLE IF NOT EXISTS uploads ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NOT NULL, + original_name VARCHAR(255) NOT NULL, + file_path VARCHAR(500) NOT NULL, + file_type VARCHAR(50) NOT NULL, + file_size BIGINT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +-- 创建索引 +CREATE INDEX idx_users_username ON users(username); +CREATE INDEX idx_users_phone ON users(phone); +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_uploads_user_id ON uploads(user_id); \ No newline at end of file diff --git a/java-backend/target/classes/api-docs.yaml b/java-backend/target/classes/api-docs.yaml new file mode 100644 index 0000000..b4a4681 --- /dev/null +++ b/java-backend/target/classes/api-docs.yaml @@ -0,0 +1,437 @@ +openapi: 3.0.0 +info: + title: 爱鉴花小程序API文档 + description: 爱鉴花小程序后端API接口文档 + version: 1.0.0 + +servers: + - url: http://localhost:8080/api/v1 + description: 本地开发服务器 + +paths: + /auth/register: + post: + summary: 用户注册 + description: 用户注册接口,支持用户名、手机号、邮箱注册 + operationId: register + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterRequest' + responses: + '200': + description: 注册成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseUser' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '409': + description: 用户已存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /auth/login: + post: + summary: 用户登录 + description: 用户登录接口,支持用户名、手机号、邮箱登录 + operationId: login + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequest' + responses: + '200': + description: 登录成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseUser' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '401': + description: 用户名或密码错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /auth/me: + get: + summary: 获取当前用户信息 + description: 获取当前登录用户信息 + operationId: getCurrentUser + security: + - bearerAuth: [] + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseUser' + '401': + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '404': + description: 用户不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /upload: + post: + summary: 上传文件 + description: 上传文件接口,支持图片、文档等文件类型 + operationId: uploadFile + security: + - bearerAuth: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + responses: + '200': + description: 上传成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseUpload' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '401': + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '500': + description: 服务器内部错误 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /upload/list: + get: + summary: 获取上传文件列表 + description: 获取当前用户上传的文件列表 + operationId: getUploads + security: + - bearerAuth: [] + responses: + '200': + description: 获取成功 + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Upload' + '401': + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /upload/{id}: + delete: + summary: 删除上传文件 + description: 删除指定ID的上传文件 + operationId: deleteUpload + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: 删除成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseString' + '401': + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + '404': + description: 文件不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseError' + + /health: + get: + summary: 服务健康检查 + description: 检查服务运行状态 + operationId: healthCheck + responses: + '200': + description: 服务正常 + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponseHealth' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + RegisterRequest: + type: object + required: + - username + - password + - phone + properties: + username: + type: string + description: 用户名 + password: + type: string + minLength: 6 + description: 密码 + phone: + type: string + description: 手机号 + email: + type: string + format: email + description: 邮箱 + userType: + type: string + default: farmer + description: 用户类型 + + LoginRequest: + type: object + required: + - login + - password + properties: + login: + type: string + description: 登录凭证(用户名/手机号/邮箱) + password: + type: string + description: 密码 + + ApiResponseMap: + type: object + properties: + code: + type: integer + description: 响应码 + message: + type: string + description: 响应消息 + data: + type: object + description: 响应数据 + + ApiResponseUser: + type: object + properties: + code: + type: integer + description: 响应码 + message: + type: string + description: 响应消息 + data: + $ref: '#/components/schemas/UserResponse' + + ApiResponseUpload: + type: object + properties: + code: + type: integer + description: 响应码 + message: + type: string + description: 响应消息 + data: + $ref: '#/components/schemas/UploadResponse' + + ApiResponseError: + type: object + properties: + code: + type: integer + description: 错误码 + message: + type: string + description: 错误消息 + data: + type: object + nullable: true + description: 错误数据 + + UserResponse: + type: object + properties: + id: + type: integer + description: 用户ID + username: + type: string + description: 用户名 + phone: + type: string + description: 手机号 + email: + type: string + format: email + description: 邮箱 + userType: + type: string + description: 用户类型 + avatarUrl: + type: string + description: 头像URL + createdAt: + type: string + format: date-time + description: 创建时间 + lastLogin: + type: string + format: date-time + description: 最后登录时间 + + UploadResponse: + type: object + properties: + url: + type: string + description: 文件访问URL + filename: + type: string + description: 存储文件名 + originalName: + type: string + description: 原始文件名 + size: + type: integer + description: 文件大小 + mimeType: + type: string + description: MIME类型 + uploadType: + type: string + description: 上传类型 + + PageResponseUpload: + type: object + properties: + code: + type: integer + description: 响应码 + message: + type: string + description: 响应消息 + data: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/Upload' + pagination: + $ref: '#/components/schemas/Pagination' + + Upload: + type: object + properties: + id: + type: integer + description: 文件ID + userId: + type: integer + description: 用户ID + originalName: + type: string + description: 原始文件名 + storedName: + type: string + description: 存储文件名 + filePath: + type: string + description: 文件路径 + fileSize: + type: integer + description: 文件大小 + mimeType: + type: string + description: MIME类型 + fileType: + type: string + description: 文件类型 + uploadType: + type: string + description: 上传类型 + createdAt: + type: string + format: date-time + description: 创建时间 + updatedAt: + type: string + format: date-time + description: 更新时间 + + Pagination: + type: object + properties: + page: + type: integer + description: 当前页码 + limit: + type: integer + description: 每页数量 + total: + type: integer + description: 总记录数 + pages: + type: integer + description: 总页数 \ No newline at end of file diff --git a/java-backend/target/classes/application.yml b/java-backend/target/classes/application.yml new file mode 100644 index 0000000..8a1a861 --- /dev/null +++ b/java-backend/target/classes/application.yml @@ -0,0 +1,47 @@ +server: + port: 3200 + +spring: + application: + name: aijianhua-backend + datasource: + url: jdbc:mysql://129.211.213.226:9527/xlxumudata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai + username: root + password: aiotAiot123! + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + maximum-pool-size: 20 + minimum-idle: 5 + connection-timeout: 30000 + idle-timeout: 10000 + max-lifetime: 1800000 + connection-test-query: SELECT 1 + jpa: + hibernate: + ddl-auto: update + show-sql: false + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + format_sql: true + open-in-view: false + flyway: + enabled: false + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB + +# JWT配置 +jwt: + secret: xluMubackendSecretKey2024! + expiration: 604800000 # 7天 + +# 日志配置 +logging: + level: + com.aijianhua: INFO + org.springframework.web: INFO + org.hibernate.SQL: INFO + pattern: + console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" \ No newline at end of file diff --git a/java-backend/target/classes/com/aijianhua/backend/Application.class b/java-backend/target/classes/com/aijianhua/backend/Application.class new file mode 100644 index 0000000..ae14b73 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/Application.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/config/SecurityConfig.class b/java-backend/target/classes/com/aijianhua/backend/config/SecurityConfig.class new file mode 100644 index 0000000..7457705 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/config/SecurityConfig.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/config/SwaggerResourceConfig.class b/java-backend/target/classes/com/aijianhua/backend/config/SwaggerResourceConfig.class new file mode 100644 index 0000000..638dea9 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/config/SwaggerResourceConfig.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/config/WebMvcConfig.class b/java-backend/target/classes/com/aijianhua/backend/config/WebMvcConfig.class new file mode 100644 index 0000000..5cfb832 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/config/WebMvcConfig.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/controller/AuthController.class b/java-backend/target/classes/com/aijianhua/backend/controller/AuthController.class new file mode 100644 index 0000000..da5ddf0 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/controller/AuthController.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/controller/HealthController.class b/java-backend/target/classes/com/aijianhua/backend/controller/HealthController.class new file mode 100644 index 0000000..c8148da Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/controller/HealthController.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/controller/UploadController.class b/java-backend/target/classes/com/aijianhua/backend/controller/UploadController.class new file mode 100644 index 0000000..8d5577b Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/controller/UploadController.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/dto/ApiResponse.class b/java-backend/target/classes/com/aijianhua/backend/dto/ApiResponse.class new file mode 100644 index 0000000..21f01d7 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/dto/ApiResponse.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/dto/LoginRequest.class b/java-backend/target/classes/com/aijianhua/backend/dto/LoginRequest.class new file mode 100644 index 0000000..39acbab Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/dto/LoginRequest.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/dto/PageResponse$PageData.class b/java-backend/target/classes/com/aijianhua/backend/dto/PageResponse$PageData.class new file mode 100644 index 0000000..a2ed333 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/dto/PageResponse$PageData.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/dto/PageResponse$Pagination.class b/java-backend/target/classes/com/aijianhua/backend/dto/PageResponse$Pagination.class new file mode 100644 index 0000000..67bf05d Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/dto/PageResponse$Pagination.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/dto/PageResponse.class b/java-backend/target/classes/com/aijianhua/backend/dto/PageResponse.class new file mode 100644 index 0000000..ee33cb3 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/dto/PageResponse.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/dto/RegisterRequest.class b/java-backend/target/classes/com/aijianhua/backend/dto/RegisterRequest.class new file mode 100644 index 0000000..eb56632 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/dto/RegisterRequest.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/dto/UploadResponse.class b/java-backend/target/classes/com/aijianhua/backend/dto/UploadResponse.class new file mode 100644 index 0000000..b2ed0e6 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/dto/UploadResponse.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/dto/UserResponse.class b/java-backend/target/classes/com/aijianhua/backend/dto/UserResponse.class new file mode 100644 index 0000000..c4803b6 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/dto/UserResponse.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/entity/Upload.class b/java-backend/target/classes/com/aijianhua/backend/entity/Upload.class new file mode 100644 index 0000000..b83a3dd Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/entity/Upload.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/entity/User.class b/java-backend/target/classes/com/aijianhua/backend/entity/User.class new file mode 100644 index 0000000..9e5553b Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/entity/User.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/exception/BusinessException.class b/java-backend/target/classes/com/aijianhua/backend/exception/BusinessException.class new file mode 100644 index 0000000..373e7bd Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/exception/BusinessException.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/exception/GlobalExceptionHandler.class b/java-backend/target/classes/com/aijianhua/backend/exception/GlobalExceptionHandler.class new file mode 100644 index 0000000..c3f0839 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/exception/GlobalExceptionHandler.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/exception/UnauthorizedException.class b/java-backend/target/classes/com/aijianhua/backend/exception/UnauthorizedException.class new file mode 100644 index 0000000..1bff234 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/exception/UnauthorizedException.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/interceptor/JwtInterceptor.class b/java-backend/target/classes/com/aijianhua/backend/interceptor/JwtInterceptor.class new file mode 100644 index 0000000..40b2192 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/interceptor/JwtInterceptor.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/repository/UploadRepository.class b/java-backend/target/classes/com/aijianhua/backend/repository/UploadRepository.class new file mode 100644 index 0000000..ff4976a Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/repository/UploadRepository.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/repository/UserRepository.class b/java-backend/target/classes/com/aijianhua/backend/repository/UserRepository.class new file mode 100644 index 0000000..dfccde3 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/repository/UserRepository.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/security/JwtAuthenticationEntryPoint.class b/java-backend/target/classes/com/aijianhua/backend/security/JwtAuthenticationEntryPoint.class new file mode 100644 index 0000000..54679e9 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/security/JwtAuthenticationEntryPoint.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/security/JwtAuthenticationFilter.class b/java-backend/target/classes/com/aijianhua/backend/security/JwtAuthenticationFilter.class new file mode 100644 index 0000000..723a7e2 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/security/JwtAuthenticationFilter.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/service/AuthService.class b/java-backend/target/classes/com/aijianhua/backend/service/AuthService.class new file mode 100644 index 0000000..b32bc92 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/service/AuthService.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/service/UploadService.class b/java-backend/target/classes/com/aijianhua/backend/service/UploadService.class new file mode 100644 index 0000000..2c9c2b6 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/service/UploadService.class differ diff --git a/java-backend/target/classes/com/aijianhua/backend/util/JwtUtil.class b/java-backend/target/classes/com/aijianhua/backend/util/JwtUtil.class new file mode 100644 index 0000000..28d9a26 Binary files /dev/null and b/java-backend/target/classes/com/aijianhua/backend/util/JwtUtil.class differ diff --git a/java-backend/target/classes/db/migration/V1__Initial_Setup.sql b/java-backend/target/classes/db/migration/V1__Initial_Setup.sql new file mode 100644 index 0000000..e7824c7 --- /dev/null +++ b/java-backend/target/classes/db/migration/V1__Initial_Setup.sql @@ -0,0 +1,29 @@ +-- 创建用户表 +CREATE TABLE IF NOT EXISTS users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + phone VARCHAR(20) UNIQUE, + email VARCHAR(100) UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + is_active BOOLEAN DEFAULT TRUE +); + +-- 创建文件上传表 +CREATE TABLE IF NOT EXISTS uploads ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NOT NULL, + original_name VARCHAR(255) NOT NULL, + file_path VARCHAR(500) NOT NULL, + file_type VARCHAR(50) NOT NULL, + file_size BIGINT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +-- 创建索引 +CREATE INDEX idx_users_username ON users(username); +CREATE INDEX idx_users_phone ON users(phone); +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_uploads_user_id ON uploads(user_id); \ No newline at end of file diff --git a/java-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/java-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..a5bc914 --- /dev/null +++ b/java-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,28 @@ +com/aijianhua/backend/repository/UploadRepository.class +com/aijianhua/backend/security/JwtAuthenticationFilter.class +com/aijianhua/backend/controller/UploadController.class +com/aijianhua/backend/service/AuthService.class +com/aijianhua/backend/dto/UserResponse.class +com/aijianhua/backend/dto/UploadResponse.class +com/aijianhua/backend/util/JwtUtil.class +com/aijianhua/backend/dto/PageResponse$Pagination.class +com/aijianhua/backend/dto/ApiResponse.class +com/aijianhua/backend/config/SecurityConfig.class +com/aijianhua/backend/interceptor/JwtInterceptor.class +com/aijianhua/backend/config/WebMvcConfig.class +com/aijianhua/backend/Application.class +com/aijianhua/backend/dto/RegisterRequest.class +com/aijianhua/backend/service/UploadService.class +com/aijianhua/backend/exception/GlobalExceptionHandler.class +com/aijianhua/backend/security/JwtAuthenticationEntryPoint.class +com/aijianhua/backend/entity/User.class +com/aijianhua/backend/repository/UserRepository.class +com/aijianhua/backend/exception/UnauthorizedException.class +com/aijianhua/backend/config/SwaggerResourceConfig.class +com/aijianhua/backend/dto/LoginRequest.class +com/aijianhua/backend/dto/PageResponse.class +com/aijianhua/backend/dto/PageResponse$PageData.class +com/aijianhua/backend/entity/Upload.class +com/aijianhua/backend/controller/HealthController.class +com/aijianhua/backend/controller/AuthController.class +com/aijianhua/backend/exception/BusinessException.class diff --git a/java-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/java-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..44fb63d --- /dev/null +++ b/java-backend/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,26 @@ +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/security/JwtAuthenticationFilter.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/config/SecurityConfig.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/exception/UnauthorizedException.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/dto/UserResponse.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/dto/ApiResponse.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/controller/UploadController.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/exception/BusinessException.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/entity/User.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/dto/UploadResponse.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/service/AuthService.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/util/JwtUtil.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/repository/UserRepository.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/interceptor/JwtInterceptor.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/repository/UploadRepository.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/controller/HealthController.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/security/JwtAuthenticationEntryPoint.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/config/WebMvcConfig.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/entity/Upload.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/Application.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/dto/LoginRequest.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/exception/GlobalExceptionHandler.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/dto/PageResponse.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/dto/RegisterRequest.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/config/SwaggerResourceConfig.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/controller/AuthController.java +/Users/ainongkeji/code/vue/aijianhua/java-backend/src/main/java/com/aijianhua/backend/service/UploadService.java