Initial commit

This commit is contained in:
wangziqi
2026-02-09 09:51:14 -08:00
commit a7ce0a089e
104 changed files with 6470 additions and 0 deletions

59
backend/pom.xml Normal file
View File

@@ -0,0 +1,59 @@
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.maternalmall</groupId>
<artifactId>backend</artifactId>
<version>1.0.0</version>
<name>maternal-mall-backend</name>
<description>Maternal mall backend</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,11 @@
package com.maternalmall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MaternalMallApplication {
public static void main(String[] args) {
SpringApplication.run(MaternalMallApplication.class, args);
}
}

View File

@@ -0,0 +1,26 @@
package com.maternalmall.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> fail(String message) {
return new ApiResponse<>(-1, message, null);
}
}

View File

@@ -0,0 +1,7 @@
package com.maternalmall.common;
public class BizException extends RuntimeException {
public BizException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,32 @@
package com.maternalmall.common;
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(BizException.class)
public ApiResponse<Void> handleBiz(BizException ex) {
return ApiResponse.fail(ex.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse<Void> handleValid(MethodArgumentNotValidException ex) {
String msg = ex.getBindingResult().getFieldErrors().stream().findFirst()
.map(e -> e.getField() + ":" + e.getDefaultMessage()).orElse("参数错误");
return ApiResponse.fail(msg);
}
@ExceptionHandler(ConstraintViolationException.class)
public ApiResponse<Void> handleConstraint(ConstraintViolationException ex) {
return ApiResponse.fail(ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ApiResponse<Void> handleUnknown(Exception ex) {
return ApiResponse.fail(ex.getMessage());
}
}

View File

@@ -0,0 +1,11 @@
package com.maternalmall.config;
import com.maternalmall.domain.User;
public class AuthContext {
public static final String CURRENT_USER = "CURRENT_USER";
public static User getUser(jakarta.servlet.http.HttpServletRequest request) {
return (User) request.getAttribute(CURRENT_USER);
}
}

View File

@@ -0,0 +1,58 @@
package com.maternalmall.config;
import com.maternalmall.domain.User;
import com.maternalmall.repository.UserRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Set;
@Component
public class AuthInterceptor implements HandlerInterceptor {
private final UserRepository userRepository;
@Value("${app.token-header:X-Token}")
private String tokenHeader;
private static final Set<String> PUBLIC_PREFIX = Set.of(
"/api/auth/login",
"/api/auth/register",
"/api/public"
);
public AuthInterceptor(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Let browser CORS preflight pass without auth token.
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
String uri = request.getRequestURI();
if (uri.equals("/") || uri.startsWith("/error") || PUBLIC_PREFIX.stream().anyMatch(uri::startsWith)) {
return true;
}
String token = request.getHeader(tokenHeader);
if (token == null || token.isBlank()) {
response.setStatus(401);
response.getWriter().write("{\"code\":401,\"message\":\"请先登录\",\"data\":null}");
return false;
}
User user = userRepository.findByToken(token).orElse(null);
if (user == null || !Boolean.TRUE.equals(user.getEnabled())) {
response.setStatus(401);
response.getWriter().write("{\"code\":401,\"message\":\"登录状态无效\",\"data\":null}");
return false;
}
request.setAttribute(AuthContext.CURRENT_USER, user);
return true;
}
}

View File

@@ -0,0 +1,25 @@
package com.maternalmall.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
public WebMvcConfig(AuthInterceptor authInterceptor) {
this.authInterceptor = authInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor).addPathPatterns("/**");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("*").allowedHeaders("*");
}
}

View File

@@ -0,0 +1,165 @@
package com.maternalmall.controller;
import com.maternalmall.common.ApiResponse;
import com.maternalmall.config.AuthContext;
import com.maternalmall.domain.*;
import com.maternalmall.service.MallService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/admin")
public class AdminController {
private final MallService mallService;
public AdminController(MallService mallService) {
this.mallService = mallService;
}
private void check(User user) {
mallService.requireRole(user, UserRole.ADMIN);
}
@GetMapping("/overview")
public ApiResponse<Map<String, Object>> overview(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminOverview());
}
@GetMapping("/orders")
public ApiResponse<List<Orders>> orders(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminOrders());
}
@PutMapping("/orders/{id}")
public ApiResponse<Orders> updateOrder(HttpServletRequest request, @PathVariable Long id, @RequestBody Map<String, String> body) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminUpdateOrder(id, body.get("status"), body.getOrDefault("logisticsInfo", "")));
}
@GetMapping("/orders/risk")
public ApiResponse<List<Map<String, Object>>> riskOrders(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminRiskOrders());
}
@PutMapping("/orders/{id}/refund-audit")
public ApiResponse<Orders> auditRefund(HttpServletRequest request, @PathVariable Long id, @RequestBody Map<String, Object> body) {
check(AuthContext.getUser(request));
boolean approve = Boolean.parseBoolean(String.valueOf(body.getOrDefault("approve", false)));
String remark = String.valueOf(body.getOrDefault("remark", ""));
return ApiResponse.ok(mallService.adminAuditRefund(id, approve, remark));
}
@PutMapping("/orders/{id}/ship-audit")
public ApiResponse<Orders> auditShipment(HttpServletRequest request, @PathVariable Long id, @RequestBody Map<String, Object> body) {
check(AuthContext.getUser(request));
boolean approve = Boolean.parseBoolean(String.valueOf(body.getOrDefault("approve", true)));
String remark = String.valueOf(body.getOrDefault("remark", ""));
return ApiResponse.ok(mallService.adminAuditShipment(id, approve, remark));
}
@GetMapping("/merchant-applications")
public ApiResponse<List<Map<String, Object>>> merchantApplications(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminMerchantApplications());
}
@PutMapping("/merchant-applications/{id}")
public ApiResponse<MerchantApplication> auditMerchant(HttpServletRequest request, @PathVariable Long id, @RequestBody Map<String, String> body) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminAuditApplication(id, body.get("status"), body.getOrDefault("remark", "")));
}
@GetMapping("/users")
public ApiResponse<List<User>> users(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminUsers());
}
@PostMapping("/users")
public ApiResponse<User> saveUser(HttpServletRequest request, @RequestBody User user) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminSaveUser(user));
}
@DeleteMapping("/users/{id}")
public ApiResponse<Void> deleteUser(HttpServletRequest request, @PathVariable Long id) {
check(AuthContext.getUser(request));
mallService.adminDeleteUser(id);
return ApiResponse.ok();
}
@GetMapping("/banners")
public ApiResponse<List<Banner>> banners(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminBanners());
}
@PostMapping("/banners")
public ApiResponse<Banner> saveBanner(HttpServletRequest request, @RequestBody Banner banner) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminSaveBanner(banner));
}
@DeleteMapping("/banners/{id}")
public ApiResponse<Void> deleteBanner(HttpServletRequest request, @PathVariable Long id) {
check(AuthContext.getUser(request));
mallService.adminDeleteBanner(id);
return ApiResponse.ok();
}
@GetMapping("/products")
public ApiResponse<List<Product>> products(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminProducts());
}
@GetMapping("/products/views")
public ApiResponse<List<Map<String, Object>>> productViews(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminProductViews());
}
@PostMapping("/products")
public ApiResponse<Product> saveProduct(HttpServletRequest request, @RequestBody Product product) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminSaveProduct(product));
}
@PutMapping("/products/{id}/approve")
public ApiResponse<Product> approveProduct(HttpServletRequest request, @PathVariable Long id, @RequestBody Map<String, Object> body) {
check(AuthContext.getUser(request));
boolean approved = Boolean.parseBoolean(String.valueOf(body.getOrDefault("approved", true)));
return ApiResponse.ok(mallService.adminApproveProduct(id, approved));
}
@DeleteMapping("/products/{id}")
public ApiResponse<Void> deleteProduct(HttpServletRequest request, @PathVariable Long id) {
check(AuthContext.getUser(request));
mallService.adminDeleteProduct(id);
return ApiResponse.ok();
}
@GetMapping("/reviews")
public ApiResponse<List<Map<String, Object>>> reviews(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminReviews());
}
@GetMapping("/logistics")
public ApiResponse<List<Map<String, Object>>> logistics(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminLogistics());
}
@GetMapping("/inventory")
public ApiResponse<List<Map<String, Object>>> inventory(HttpServletRequest request) {
check(AuthContext.getUser(request));
return ApiResponse.ok(mallService.adminInventory());
}
}

