add
This commit is contained in:
11
backend/src/main/java/com/car/CarRentalApplication.java
Normal file
11
backend/src/main/java/com/car/CarRentalApplication.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.car;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class CarRentalApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CarRentalApplication.class, args);
|
||||
}
|
||||
}
|
||||
13
backend/src/main/java/com/car/common/ApiException.java
Normal file
13
backend/src/main/java/com/car/common/ApiException.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.car.common;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class ApiException extends RuntimeException {
|
||||
private final int code;
|
||||
|
||||
public ApiException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
26
backend/src/main/java/com/car/common/ApiResponse.java
Normal file
26
backend/src/main/java/com/car/common/ApiResponse.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package com.car.common;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ApiResponse<T> {
|
||||
private int code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
public static <T> ApiResponse<T> ok(T data) {
|
||||
return new ApiResponse<>(0, "OK", data);
|
||||
}
|
||||
|
||||
public static ApiResponse<Void> ok() {
|
||||
return new ApiResponse<>(0, "OK", null);
|
||||
}
|
||||
|
||||
public static ApiResponse<Void> error(int code, String message) {
|
||||
return new ApiResponse<>(code, message, null);
|
||||
}
|
||||
}
|
||||
10
backend/src/main/java/com/car/common/ErrorCode.java
Normal file
10
backend/src/main/java/com/car/common/ErrorCode.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.car.common;
|
||||
|
||||
public class ErrorCode {
|
||||
public static final int BAD_REQUEST = 400;
|
||||
public static final int UNAUTHORIZED = 401;
|
||||
public static final int FORBIDDEN = 403;
|
||||
public static final int NOT_FOUND = 404;
|
||||
public static final int CONFLICT = 409;
|
||||
public static final int SERVER_ERROR = 500;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.car.common;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(ApiException.class)
|
||||
public ApiResponse<Void> handleApiException(ApiException ex) {
|
||||
return ApiResponse.error(ex.getCode(), ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(NotLoginException.class)
|
||||
public ApiResponse<Void> handleNotLogin(NotLoginException ex) {
|
||||
return ApiResponse.error(ErrorCode.UNAUTHORIZED, "请先登录");
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ApiResponse<Void> handleValid(MethodArgumentNotValidException ex) {
|
||||
String msg = ex.getBindingResult().getAllErrors().isEmpty()
|
||||
? "参数错误"
|
||||
: ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
|
||||
return ApiResponse.error(ErrorCode.BAD_REQUEST, msg);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public ApiResponse<Void> handleConstraint(ConstraintViolationException ex) {
|
||||
return ApiResponse.error(ErrorCode.BAD_REQUEST, ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ApiResponse<Void> handleGeneric(Exception ex) {
|
||||
return ApiResponse.error(ErrorCode.SERVER_ERROR, ex.getMessage());
|
||||
}
|
||||
}
|
||||
13
backend/src/main/java/com/car/config/SecurityConfig.java
Normal file
13
backend/src/main/java/com/car/config/SecurityConfig.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.car.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
@Bean
|
||||
public BCryptPasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
32
backend/src/main/java/com/car/config/StpInterfaceImpl.java
Normal file
32
backend/src/main/java/com/car/config/StpInterfaceImpl.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.car.config;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import com.car.entity.User;
|
||||
import com.car.mapper.UserMapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class StpInterfaceImpl implements StpInterface {
|
||||
private final UserMapper userMapper;
|
||||
|
||||
public StpInterfaceImpl(UserMapper userMapper) {
|
||||
this.userMapper = userMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
User user = userMapper.findById(Long.valueOf(loginId.toString()));
|
||||
if (user == null || user.getRole() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.singletonList(user.getRole());
|
||||
}
|
||||
}
|
||||
17
backend/src/main/java/com/car/config/WebConfig.java
Normal file
17
backend/src/main/java/com/car/config/WebConfig.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.car.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("*")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true);
|
||||
}
|
||||
}
|
||||
107
backend/src/main/java/com/car/controller/AdminController.java
Normal file
107
backend/src/main/java/com/car/controller/AdminController.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package com.car.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.car.common.ApiResponse;
|
||||
import com.car.common.ApiException;
|
||||
import com.car.common.ErrorCode;
|
||||
import com.car.entity.Car;
|
||||
import com.car.entity.Order;
|
||||
import com.car.entity.User;
|
||||
import com.car.service.CarService;
|
||||
import com.car.service.OrderService;
|
||||
import com.car.service.StatsService;
|
||||
import com.car.service.UserService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/admin")
|
||||
public class AdminController {
|
||||
private final UserService userService;
|
||||
private final CarService carService;
|
||||
private final OrderService orderService;
|
||||
private final StatsService statsService;
|
||||
|
||||
public AdminController(UserService userService, CarService carService, OrderService orderService, StatsService statsService) {
|
||||
this.userService = userService;
|
||||
this.carService = carService;
|
||||
this.orderService = orderService;
|
||||
this.statsService = statsService;
|
||||
}
|
||||
|
||||
private void checkAdmin() {
|
||||
if (!StpUtil.hasRole("ADMIN")) {
|
||||
throw new ApiException(ErrorCode.FORBIDDEN, "需要管理员权限");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/users")
|
||||
public ApiResponse<List<User>> listUsers(@RequestParam(required = false) String keyword) {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(userService.listUsers(keyword));
|
||||
}
|
||||
|
||||
@PostMapping("/users/{userId}/status")
|
||||
public ApiResponse<User> changeStatus(@PathVariable Long userId, @RequestParam String status) {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(userService.changeStatus(userId, status));
|
||||
}
|
||||
|
||||
@PostMapping("/real-name/{userId}/approve")
|
||||
public ApiResponse<User> approve(@PathVariable Long userId) {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(userService.reviewRealName(userId, true));
|
||||
}
|
||||
|
||||
@PostMapping("/real-name/{userId}/reject")
|
||||
public ApiResponse<User> reject(@PathVariable Long userId) {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(userService.reviewRealName(userId, false));
|
||||
}
|
||||
|
||||
@GetMapping("/cars")
|
||||
public ApiResponse<List<Car>> listCars(@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) Boolean isSpecial) {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(carService.list(keyword, isSpecial));
|
||||
}
|
||||
|
||||
@PostMapping("/cars")
|
||||
public ApiResponse<Car> createCar(@RequestBody Car car) {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(carService.create(car));
|
||||
}
|
||||
|
||||
@PutMapping("/cars")
|
||||
public ApiResponse<Car> updateCar(@RequestBody Car car) {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(carService.update(car));
|
||||
}
|
||||
|
||||
@DeleteMapping("/cars/{id}")
|
||||
public ApiResponse<Void> deleteCar(@PathVariable Long id) {
|
||||
checkAdmin();
|
||||
carService.delete(id);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/orders")
|
||||
public ApiResponse<List<Order>> listOrders(@RequestParam(required = false) String status) {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(orderService.listAll(status));
|
||||
}
|
||||
|
||||
@GetMapping("/stats/orders")
|
||||
public ApiResponse<Map<String, Object>> orderStats() {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(statsService.orderStats());
|
||||
}
|
||||
|
||||
@GetMapping("/stats/finance")
|
||||
public ApiResponse<Map<String, Object>> financeStats() {
|
||||
checkAdmin();
|
||||
return ApiResponse.ok(statsService.financeStats());
|
||||
}
|
||||
}
|
||||
44
backend/src/main/java/com/car/controller/AuthController.java
Normal file
44
backend/src/main/java/com/car/controller/AuthController.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.car.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.car.common.ApiResponse;
|
||||
import com.car.dto.LoginRequest;
|
||||
import com.car.dto.RegisterRequest;
|
||||
import com.car.entity.User;
|
||||
import com.car.service.UserService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
private final UserService userService;
|
||||
|
||||
public AuthController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public ApiResponse<User> register(@Valid @RequestBody RegisterRequest request) {
|
||||
return ApiResponse.ok(userService.register(request));
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ApiResponse<Map<String, Object>> login(@Valid @RequestBody LoginRequest request) {
|
||||
User user = userService.login(request);
|
||||
StpUtil.login(user.getId());
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("token", StpUtil.getTokenValue());
|
||||
data.put("role", user.getRole());
|
||||
return ApiResponse.ok(data);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ApiResponse<Void> logout() {
|
||||
StpUtil.logout();
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
}
|
||||
29
backend/src/main/java/com/car/controller/CarController.java
Normal file
29
backend/src/main/java/com/car/controller/CarController.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package com.car.controller;
|
||||
|
||||
import com.car.common.ApiResponse;
|
||||
import com.car.entity.Car;
|
||||
import com.car.service.CarService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/cars")
|
||||
public class CarController {
|
||||
private final CarService carService;
|
||||
|
||||
public CarController(CarService carService) {
|
||||
this.carService = carService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Car>> list(@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) Boolean isSpecial) {
|
||||
return ApiResponse.ok(carService.list(keyword, isSpecial));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Car> detail(@PathVariable Long id) {
|
||||
return ApiResponse.ok(carService.get(id));
|
||||
}
|
||||
}
|
||||
109
backend/src/main/java/com/car/controller/UserController.java
Normal file
109
backend/src/main/java/com/car/controller/UserController.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package com.car.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.car.common.ApiResponse;
|
||||
import com.car.dto.BalanceRequest;
|
||||
import com.car.dto.OrderCreateRequest;
|
||||
import com.car.dto.PayRequest;
|
||||
import com.car.dto.RealNameRequest;
|
||||
import com.car.entity.Favorite;
|
||||
import com.car.entity.Order;
|
||||
import com.car.entity.User;
|
||||
import com.car.service.FavoriteService;
|
||||
import com.car.service.OrderService;
|
||||
import com.car.service.UserService;
|
||||
import com.car.util.AuthUtil;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserController {
|
||||
private final UserService userService;
|
||||
private final FavoriteService favoriteService;
|
||||
private final OrderService orderService;
|
||||
|
||||
public UserController(UserService userService, FavoriteService favoriteService, OrderService orderService) {
|
||||
this.userService = userService;
|
||||
this.favoriteService = favoriteService;
|
||||
this.orderService = orderService;
|
||||
}
|
||||
|
||||
@GetMapping("/me")
|
||||
public ApiResponse<User> me() {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
User user = userService.getById(userId);
|
||||
if (user != null) {
|
||||
user.setPassword(null);
|
||||
}
|
||||
return ApiResponse.ok(user);
|
||||
}
|
||||
|
||||
@PutMapping("/profile")
|
||||
public ApiResponse<User> updateProfile(@RequestBody User user) {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
user.setId(userId);
|
||||
return ApiResponse.ok(userService.updateProfile(user));
|
||||
}
|
||||
|
||||
@PostMapping("/real-name")
|
||||
public ApiResponse<User> realName(@Valid @RequestBody RealNameRequest request) {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
return ApiResponse.ok(userService.submitRealName(userId, request));
|
||||
}
|
||||
|
||||
@PostMapping("/balance")
|
||||
public ApiResponse<User> addBalance(@Valid @RequestBody BalanceRequest request) {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
return ApiResponse.ok(userService.addBalance(userId, request));
|
||||
}
|
||||
|
||||
@PostMapping("/favorite/{carId}")
|
||||
public ApiResponse<Void> addFavorite(@PathVariable Long carId) {
|
||||
favoriteService.add(AuthUtil.getUserId(), carId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@DeleteMapping("/favorite/{carId}")
|
||||
public ApiResponse<Void> removeFavorite(@PathVariable Long carId) {
|
||||
favoriteService.remove(AuthUtil.getUserId(), carId);
|
||||
return ApiResponse.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/favorite")
|
||||
public ApiResponse<List<Favorite>> listFavorites() {
|
||||
return ApiResponse.ok(favoriteService.list(AuthUtil.getUserId()));
|
||||
}
|
||||
|
||||
@PostMapping("/order")
|
||||
public ApiResponse<Order> createOrder(@Valid @RequestBody OrderCreateRequest request) {
|
||||
return ApiResponse.ok(orderService.create(AuthUtil.getUserId(), request));
|
||||
}
|
||||
|
||||
@PostMapping("/order/pay")
|
||||
public ApiResponse<Order> pay(@Valid @RequestBody PayRequest request) {
|
||||
return ApiResponse.ok(orderService.pay(AuthUtil.getUserId(), request));
|
||||
}
|
||||
|
||||
@PostMapping("/order/{orderId}/cancel")
|
||||
public ApiResponse<Order> cancel(@PathVariable Long orderId) {
|
||||
return ApiResponse.ok(orderService.cancel(AuthUtil.getUserId(), orderId));
|
||||
}
|
||||
|
||||
@PostMapping("/order/{orderId}/return")
|
||||
public ApiResponse<Order> returnCar(@PathVariable Long orderId) {
|
||||
return ApiResponse.ok(orderService.returnCar(AuthUtil.getUserId(), orderId));
|
||||
}
|
||||
|
||||
@GetMapping("/order")
|
||||
public ApiResponse<List<Order>> myOrders(@RequestParam(required = false) String status) {
|
||||
return ApiResponse.ok(orderService.listByUser(AuthUtil.getUserId(), status));
|
||||
}
|
||||
|
||||
@GetMapping("/check-login")
|
||||
public ApiResponse<Boolean> checkLogin() {
|
||||
return ApiResponse.ok(StpUtil.isLogin());
|
||||
}
|
||||
}
|
||||
10
backend/src/main/java/com/car/dto/BalanceRequest.java
Normal file
10
backend/src/main/java/com/car/dto/BalanceRequest.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.car.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class BalanceRequest {
|
||||
@NotNull(message = "金额不能为空")
|
||||
private Double amount;
|
||||
}
|
||||
12
backend/src/main/java/com/car/dto/LoginRequest.java
Normal file
12
backend/src/main/java/com/car/dto/LoginRequest.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.car.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
}
|
||||
16
backend/src/main/java/com/car/dto/OrderCreateRequest.java
Normal file
16
backend/src/main/java/com/car/dto/OrderCreateRequest.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.car.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
public class OrderCreateRequest {
|
||||
@NotNull(message = "车辆不能为空")
|
||||
private Long carId;
|
||||
@NotNull(message = "开始日期不能为空")
|
||||
private LocalDate startDate;
|
||||
@NotNull(message = "结束日期不能为空")
|
||||
private LocalDate endDate;
|
||||
}
|
||||
13
backend/src/main/java/com/car/dto/PayRequest.java
Normal file
13
backend/src/main/java/com/car/dto/PayRequest.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.car.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PayRequest {
|
||||
@NotNull(message = "订单不能为空")
|
||||
private Long orderId;
|
||||
@NotBlank(message = "支付方式不能为空")
|
||||
private String payType;
|
||||
}
|
||||
14
backend/src/main/java/com/car/dto/RealNameRequest.java
Normal file
14
backend/src/main/java/com/car/dto/RealNameRequest.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.car.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RealNameRequest {
|
||||
@NotBlank(message = "真实姓名不能为空")
|
||||
private String realName;
|
||||
@NotBlank(message = "身份证号不能为空")
|
||||
private String idNumber;
|
||||
private String idFront;
|
||||
private String idBack;
|
||||
}
|
||||
14
backend/src/main/java/com/car/dto/RegisterRequest.java
Normal file
14
backend/src/main/java/com/car/dto/RegisterRequest.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.car.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RegisterRequest {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
private String phone;
|
||||
private String email;
|
||||
}
|
||||
25
backend/src/main/java/com/car/entity/Car.java
Normal file
25
backend/src/main/java/com/car/entity/Car.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.car.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Car {
|
||||
private Long id;
|
||||
private String brand;
|
||||
private String model;
|
||||
private String plateNo;
|
||||
private Double pricePerDay;
|
||||
private Double deposit;
|
||||
private String status;
|
||||
private Boolean isSpecial;
|
||||
private String images;
|
||||
private String description;
|
||||
private Integer seats;
|
||||
private String transmission;
|
||||
private String fuelType;
|
||||
private Integer mileage;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
13
backend/src/main/java/com/car/entity/Favorite.java
Normal file
13
backend/src/main/java/com/car/entity/Favorite.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.car.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Favorite {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long carId;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
25
backend/src/main/java/com/car/entity/Order.java
Normal file
25
backend/src/main/java/com/car/entity/Order.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.car.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Order {
|
||||
private Long id;
|
||||
private String orderNo;
|
||||
private Long userId;
|
||||
private Long carId;
|
||||
private LocalDate startDate;
|
||||
private LocalDate endDate;
|
||||
private Integer days;
|
||||
private Double pricePerDay;
|
||||
private Double deposit;
|
||||
private Double totalAmount;
|
||||
private String status;
|
||||
private String payType;
|
||||
private LocalDateTime paidAt;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
15
backend/src/main/java/com/car/entity/Payment.java
Normal file
15
backend/src/main/java/com/car/entity/Payment.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.car.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Payment {
|
||||
private Long id;
|
||||
private Long orderId;
|
||||
private Long userId;
|
||||
private Double amount;
|
||||
private String type;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
24
backend/src/main/java/com/car/entity/User.java
Normal file
24
backend/src/main/java/com/car/entity/User.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.car.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class User {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String password;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String role;
|
||||
private String status;
|
||||
private Double balance;
|
||||
private String realNameStatus;
|
||||
private String realName;
|
||||
private String idNumber;
|
||||
private String idFront;
|
||||
private String idBack;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
20
backend/src/main/java/com/car/mapper/CarMapper.java
Normal file
20
backend/src/main/java/com/car/mapper/CarMapper.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.car.mapper;
|
||||
|
||||
import com.car.entity.Car;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface CarMapper {
|
||||
Car findById(@Param("id") Long id);
|
||||
|
||||
List<Car> findAll(@Param("keyword") String keyword, @Param("isSpecial") Boolean isSpecial);
|
||||
|
||||
int insert(Car car);
|
||||
|
||||
int update(Car car);
|
||||
|
||||
int delete(@Param("id") Long id);
|
||||
}
|
||||
18
backend/src/main/java/com/car/mapper/FavoriteMapper.java
Normal file
18
backend/src/main/java/com/car/mapper/FavoriteMapper.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.car.mapper;
|
||||
|
||||
import com.car.entity.Favorite;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface FavoriteMapper {
|
||||
Favorite findByUserAndCar(@Param("userId") Long userId, @Param("carId") Long carId);
|
||||
|
||||
int insert(Favorite favorite);
|
||||
|
||||
int delete(@Param("id") Long id);
|
||||
|
||||
List<Favorite> findByUserId(@Param("userId") Long userId);
|
||||
}
|
||||
27
backend/src/main/java/com/car/mapper/OrderMapper.java
Normal file
27
backend/src/main/java/com/car/mapper/OrderMapper.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package com.car.mapper;
|
||||
|
||||
import com.car.entity.Order;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OrderMapper {
|
||||
Order findById(@Param("id") Long id);
|
||||
|
||||
Order findByOrderNo(@Param("orderNo") String orderNo);
|
||||
|
||||
int insert(Order order);
|
||||
|
||||
int update(Order order);
|
||||
|
||||
List<Order> findByUserId(@Param("userId") Long userId, @Param("status") String status);
|
||||
|
||||
List<Order> findAll(@Param("status") String status);
|
||||
|
||||
int countOverlap(@Param("carId") Long carId,
|
||||
@Param("startDate") LocalDate startDate,
|
||||
@Param("endDate") LocalDate endDate);
|
||||
}
|
||||
14
backend/src/main/java/com/car/mapper/PaymentMapper.java
Normal file
14
backend/src/main/java/com/car/mapper/PaymentMapper.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.car.mapper;
|
||||
|
||||
import com.car.entity.Payment;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface PaymentMapper {
|
||||
int insert(Payment payment);
|
||||
|
||||
List<Payment> findByUserId(@Param("userId") Long userId);
|
||||
}
|
||||
13
backend/src/main/java/com/car/mapper/StatsMapper.java
Normal file
13
backend/src/main/java/com/car/mapper/StatsMapper.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.car.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface StatsMapper {
|
||||
Map<String, Object> orderStats(@Param("status") String status);
|
||||
|
||||
Map<String, Object> financeStats();
|
||||
}
|
||||
20
backend/src/main/java/com/car/mapper/UserMapper.java
Normal file
20
backend/src/main/java/com/car/mapper/UserMapper.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.car.mapper;
|
||||
|
||||
import com.car.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserMapper {
|
||||
User findByUsername(@Param("username") String username);
|
||||
|
||||
User findById(@Param("id") Long id);
|
||||
|
||||
int insert(User user);
|
||||
|
||||
int update(User user);
|
||||
|
||||
List<User> findAll(@Param("keyword") String keyword);
|
||||
}
|
||||
54
backend/src/main/java/com/car/service/CarService.java
Normal file
54
backend/src/main/java/com/car/service/CarService.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package com.car.service;
|
||||
|
||||
import com.car.common.ApiException;
|
||||
import com.car.common.ErrorCode;
|
||||
import com.car.entity.Car;
|
||||
import com.car.mapper.CarMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class CarService {
|
||||
private final CarMapper carMapper;
|
||||
|
||||
public CarService(CarMapper carMapper) {
|
||||
this.carMapper = carMapper;
|
||||
}
|
||||
|
||||
public List<Car> list(String keyword, Boolean isSpecial) {
|
||||
return carMapper.findAll(keyword, isSpecial);
|
||||
}
|
||||
|
||||
public Car get(Long id) {
|
||||
Car car = carMapper.findById(id);
|
||||
if (car == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "车辆不存在");
|
||||
}
|
||||
return car;
|
||||
}
|
||||
|
||||
public Car create(Car car) {
|
||||
if (car.getStatus() == null) {
|
||||
car.setStatus("AVAILABLE");
|
||||
}
|
||||
if (car.getIsSpecial() == null) {
|
||||
car.setIsSpecial(false);
|
||||
}
|
||||
carMapper.insert(car);
|
||||
return car;
|
||||
}
|
||||
|
||||
public Car update(Car car) {
|
||||
Car db = carMapper.findById(car.getId());
|
||||
if (db == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "车辆不存在");
|
||||
}
|
||||
carMapper.update(car);
|
||||
return carMapper.findById(car.getId());
|
||||
}
|
||||
|
||||
public void delete(Long id) {
|
||||
carMapper.delete(id);
|
||||
}
|
||||
}
|
||||
41
backend/src/main/java/com/car/service/FavoriteService.java
Normal file
41
backend/src/main/java/com/car/service/FavoriteService.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package com.car.service;
|
||||
|
||||
import com.car.common.ApiException;
|
||||
import com.car.common.ErrorCode;
|
||||
import com.car.entity.Favorite;
|
||||
import com.car.mapper.FavoriteMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class FavoriteService {
|
||||
private final FavoriteMapper favoriteMapper;
|
||||
|
||||
public FavoriteService(FavoriteMapper favoriteMapper) {
|
||||
this.favoriteMapper = favoriteMapper;
|
||||
}
|
||||
|
||||
public void add(Long userId, Long carId) {
|
||||
Favorite existing = favoriteMapper.findByUserAndCar(userId, carId);
|
||||
if (existing != null) {
|
||||
throw new ApiException(ErrorCode.CONFLICT, "已收藏");
|
||||
}
|
||||
Favorite favorite = new Favorite();
|
||||
favorite.setUserId(userId);
|
||||
favorite.setCarId(carId);
|
||||
favoriteMapper.insert(favorite);
|
||||
}
|
||||
|
||||
public void remove(Long userId, Long carId) {
|
||||
Favorite existing = favoriteMapper.findByUserAndCar(userId, carId);
|
||||
if (existing == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "收藏不存在");
|
||||
}
|
||||
favoriteMapper.delete(existing.getId());
|
||||
}
|
||||
|
||||
public List<Favorite> list(Long userId) {
|
||||
return favoriteMapper.findByUserId(userId);
|
||||
}
|
||||
}
|
||||
151
backend/src/main/java/com/car/service/OrderService.java
Normal file
151
backend/src/main/java/com/car/service/OrderService.java
Normal file
@@ -0,0 +1,151 @@
|
||||
package com.car.service;
|
||||
|
||||
import com.car.common.ApiException;
|
||||
import com.car.common.ErrorCode;
|
||||
import com.car.dto.OrderCreateRequest;
|
||||
import com.car.dto.PayRequest;
|
||||
import com.car.entity.Car;
|
||||
import com.car.entity.Order;
|
||||
import com.car.entity.Payment;
|
||||
import com.car.entity.User;
|
||||
import com.car.mapper.CarMapper;
|
||||
import com.car.mapper.OrderMapper;
|
||||
import com.car.mapper.PaymentMapper;
|
||||
import com.car.mapper.UserMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class OrderService {
|
||||
private final OrderMapper orderMapper;
|
||||
private final CarMapper carMapper;
|
||||
private final UserMapper userMapper;
|
||||
private final PaymentMapper paymentMapper;
|
||||
|
||||
public OrderService(OrderMapper orderMapper, CarMapper carMapper, UserMapper userMapper, PaymentMapper paymentMapper) {
|
||||
this.orderMapper = orderMapper;
|
||||
this.carMapper = carMapper;
|
||||
this.userMapper = userMapper;
|
||||
this.paymentMapper = paymentMapper;
|
||||
}
|
||||
|
||||
public Order create(Long userId, OrderCreateRequest request) {
|
||||
Car car = carMapper.findById(request.getCarId());
|
||||
if (car == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "车辆不存在");
|
||||
}
|
||||
if (!"AVAILABLE".equals(car.getStatus())) {
|
||||
throw new ApiException(ErrorCode.CONFLICT, "车辆不可租");
|
||||
}
|
||||
User user = userMapper.findById(userId);
|
||||
if (user == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在");
|
||||
}
|
||||
if (!"APPROVED".equals(user.getRealNameStatus())) {
|
||||
throw new ApiException(ErrorCode.FORBIDDEN, "请先完成实名认证审核");
|
||||
}
|
||||
LocalDate start = request.getStartDate();
|
||||
LocalDate end = request.getEndDate();
|
||||
if (end.isBefore(start)) {
|
||||
throw new ApiException(ErrorCode.BAD_REQUEST, "结束日期不能早于开始日期");
|
||||
}
|
||||
int overlap = orderMapper.countOverlap(request.getCarId(), start, end);
|
||||
if (overlap > 0) {
|
||||
throw new ApiException(ErrorCode.CONFLICT, "该日期已被预订");
|
||||
}
|
||||
long days = ChronoUnit.DAYS.between(start, end) + 1;
|
||||
Order order = new Order();
|
||||
order.setOrderNo("ORD" + UUID.randomUUID().toString().replace("-", ""));
|
||||
order.setUserId(userId);
|
||||
order.setCarId(request.getCarId());
|
||||
order.setStartDate(start);
|
||||
order.setEndDate(end);
|
||||
order.setDays((int) days);
|
||||
order.setPricePerDay(car.getPricePerDay());
|
||||
order.setDeposit(car.getDeposit());
|
||||
order.setTotalAmount(car.getPricePerDay() * days + car.getDeposit());
|
||||
order.setStatus("PENDING_PAY");
|
||||
orderMapper.insert(order);
|
||||
return orderMapper.findById(order.getId());
|
||||
}
|
||||
|
||||
public Order pay(Long userId, PayRequest request) {
|
||||
Order order = orderMapper.findById(request.getOrderId());
|
||||
if (order == null || !order.getUserId().equals(userId)) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "订单不存在");
|
||||
}
|
||||
if (!"PENDING_PAY".equals(order.getStatus())) {
|
||||
throw new ApiException(ErrorCode.CONFLICT, "订单无法支付");
|
||||
}
|
||||
if (!"BALANCE".equalsIgnoreCase(request.getPayType())) {
|
||||
throw new ApiException(ErrorCode.BAD_REQUEST, "仅支持余额支付");
|
||||
}
|
||||
User user = userMapper.findById(userId);
|
||||
if (user.getBalance() < order.getTotalAmount()) {
|
||||
throw new ApiException(ErrorCode.CONFLICT, "余额不足");
|
||||
}
|
||||
user.setBalance(user.getBalance() - order.getTotalAmount());
|
||||
userMapper.update(user);
|
||||
|
||||
Car car = carMapper.findById(order.getCarId());
|
||||
car.setStatus("RENTED");
|
||||
carMapper.update(car);
|
||||
|
||||
order.setStatus("RENTING");
|
||||
order.setPayType("BALANCE");
|
||||
order.setPaidAt(java.time.LocalDateTime.now());
|
||||
orderMapper.update(order);
|
||||
|
||||
Payment payment = new Payment();
|
||||
payment.setOrderId(order.getId());
|
||||
payment.setUserId(userId);
|
||||
payment.setAmount(order.getTotalAmount());
|
||||
payment.setType("BALANCE");
|
||||
paymentMapper.insert(payment);
|
||||
|
||||
return orderMapper.findById(order.getId());
|
||||
}
|
||||
|
||||
public Order cancel(Long userId, Long orderId) {
|
||||
Order order = orderMapper.findById(orderId);
|
||||
if (order == null || !order.getUserId().equals(userId)) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "订单不存在");
|
||||
}
|
||||
if (!"PENDING_PAY".equals(order.getStatus())) {
|
||||
throw new ApiException(ErrorCode.CONFLICT, "订单无法取消");
|
||||
}
|
||||
order.setStatus("CANCELLED");
|
||||
orderMapper.update(order);
|
||||
return orderMapper.findById(orderId);
|
||||
}
|
||||
|
||||
public Order returnCar(Long userId, Long orderId) {
|
||||
Order order = orderMapper.findById(orderId);
|
||||
if (order == null || !order.getUserId().equals(userId)) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "订单不存在");
|
||||
}
|
||||
if (!"RENTING".equals(order.getStatus())) {
|
||||
throw new ApiException(ErrorCode.CONFLICT, "订单无法归还");
|
||||
}
|
||||
order.setStatus("RETURNED");
|
||||
orderMapper.update(order);
|
||||
|
||||
Car car = carMapper.findById(order.getCarId());
|
||||
car.setStatus("AVAILABLE");
|
||||
carMapper.update(car);
|
||||
|
||||
return orderMapper.findById(orderId);
|
||||
}
|
||||
|
||||
public List<Order> listByUser(Long userId, String status) {
|
||||
return orderMapper.findByUserId(userId, status);
|
||||
}
|
||||
|
||||
public List<Order> listAll(String status) {
|
||||
return orderMapper.findAll(status);
|
||||
}
|
||||
}
|
||||
23
backend/src/main/java/com/car/service/StatsService.java
Normal file
23
backend/src/main/java/com/car/service/StatsService.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.car.service;
|
||||
|
||||
import com.car.mapper.StatsMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class StatsService {
|
||||
private final StatsMapper statsMapper;
|
||||
|
||||
public StatsService(StatsMapper statsMapper) {
|
||||
this.statsMapper = statsMapper;
|
||||
}
|
||||
|
||||
public Map<String, Object> orderStats() {
|
||||
return statsMapper.orderStats(null);
|
||||
}
|
||||
|
||||
public Map<String, Object> financeStats() {
|
||||
return statsMapper.financeStats();
|
||||
}
|
||||
}
|
||||
126
backend/src/main/java/com/car/service/UserService.java
Normal file
126
backend/src/main/java/com/car/service/UserService.java
Normal file
@@ -0,0 +1,126 @@
|
||||
package com.car.service;
|
||||
|
||||
import com.car.common.ApiException;
|
||||
import com.car.common.ErrorCode;
|
||||
import com.car.dto.BalanceRequest;
|
||||
import com.car.dto.LoginRequest;
|
||||
import com.car.dto.RealNameRequest;
|
||||
import com.car.dto.RegisterRequest;
|
||||
import com.car.entity.User;
|
||||
import com.car.mapper.UserMapper;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
private final UserMapper userMapper;
|
||||
private final BCryptPasswordEncoder encoder;
|
||||
|
||||
public UserService(UserMapper userMapper, BCryptPasswordEncoder encoder) {
|
||||
this.userMapper = userMapper;
|
||||
this.encoder = encoder;
|
||||
}
|
||||
|
||||
public User register(RegisterRequest request) {
|
||||
User existing = userMapper.findByUsername(request.getUsername());
|
||||
if (existing != null) {
|
||||
throw new ApiException(ErrorCode.CONFLICT, "用户名已存在");
|
||||
}
|
||||
User user = new User();
|
||||
user.setUsername(request.getUsername());
|
||||
user.setPassword(encoder.encode(request.getPassword()));
|
||||
user.setPhone(request.getPhone());
|
||||
user.setEmail(request.getEmail());
|
||||
user.setRole("USER");
|
||||
user.setStatus("ACTIVE");
|
||||
user.setBalance(0.0);
|
||||
user.setRealNameStatus("NONE");
|
||||
userMapper.insert(user);
|
||||
user.setPassword(null);
|
||||
return user;
|
||||
}
|
||||
|
||||
public User login(LoginRequest request) {
|
||||
User user = userMapper.findByUsername(request.getUsername());
|
||||
if (user == null || !encoder.matches(request.getPassword(), user.getPassword())) {
|
||||
throw new ApiException(ErrorCode.UNAUTHORIZED, "用户名或密码错误");
|
||||
}
|
||||
if (!"ACTIVE".equals(user.getStatus())) {
|
||||
throw new ApiException(ErrorCode.FORBIDDEN, "账号已禁用");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
public User getById(Long id) {
|
||||
return userMapper.findById(id);
|
||||
}
|
||||
|
||||
public User updateProfile(User input) {
|
||||
User db = userMapper.findById(input.getId());
|
||||
if (db == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在");
|
||||
}
|
||||
db.setPhone(input.getPhone());
|
||||
db.setEmail(input.getEmail());
|
||||
userMapper.update(db);
|
||||
db.setPassword(null);
|
||||
return db;
|
||||
}
|
||||
|
||||
public User submitRealName(Long userId, RealNameRequest request) {
|
||||
User db = userMapper.findById(userId);
|
||||
if (db == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在");
|
||||
}
|
||||
db.setRealName(request.getRealName());
|
||||
db.setIdNumber(request.getIdNumber());
|
||||
db.setIdFront(request.getIdFront());
|
||||
db.setIdBack(request.getIdBack());
|
||||
db.setRealNameStatus("PENDING");
|
||||
userMapper.update(db);
|
||||
db.setPassword(null);
|
||||
return db;
|
||||
}
|
||||
|
||||
public User reviewRealName(Long userId, boolean approved) {
|
||||
User db = userMapper.findById(userId);
|
||||
if (db == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在");
|
||||
}
|
||||
db.setRealNameStatus(approved ? "APPROVED" : "REJECTED");
|
||||
userMapper.update(db);
|
||||
db.setPassword(null);
|
||||
return db;
|
||||
}
|
||||
|
||||
public User changeStatus(Long userId, String status) {
|
||||
User db = userMapper.findById(userId);
|
||||
if (db == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在");
|
||||
}
|
||||
db.setStatus(status);
|
||||
userMapper.update(db);
|
||||
db.setPassword(null);
|
||||
return db;
|
||||
}
|
||||
|
||||
public User addBalance(Long userId, BalanceRequest request) {
|
||||
if (request.getAmount() <= 0) {
|
||||
throw new ApiException(ErrorCode.BAD_REQUEST, "金额必须大于0");
|
||||
}
|
||||
User db = userMapper.findById(userId);
|
||||
if (db == null) {
|
||||
throw new ApiException(ErrorCode.NOT_FOUND, "用户不存在");
|
||||
}
|
||||
db.setBalance(db.getBalance() + request.getAmount());
|
||||
userMapper.update(db);
|
||||
db.setPassword(null);
|
||||
return db;
|
||||
}
|
||||
|
||||
public List<User> listUsers(String keyword) {
|
||||
return userMapper.findAll(keyword);
|
||||
}
|
||||
}
|
||||
9
backend/src/main/java/com/car/util/AuthUtil.java
Normal file
9
backend/src/main/java/com/car/util/AuthUtil.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package com.car.util;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
public class AuthUtil {
|
||||
public static Long getUserId() {
|
||||
return Long.valueOf(StpUtil.getLoginId().toString());
|
||||
}
|
||||
}
|
||||
26
backend/src/main/resources/application.yml
Normal file
26
backend/src/main/resources/application.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/car_rental?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: root
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
time-zone: Asia/Shanghai
|
||||
|
||||
mybatis:
|
||||
mapper-locations: classpath:mappers/*.xml
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
|
||||
sa-token:
|
||||
token-name: Authorization
|
||||
token-style: jwt
|
||||
timeout: 86400
|
||||
active-timeout: -1
|
||||
is-concurrent: true
|
||||
is-share: true
|
||||
token-prefix: Bearer
|
||||
is-log: false
|
||||
71
backend/src/main/resources/mappers/CarMapper.xml
Normal file
71
backend/src/main/resources/mappers/CarMapper.xml
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.car.mapper.CarMapper">
|
||||
<resultMap id="CarMap" type="com.car.entity.Car">
|
||||
<id column="id" property="id"/>
|
||||
<result column="brand" property="brand"/>
|
||||
<result column="model" property="model"/>
|
||||
<result column="plate_no" property="plateNo"/>
|
||||
<result column="price_per_day" property="pricePerDay"/>
|
||||
<result column="deposit" property="deposit"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="is_special" property="isSpecial"/>
|
||||
<result column="images" property="images"/>
|
||||
<result column="description" property="description"/>
|
||||
<result column="seats" property="seats"/>
|
||||
<result column="transmission" property="transmission"/>
|
||||
<result column="fuel_type" property="fuelType"/>
|
||||
<result column="mileage" property="mileage"/>
|
||||
<result column="created_at" property="createdAt"/>
|
||||
<result column="updated_at" property="updatedAt"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="findById" resultMap="CarMap">
|
||||
select * from cars where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultMap="CarMap">
|
||||
select * from cars
|
||||
<where>
|
||||
<if test="keyword != null and keyword != ''">
|
||||
(brand like concat('%', #{keyword}, '%')
|
||||
or model like concat('%', #{keyword}, '%')
|
||||
or plate_no like concat('%', #{keyword}, '%'))
|
||||
</if>
|
||||
<if test="isSpecial != null">
|
||||
and is_special = #{isSpecial}
|
||||
</if>
|
||||
</where>
|
||||
order by id desc
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into cars(brand, model, plate_no, price_per_day, deposit, status, is_special, images, description, seats, transmission, fuel_type, mileage, created_at, updated_at)
|
||||
values (#{brand}, #{model}, #{plateNo}, #{pricePerDay}, #{deposit}, #{status}, #{isSpecial}, #{images}, #{description}, #{seats}, #{transmission}, #{fuelType}, #{mileage}, now(), now())
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
update cars
|
||||
set brand = #{brand},
|
||||
model = #{model},
|
||||
plate_no = #{plateNo},
|
||||
price_per_day = #{pricePerDay},
|
||||
deposit = #{deposit},
|
||||
status = #{status},
|
||||
is_special = #{isSpecial},
|
||||
images = #{images},
|
||||
description = #{description},
|
||||
seats = #{seats},
|
||||
transmission = #{transmission},
|
||||
fuel_type = #{fuelType},
|
||||
mileage = #{mileage},
|
||||
updated_at = now()
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="delete">
|
||||
delete from cars where id = #{id}
|
||||
</delete>
|
||||
</mapper>
|
||||
29
backend/src/main/resources/mappers/FavoriteMapper.xml
Normal file
29
backend/src/main/resources/mappers/FavoriteMapper.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.car.mapper.FavoriteMapper">
|
||||
<resultMap id="FavoriteMap" type="com.car.entity.Favorite">
|
||||
<id column="id" property="id"/>
|
||||
<result column="user_id" property="userId"/>
|
||||
<result column="car_id" property="carId"/>
|
||||
<result column="created_at" property="createdAt"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="findByUserAndCar" resultMap="FavoriteMap">
|
||||
select * from favorites where user_id = #{userId} and car_id = #{carId} limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into favorites(user_id, car_id, created_at)
|
||||
values (#{userId}, #{carId}, now())
|
||||
</insert>
|
||||
|
||||
<delete id="delete">
|
||||
delete from favorites where id = #{id}
|
||||
</delete>
|
||||
|
||||
<select id="findByUserId" resultMap="FavoriteMap">
|
||||
select * from favorites where user_id = #{userId} order by id desc
|
||||
</select>
|
||||
</mapper>
|
||||
81
backend/src/main/resources/mappers/OrderMapper.xml
Normal file
81
backend/src/main/resources/mappers/OrderMapper.xml
Normal file
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.car.mapper.OrderMapper">
|
||||
<resultMap id="OrderMap" type="com.car.entity.Order">
|
||||
<id column="id" property="id"/>
|
||||
<result column="order_no" property="orderNo"/>
|
||||
<result column="user_id" property="userId"/>
|
||||
<result column="car_id" property="carId"/>
|
||||
<result column="start_date" property="startDate"/>
|
||||
<result column="end_date" property="endDate"/>
|
||||
<result column="days" property="days"/>
|
||||
<result column="price_per_day" property="pricePerDay"/>
|
||||
<result column="deposit" property="deposit"/>
|
||||
<result column="total_amount" property="totalAmount"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="pay_type" property="payType"/>
|
||||
<result column="paid_at" property="paidAt"/>
|
||||
<result column="created_at" property="createdAt"/>
|
||||
<result column="updated_at" property="updatedAt"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="findById" resultMap="OrderMap">
|
||||
select * from orders where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="findByOrderNo" resultMap="OrderMap">
|
||||
select * from orders where order_no = #{orderNo}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into orders(order_no, user_id, car_id, start_date, end_date, days,
|
||||
price_per_day, deposit, total_amount, status, pay_type, paid_at, created_at, updated_at)
|
||||
values (#{orderNo}, #{userId}, #{carId}, #{startDate}, #{endDate}, #{days},
|
||||
#{pricePerDay}, #{deposit}, #{totalAmount}, #{status}, #{payType}, #{paidAt}, now(), now())
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
update orders
|
||||
set start_date = #{startDate},
|
||||
end_date = #{endDate},
|
||||
days = #{days},
|
||||
price_per_day = #{pricePerDay},
|
||||
deposit = #{deposit},
|
||||
total_amount = #{totalAmount},
|
||||
status = #{status},
|
||||
pay_type = #{payType},
|
||||
paid_at = #{paidAt},
|
||||
updated_at = now()
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="findByUserId" resultMap="OrderMap">
|
||||
select * from orders
|
||||
<where>
|
||||
user_id = #{userId}
|
||||
<if test="status != null and status != ''">
|
||||
and status = #{status}
|
||||
</if>
|
||||
</where>
|
||||
order by id desc
|
||||
</select>
|
||||
|
||||
<select id="findAll" resultMap="OrderMap">
|
||||
select * from orders
|
||||
<where>
|
||||
<if test="status != null and status != ''">
|
||||
status = #{status}
|
||||
</if>
|
||||
</where>
|
||||
order by id desc
|
||||
</select>
|
||||
|
||||
<select id="countOverlap" resultType="int">
|
||||
select count(1) from orders
|
||||
where car_id = #{carId}
|
||||
and status in ('PAID', 'RENTING')
|
||||
and not (end_date < #{startDate} or start_date > #{endDate})
|
||||
</select>
|
||||
</mapper>
|
||||
23
backend/src/main/resources/mappers/PaymentMapper.xml
Normal file
23
backend/src/main/resources/mappers/PaymentMapper.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.car.mapper.PaymentMapper">
|
||||
<resultMap id="PaymentMap" type="com.car.entity.Payment">
|
||||
<id column="id" property="id"/>
|
||||
<result column="order_id" property="orderId"/>
|
||||
<result column="user_id" property="userId"/>
|
||||
<result column="amount" property="amount"/>
|
||||
<result column="type" property="type"/>
|
||||
<result column="created_at" property="createdAt"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into payments(order_id, user_id, amount, type, created_at)
|
||||
values (#{orderId}, #{userId}, #{amount}, #{type}, now())
|
||||
</insert>
|
||||
|
||||
<select id="findByUserId" resultMap="PaymentMap">
|
||||
select * from payments where user_id = #{userId} order by id desc
|
||||
</select>
|
||||
</mapper>
|
||||
21
backend/src/main/resources/mappers/StatsMapper.xml
Normal file
21
backend/src/main/resources/mappers/StatsMapper.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.car.mapper.StatsMapper">
|
||||
<select id="orderStats" resultType="map">
|
||||
select count(1) as total,
|
||||
sum(case when status = 'RENTING' then 1 else 0 end) as renting,
|
||||
sum(case when status = 'RETURNED' then 1 else 0 end) as returned,
|
||||
sum(case when status = 'PENDING_PAY' then 1 else 0 end) as pendingPay
|
||||
from orders
|
||||
</select>
|
||||
|
||||
<select id="financeStats" resultType="map">
|
||||
select ifnull(sum(total_amount), 0) as income,
|
||||
ifnull(sum(deposit), 0) as deposit,
|
||||
count(1) as orderCount
|
||||
from orders
|
||||
where status in ('PAID', 'RENTING', 'RETURNED')
|
||||
</select>
|
||||
</mapper>
|
||||
65
backend/src/main/resources/mappers/UserMapper.xml
Normal file
65
backend/src/main/resources/mappers/UserMapper.xml
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.car.mapper.UserMapper">
|
||||
<resultMap id="UserMap" type="com.car.entity.User">
|
||||
<id column="id" property="id"/>
|
||||
<result column="username" property="username"/>
|
||||
<result column="password" property="password"/>
|
||||
<result column="phone" property="phone"/>
|
||||
<result column="email" property="email"/>
|
||||
<result column="role" property="role"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="balance" property="balance"/>
|
||||
<result column="real_name_status" property="realNameStatus"/>
|
||||
<result column="real_name" property="realName"/>
|
||||
<result column="id_number" property="idNumber"/>
|
||||
<result column="id_front" property="idFront"/>
|
||||
<result column="id_back" property="idBack"/>
|
||||
<result column="created_at" property="createdAt"/>
|
||||
<result column="updated_at" property="updatedAt"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="findByUsername" resultMap="UserMap">
|
||||
select * from users where username = #{username} limit 1
|
||||
</select>
|
||||
|
||||
<select id="findById" resultMap="UserMap">
|
||||
select * from users where id = #{id}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into users(username, password, phone, email, role, status, balance, real_name_status, created_at, updated_at)
|
||||
values (#{username}, #{password}, #{phone}, #{email}, #{role}, #{status}, #{balance}, #{realNameStatus}, now(), now())
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
update users
|
||||
set password = #{password},
|
||||
phone = #{phone},
|
||||
email = #{email},
|
||||
role = #{role},
|
||||
status = #{status},
|
||||
balance = #{balance},
|
||||
real_name_status = #{realNameStatus},
|
||||
real_name = #{realName},
|
||||
id_number = #{idNumber},
|
||||
id_front = #{idFront},
|
||||
id_back = #{idBack},
|
||||
updated_at = now()
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="findAll" resultMap="UserMap">
|
||||
select * from users
|
||||
<where>
|
||||
<if test="keyword != null and keyword != ''">
|
||||
(username like concat('%', #{keyword}, '%')
|
||||
or phone like concat('%', #{keyword}, '%')
|
||||
or email like concat('%', #{keyword}, '%'))
|
||||
</if>
|
||||
</where>
|
||||
order by id desc
|
||||
</select>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user