docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
105
java-backend/pom.xml
Normal file
105
java-backend/pom.xml
Normal file
@@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.aijianhua</groupId>
|
||||
<artifactId>backend</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>爱鉴花后端服务</name>
|
||||
<description>爱鉴花小程序Java版后端服务</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.0</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Starters -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL Connector -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.28</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>0.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Flyway -->
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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/");
|
||||
}
|
||||
}
|
||||
@@ -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/**");
|
||||
}
|
||||
}
|
||||
@@ -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<Map<String, Object>> register(@Valid @RequestBody RegisterRequest registerRequest) {
|
||||
User user = authService.register(registerRequest);
|
||||
|
||||
// 生成JWT token
|
||||
String token = authService.generateToken(user);
|
||||
|
||||
// 构造响应数据
|
||||
Map<String, Object> 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<Map<String, Object>> login(@Valid @RequestBody LoginRequest loginRequest) {
|
||||
User user = authService.login(loginRequest);
|
||||
|
||||
// 生成JWT token
|
||||
String token = authService.generateToken(user);
|
||||
|
||||
// 构造响应数据
|
||||
Map<String, Object> 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<UserResponse> getCurrentUser(@RequestAttribute("userId") Long userId) {
|
||||
UserResponse userResponse = authService.getUserInfo(userId);
|
||||
return ApiResponse.success(userResponse);
|
||||
}
|
||||
}
|
||||
@@ -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<Map<String, Object>> healthCheck() {
|
||||
Map<String, Object> healthInfo = new HashMap<>();
|
||||
healthInfo.put("status", "UP");
|
||||
healthInfo.put("timestamp", LocalDateTime.now());
|
||||
healthInfo.put("service", "aijianhua-backend");
|
||||
|
||||
return ApiResponse.success(healthInfo);
|
||||
}
|
||||
}
|
||||
@@ -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<UploadResponse> 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<Upload> 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<Upload> uploads = uploadService.getUploads(userId, type, page, limit);
|
||||
|
||||
return PageResponse.success(uploads);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除上传文件
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Map<String, String>> deleteUpload(
|
||||
@PathVariable Long id,
|
||||
HttpServletRequest request) throws IOException {
|
||||
|
||||
// 从请求中获取用户ID
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
|
||||
// 删除文件
|
||||
uploadService.deleteUpload(id, userId);
|
||||
|
||||
Map<String, String> data = new HashMap<>();
|
||||
data.put("message", "删除成功");
|
||||
return ApiResponse.success(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.aijianhua.backend.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiResponse<T> {
|
||||
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 <T> ApiResponse<T> success(T data) {
|
||||
return new ApiResponse<>(200, "操作成功", data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
return new ApiResponse<>(200, message, data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> created(T data) {
|
||||
return new ApiResponse<>(201, "创建成功", data);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(Integer code, String message) {
|
||||
return new ApiResponse<>(code, message, null);
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message) {
|
||||
return new ApiResponse<>(500, message, null);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.aijianhua.backend.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
@Data
|
||||
public class PageResponse<T> {
|
||||
private Integer code;
|
||||
private String message;
|
||||
private PageData<T> data;
|
||||
|
||||
public PageResponse() {}
|
||||
|
||||
public PageResponse(Integer code, String message, PageData<T> data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static <T> PageResponse<T> success(Page<T> page) {
|
||||
PageData<T> 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<T> {
|
||||
private Iterable<T> items;
|
||||
private Pagination pagination;
|
||||
|
||||
public void setItems(Iterable<T> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<ApiResponse<Map<String, String>>> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
Map<String, String> 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<ApiResponse<Map<String, String>>> handleConstraintViolationException(ConstraintViolationException ex) {
|
||||
Map<String, String> 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<ApiResponse<Object>> handleBusinessException(BusinessException ex) {
|
||||
return ResponseEntity.status(ex.getCode()).body(new ApiResponse<>(ex.getCode(), ex.getMessage(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理未授权异常
|
||||
*/
|
||||
@ExceptionHandler(UnauthorizedException.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleUnauthorizedException(UnauthorizedException ex) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ApiResponse<>(401, ex.getMessage(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理其他所有未处理的异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleGenericException(Exception ex) {
|
||||
// 记录日志
|
||||
ex.printStackTrace();
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ApiResponse<>(500, "系统内部错误", null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.aijianhua.backend.exception;
|
||||
|
||||
public class UnauthorizedException extends RuntimeException {
|
||||
public UnauthorizedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Upload, Long> {
|
||||
Page<Upload> findByUserId(Long userId, Pageable pageable);
|
||||
Page<Upload> findByUserIdAndUploadType(Long userId, String uploadType, Pageable pageable);
|
||||
}
|
||||
@@ -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<User, Long> {
|
||||
Optional<User> findByUsernameOrPhoneOrEmailAndStatus(String username, String phone, String email, Integer status);
|
||||
Optional<User> findByUsername(String username);
|
||||
Optional<User> findByPhone(String phone);
|
||||
Optional<User> findByEmail(String email);
|
||||
Boolean existsByUsername(String username);
|
||||
Boolean existsByPhone(String phone);
|
||||
Boolean existsByEmail(String email);
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<User> 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<User> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<Upload> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> 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());
|
||||
}
|
||||
}
|
||||
437
java-backend/src/main/resources/api-docs.yaml
Normal file
437
java-backend/src/main/resources/api-docs.yaml
Normal file
@@ -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: 总页数
|
||||
47
java-backend/src/main/resources/application.yml
Normal file
47
java-backend/src/main/resources/application.yml
Normal file
@@ -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"
|
||||
@@ -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);
|
||||
437
java-backend/target/classes/api-docs.yaml
Normal file
437
java-backend/target/classes/api-docs.yaml
Normal file
@@ -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: 总页数
|
||||
47
java-backend/target/classes/application.yml
Normal file
47
java-backend/target/classes/application.yml
Normal file
@@ -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"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user