View File

@@ -0,0 +1,47 @@
package com.maternalmall.controller;
import com.maternalmall.common.ApiResponse;
import com.maternalmall.config.AuthContext;
import com.maternalmall.domain.User;
import com.maternalmall.repository.UserRepository;
import com.maternalmall.service.AuthService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthService authService;
private final UserRepository userRepository;
public AuthController(AuthService authService, UserRepository userRepository) {
this.authService = authService;
this.userRepository = userRepository;
}
@PostMapping("/register")
public ApiResponse<Map<String, Object>> register(@RequestBody Map<String, String> body) {
return ApiResponse.ok(authService.register(body.get("username"), body.get("password"), body.getOrDefault("role", "CUSTOMER")));
}
@PostMapping("/login")
public ApiResponse<Map<String, Object>> login(@RequestBody Map<String, String> body) {
return ApiResponse.ok(authService.login(body.get("username"), body.get("password")));
}
@GetMapping("/me")
public ApiResponse<User> me(HttpServletRequest request) {
return ApiResponse.ok(AuthContext.getUser(request));
}
@PutMapping("/me")
public ApiResponse<User> updateMe(HttpServletRequest request, @RequestBody Map<String, String> body) {
User user = AuthContext.getUser(request);
user.setNickname(body.getOrDefault("nickname", user.getNickname()));
user.setPhone(body.getOrDefault("phone", user.getPhone()));
user.setAddress(body.getOrDefault("address", user.getAddress()));
return ApiResponse.ok(userRepository.save(user));
}
}

View File

@@ -0,0 +1,157 @@
package com.maternalmall.controller;
import com.maternalmall.common.ApiResponse;
import com.maternalmall.config.AuthContext;
import com.maternalmall.domain.*;
import com.maternalmall.service.MallService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/customer")
public class CustomerController {
private final MallService mallService;
public CustomerController(MallService mallService) {
this.mallService = mallService;
}
@GetMapping("/cart")
public ApiResponse<List<CartItem>> cart(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.cartList(user.getId()));
}
@GetMapping("/cart/views")
public ApiResponse<List<Map<String, Object>>> cartViews(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.cartViews(user.getId()));
}
@PostMapping("/cart")
public ApiResponse<CartItem> addCart(HttpServletRequest request, @RequestBody Map<String, Object> body) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.addCart(user.getId(), Long.valueOf(body.get("productId").toString()), Integer.valueOf(body.get("quantity").toString())));
}
@DeleteMapping("/cart/{productId}")
public ApiResponse<Void> removeCart(HttpServletRequest request, @PathVariable Long productId) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
mallService.removeCart(user.getId(), productId);
return ApiResponse.ok();
}
@GetMapping("/favorites")
public ApiResponse<List<Favorite>> favorites(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.favoriteList(user.getId()));
}
@GetMapping("/favorites/views")
public ApiResponse<List<Map<String, Object>>> favoriteViews(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.favoriteViews(user.getId()));
}
@PostMapping("/favorites")
public ApiResponse<Favorite> addFavorite(HttpServletRequest request, @RequestBody Map<String, Object> body) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.addFavorite(user.getId(), Long.valueOf(body.get("productId").toString())));
}
@DeleteMapping("/favorites/{productId}")
public ApiResponse<Void> removeFavorite(HttpServletRequest request, @PathVariable Long productId) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
mallService.removeFavorite(user.getId(), productId);
return ApiResponse.ok();
}
@PostMapping("/orders/checkout")
public ApiResponse<Orders> checkout(HttpServletRequest request, @RequestBody Map<String, String> body) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.checkout(user.getId(), body.get("address")));
}
@PostMapping("/orders/buy-now")
public ApiResponse<Orders> buyNow(HttpServletRequest request, @RequestBody Map<String, Object> body) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
Long productId = Long.valueOf(body.get("productId").toString());
Integer quantity = Integer.valueOf(String.valueOf(body.getOrDefault("quantity", 1)));
String address = String.valueOf(body.getOrDefault("address", ""));
return ApiResponse.ok(mallService.buyNow(user.getId(), productId, quantity, address));
}
@GetMapping("/orders")
public ApiResponse<List<Orders>> orders(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.customerOrders(user.getId()));
}
@GetMapping("/orders/{orderId}/items")
public ApiResponse<List<OrderItem>> orderItems(HttpServletRequest request, @PathVariable Long orderId) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.customerOrderItems(user.getId(), orderId));
}
@PutMapping("/orders/{orderId}/address")
public ApiResponse<Orders> updateAddress(HttpServletRequest request, @PathVariable Long orderId, @RequestBody Map<String, String> body) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.customerUpdateAddress(user.getId(), orderId, body.get("address")));
}
@PutMapping("/orders/{orderId}/refund")
public ApiResponse<Orders> refund(HttpServletRequest request, @PathVariable Long orderId, @RequestBody Map<String, String> body) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.customerRefund(user.getId(), orderId, body.get("reason")));
}
@DeleteMapping("/orders/{orderId}")
public ApiResponse<Void> deleteOrder(HttpServletRequest request, @PathVariable Long orderId) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
mallService.customerDeleteOrder(user.getId(), orderId);
return ApiResponse.ok();
}
@GetMapping("/orders/{orderId}/logistics")
public ApiResponse<List<LogisticsRecord>> logistics(HttpServletRequest request, @PathVariable Long orderId) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.logisticsOfOrder(user.getId(), orderId));
}
@PostMapping("/reviews")
public ApiResponse<Review> review(HttpServletRequest request, @RequestBody Map<String, Object> body) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.CUSTOMER);
return ApiResponse.ok(mallService.addReview(
user.getId(),
Long.valueOf(body.get("orderId").toString()),
Long.valueOf(body.get("productId").toString()),
Integer.valueOf(body.get("rating").toString()),
body.get("content").toString()));
}
@PostMapping("/merchant-applications")
public ApiResponse<MerchantApplication> applyMerchant(HttpServletRequest request, @RequestBody Map<String, String> body) {
User user = AuthContext.getUser(request);
return ApiResponse.ok(mallService.applyMerchant(user.getId(), body.getOrDefault("qualification", "")));
}
}

View File

@@ -0,0 +1,100 @@
package com.maternalmall.controller;
import com.maternalmall.common.ApiResponse;
import com.maternalmall.config.AuthContext;
import com.maternalmall.domain.*;
import com.maternalmall.service.MallService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/merchant")
public class MerchantController {
private final MallService mallService;
public MerchantController(MallService mallService) {
this.mallService = mallService;
}
@GetMapping("/overview")
public ApiResponse<Map<String, Object>> overview(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
return ApiResponse.ok(mallService.merchantOverview(user.getId()));
}
@GetMapping("/products")
public ApiResponse<List<Product>> products(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
return ApiResponse.ok(mallService.merchantProducts(user.getId()));
}
@PostMapping("/products")
public ApiResponse<Product> saveProduct(HttpServletRequest request, @RequestBody Product product) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
return ApiResponse.ok(mallService.merchantSaveProduct(user.getId(), product));
}
@DeleteMapping("/products/{id}")
public ApiResponse<Void> deleteProduct(HttpServletRequest request, @PathVariable Long id) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
mallService.merchantDeleteProduct(user.getId(), id);
return ApiResponse.ok();
}
@GetMapping("/orders")
public ApiResponse<List<Orders>> orders(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
return ApiResponse.ok(mallService.merchantOrders(user.getId()));
}
@PutMapping("/orders/{id}/ship")
public ApiResponse<Orders> ship(HttpServletRequest request, @PathVariable Long id, @RequestBody Map<String, String> body) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
return ApiResponse.ok(mallService.merchantShip(user.getId(), id, body.getOrDefault("note", "已发货")));
}
@PutMapping("/orders/{id}/refund")
public ApiResponse<Orders> refund(HttpServletRequest request, @PathVariable Long id, @RequestBody Map<String, Object> body) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
return ApiResponse.ok(mallService.merchantProcessRefund(user.getId(), id, Boolean.parseBoolean(body.get("agree").toString())));
}
@GetMapping("/reviews")
public ApiResponse<List<Map<String, Object>>> reviews(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
return ApiResponse.ok(mallService.merchantReviews(user.getId()));
}
@GetMapping("/logistics")
public ApiResponse<List<Map<String, Object>>> logistics(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
return ApiResponse.ok(mallService.merchantLogistics(user.getId()));
}
@GetMapping("/inventory")
public ApiResponse<List<Map<String, Object>>> inventory(HttpServletRequest request) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
return ApiResponse.ok(mallService.merchantInventory(user.getId()));
}
@DeleteMapping("/inventory/{id}")
public ApiResponse<Void> deleteInventory(HttpServletRequest request, @PathVariable Long id) {
User user = AuthContext.getUser(request);
mallService.requireRole(user, UserRole.MERCHANT);
mallService.merchantDeleteInventory(user.getId(), id);
return ApiResponse.ok();
}
}

View File

@@ -0,0 +1,43 @@
package com.maternalmall.controller;
import com.maternalmall.common.ApiResponse;
import com.maternalmall.domain.Banner;
import com.maternalmall.domain.Product;
import com.maternalmall.domain.Review;
import com.maternalmall.repository.ReviewRepository;
import com.maternalmall.service.MallService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/public")
public class PublicController {
private final MallService mallService;
private final ReviewRepository reviewRepository;
public PublicController(MallService mallService, ReviewRepository reviewRepository) {
this.mallService = mallService;
this.reviewRepository = reviewRepository;
}
@GetMapping("/products")
public ApiResponse<List<Product>> products(@RequestParam(required = false) String keyword) {
return ApiResponse.ok(mallService.searchProducts(keyword));
}
@GetMapping("/products/{id}")
public ApiResponse<Product> product(@PathVariable Long id) {
return ApiResponse.ok(mallService.getProduct(id));
}
@GetMapping("/products/{id}/reviews")
public ApiResponse<List<Review>> reviews(@PathVariable Long id) {
return ApiResponse.ok(reviewRepository.findByProductId(id));
}
@GetMapping("/banners")
public ApiResponse<List<Banner>> banners() {
return ApiResponse.ok(mallService.banners());
}
}

View File

@@ -0,0 +1,25 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
@Data
@Entity
@Table(name = "banner")
public class Banner {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 255)
private String imageUrl;
@Column(length = 255)
private String linkUrl;
@Column(nullable = false)
private Integer sortNo = 0;
@Column(nullable = false)
private Boolean enabled = true;
}

View File

@@ -0,0 +1,22 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
@Data
@Entity
@Table(name = "cart_item", uniqueConstraints = @UniqueConstraint(columnNames = {"customerId", "productId"}))
public class CartItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long customerId;
@Column(nullable = false)
private Long productId;
@Column(nullable = false)
private Integer quantity;
}

View File

@@ -0,0 +1,19 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
@Data
@Entity
@Table(name = "favorite", uniqueConstraints = @UniqueConstraint(columnNames = {"customerId", "productId"}))
public class Favorite {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long customerId;
@Column(nullable = false)
private Long productId;
}

View File

@@ -0,0 +1,31 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "inventory_record")
public class InventoryRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long productId;
@Column(nullable = false)
private Long merchantId;
@Column(nullable = false)
private Integer changeQty;
@Column(length = 255)
private String note;
@CreationTimestamp
private LocalDateTime createdAt;
}

View File

@@ -0,0 +1,31 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "logistics_record")
public class LogisticsRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long orderId;
@Column(nullable = false)
private Long merchantId;
@Column(length = 255, nullable = false)
private String status;
@Column(length = 255)
private String note;
@CreationTimestamp
private LocalDateTime createdAt;
}

View File

@@ -0,0 +1,35 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "merchant_application")
public class MerchantApplication {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long userId;
@Column(length = 255)
private String qualification;
@Column(length = 30, nullable = false)
private String status = "PENDING";
@Column(length = 255)
private String remark;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,30 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
import java.math.BigDecimal;
@Data
@Entity
@Table(name = "order_item")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long orderId;
@Column(nullable = false)
private Long productId;
@Column(nullable = false, length = 120)
private String productName;
@Column(nullable = false)
private Integer quantity;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal unitPrice;
}

View File

@@ -0,0 +1,11 @@
package com.maternalmall.domain;
public enum OrderStatus {
PENDING_PAYMENT,
PAID,
SHIPPED,
COMPLETED,
REFUND_REQUESTED,
REFUNDED,
CANCELLED
}

View File

@@ -0,0 +1,49 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "orders")
public class Orders {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String orderNo;
@Column(nullable = false)
private Long customerId;
@Column(nullable = false)
private Long merchantId;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal totalAmount;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 30)
private OrderStatus status;
@Column(length = 255)
private String address;
@Column(length = 255)
private String logisticsInfo;
@Column(length = 255)
private String refundReason;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,48 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 120)
private String name;
@Column(length = 50)
private String category;
@Column(length = 1000)
private String description;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
@Column(nullable = false)
private Integer stock;
@Column(length = 255)
private String imageUrl;
@Column(nullable = false)
private Long merchantId;
@Column(nullable = false)
private Boolean approved = false;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,34 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "review")
public class Review {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long orderId;
@Column(nullable = false)
private Long productId;
@Column(nullable = false)
private Long customerId;
@Column(nullable = false)
private Integer rating;
@Column(length = 1000)
private String content;
@CreationTimestamp
private LocalDateTime createdAt;
}

View File

@@ -0,0 +1,48 @@
package com.maternalmall.domain;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 50)
private String username;
@Column(nullable = false, length = 100)
private String password;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private UserRole role;
@Column(length = 50)
private String nickname;
@Column(length = 20)
private String phone;
@Column(length = 255)
private String address;
@Column(length = 100)
private String token;
@Column(nullable = false)
private Boolean enabled = true;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,7 @@
package com.maternalmall.domain;
public enum UserRole {
CUSTOMER,
MERCHANT,
ADMIN
}

View File

@@ -0,0 +1,10 @@
package com.maternalmall.repository;
import com.maternalmall.domain.Banner;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface BannerRepository extends JpaRepository<Banner, Long> {
List<Banner> findByEnabledTrueOrderBySortNoAsc();
}

View File

@@ -0,0 +1,22 @@
package com.maternalmall.repository;
import com.maternalmall.domain.CartItem;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
public interface CartItemRepository extends JpaRepository<CartItem, Long> {
List<CartItem> findByCustomerId(Long customerId);
Optional<CartItem> findByCustomerIdAndProductId(Long customerId, Long productId);
@Modifying
@Transactional
void deleteByCustomerIdAndProductId(Long customerId, Long productId);
@Modifying
@Transactional
void deleteByCustomerId(Long customerId);
}

View File

@@ -0,0 +1,18 @@
package com.maternalmall.repository;
import com.maternalmall.domain.Favorite;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
public interface FavoriteRepository extends JpaRepository<Favorite, Long> {
List<Favorite> findByCustomerId(Long customerId);
Optional<Favorite> findByCustomerIdAndProductId(Long customerId, Long productId);
@Modifying
@Transactional
void deleteByCustomerIdAndProductId(Long customerId, Long productId);
}

View File

@@ -0,0 +1,11 @@
package com.maternalmall.repository;
import com.maternalmall.domain.InventoryRecord;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface InventoryRecordRepository extends JpaRepository<InventoryRecord, Long> {
List<InventoryRecord> findByMerchantId(Long merchantId);
List<InventoryRecord> findByProductId(Long productId);
}

View File

@@ -0,0 +1,11 @@
package com.maternalmall.repository;
import com.maternalmall.domain.LogisticsRecord;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface LogisticsRecordRepository extends JpaRepository<LogisticsRecord, Long> {
List<LogisticsRecord> findByOrderId(Long orderId);
List<LogisticsRecord> findByMerchantId(Long merchantId);
}

View File

@@ -0,0 +1,11 @@
package com.maternalmall.repository;
import com.maternalmall.domain.MerchantApplication;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MerchantApplicationRepository extends JpaRepository<MerchantApplication, Long> {
List<MerchantApplication> findByStatus(String status);
boolean existsByUserIdAndStatus(Long userId, String status);
}

View File

@@ -0,0 +1,10 @@
package com.maternalmall.repository;
import com.maternalmall.domain.OrderItem;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface OrderItemRepository extends JpaRepository<OrderItem, Long> {
List<OrderItem> findByOrderId(Long orderId);
}

View File

@@ -0,0 +1,13 @@
package com.maternalmall.repository;
import com.maternalmall.domain.OrderStatus;
import com.maternalmall.domain.Orders;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface OrdersRepository extends JpaRepository<Orders, Long> {
List<Orders> findByCustomerId(Long customerId);
List<Orders> findByMerchantId(Long merchantId);
List<Orders> findByStatus(OrderStatus status);
}

View File

@@ -0,0 +1,11 @@
package com.maternalmall.repository;
import com.maternalmall.domain.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByApprovedTrueAndNameContainingIgnoreCase(String name);
List<Product> findByMerchantId(Long merchantId);
}

View File

@@ -0,0 +1,12 @@
package com.maternalmall.repository;
import com.maternalmall.domain.Review;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ReviewRepository extends JpaRepository<Review, Long> {
List<Review> findByProductId(Long productId);
List<Review> findByCustomerId(Long customerId);
boolean existsByOrderIdAndProductIdAndCustomerId(Long orderId, Long productId, Long customerId);
}

View File

@@ -0,0 +1,11 @@
package com.maternalmall.repository;
import com.maternalmall.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByToken(String token);
}

View File

@@ -0,0 +1,65 @@
package com.maternalmall.service;
import com.maternalmall.common.BizException;
import com.maternalmall.domain.User;
import com.maternalmall.domain.UserRole;
import com.maternalmall.repository.UserRepository;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service
public class AuthService {
private final UserRepository userRepository;
public AuthService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Map<String, Object> register(String username, String password, String role) {
username = username == null ? "" : username.trim();
password = password == null ? "" : password.trim();
if (username.isEmpty()) {
throw new BizException("用户名不能为空");
}
if (password.isEmpty()) {
throw new BizException("密码不能为空");
}
userRepository.findByUsername(username).ifPresent(u -> {
throw new BizException("账号已存在");
});
if (role != null && !role.isBlank() && !"CUSTOMER".equalsIgnoreCase(role)) {
throw new BizException("仅支持注册顾客账号,商家需提交入驻申请");
}
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setRole(UserRole.CUSTOMER);
user.setNickname(username);
userRepository.save(user);
return Map.of("userId", user.getId());
}
public Map<String, Object> login(String username, String password) {
User user = userRepository.findByUsername(username).orElseThrow(() -> new BizException("用户不存在"));
if (!user.getPassword().equals(password)) {
throw new BizException("密码错误");
}
if (!Boolean.TRUE.equals(user.getEnabled())) {
throw new BizException("账号已被禁用,请联系管理员");
}
String token = UUID.randomUUID().toString().replace("-", "");
user.setToken(token);
userRepository.save(user);
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("id", user.getId());
data.put("username", user.getUsername());
data.put("role", user.getRole());
data.put("nickname", user.getNickname());
return data;
}
}

View File

@@ -0,0 +1,887 @@
package com.maternalmall.service;
import com.maternalmall.common.BizException;
import com.maternalmall.domain.*;
import com.maternalmall.repository.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
@Service
public class MallService {
private final ProductRepository productRepository;
private final CartItemRepository cartItemRepository;
private final FavoriteRepository favoriteRepository;
private final OrdersRepository ordersRepository;
private final OrderItemRepository orderItemRepository;
private final ReviewRepository reviewRepository;
private final LogisticsRecordRepository logisticsRecordRepository;
private final InventoryRecordRepository inventoryRecordRepository;
private final MerchantApplicationRepository merchantApplicationRepository;
private final BannerRepository bannerRepository;
private final UserRepository userRepository;
public MallService(ProductRepository productRepository,
CartItemRepository cartItemRepository,
FavoriteRepository favoriteRepository,
OrdersRepository ordersRepository,
OrderItemRepository orderItemRepository,
ReviewRepository reviewRepository,
LogisticsRecordRepository logisticsRecordRepository,
InventoryRecordRepository inventoryRecordRepository,
MerchantApplicationRepository merchantApplicationRepository,
BannerRepository bannerRepository,
UserRepository userRepository) {
this.productRepository = productRepository;
this.cartItemRepository = cartItemRepository;
this.favoriteRepository = favoriteRepository;
this.ordersRepository = ordersRepository;
this.orderItemRepository = orderItemRepository;
this.reviewRepository = reviewRepository;
this.logisticsRecordRepository = logisticsRecordRepository;
this.inventoryRecordRepository = inventoryRecordRepository;
this.merchantApplicationRepository = merchantApplicationRepository;
this.bannerRepository = bannerRepository;
this.userRepository = userRepository;
}
public void requireRole(User user, UserRole role) {
if (user.getRole() != role) {
throw new BizException("无权限操作");
}
}
public List<Product> searchProducts(String keyword) {
return productRepository.findByApprovedTrueAndNameContainingIgnoreCase(keyword == null ? "" : keyword);
}
public Product getProduct(Long id) {
return productRepository.findById(id).orElseThrow(() -> new BizException("商品不存在"));
}
public List<Banner> banners() {
return bannerRepository.findByEnabledTrueOrderBySortNoAsc();
}
public List<CartItem> cartList(Long customerId) {
return cartItemRepository.findByCustomerId(customerId);
}
public List<Map<String, Object>> cartViews(Long customerId) {
List<CartItem> items = cartItemRepository.findByCustomerId(customerId);
List<Map<String, Object>> views = new ArrayList<>();
for (CartItem item : items) {
Product p = getProduct(item.getProductId());
Map<String, Object> row = new HashMap<>();
row.put("productId", p.getId());
row.put("productName", p.getName());
row.put("category", p.getCategory());
row.put("imageUrl", p.getImageUrl());
row.put("unitPrice", p.getPrice());
row.put("quantity", item.getQuantity());
row.put("subtotal", p.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())));
row.put("stock", p.getStock());
views.add(row);
}
return views;
}
public CartItem addCart(Long customerId, Long productId, Integer quantity) {
Product product = getProduct(productId);
if (!product.getApproved()) {
throw new BizException("商品未上架");
}
CartItem item = cartItemRepository.findByCustomerIdAndProductId(customerId, productId).orElseGet(CartItem::new);
item.setCustomerId(customerId);
item.setProductId(productId);
item.setQuantity((item.getQuantity() == null ? 0 : item.getQuantity()) + quantity);
return cartItemRepository.save(item);
}
public void removeCart(Long customerId, Long productId) {
cartItemRepository.deleteByCustomerIdAndProductId(customerId, productId);
}
public List<Favorite> favoriteList(Long customerId) {
return favoriteRepository.findByCustomerId(customerId);
}
public List<Map<String, Object>> favoriteViews(Long customerId) {
List<Favorite> favorites = favoriteRepository.findByCustomerId(customerId);
List<Map<String, Object>> views = new ArrayList<>();
for (Favorite favorite : favorites) {
Product p = getProduct(favorite.getProductId());
Map<String, Object> row = new HashMap<>();
row.put("productId", p.getId());
row.put("productName", p.getName());
row.put("category", p.getCategory());
row.put("imageUrl", p.getImageUrl());
row.put("unitPrice", p.getPrice());
row.put("stock", p.getStock());
views.add(row);
}
return views;
}
public Favorite addFavorite(Long customerId, Long productId) {
favoriteRepository.findByCustomerIdAndProductId(customerId, productId).ifPresent(f -> {
throw new BizException("已收藏");
});
Favorite favorite = new Favorite();
favorite.setCustomerId(customerId);
favorite.setProductId(productId);
return favoriteRepository.save(favorite);
}
public void removeFavorite(Long customerId, Long productId) {
favoriteRepository.deleteByCustomerIdAndProductId(customerId, productId);
}
@Transactional
public Orders checkout(Long customerId, String address) {
List<CartItem> cartItems = cartItemRepository.findByCustomerId(customerId);
if (cartItems.isEmpty()) {
throw new BizException("购物车为空");
}
Long merchantId = null;
BigDecimal total = BigDecimal.ZERO;
Orders order = new Orders();
order.setOrderNo("MM" + System.currentTimeMillis());
order.setCustomerId(customerId);
order.setStatus(OrderStatus.PAID);
order.setAddress(address);
for (CartItem cartItem : cartItems) {
Product product = getProduct(cartItem.getProductId());
if (product.getStock() < cartItem.getQuantity()) {
throw new BizException("库存不足: " + product.getName());
}
if (merchantId == null) {
merchantId = product.getMerchantId();
} else if (!merchantId.equals(product.getMerchantId())) {
throw new BizException("当前基础版本仅支持单商家结算");
}
product.setStock(product.getStock() - cartItem.getQuantity());
productRepository.save(product);
InventoryRecord record = new InventoryRecord();
record.setMerchantId(product.getMerchantId());
record.setProductId(product.getId());
record.setChangeQty(-cartItem.getQuantity());
record.setNote("订单扣减库存");
inventoryRecordRepository.save(record);
BigDecimal line = product.getPrice().multiply(BigDecimal.valueOf(cartItem.getQuantity()));
total = total.add(line);
}
order.setMerchantId(merchantId);
order.setTotalAmount(total);
ordersRepository.save(order);
for (CartItem cartItem : cartItems) {
Product product = getProduct(cartItem.getProductId());
OrderItem item = new OrderItem();
item.setOrderId(order.getId());
item.setProductId(product.getId());
item.setProductName(product.getName());
item.setQuantity(cartItem.getQuantity());
item.setUnitPrice(product.getPrice());
orderItemRepository.save(item);
}
cartItemRepository.deleteByCustomerId(customerId);
return order;
}
@Transactional
public Orders buyNow(Long customerId, Long productId, Integer quantity, String address) {
if (quantity == null || quantity <= 0) {
throw new BizException("购买数量必须大于0");
}
Product product = getProduct(productId);
if (!Boolean.TRUE.equals(product.getApproved())) {
throw new BizException("商品未上架");
}
if (product.getStock() < quantity) {
throw new BizException("库存不足: " + product.getName());
}
product.setStock(product.getStock() - quantity);
productRepository.save(product);
InventoryRecord record = new InventoryRecord();
record.setMerchantId(product.getMerchantId());
record.setProductId(product.getId());
record.setChangeQty(-quantity);
record.setNote("立即购买扣减库存");
inventoryRecordRepository.save(record);
Orders order = new Orders();
order.setOrderNo("MM" + System.currentTimeMillis());
order.setCustomerId(customerId);
order.setMerchantId(product.getMerchantId());
order.setStatus(OrderStatus.PAID);
order.setAddress(address);
order.setTotalAmount(product.getPrice().multiply(BigDecimal.valueOf(quantity)));
ordersRepository.save(order);
OrderItem item = new OrderItem();
item.setOrderId(order.getId());
item.setProductId(product.getId());
item.setProductName(product.getName());
item.setQuantity(quantity);
item.setUnitPrice(product.getPrice());
orderItemRepository.save(item);
return order;
}
public List<Orders> customerOrders(Long customerId) {
return ordersRepository.findByCustomerId(customerId);
}
public List<OrderItem> customerOrderItems(Long customerId, Long orderId) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (!order.getCustomerId().equals(customerId)) {
throw new BizException("无权限操作");
}
return orderItemRepository.findByOrderId(orderId);
}
public Orders customerUpdateAddress(Long customerId, Long orderId, String address) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (!order.getCustomerId().equals(customerId)) {
throw new BizException("无权限操作");
}
if (order.getStatus() == OrderStatus.SHIPPED || order.getStatus() == OrderStatus.COMPLETED) {
throw new BizException("该状态不可修改地址");
}
order.setAddress(address);
return ordersRepository.save(order);
}
public Orders customerRefund(Long customerId, Long orderId, String reason) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (!order.getCustomerId().equals(customerId)) {
throw new BizException("无权限操作");
}
order.setStatus(OrderStatus.REFUND_REQUESTED);
order.setRefundReason(reason);
return ordersRepository.save(order);
}
public void customerDeleteOrder(Long customerId, Long orderId) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (!order.getCustomerId().equals(customerId)) {
throw new BizException("无权限操作");
}
if (!(order.getStatus() == OrderStatus.COMPLETED || order.getStatus() == OrderStatus.REFUNDED || order.getStatus() == OrderStatus.CANCELLED)) {
throw new BizException("当前状态不可删除");
}
ordersRepository.delete(order);
}
public List<LogisticsRecord> logisticsOfOrder(Long customerId, Long orderId) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (!order.getCustomerId().equals(customerId)) {
throw new BizException("无权限操作");
}
return logisticsRecordRepository.findByOrderId(orderId);
}
public Review addReview(Long customerId, Long orderId, Long productId, Integer rating, String content) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (!order.getCustomerId().equals(customerId)) {
throw new BizException("无权限操作");
}
if (rating == null || rating < 1 || rating > 5) {
throw new BizException("评分范围为1-5分");
}
if (!(order.getStatus() == OrderStatus.PAID
|| order.getStatus() == OrderStatus.SHIPPED
|| order.getStatus() == OrderStatus.COMPLETED
|| order.getStatus() == OrderStatus.REFUND_REQUESTED
|| order.getStatus() == OrderStatus.REFUNDED)) {
throw new BizException("仅已购买商品可评价");
}
boolean ordered = orderItemRepository.findByOrderId(orderId)
.stream()
.anyMatch(i -> i.getProductId().equals(productId));
if (!ordered) {
throw new BizException("该商品不在订单内");
}
if (reviewRepository.existsByOrderIdAndProductIdAndCustomerId(orderId, productId, customerId)) {
throw new BizException("该商品已评价");
}
Review review = new Review();
review.setOrderId(orderId);
review.setProductId(productId);
review.setCustomerId(customerId);
review.setRating(rating);
review.setContent(content);
return reviewRepository.save(review);
}
public Map<String, Object> merchantOverview(Long merchantId) {
List<Orders> orders = ordersRepository.findByMerchantId(merchantId);
BigDecimal sales = orders.stream()
.filter(o -> o.getStatus() == OrderStatus.PAID || o.getStatus() == OrderStatus.SHIPPED || o.getStatus() == OrderStatus.COMPLETED)
.map(Orders::getTotalAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Map<Long, Integer> hotMap = new HashMap<>();
Map<String, Long> categoryCount = new HashMap<>();
for (Orders order : orders) {
for (OrderItem item : orderItemRepository.findByOrderId(order.getId())) {
hotMap.put(item.getProductId(), hotMap.getOrDefault(item.getProductId(), 0) + item.getQuantity());
}
}
for (Product p : productRepository.findByMerchantId(merchantId)) {
String category = p.getCategory() == null ? "未分类" : p.getCategory();
categoryCount.put(category, categoryCount.getOrDefault(category, 0L) + 1);
}
return Map.of(
"orderCount", orders.size(),
"salesAmount", sales,
"hotProducts", hotMap,
"categoryRatio", categoryCount,
"notifications", List.of("请及时处理退款申请", "注意库存预警")
);
}
public Product merchantSaveProduct(Long merchantId, Product body) {
Product product = body;
if (product.getId() != null) {
Product old = productRepository.findById(product.getId()).orElseThrow(() -> new BizException("商品不存在"));
if (!old.getMerchantId().equals(merchantId)) {
throw new BizException("无权限操作");
}
}
product.setMerchantId(merchantId);
if (product.getApproved() == null) {
product.setApproved(false);
}
Product saved = productRepository.save(product);
InventoryRecord record = new InventoryRecord();
record.setMerchantId(merchantId);
record.setProductId(saved.getId());
record.setChangeQty(saved.getStock());
record.setNote("商品上架或更新库存");
inventoryRecordRepository.save(record);
return saved;
}
public List<Product> merchantProducts(Long merchantId) {
return productRepository.findByMerchantId(merchantId);
}
public void merchantDeleteProduct(Long merchantId, Long productId) {
Product product = getProduct(productId);
if (!product.getMerchantId().equals(merchantId)) {
throw new BizException("无权限操作");
}
productRepository.delete(product);
}
public List<Orders> merchantOrders(Long merchantId) {
return ordersRepository.findByMerchantId(merchantId);
}
public Orders merchantShip(Long merchantId, Long orderId, String note) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (!order.getMerchantId().equals(merchantId)) {
throw new BizException("无权限操作");
}
order.setStatus(OrderStatus.SHIPPED);
order.setLogisticsInfo(note);
ordersRepository.save(order);
LogisticsRecord record = new LogisticsRecord();
record.setOrderId(orderId);
record.setMerchantId(merchantId);
record.setStatus("SHIPPED");
record.setNote(note);
logisticsRecordRepository.save(record);
return order;
}
public Orders merchantProcessRefund(Long merchantId, Long orderId, boolean agree) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (!order.getMerchantId().equals(merchantId)) {
throw new BizException("无权限操作");
}
if (order.getStatus() != OrderStatus.REFUND_REQUESTED) {
throw new BizException("仅退款申请中的订单可处理");
}
order.setStatus(agree ? OrderStatus.REFUNDED : OrderStatus.PAID);
return ordersRepository.save(order);
}
public List<Map<String, Object>> merchantReviews(Long merchantId) {
List<Product> products = productRepository.findByMerchantId(merchantId);
Set<Long> productIds = new HashSet<>();
for (Product product : products) {
productIds.add(product.getId());
}
List<Review> reviews = reviewRepository.findAll();
Map<Long, Orders> orderMap = new HashMap<>();
for (Orders order : ordersRepository.findAll()) {
orderMap.put(order.getId(), order);
}
List<Map<String, Object>> views = new ArrayList<>();
for (Review r : reviews) {
if (!productIds.contains(r.getProductId())) continue;
Orders order = orderMap.get(r.getOrderId());
Map<String, Object> row = new HashMap<>();
row.put("id", r.getId());
row.put("orderId", r.getOrderId());
row.put("orderNo", order == null ? "" : order.getOrderNo());
row.put("productId", r.getProductId());
row.put("productName", products.stream().filter(p -> p.getId().equals(r.getProductId())).findFirst().map(Product::getName).orElse(""));
row.put("rating", r.getRating());
row.put("content", r.getContent());
row.put("createdAt", r.getCreatedAt());
views.add(row);
}
return views;
}
public List<Map<String, Object>> merchantLogistics(Long merchantId) {
List<LogisticsRecord> records = logisticsRecordRepository.findByMerchantId(merchantId);
Map<Long, Orders> orderMap = new HashMap<>();
for (Orders order : ordersRepository.findAll()) {
orderMap.put(order.getId(), order);
}
List<Map<String, Object>> views = new ArrayList<>();
for (LogisticsRecord rec : records) {
Orders order = orderMap.get(rec.getOrderId());
Map<String, Object> row = new HashMap<>();
row.put("id", rec.getId());
row.put("orderId", rec.getOrderId());
row.put("orderNo", order == null ? "" : order.getOrderNo());
row.put("status", rec.getStatus());
row.put("note", rec.getNote());
row.put("createdAt", rec.getCreatedAt());
views.add(row);
}
return views;
}
public List<Map<String, Object>> merchantInventory(Long merchantId) {
List<InventoryRecord> records = inventoryRecordRepository.findByMerchantId(merchantId);
Map<Long, Product> productMap = new HashMap<>();
for (Product product : productRepository.findByMerchantId(merchantId)) {
productMap.put(product.getId(), product);
}
List<Map<String, Object>> views = new ArrayList<>();
for (InventoryRecord rec : records) {
Product product = productMap.get(rec.getProductId());
Map<String, Object> row = new HashMap<>();
row.put("id", rec.getId());
row.put("productId", rec.getProductId());
row.put("productName", product == null ? "" : product.getName());
row.put("changeQty", rec.getChangeQty());
row.put("note", rec.getNote());
row.put("createdAt", rec.getCreatedAt());
views.add(row);
}
return views;
}
public void merchantDeleteInventory(Long merchantId, Long id) {
InventoryRecord rec = inventoryRecordRepository.findById(id).orElseThrow(() -> new BizException("记录不存在"));
if (!rec.getMerchantId().equals(merchantId)) {
throw new BizException("无权限操作");
}
inventoryRecordRepository.delete(rec);
}
public MerchantApplication applyMerchant(Long userId, String qualification) {
User user = userRepository.findById(userId).orElseThrow(() -> new BizException("用户不存在"));
if (user.getRole() != UserRole.CUSTOMER) {
throw new BizException("仅顾客账号可提交入驻申请");
}
if (merchantApplicationRepository.existsByUserIdAndStatus(userId, "PENDING")) {
throw new BizException("已有待审核申请,请勿重复提交");
}
MerchantApplication m = new MerchantApplication();
m.setUserId(userId);
m.setQualification(qualification);
m.setStatus("PENDING");
return merchantApplicationRepository.save(m);
}
public Map<String, Object> adminOverview() {
List<Orders> orders = ordersRepository.findAll();
BigDecimal sales = orders.stream().map(Orders::getTotalAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
Map<String, Long> categoryCount = new HashMap<>();
Map<Long, Integer> hotMap = new HashMap<>();
for (Product p : productRepository.findAll()) {
String category = p.getCategory() == null ? "未分类" : p.getCategory();
categoryCount.put(category, categoryCount.getOrDefault(category, 0L) + 1);
}
for (Orders order : orders) {
for (OrderItem item : orderItemRepository.findByOrderId(order.getId())) {
hotMap.put(item.getProductId(), hotMap.getOrDefault(item.getProductId(), 0) + item.getQuantity());
}
}
return Map.of(
"orderCount", orders.size(),
"salesAmount", sales,
"categoryRatio", categoryCount,
"hotProducts", hotMap
);
}
public List<Orders> adminOrders() {
return ordersRepository.findAll();
}
public Orders adminUpdateOrder(Long orderId, String status, String logisticsInfo) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
try {
order.setStatus(OrderStatus.valueOf(status));
} catch (IllegalArgumentException e) {
throw new BizException("订单状态不合法");
}
order.setLogisticsInfo(logisticsInfo);
return ordersRepository.save(order);
}
public List<Map<String, Object>> adminRiskOrders() {
List<Orders> orders = ordersRepository.findAll();
List<Map<String, Object>> risks = new ArrayList<>();
LocalDateTime now = LocalDateTime.now();
for (Orders order : orders) {
if (order.getTotalAmount().compareTo(new BigDecimal("5000")) >= 0) {
risks.add(buildRisk(order, "HIGH_AMOUNT", "订单金额较高,请人工核查"));
}
if (order.getStatus() == OrderStatus.PAID && order.getCreatedAt() != null && order.getCreatedAt().isBefore(now.minusDays(2))) {
risks.add(buildRisk(order, "SHIP_DELAY", "已支付超过48小时未发货"));
}
if (order.getStatus() == OrderStatus.SHIPPED && logisticsRecordRepository.findByOrderId(order.getId()).isEmpty()) {
risks.add(buildRisk(order, "NO_LOGISTICS", "订单标记已发货但无物流轨迹"));
}
if (order.getStatus() == OrderStatus.REFUND_REQUESTED) {
risks.add(buildRisk(order, "REFUND_PENDING", "退款申请待审核"));
}
}
return risks;
}
private Map<String, Object> buildRisk(Orders order, String type, String reason) {
Map<String, Object> row = new HashMap<>();
row.put("orderId", order.getId());
row.put("orderNo", order.getOrderNo());
row.put("status", order.getStatus());
row.put("totalAmount", order.getTotalAmount());
row.put("customerId", order.getCustomerId());
row.put("merchantId", order.getMerchantId());
row.put("riskType", type);
row.put("riskReason", reason);
return row;
}
public Orders adminAuditRefund(Long orderId, boolean approve, String remark) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (order.getStatus() != OrderStatus.REFUND_REQUESTED) {
throw new BizException("当前订单不在退款审核状态");
}
order.setStatus(approve ? OrderStatus.REFUNDED : OrderStatus.PAID);
if (remark != null && !remark.trim().isEmpty()) {
String old = order.getRefundReason() == null ? "" : order.getRefundReason();
String merged = old.isBlank() ? remark.trim() : old + ";管理员备注:" + remark.trim();
order.setRefundReason(merged);
}
Orders saved = ordersRepository.save(order);
LogisticsRecord record = new LogisticsRecord();
record.setOrderId(orderId);
record.setMerchantId(order.getMerchantId());
record.setStatus(approve ? "REFUND_APPROVED" : "REFUND_REJECTED");
record.setNote((approve ? "管理员通过退款审核" : "管理员驳回退款申请") + (remark == null || remark.trim().isEmpty() ? "" : ("" + remark.trim())));
logisticsRecordRepository.save(record);
return saved;
}
public Orders adminAuditShipment(Long orderId, boolean approved, String remark) {
Orders order = ordersRepository.findById(orderId).orElseThrow(() -> new BizException("订单不存在"));
if (order.getStatus() != OrderStatus.SHIPPED && order.getStatus() != OrderStatus.PAID) {
throw new BizException("仅已支付或已发货订单可审核发货");
}
if (approved && order.getStatus() == OrderStatus.PAID) {
order.setStatus(OrderStatus.SHIPPED);
}
if (!approved && order.getStatus() == OrderStatus.SHIPPED) {
order.setStatus(OrderStatus.PAID);
}
if (remark != null && !remark.trim().isEmpty()) {
order.setLogisticsInfo(remark.trim());
}
Orders saved = ordersRepository.save(order);
LogisticsRecord record = new LogisticsRecord();
record.setOrderId(orderId);
record.setMerchantId(order.getMerchantId());
record.setStatus(approved ? "SHIP_AUDIT_PASS" : "SHIP_AUDIT_REJECT");
record.setNote((approved ? "管理员发货审核通过" : "管理员发货审核驳回") + (remark == null || remark.trim().isEmpty() ? "" : ("" + remark.trim())));
logisticsRecordRepository.save(record);
return saved;
}
public List<Map<String, Object>> adminMerchantApplications() {
List<MerchantApplication> apps = merchantApplicationRepository.findAll();
List<Map<String, Object>> views = new ArrayList<>();
Map<Long, User> userMap = new HashMap<>();
for (User user : userRepository.findAll()) {
userMap.put(user.getId(), user);
}
for (MerchantApplication app : apps) {
User applicant = userMap.get(app.getUserId());
Map<String, Object> row = new HashMap<>();
row.put("id", app.getId());
row.put("userId", app.getUserId());
row.put("applicantUsername", applicant == null ? "" : applicant.getUsername());
row.put("applicantNickname", applicant == null ? "" : applicant.getNickname());
row.put("qualification", app.getQualification());
row.put("status", app.getStatus());
row.put("remark", app.getRemark());
views.add(row);
}
return views;
}
public MerchantApplication adminAuditApplication(Long id, String status, String remark) {
MerchantApplication app = merchantApplicationRepository.findById(id).orElseThrow(() -> new BizException("申请不存在"));
app.setStatus(status);
app.setRemark(remark);
merchantApplicationRepository.save(app);
if ("APPROVED".equals(status)) {
User user = userRepository.findById(app.getUserId()).orElseThrow(() -> new BizException("用户不存在"));
user.setRole(UserRole.MERCHANT);
userRepository.save(user);
}
return app;
}
public List<User> adminUsers() {
return userRepository.findAll();
}
public User adminSaveUser(User user) {
String username = user.getUsername() == null ? "" : user.getUsername().trim();
String password = user.getPassword() == null ? "" : user.getPassword().trim();
if (username.isEmpty()) {
throw new BizException("用户名不能为空");
}
if (password.isEmpty()) {
throw new BizException("密码不能为空");
}
if (user.getId() == null) {
userRepository.findByUsername(username).ifPresent(u -> {
throw new BizException("账号已存在");
});
} else {
User existing = userRepository.findById(user.getId()).orElseThrow(() -> new BizException("用户不存在"));
userRepository.findByUsername(username).ifPresent(u -> {
if (!u.getId().equals(existing.getId())) {
throw new BizException("账号已存在");
}
});
}
user.setUsername(username);
user.setPassword(password);
if (user.getRole() == null) {
user.setRole(UserRole.CUSTOMER);
}
if (user.getNickname() == null || user.getNickname().trim().isEmpty()) {
user.setNickname(username);
}
if (user.getEnabled() == null) {
user.setEnabled(true);
}
return userRepository.save(user);
}
public void adminDeleteUser(Long id) {
userRepository.deleteById(id);
}
public List<Banner> adminBanners() {
return bannerRepository.findAll();
}
public Banner adminSaveBanner(Banner banner) {
return bannerRepository.save(banner);
}
public void adminDeleteBanner(Long id) {
bannerRepository.deleteById(id);
}
public List<Product> adminProducts() {
return productRepository.findAll();
}
public List<Map<String, Object>> adminProductViews() {
List<Product> products = productRepository.findAll();
Map<Long, User> userMap = new HashMap<>();
for (User user : userRepository.findAll()) {
userMap.put(user.getId(), user);
}
List<Map<String, Object>> views = new ArrayList<>();
for (Product p : products) {
User merchant = userMap.get(p.getMerchantId());
Map<String, Object> row = new HashMap<>();
row.put("id", p.getId());
row.put("name", p.getName());
row.put("category", p.getCategory());
row.put("description", p.getDescription());
row.put("price", p.getPrice());
row.put("stock", p.getStock());
row.put("imageUrl", p.getImageUrl());
row.put("merchantId", p.getMerchantId());
row.put("merchantUsername", merchant == null ? "" : merchant.getUsername());
row.put("merchantName", merchant == null ? "" : merchant.getNickname());
row.put("approved", p.getApproved());
row.put("createdAt", p.getCreatedAt());
views.add(row);
}
return views;
}
public Product adminSaveProduct(Product input) {
if (input.getName() == null || input.getName().trim().isEmpty()) {
throw new BizException("商品名称不能为空");
}
if (input.getMerchantId() == null) {
throw new BizException("商家ID不能为空");
}
if (input.getPrice() == null || input.getStock() == null) {
throw new BizException("商品价格或库存不能为空");
}
Product product = input;
if (input.getId() != null) {
product = productRepository.findById(input.getId()).orElseThrow(() -> new BizException("商品不存在"));
product.setName(input.getName());
product.setCategory(input.getCategory());
product.setDescription(input.getDescription());
product.setPrice(input.getPrice());
product.setStock(input.getStock());
product.setImageUrl(input.getImageUrl());
product.setMerchantId(input.getMerchantId());
product.setApproved(Boolean.TRUE.equals(input.getApproved()));
} else {
if (product.getApproved() == null) {
product.setApproved(false);
}
}
return productRepository.save(product);
}
public Product adminApproveProduct(Long productId, boolean approved) {
Product product = productRepository.findById(productId).orElseThrow(() -> new BizException("商品不存在"));
product.setApproved(approved);
return productRepository.save(product);
}
public void adminDeleteProduct(Long id) {
productRepository.deleteById(id);
}
public List<Map<String, Object>> adminReviews() {
List<Review> reviews = reviewRepository.findAll();
Map<Long, User> userMap = new HashMap<>();
for (User user : userRepository.findAll()) {
userMap.put(user.getId(), user);
}
Map<Long, Orders> orderMap = new HashMap<>();
for (Orders order : ordersRepository.findAll()) {
orderMap.put(order.getId(), order);
}
Map<Long, Product> productMap = new HashMap<>();
for (Product product : productRepository.findAll()) {
productMap.put(product.getId(), product);
}
List<Map<String, Object>> views = new ArrayList<>();
for (Review r : reviews) {
User customer = userMap.get(r.getCustomerId());
Orders order = orderMap.get(r.getOrderId());
Product product = productMap.get(r.getProductId());
Map<String, Object> row = new HashMap<>();
row.put("id", r.getId());
row.put("orderId", r.getOrderId());
row.put("orderNo", order == null ? "" : order.getOrderNo());
row.put("productId", r.getProductId());
row.put("productName", product == null ? "" : product.getName());
row.put("customerId", r.getCustomerId());
row.put("customerUsername", customer == null ? "" : customer.getUsername());
row.put("rating", r.getRating());
row.put("content", r.getContent());
row.put("createdAt", r.getCreatedAt());
views.add(row);
}
return views;
}
public List<Map<String, Object>> adminLogistics() {
List<LogisticsRecord> records = logisticsRecordRepository.findAll();
Map<Long, User> userMap = new HashMap<>();
for (User user : userRepository.findAll()) {
userMap.put(user.getId(), user);
}
Map<Long, Orders> orderMap = new HashMap<>();
for (Orders order : ordersRepository.findAll()) {
orderMap.put(order.getId(), order);
}
List<Map<String, Object>> views = new ArrayList<>();
for (LogisticsRecord rec : records) {
User merchant = userMap.get(rec.getMerchantId());
Orders order = orderMap.get(rec.getOrderId());
Map<String, Object> row = new HashMap<>();
row.put("id", rec.getId());
row.put("orderId", rec.getOrderId());
row.put("orderNo", order == null ? "" : order.getOrderNo());
row.put("merchantId", rec.getMerchantId());
row.put("merchantUsername", merchant == null ? "" : merchant.getUsername());
row.put("status", rec.getStatus());
row.put("note", rec.getNote());
row.put("createdAt", rec.getCreatedAt());
views.add(row);
}
return views;
}
public List<Map<String, Object>> adminInventory() {
List<InventoryRecord> records = inventoryRecordRepository.findAll();
Map<Long, User> userMap = new HashMap<>();
for (User user : userRepository.findAll()) {
userMap.put(user.getId(), user);
}
Map<Long, Product> productMap = new HashMap<>();
for (Product product : productRepository.findAll()) {
productMap.put(product.getId(), product);
}
List<Map<String, Object>> views = new ArrayList<>();
for (InventoryRecord rec : records) {
User merchant = userMap.get(rec.getMerchantId());
Product product = productMap.get(rec.getProductId());
Map<String, Object> row = new HashMap<>();
row.put("id", rec.getId());
row.put("productId", rec.getProductId());
row.put("productName", product == null ? "" : product.getName());
row.put("merchantId", rec.getMerchantId());
row.put("merchantUsername", merchant == null ? "" : merchant.getUsername());
row.put("changeQty", rec.getChangeQty());
row.put("note", rec.getNote());
row.put("createdAt", rec.getCreatedAt());
views.add(row);
}
return views;
}
}

View File

@@ -0,0 +1,20 @@
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/maternal_mall?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: qq5211314
jpa:
hibernate:
ddl-auto: update
show-sql: false
properties:
hibernate:
format_sql: true
jackson:
time-zone: Asia/Shanghai
app:
token-header: X-Token

View File

@@ -0,0 +1,20 @@
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/maternal_mall?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: qq5211314
jpa:
hibernate:
ddl-auto: update
show-sql: false
properties:
hibernate:
format_sql: true
jackson:
time-zone: Asia/Shanghai
app:
token-header: X-Token