初始化v1
This commit is contained in:
13
src/main/java/com/nlshop/NlShopApplication.java
Normal file
13
src/main/java/com/nlshop/NlShopApplication.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.nlshop.mapper")
|
||||
public class NlShopApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(NlShopApplication.class, args);
|
||||
}
|
||||
}
|
||||
142
src/main/java/com/nlshop/config/StartupUrlPrinter.java
Normal file
142
src/main/java/com/nlshop/config/StartupUrlPrinter.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package com.nlshop.config;
|
||||
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
@Order(1)
|
||||
public class StartupUrlPrinter implements ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent event) {
|
||||
RequestMappingHandlerMapping mapping = event.getApplicationContext()
|
||||
.getBean(RequestMappingHandlerMapping.class);
|
||||
|
||||
String baseUrl = "http://localhost:14001";
|
||||
int port = event.getApplicationContext().getEnvironment()
|
||||
.getProperty("server.port", Integer.class, 14001);
|
||||
if (port != 14001) {
|
||||
baseUrl = "http://localhost:" + port;
|
||||
}
|
||||
|
||||
List<String> userPages = new ArrayList<>();
|
||||
List<String> userApis = new ArrayList<>();
|
||||
List<String> merchantPages = new ArrayList<>();
|
||||
List<String> merchantApis = new ArrayList<>();
|
||||
|
||||
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
|
||||
|
||||
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
|
||||
RequestMappingInfo mappingInfo = entry.getKey();
|
||||
HandlerMethod handlerMethod = entry.getValue();
|
||||
|
||||
// 检查patternsCondition是否为null
|
||||
if (mappingInfo.getPatternsCondition() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Set<String> patterns = mappingInfo.getPatternsCondition().getPatterns();
|
||||
if (patterns == null || patterns.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String pattern : patterns) {
|
||||
String url = baseUrl + pattern;
|
||||
String className = handlerMethod.getBeanType().getSimpleName();
|
||||
|
||||
if (pattern.startsWith("/merchant")) {
|
||||
if (pattern.endsWith(".html")) {
|
||||
merchantPages.add(url);
|
||||
} else {
|
||||
merchantApis.add(url + " [" + getHttpMethods(mappingInfo) + "]");
|
||||
}
|
||||
} else if (!pattern.startsWith("/error") && !pattern.startsWith("/static")
|
||||
&& !pattern.startsWith("/upload") && !pattern.equals("/")) {
|
||||
if (pattern.endsWith(".html")) {
|
||||
userPages.add(url);
|
||||
} else {
|
||||
userApis.add(url + " [" + getHttpMethods(mappingInfo) + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加视图控制器映射的页面
|
||||
userPages.add(baseUrl + "/login.html");
|
||||
userPages.add(baseUrl + "/register.html");
|
||||
userPages.add(baseUrl + "/index.html");
|
||||
userPages.add(baseUrl + "/cart.html");
|
||||
userPages.add(baseUrl + "/orders.html");
|
||||
userPages.add(baseUrl + "/order-detail.html");
|
||||
userPages.add(baseUrl + "/product-detail.html");
|
||||
userPages.add(baseUrl + "/checkout.html");
|
||||
userPages.add(baseUrl + "/profile.html");
|
||||
|
||||
merchantPages.add(baseUrl + "/merchant/login.html");
|
||||
merchantPages.add(baseUrl + "/merchant/dashboard.html");
|
||||
merchantPages.add(baseUrl + "/merchant/products.html");
|
||||
merchantPages.add(baseUrl + "/merchant/orders.html");
|
||||
|
||||
// 打印所有URL
|
||||
printUrls("用户端页面", userPages, baseUrl);
|
||||
printUrls("用户端API", userApis, baseUrl);
|
||||
printUrls("商家端页面", merchantPages, baseUrl);
|
||||
printUrls("商家端API", merchantApis, baseUrl);
|
||||
}
|
||||
|
||||
private String getHttpMethods(RequestMappingInfo mappingInfo) {
|
||||
if (mappingInfo.getMethodsCondition() == null) {
|
||||
return "GET";
|
||||
}
|
||||
|
||||
Set<String> methods = mappingInfo.getMethodsCondition().getMethods().stream()
|
||||
.map(m -> m.name())
|
||||
.collect(Collectors.toSet());
|
||||
if (methods.isEmpty()) {
|
||||
return "GET";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String method : methods) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(method);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void printUrls(String category, List<String> urls, String baseUrl) {
|
||||
if (urls.isEmpty()) return;
|
||||
|
||||
String separator = createSeparator(80);
|
||||
System.out.println("\n" + separator);
|
||||
System.out.println("📋 " + category + " (" + urls.size() + " 个)");
|
||||
System.out.println(separator);
|
||||
|
||||
for (int i = 0; i < urls.size(); i++) {
|
||||
String url = urls.get(i);
|
||||
System.out.println(String.format("%3d. %s", i + 1, url));
|
||||
}
|
||||
|
||||
System.out.println(separator);
|
||||
}
|
||||
|
||||
private String createSeparator(int length) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append("=");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
71
src/main/java/com/nlshop/config/WebMvcConfig.java
Normal file
71
src/main/java/com/nlshop/config/WebMvcConfig.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package com.nlshop.config;
|
||||
|
||||
import com.nlshop.interceptor.AuthInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
private AuthInterceptor authInterceptor;
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
// 用户端页面(去掉/user前缀)
|
||||
registry.addViewController("/login.html").setViewName("user/login");
|
||||
registry.addViewController("/register.html").setViewName("user/register");
|
||||
registry.addViewController("/index.html").setViewName("user/index");
|
||||
registry.addViewController("/cart.html").setViewName("user/cart");
|
||||
registry.addViewController("/orders.html").setViewName("user/orders");
|
||||
registry.addViewController("/order-detail.html").setViewName("user/order-detail");
|
||||
registry.addViewController("/product-detail.html").setViewName("user/product-detail");
|
||||
registry.addViewController("/checkout.html").setViewName("user/checkout");
|
||||
registry.addViewController("/payment.html").setViewName("user/payment");
|
||||
registry.addViewController("/payment-success.html").setViewName("user/payment-success");
|
||||
registry.addViewController("/profile.html").setViewName("user/profile");
|
||||
|
||||
// 商家端页面(保持/merchant前缀)
|
||||
registry.addViewController("/merchant/login.html").setViewName("merchant/login");
|
||||
registry.addViewController("/merchant/register.html").setViewName("merchant/register");
|
||||
registry.addViewController("/merchant/dashboard.html").setViewName("merchant/dashboard");
|
||||
registry.addViewController("/merchant/products.html").setViewName("merchant/products");
|
||||
registry.addViewController("/merchant/product-edit.html").setViewName("merchant/product-edit");
|
||||
registry.addViewController("/merchant/orders.html").setViewName("merchant/orders");
|
||||
registry.addViewController("/merchant/inventory.html").setViewName("merchant/inventory");
|
||||
registry.addViewController("/merchant/statistics.html").setViewName("merchant/statistics");
|
||||
registry.addViewController("/merchant/announcements.html").setViewName("merchant/announcements");
|
||||
registry.addViewController("/merchant/messages.html").setViewName("merchant/messages");
|
||||
registry.addViewController("/merchant/reviews.html").setViewName("merchant/reviews");
|
||||
registry.addViewController("/merchant/users.html").setViewName("merchant/users");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(authInterceptor)
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(
|
||||
// 登录/注册API接口
|
||||
"/login", "/register",
|
||||
"/merchant/login", "/merchant/register",
|
||||
// 登录/注册HTML页面
|
||||
"/login.html", "/register.html",
|
||||
"/merchant/login.html", "/merchant/register.html",
|
||||
// 静态资源
|
||||
"/static/**", "/upload/**",
|
||||
"/error", "/favicon.ico"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/upload/**")
|
||||
.addResourceLocations("file:src/main/resources/static/upload/");
|
||||
registry.addResourceHandler("/static/**")
|
||||
.addResourceLocations("classpath:/static/");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Announcement;
|
||||
import com.nlshop.service.merchant.AnnouncementService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController("merchantAnnouncementController")
|
||||
@RequestMapping("/merchant/announcements")
|
||||
public class AnnouncementController {
|
||||
|
||||
@Autowired
|
||||
private AnnouncementService announcementService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Announcement> createAnnouncement(@RequestBody Announcement announcement,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Announcement created = announcementService.createAnnouncement(merchantId, announcement);
|
||||
return ApiResponse.success(created);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Announcement>> getAnnouncements(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Announcement> announcements = announcementService.getAnnouncements(merchantId);
|
||||
return ApiResponse.success(announcements);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Announcement> getAnnouncement(@PathVariable Long id) {
|
||||
Announcement announcement = announcementService.getAnnouncement(id);
|
||||
return ApiResponse.success(announcement);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Void> updateAnnouncement(@PathVariable Long id,
|
||||
@RequestBody Announcement announcement) {
|
||||
announcement.setId(id);
|
||||
announcementService.updateAnnouncement(announcement);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> deleteAnnouncement(@PathVariable Long id) {
|
||||
announcementService.deleteAnnouncement(id);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Inventory;
|
||||
import com.nlshop.service.merchant.InventoryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/inventory")
|
||||
public class InventoryController {
|
||||
|
||||
@Autowired
|
||||
private InventoryService inventoryService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Inventory>> getInventory(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Inventory> inventory = inventoryService.getInventory(merchantId);
|
||||
return ApiResponse.success(inventory);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Inventory> addInventory(@RequestBody Inventory inventory,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Inventory added = inventoryService.addInventory(merchantId, inventory);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/in")
|
||||
public ApiResponse<Void> stockIn(@PathVariable Long id,
|
||||
@RequestParam BigDecimal quantity,
|
||||
@RequestParam String operator) {
|
||||
inventoryService.stockIn(id, quantity, operator);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/out")
|
||||
public ApiResponse<Void> stockOut(@PathVariable Long id,
|
||||
@RequestParam BigDecimal quantity,
|
||||
@RequestParam String operator) {
|
||||
inventoryService.stockOut(id, quantity, operator);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/alerts")
|
||||
public ApiResponse<List<Inventory>> getLowStockAlerts(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Inventory> alerts = inventoryService.getLowStockAlerts(merchantId);
|
||||
return ApiResponse.success(alerts);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Void> updateInventory(@PathVariable Long id,
|
||||
@RequestBody Inventory inventory) {
|
||||
inventory.setId(id);
|
||||
inventoryService.updateInventory(inventory);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> deleteInventory(@PathVariable Long id) {
|
||||
inventoryService.deleteInventory(id);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.request.LoginRequest;
|
||||
import com.nlshop.dto.request.MerchantRegisterRequest;
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.LoginResponse;
|
||||
import com.nlshop.entity.Merchant;
|
||||
import com.nlshop.service.merchant.MerchantService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant")
|
||||
public class MerchantController {
|
||||
|
||||
@Autowired
|
||||
private MerchantService merchantService;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ApiResponse<Merchant> register(@Validated @RequestBody MerchantRegisterRequest request) {
|
||||
Merchant merchant = merchantService.register(request);
|
||||
return ApiResponse.success(merchant);
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ApiResponse<LoginResponse> login(@Validated @RequestBody LoginRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
Merchant merchant = merchantService.login(request.getUsername(), request.getPassword());
|
||||
|
||||
// 将用户信息存入Session
|
||||
HttpSession session = httpRequest.getSession(true);
|
||||
session.setAttribute("userId", merchant.getId());
|
||||
session.setAttribute("userType", "merchant");
|
||||
session.setAttribute("username", merchant.getUsername());
|
||||
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setToken(""); // 不再使用token,设为空字符串
|
||||
response.setUserId(merchant.getId());
|
||||
response.setUserType("merchant");
|
||||
response.setUsername(merchant.getUsername());
|
||||
response.setName(merchant.getStoreName());
|
||||
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ApiResponse<Void> logout(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
session.invalidate();
|
||||
}
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/profile")
|
||||
public ApiResponse<Merchant> getProfile(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Merchant merchant = merchantService.getProfile(merchantId);
|
||||
return ApiResponse.success(merchant);
|
||||
}
|
||||
|
||||
@PutMapping("/profile")
|
||||
public ApiResponse<Merchant> updateProfile(@RequestBody Merchant merchant,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Merchant updated = merchantService.updateProfile(merchantId, merchant);
|
||||
return ApiResponse.success(updated);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Message;
|
||||
import com.nlshop.service.merchant.MessageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController("merchantMessageController")
|
||||
@RequestMapping("/merchant/messages")
|
||||
public class MessageController {
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Message>> getMessages(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Message> messages = messageService.getMessages(merchantId);
|
||||
return ApiResponse.success(messages);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Message> getMessage(@PathVariable Long id) {
|
||||
Message message = messageService.getMessage(id);
|
||||
return ApiResponse.success(message);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/reply")
|
||||
public ApiResponse<Void> replyMessage(@PathVariable Long id,
|
||||
@RequestParam String reply) {
|
||||
messageService.replyMessage(id, reply);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.OrderDetailResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.service.merchant.OrderManageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/orders")
|
||||
public class OrderManageController {
|
||||
|
||||
@Autowired
|
||||
private OrderManageService orderManageService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResponse<Order>> getOrders(
|
||||
@RequestParam(required = false) String status,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
PageResponse<Order> response = orderManageService.getOrders(merchantId, status, pageNum, pageSize);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<OrderDetailResponse> getOrderDetail(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
OrderDetailResponse orderDetail = orderManageService.getOrderDetail(id, merchantId);
|
||||
return ApiResponse.success(orderDetail);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/accept")
|
||||
public ApiResponse<Void> acceptOrder(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
orderManageService.acceptOrder(id, merchantId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/reject")
|
||||
public ApiResponse<Void> rejectOrder(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
orderManageService.rejectOrder(id, merchantId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/status")
|
||||
public ApiResponse<Void> updateOrderStatus(@PathVariable Long id,
|
||||
@RequestParam String status,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
orderManageService.updateOrderStatus(id, merchantId, status);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.ProductCustom;
|
||||
import com.nlshop.entity.ProductSpec;
|
||||
import com.nlshop.entity.ProductTopping;
|
||||
import com.nlshop.service.merchant.ProductManageService;
|
||||
import com.nlshop.util.FileUploadUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/products")
|
||||
public class ProductManageController {
|
||||
|
||||
@Autowired
|
||||
private ProductManageService productManageService;
|
||||
|
||||
@Autowired
|
||||
private FileUploadUtil fileUploadUtil;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResponse<Product>> getProducts(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
PageResponse<Product> response = productManageService.getProducts(merchantId, pageNum, pageSize);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Map<String, Object>> getProduct(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Map<String, Object> product = productManageService.getProduct(id, merchantId);
|
||||
return ApiResponse.success(product);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Product> createProduct(@RequestParam String name,
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam String basePrice,
|
||||
@RequestParam(required = false) MultipartFile image,
|
||||
HttpServletRequest httpRequest) {
|
||||
Long merchantId = (Long) httpRequest.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
Product product = new Product();
|
||||
product.setName(name);
|
||||
product.setCategory(category);
|
||||
product.setDescription(description);
|
||||
if (basePrice != null && !basePrice.isEmpty()) {
|
||||
product.setBasePrice(new java.math.BigDecimal(basePrice));
|
||||
}
|
||||
|
||||
if (image != null && !image.isEmpty()) {
|
||||
try {
|
||||
String imagePath = fileUploadUtil.uploadProductImage(image);
|
||||
product.setImage(imagePath);
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.error("图片上传失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Product created = productManageService.createProduct(merchantId, product);
|
||||
return ApiResponse.success(created);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Product> updateProduct(@PathVariable Long id,
|
||||
@RequestParam String name,
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(required = false) String description,
|
||||
@RequestParam String basePrice,
|
||||
@RequestParam(required = false) MultipartFile image,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
Product product = new Product();
|
||||
product.setName(name);
|
||||
product.setCategory(category);
|
||||
product.setDescription(description);
|
||||
if (basePrice != null && !basePrice.isEmpty()) {
|
||||
product.setBasePrice(new java.math.BigDecimal(basePrice));
|
||||
}
|
||||
|
||||
if (image != null && !image.isEmpty()) {
|
||||
try {
|
||||
String imagePath = fileUploadUtil.uploadProductImage(image);
|
||||
product.setImage(imagePath);
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.error("图片上传失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Product updated = productManageService.updateProduct(id, merchantId, product);
|
||||
return ApiResponse.success(updated);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/offline")
|
||||
public ApiResponse<Void> offlineProduct(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
productManageService.offlineProduct(id, merchantId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/online")
|
||||
public ApiResponse<Void> onlineProduct(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
productManageService.onlineProduct(id, merchantId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/specs")
|
||||
public ApiResponse<List<ProductSpec>> getSpecs(@PathVariable Long id) {
|
||||
List<ProductSpec> specs = productManageService.getSpecs(id);
|
||||
return ApiResponse.success(specs);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/specs")
|
||||
public ApiResponse<ProductSpec> addSpec(@PathVariable Long id,
|
||||
@RequestBody ProductSpec spec) {
|
||||
ProductSpec added = productManageService.addSpec(id, spec);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PutMapping("/specs/{specId}")
|
||||
public ApiResponse<Void> updateSpec(@PathVariable Long specId,
|
||||
@RequestBody ProductSpec spec) {
|
||||
spec.setId(specId);
|
||||
productManageService.updateSpec(spec);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/specs/{specId}")
|
||||
public ApiResponse<Void> deleteSpec(@PathVariable Long specId) {
|
||||
productManageService.deleteSpec(specId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/toppings")
|
||||
public ApiResponse<List<ProductTopping>> getToppings(@PathVariable Long id) {
|
||||
List<ProductTopping> toppings = productManageService.getToppings(id);
|
||||
return ApiResponse.success(toppings);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/toppings")
|
||||
public ApiResponse<ProductTopping> addTopping(@PathVariable Long id,
|
||||
@RequestBody ProductTopping topping) {
|
||||
ProductTopping added = productManageService.addTopping(id, topping);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PutMapping("/toppings/{toppingId}")
|
||||
public ApiResponse<Void> updateTopping(@PathVariable Long toppingId,
|
||||
@RequestBody ProductTopping topping) {
|
||||
topping.setId(toppingId);
|
||||
productManageService.updateTopping(topping);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/toppings/{toppingId}")
|
||||
public ApiResponse<Void> deleteTopping(@PathVariable Long toppingId) {
|
||||
productManageService.deleteTopping(toppingId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/customs")
|
||||
public ApiResponse<List<ProductCustom>> getCustoms(@PathVariable Long id) {
|
||||
List<ProductCustom> customs = productManageService.getCustoms(id);
|
||||
return ApiResponse.success(customs);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/customs")
|
||||
public ApiResponse<ProductCustom> addCustom(@PathVariable Long id,
|
||||
@RequestBody ProductCustom custom) {
|
||||
ProductCustom added = productManageService.addCustom(id, custom);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PutMapping("/customs/{customId}")
|
||||
public ApiResponse<Void> updateCustom(@PathVariable Long customId,
|
||||
@RequestBody ProductCustom custom) {
|
||||
custom.setId(customId);
|
||||
productManageService.updateCustom(custom);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@DeleteMapping("/customs/{customId}")
|
||||
public ApiResponse<Void> deleteCustom(@PathVariable Long customId) {
|
||||
productManageService.deleteCustom(customId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Review;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.service.merchant.ReviewService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController("merchantReviewController")
|
||||
@RequestMapping("/merchant/reviews")
|
||||
public class ReviewController {
|
||||
|
||||
@Autowired
|
||||
private ReviewService reviewService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Review>> getReviews(HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Review> reviews = reviewService.getReviews(merchantId);
|
||||
return ApiResponse.success(reviews);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Review> getReview(@PathVariable Long id) {
|
||||
Review review = reviewService.getReview(id);
|
||||
return ApiResponse.success(review);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/reply")
|
||||
public ApiResponse<Void> replyReview(@PathVariable Long id,
|
||||
@RequestParam String reply,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
reviewService.replyReview(merchantId, id, reply);
|
||||
return ApiResponse.success(null);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/{id}/handle")
|
||||
public ApiResponse<Void> handleComplaint(@PathVariable Long id,
|
||||
@RequestParam String reply,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
reviewService.handleComplaint(merchantId, id, reply);
|
||||
return ApiResponse.success(null);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.service.merchant.StatisticsService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/statistics")
|
||||
public class StatisticsController {
|
||||
|
||||
@Autowired
|
||||
private StatisticsService statisticsService;
|
||||
|
||||
@GetMapping("/sales")
|
||||
public ApiResponse<Map<String, Object>> getSalesStatistics(
|
||||
@RequestParam(defaultValue = "day") String period,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Map<String, Object> stats = statisticsService.getSalesStatistics(merchantId, period);
|
||||
return ApiResponse.success(stats);
|
||||
}
|
||||
|
||||
@GetMapping("/revenue")
|
||||
public ApiResponse<Map<String, Object>> getRevenueStatistics(
|
||||
@RequestParam(defaultValue = "day") String period,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Map<String, Object> stats = statisticsService.getRevenueStatistics(merchantId, period);
|
||||
return ApiResponse.success(stats);
|
||||
}
|
||||
|
||||
@GetMapping("/hot-products")
|
||||
public ApiResponse<List<Map<String, Object>>> getHotProducts(
|
||||
@RequestParam(defaultValue = "10") int limit,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Map<String, Object>> products = statisticsService.getHotProducts(merchantId, limit);
|
||||
return ApiResponse.success(products);
|
||||
}
|
||||
|
||||
@GetMapping("/active-users")
|
||||
public ApiResponse<List<Map<String, Object>>> getActiveUsers(
|
||||
@RequestParam(defaultValue = "30") int days,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Map<String, Object>> users = statisticsService.getActiveUsers(merchantId, days);
|
||||
return ApiResponse.success(users);
|
||||
}
|
||||
|
||||
@GetMapping("/sticky-users")
|
||||
public ApiResponse<List<Map<String, Object>>> getStickyUsers(
|
||||
@RequestParam(defaultValue = "30") int days,
|
||||
@RequestParam(defaultValue = "5") int minOrders,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Map<String, Object>> users = statisticsService.getStickyUsers(merchantId, days, minOrders);
|
||||
return ApiResponse.success(users);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.nlshop.controller.merchant;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.User;
|
||||
import com.nlshop.service.merchant.UserManageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/merchant/users")
|
||||
public class UserManageController {
|
||||
|
||||
@Autowired
|
||||
private UserManageService userManageService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResponse<User>> getUsers(
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest request) {
|
||||
Long merchantId = (Long) request.getAttribute("userId");
|
||||
if (merchantId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
PageResponse<User> response = userManageService.getUsers(pageNum, pageSize);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Announcement;
|
||||
import com.nlshop.service.merchant.AnnouncementService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController("userAnnouncementController")
|
||||
@RequestMapping("/announcements")
|
||||
public class AnnouncementController {
|
||||
|
||||
@Autowired
|
||||
private AnnouncementService announcementService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Announcement>> getAnnouncements() {
|
||||
List<Announcement> announcements = announcementService.getPublishedAnnouncements();
|
||||
return ApiResponse.success(announcements);
|
||||
}
|
||||
}
|
||||
116
src/main/java/com/nlshop/controller/user/CartController.java
Normal file
116
src/main/java/com/nlshop/controller/user/CartController.java
Normal file
@@ -0,0 +1,116 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.request.CartAddRequest;
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.CartItem;
|
||||
import com.nlshop.service.user.CartService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/cart")
|
||||
public class CartController {
|
||||
|
||||
@Autowired
|
||||
private CartService cartService;
|
||||
|
||||
@PostMapping("/add")
|
||||
public ApiResponse<CartItem> addToCart(@Validated @RequestBody CartAddRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
Long userId = (Long) httpRequest.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
CartItem item = cartService.addToCart(userId, request);
|
||||
return ApiResponse.success(item);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<CartItem>> getCart(HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<CartItem> items = cartService.getCart(userId);
|
||||
return ApiResponse.success(items);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Void> updateQuantity(@PathVariable Long id,
|
||||
@RequestParam Integer quantity,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
cartService.updateQuantity(userId, id, quantity);
|
||||
return ApiResponse.success(null);
|
||||
} catch (RuntimeException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResponse<Void> deleteCartItem(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
cartService.deleteCartItem(userId, id);
|
||||
return ApiResponse.success(null);
|
||||
} catch (RuntimeException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/batch")
|
||||
public ApiResponse<Void> deleteBatch(@RequestBody List<Long> ids,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return ApiResponse.error("请选择要删除的商品");
|
||||
}
|
||||
try {
|
||||
cartService.deleteBatch(userId, ids);
|
||||
return ApiResponse.success(null);
|
||||
} catch (RuntimeException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步cookies中的购物车数据(用于登录后同步)
|
||||
*/
|
||||
@PostMapping("/sync")
|
||||
public ApiResponse<Void> syncCart(@RequestBody List<CartAddRequest> items,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
if (items == null || items.isEmpty()) {
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
try {
|
||||
cartService.syncCartFromCookies(userId, items);
|
||||
return ApiResponse.success(null);
|
||||
} catch (Exception e) {
|
||||
// 同步失败不应该影响登录流程,只记录日志
|
||||
System.err.println("同步购物车失败: userId=" + userId + ", error=" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return ApiResponse.success(null); // 返回成功,避免影响登录流程
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Message;
|
||||
import com.nlshop.service.user.MessageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController("userMessageController")
|
||||
@RequestMapping("/message")
|
||||
public class MessageController {
|
||||
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Message> createMessage(@RequestParam Long merchantId,
|
||||
@RequestParam(required = false) Long orderId,
|
||||
@RequestParam String content,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Message message = messageService.createMessage(userId, merchantId, orderId, content);
|
||||
return ApiResponse.success(message);
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Message>> getMessages(HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Message> messages = messageService.getMessages(userId);
|
||||
return ApiResponse.success(messages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.request.OrderCreateRequest;
|
||||
import com.nlshop.dto.request.PaymentRequest;
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.OrderDetailResponse;
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.entity.OrderTrack;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.service.user.OrderService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/order")
|
||||
public class OrderController {
|
||||
|
||||
@Autowired
|
||||
private OrderService orderService;
|
||||
|
||||
@PostMapping("/create")
|
||||
public ApiResponse<Order> createOrder(@Validated @RequestBody OrderCreateRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
Long userId = (Long) httpRequest.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
Order order = orderService.createOrder(userId, request);
|
||||
return ApiResponse.success(order);
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/pay")
|
||||
public ApiResponse<Void> payOrder(@PathVariable Long id,
|
||||
@Validated @RequestBody PaymentRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
Long userId = (Long) httpRequest.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
orderService.payOrder(userId, id, request);
|
||||
return ApiResponse.success(null);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/orders")
|
||||
public ApiResponse<List<Order>> getOrders(@RequestParam(required = false) String status,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<Order> orders = orderService.getOrders(userId, status);
|
||||
return ApiResponse.success(orders);
|
||||
}
|
||||
|
||||
@GetMapping("/orders/{id}")
|
||||
public ApiResponse<OrderDetailResponse> getOrderDetail(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
OrderDetailResponse orderDetail = orderService.getOrderDetail(id, userId);
|
||||
return ApiResponse.success(orderDetail);
|
||||
}
|
||||
|
||||
@GetMapping("/orders/{id}/track")
|
||||
public ApiResponse<List<OrderTrack>> getOrderTrack(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
List<OrderTrack> tracks = orderService.getOrderTrack(userId, id);
|
||||
return ApiResponse.success(tracks);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import com.nlshop.service.user.ProductService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/products")
|
||||
public class ProductController {
|
||||
|
||||
@Autowired
|
||||
private ProductService productService;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<PageResponse<Product>> getProducts(
|
||||
@RequestParam(required = false) String category,
|
||||
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||
PageResponse<Product> response = productService.getProducts(category, pageNum, pageSize);
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<Map<String, Object>> getProductDetail(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = null;
|
||||
try {
|
||||
userId = (Long) request.getAttribute("userId");
|
||||
} catch (Exception e) {
|
||||
// 未登录用户也可以查看商品详情
|
||||
}
|
||||
Map<String, Object> detail = productService.getProductDetail(id, userId);
|
||||
return ApiResponse.success(detail);
|
||||
}
|
||||
|
||||
@GetMapping("/hot")
|
||||
public ApiResponse<List<Product>> getHotProducts(@RequestParam(defaultValue = "10") int limit) {
|
||||
List<Product> products = productService.getHotProducts(limit);
|
||||
return ApiResponse.success(products);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量记录浏览行为(用于登录后同步cookies中的浏览历史)
|
||||
*/
|
||||
@PostMapping("/record-views")
|
||||
public ApiResponse<Void> recordViews(@RequestBody List<Long> productIds,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
if (productIds == null || productIds.isEmpty()) {
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
// 批量记录浏览行为
|
||||
for (Long productId : productIds) {
|
||||
try {
|
||||
cfService.recordView(userId, productId);
|
||||
} catch (Exception e) {
|
||||
// 单个失败不影响其他,只记录日志
|
||||
System.err.println("记录浏览行为失败: userId=" + userId + ", productId=" + productId + ", error=" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.service.user.RecommendService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/recommend")
|
||||
public class RecommendController {
|
||||
|
||||
@Autowired
|
||||
private RecommendService recommendService;
|
||||
|
||||
@GetMapping
|
||||
public ApiResponse<List<Product>> getRecommendations(
|
||||
@RequestParam(defaultValue = "10") int limit,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
// 推荐功能可以允许未登录用户,userId可以为null
|
||||
List<Product> products = recommendService.getRecommendations(userId, limit);
|
||||
return ApiResponse.success(products);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.entity.Review;
|
||||
import com.nlshop.mapper.ReviewMapper;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController("userReviewController")
|
||||
@RequestMapping("/review")
|
||||
public class ReviewController {
|
||||
|
||||
@Autowired
|
||||
private ReviewMapper reviewMapper;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
@PostMapping
|
||||
public ApiResponse<Review> createReview(@RequestParam Long orderId,
|
||||
@RequestParam Long productId,
|
||||
@RequestParam Integer rating,
|
||||
@RequestParam(required = false) String content,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
// 检查是否已经评价过
|
||||
Review existing = reviewMapper.selectByOrderId(orderId);
|
||||
if (existing != null) {
|
||||
return ApiResponse.error("该订单已评价");
|
||||
}
|
||||
|
||||
Review review = new Review();
|
||||
review.setUserId(userId);
|
||||
review.setOrderId(orderId);
|
||||
review.setProductId(productId);
|
||||
review.setRating(rating);
|
||||
review.setContent(content);
|
||||
reviewMapper.insert(review);
|
||||
|
||||
// 记录评分行为到user_behavior表(用于协同过滤算法)
|
||||
cfService.recordRating(userId, productId, new BigDecimal(rating));
|
||||
|
||||
return ApiResponse.success(review);
|
||||
}
|
||||
|
||||
@GetMapping("/by-orders")
|
||||
public ApiResponse<List<Review>> getReviewsByOrders(@RequestParam String orderIds,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
try {
|
||||
List<Long> orderIdList = Arrays.stream(orderIds.split(","))
|
||||
.map(Long::parseLong)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Review> reviews = reviewMapper.selectByOrderIdList(orderIdList);
|
||||
return ApiResponse.success(reviews);
|
||||
} catch (Exception e) {
|
||||
return ApiResponse.error("参数错误");
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/by-order")
|
||||
public ApiResponse<Review> getReviewByOrder(@RequestParam Long orderId,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
|
||||
Review review = reviewMapper.selectByOrderId(orderId);
|
||||
if (review == null) {
|
||||
return ApiResponse.error("未找到评论");
|
||||
}
|
||||
|
||||
// 验证评论是否属于当前用户
|
||||
if (!review.getUserId().equals(userId)) {
|
||||
return ApiResponse.error("无权访问");
|
||||
}
|
||||
|
||||
return ApiResponse.success(review);
|
||||
}
|
||||
}
|
||||
145
src/main/java/com/nlshop/controller/user/UserController.java
Normal file
145
src/main/java/com/nlshop/controller/user/UserController.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package com.nlshop.controller.user;
|
||||
|
||||
import com.nlshop.dto.request.LoginRequest;
|
||||
import com.nlshop.dto.request.UserRegisterRequest;
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import com.nlshop.dto.response.LoginResponse;
|
||||
import com.nlshop.entity.User;
|
||||
import com.nlshop.entity.UserAddress;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.service.user.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ApiResponse<User> register(@Validated @RequestBody UserRegisterRequest request) {
|
||||
User user = userService.register(request);
|
||||
return ApiResponse.success(user);
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ApiResponse<LoginResponse> login(@Validated @RequestBody LoginRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
User user = userService.login(request.getUsername(), request.getPassword());
|
||||
|
||||
// 将用户信息存入Session
|
||||
HttpSession session = httpRequest.getSession(true);
|
||||
session.setAttribute("userId", user.getId());
|
||||
session.setAttribute("userType", "user");
|
||||
session.setAttribute("username", user.getUsername());
|
||||
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setToken(""); // 不再使用token,设为空字符串
|
||||
response.setUserId(user.getId());
|
||||
response.setUserType("user");
|
||||
response.setUsername(user.getUsername());
|
||||
response.setName(user.getName());
|
||||
|
||||
return ApiResponse.success(response);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ApiResponse<Void> logout(HttpServletRequest request) {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
session.invalidate();
|
||||
}
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@GetMapping("/profile")
|
||||
public ApiResponse<User> getProfile(HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
User user = userService.getProfile(userId);
|
||||
return ApiResponse.success(user);
|
||||
}
|
||||
|
||||
@PutMapping("/profile")
|
||||
public ApiResponse<User> updateProfile(@RequestBody User user, HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
User updated = userService.updateProfile(userId, user);
|
||||
return ApiResponse.success(updated);
|
||||
}
|
||||
|
||||
@PostMapping("/avatar")
|
||||
public ApiResponse<String> uploadAvatar(@RequestParam("file") MultipartFile file,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
// 文件上传逻辑在Controller中处理,这里简化
|
||||
return ApiResponse.success("上传成功");
|
||||
}
|
||||
|
||||
@GetMapping("/address")
|
||||
public ApiResponse<List<UserAddress>> getAddresses(HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
List<UserAddress> addresses = userService.getAddresses(userId);
|
||||
return ApiResponse.success(addresses);
|
||||
}
|
||||
|
||||
@PostMapping("/address")
|
||||
public ApiResponse<UserAddress> addAddress(@RequestBody UserAddress address,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
UserAddress added = userService.addAddress(userId, address);
|
||||
return ApiResponse.success(added);
|
||||
}
|
||||
|
||||
@PutMapping("/address/{id}")
|
||||
public ApiResponse<UserAddress> updateAddress(@PathVariable Long id,
|
||||
@RequestBody UserAddress address,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
UserAddress updated = userService.updateAddress(userId, id, address);
|
||||
return ApiResponse.success(updated);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/address/{id}")
|
||||
public ApiResponse<Void> deleteAddress(@PathVariable Long id,
|
||||
HttpServletRequest request) {
|
||||
Long userId = (Long) request.getAttribute("userId");
|
||||
if (userId == null) {
|
||||
return ApiResponse.error("请先登录");
|
||||
}
|
||||
try {
|
||||
userService.deleteAddress(userId, id);
|
||||
return ApiResponse.success(null);
|
||||
} catch (BusinessException e) {
|
||||
return ApiResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/main/java/com/nlshop/dto/request/CartAddRequest.java
Normal file
19
src/main/java/com/nlshop/dto/request/CartAddRequest.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
public class CartAddRequest {
|
||||
@NotNull(message = "商品ID不能为空")
|
||||
private Long productId;
|
||||
|
||||
private Long specId;
|
||||
|
||||
@NotNull(message = "数量不能为空")
|
||||
private Integer quantity;
|
||||
|
||||
private String customSweetness;
|
||||
private String customIce;
|
||||
private String toppings; // JSON格式
|
||||
}
|
||||
13
src/main/java/com/nlshop/dto/request/LoginRequest.java
Normal file
13
src/main/java/com/nlshop/dto/request/LoginRequest.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class MerchantRegisterRequest {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "门店名称不能为空")
|
||||
private String storeName;
|
||||
|
||||
private String address;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String managerName;
|
||||
}
|
||||
17
src/main/java/com/nlshop/dto/request/OrderCreateRequest.java
Normal file
17
src/main/java/com/nlshop/dto/request/OrderCreateRequest.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Data
|
||||
public class OrderCreateRequest {
|
||||
@NotNull(message = "商家ID不能为空")
|
||||
private Long merchantId;
|
||||
|
||||
@NotBlank(message = "收货地址不能为空")
|
||||
private String address;
|
||||
|
||||
@NotBlank(message = "联系电话不能为空")
|
||||
private String contactPhone;
|
||||
}
|
||||
10
src/main/java/com/nlshop/dto/request/PaymentRequest.java
Normal file
10
src/main/java/com/nlshop/dto/request/PaymentRequest.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class PaymentRequest {
|
||||
@NotBlank(message = "支付方式不能为空")
|
||||
private String paymentMethod; // wechat/alipay
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.nlshop.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@Data
|
||||
public class UserRegisterRequest {
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Pattern(regexp = ".{6,}", message = "密码长度至少6位")
|
||||
private String password;
|
||||
|
||||
private String name;
|
||||
private Integer gender;
|
||||
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
private String email;
|
||||
private String address;
|
||||
}
|
||||
40
src/main/java/com/nlshop/dto/response/ApiResponse.java
Normal file
40
src/main/java/com/nlshop/dto/response/ApiResponse.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.nlshop.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ApiResponse<T> {
|
||||
private Integer code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setCode(200);
|
||||
response.setMessage("操作成功");
|
||||
response.setData(data);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setCode(200);
|
||||
response.setMessage(message);
|
||||
response.setData(data);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setCode(500);
|
||||
response.setMessage(message);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(Integer code, String message) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setCode(code);
|
||||
response.setMessage(message);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
12
src/main/java/com/nlshop/dto/response/LoginResponse.java
Normal file
12
src/main/java/com/nlshop/dto/response/LoginResponse.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class LoginResponse {
|
||||
private String token;
|
||||
private Long userId;
|
||||
private String userType; // user/merchant
|
||||
private String username;
|
||||
private String name;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.nlshop.dto.response;
|
||||
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.entity.OrderItem;
|
||||
import com.nlshop.entity.OrderTrack;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class OrderDetailResponse {
|
||||
private Order order;
|
||||
private List<OrderItem> items;
|
||||
private List<OrderTrack> tracks;
|
||||
private Map<Long, String> productImages; // 商品ID到图片URL的映射
|
||||
|
||||
public static OrderDetailResponse of(Order order, List<OrderItem> items, List<OrderTrack> tracks, Map<Long, String> productImages) {
|
||||
OrderDetailResponse response = new OrderDetailResponse();
|
||||
response.setOrder(order);
|
||||
response.setItems(items);
|
||||
response.setTracks(tracks);
|
||||
response.setProductImages(productImages);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
23
src/main/java/com/nlshop/dto/response/PageResponse.java
Normal file
23
src/main/java/com/nlshop/dto/response/PageResponse.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.nlshop.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class PageResponse<T> {
|
||||
private List<T> list;
|
||||
private Long total;
|
||||
private Integer pageNum;
|
||||
private Integer pageSize;
|
||||
private Integer pages;
|
||||
|
||||
public static <T> PageResponse<T> of(List<T> list, Long total, Integer pageNum, Integer pageSize) {
|
||||
PageResponse<T> response = new PageResponse<>();
|
||||
response.setList(list);
|
||||
response.setTotal(total);
|
||||
response.setPageNum(pageNum);
|
||||
response.setPageSize(pageSize);
|
||||
response.setPages((int) Math.ceil((double) total / pageSize));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
15
src/main/java/com/nlshop/entity/Announcement.java
Normal file
15
src/main/java/com/nlshop/entity/Announcement.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Announcement {
|
||||
private Long id;
|
||||
private Long merchantId;
|
||||
private String title;
|
||||
private String content;
|
||||
private String type; // ACTIVITY-活动,NOTICE-通知
|
||||
private Integer status; // 0-下架,1-发布
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
21
src/main/java/com/nlshop/entity/CartItem.java
Normal file
21
src/main/java/com/nlshop/entity/CartItem.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class CartItem {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long productId;
|
||||
private Long specId;
|
||||
private Integer quantity;
|
||||
private String customSweetness;
|
||||
private String customIce;
|
||||
private String toppings; // JSON格式
|
||||
private LocalDateTime createTime;
|
||||
|
||||
// 关联查询字段
|
||||
private Product product;
|
||||
private ProductSpec spec;
|
||||
}
|
||||
17
src/main/java/com/nlshop/entity/Inventory.java
Normal file
17
src/main/java/com/nlshop/entity/Inventory.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Inventory {
|
||||
private Long id;
|
||||
private Long merchantId;
|
||||
private String materialName;
|
||||
private BigDecimal quantity;
|
||||
private String unit;
|
||||
private BigDecimal minThreshold;
|
||||
private LocalDateTime lastInTime;
|
||||
private LocalDateTime lastOutTime;
|
||||
}
|
||||
15
src/main/java/com/nlshop/entity/InventoryLog.java
Normal file
15
src/main/java/com/nlshop/entity/InventoryLog.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class InventoryLog {
|
||||
private Long id;
|
||||
private Long inventoryId;
|
||||
private String operationType; // IN-入库,OUT-出库
|
||||
private BigDecimal quantity;
|
||||
private String operator;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
19
src/main/java/com/nlshop/entity/Merchant.java
Normal file
19
src/main/java/com/nlshop/entity/Merchant.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Merchant {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String password;
|
||||
private String storeName;
|
||||
private String address;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String managerName;
|
||||
private String avatar;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
21
src/main/java/com/nlshop/entity/Message.java
Normal file
21
src/main/java/com/nlshop/entity/Message.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Message {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long merchantId;
|
||||
private Long orderId;
|
||||
private String content;
|
||||
private String reply;
|
||||
private Integer status; // 0-未回复,1-已回复
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime replyTime;
|
||||
|
||||
// 关联查询字段
|
||||
private User user;
|
||||
private Order order;
|
||||
}
|
||||
25
src/main/java/com/nlshop/entity/Order.java
Normal file
25
src/main/java/com/nlshop/entity/Order.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Order {
|
||||
private Long id;
|
||||
private String orderNo;
|
||||
private Long userId;
|
||||
private Long merchantId;
|
||||
private BigDecimal totalAmount;
|
||||
private String paymentMethod; // wechat/alipay
|
||||
private Integer paymentStatus; // 0-未支付,1-已支付
|
||||
private String orderStatus; // PENDING_PAY, MAKING, READY, COMPLETED, CANCELLED
|
||||
private String address;
|
||||
private String contactPhone;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime payTime;
|
||||
private LocalDateTime completeTime;
|
||||
|
||||
// 关联查询字段
|
||||
private Merchant merchant;
|
||||
}
|
||||
19
src/main/java/com/nlshop/entity/OrderItem.java
Normal file
19
src/main/java/com/nlshop/entity/OrderItem.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class OrderItem {
|
||||
private Long id;
|
||||
private Long orderId;
|
||||
private Long productId;
|
||||
private String productName;
|
||||
private String specName;
|
||||
private Integer quantity;
|
||||
private BigDecimal price;
|
||||
private String customSweetness;
|
||||
private String customIce;
|
||||
private String toppings; // JSON格式
|
||||
private BigDecimal subtotal;
|
||||
}
|
||||
13
src/main/java/com/nlshop/entity/OrderTrack.java
Normal file
13
src/main/java/com/nlshop/entity/OrderTrack.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class OrderTrack {
|
||||
private Long id;
|
||||
private Long orderId;
|
||||
private String status;
|
||||
private String description;
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
18
src/main/java/com/nlshop/entity/Product.java
Normal file
18
src/main/java/com/nlshop/entity/Product.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Product {
|
||||
private Long id;
|
||||
private Long merchantId;
|
||||
private String name;
|
||||
private String category;
|
||||
private String description;
|
||||
private BigDecimal basePrice;
|
||||
private String image;
|
||||
private Integer status; // 0-下架,1-上架
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
13
src/main/java/com/nlshop/entity/ProductCustom.java
Normal file
13
src/main/java/com/nlshop/entity/ProductCustom.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ProductCustom {
|
||||
private Long id;
|
||||
private Long productId;
|
||||
private String optionType; // sweetness-甜度,ice-冰度
|
||||
private String optionValue;
|
||||
private BigDecimal priceAdjust;
|
||||
}
|
||||
12
src/main/java/com/nlshop/entity/ProductSpec.java
Normal file
12
src/main/java/com/nlshop/entity/ProductSpec.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ProductSpec {
|
||||
private Long id;
|
||||
private Long productId;
|
||||
private String specName;
|
||||
private BigDecimal priceAdjust;
|
||||
}
|
||||
12
src/main/java/com/nlshop/entity/ProductTopping.java
Normal file
12
src/main/java/com/nlshop/entity/ProductTopping.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class ProductTopping {
|
||||
private Long id;
|
||||
private Long productId;
|
||||
private String toppingName;
|
||||
private BigDecimal price;
|
||||
}
|
||||
20
src/main/java/com/nlshop/entity/Review.java
Normal file
20
src/main/java/com/nlshop/entity/Review.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class Review {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long orderId;
|
||||
private Long productId;
|
||||
private Integer rating; // 1-5
|
||||
private String content;
|
||||
private String reply;
|
||||
private LocalDateTime createTime;
|
||||
|
||||
// 关联查询字段
|
||||
private User user;
|
||||
private Product product;
|
||||
}
|
||||
19
src/main/java/com/nlshop/entity/User.java
Normal file
19
src/main/java/com/nlshop/entity/User.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class User {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String password;
|
||||
private String name;
|
||||
private Integer gender; // 0-女,1-男
|
||||
private String phone;
|
||||
private String email;
|
||||
private String avatar;
|
||||
private String address;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
15
src/main/java/com/nlshop/entity/UserAddress.java
Normal file
15
src/main/java/com/nlshop/entity/UserAddress.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class UserAddress {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private String address;
|
||||
private String contactName;
|
||||
private String contactPhone;
|
||||
private Integer isDefault; // 0-否,1-是
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
15
src/main/java/com/nlshop/entity/UserBehavior.java
Normal file
15
src/main/java/com/nlshop/entity/UserBehavior.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class UserBehavior {
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long productId;
|
||||
private String behaviorType; // VIEW-浏览,PURCHASE-购买,RATING-评分
|
||||
private BigDecimal score; // 0-5
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
13
src/main/java/com/nlshop/entity/UserSimilarity.java
Normal file
13
src/main/java/com/nlshop/entity/UserSimilarity.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class UserSimilarity {
|
||||
private Long user1Id;
|
||||
private Long user2Id;
|
||||
private BigDecimal similarityScore;
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
18
src/main/java/com/nlshop/exception/BusinessException.java
Normal file
18
src/main/java/com/nlshop/exception/BusinessException.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.nlshop.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
private Integer code;
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.code = 500;
|
||||
}
|
||||
|
||||
public BusinessException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.nlshop.exception;
|
||||
|
||||
import com.nlshop.dto.response.ApiResponse;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.ConstraintViolationException;
|
||||
import java.util.Set;
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleException(Exception e) {
|
||||
e.printStackTrace();
|
||||
return ApiResponse.error("系统错误: " + e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleBusinessException(BusinessException e) {
|
||||
return ApiResponse.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleValidationException(MethodArgumentNotValidException e) {
|
||||
FieldError fieldError = e.getBindingResult().getFieldError();
|
||||
String message = fieldError != null ? fieldError.getDefaultMessage() : "参数验证失败";
|
||||
return ApiResponse.error(400, message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(BindException.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleBindException(BindException e) {
|
||||
FieldError fieldError = e.getBindingResult().getFieldError();
|
||||
String message = fieldError != null ? fieldError.getDefaultMessage() : "参数验证失败";
|
||||
return ApiResponse.error(400, message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
@ResponseBody
|
||||
public ApiResponse<?> handleConstraintViolationException(ConstraintViolationException e) {
|
||||
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
|
||||
String message = violations.iterator().next().getMessage();
|
||||
return ApiResponse.error(400, message);
|
||||
}
|
||||
}
|
||||
74
src/main/java/com/nlshop/interceptor/AuthInterceptor.java
Normal file
74
src/main/java/com/nlshop/interceptor/AuthInterceptor.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package com.nlshop.interceptor;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class AuthInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
// 允许OPTIONS请求
|
||||
if ("OPTIONS".equals(request.getMethod())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String requestPath = request.getRequestURI();
|
||||
boolean isHtmlPage = requestPath.endsWith(".html");
|
||||
boolean isMerchantPath = requestPath.startsWith("/merchant");
|
||||
|
||||
// 从Session中获取用户信息
|
||||
HttpSession session = request.getSession(false);
|
||||
Long userId = null;
|
||||
String userType = null;
|
||||
|
||||
if (session != null) {
|
||||
userId = (Long) session.getAttribute("userId");
|
||||
userType = (String) session.getAttribute("userType");
|
||||
|
||||
if (userId != null && userType != null) {
|
||||
// 将用户信息存入request
|
||||
request.setAttribute("userId", userId);
|
||||
request.setAttribute("userType", userType);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查用户是否已登录
|
||||
boolean isLoggedIn = (userId != null && userType != null);
|
||||
|
||||
// 如果未登录,进行拦截
|
||||
if (!isLoggedIn) {
|
||||
// 保存原始URL到session,登录后可以跳转回来
|
||||
if (session != null && !requestPath.equals("/login.html") &&
|
||||
!requestPath.equals("/register.html") &&
|
||||
!requestPath.equals("/merchant/login.html") &&
|
||||
!requestPath.equals("/merchant/register.html")) {
|
||||
session.setAttribute("returnUrl", requestPath);
|
||||
}
|
||||
|
||||
if (isHtmlPage) {
|
||||
// HTML页面请求:重定向到登录页
|
||||
if (isMerchantPath) {
|
||||
response.sendRedirect("/merchant/login.html");
|
||||
} else {
|
||||
response.sendRedirect("/login.html");
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// API请求:返回401 JSON错误
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":401,\"message\":\"请先登录\"}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 已登录,允许继续
|
||||
return true;
|
||||
}
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/AnnouncementMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/AnnouncementMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Announcement;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface AnnouncementMapper {
|
||||
int insert(Announcement announcement);
|
||||
List<Announcement> selectByMerchantId(Long merchantId);
|
||||
List<Announcement> selectPublished();
|
||||
Announcement selectById(Long id);
|
||||
int update(Announcement announcement);
|
||||
int delete(Long id);
|
||||
}
|
||||
21
src/main/java/com/nlshop/mapper/CartMapper.java
Normal file
21
src/main/java/com/nlshop/mapper/CartMapper.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.CartItem;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface CartMapper {
|
||||
int insert(CartItem item);
|
||||
List<CartItem> selectByUserId(Long userId);
|
||||
CartItem selectById(Long id);
|
||||
CartItem selectByUserAndProduct(@Param("userId") Long userId, @Param("productId") Long productId,
|
||||
@Param("specId") Long specId, @Param("customSweetness") String customSweetness,
|
||||
@Param("customIce") String customIce, @Param("toppings") String toppings);
|
||||
int update(CartItem item);
|
||||
int updateQuantity(@Param("id") Long id, @Param("quantity") Integer quantity);
|
||||
int delete(Long id);
|
||||
int deleteByUserId(Long userId);
|
||||
int deleteBatch(@Param("ids") List<Long> ids, @Param("userId") Long userId);
|
||||
}
|
||||
11
src/main/java/com/nlshop/mapper/InventoryLogMapper.java
Normal file
11
src/main/java/com/nlshop/mapper/InventoryLogMapper.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.InventoryLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface InventoryLogMapper {
|
||||
int insert(InventoryLog log);
|
||||
List<InventoryLog> selectByInventoryId(Long inventoryId);
|
||||
}
|
||||
17
src/main/java/com/nlshop/mapper/InventoryMapper.java
Normal file
17
src/main/java/com/nlshop/mapper/InventoryMapper.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Inventory;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface InventoryMapper {
|
||||
int insert(Inventory inventory);
|
||||
List<Inventory> selectByMerchantId(Long merchantId);
|
||||
Inventory selectById(Long id);
|
||||
Inventory selectByMerchantAndMaterial(@Param("merchantId") Long merchantId, @Param("materialName") String materialName);
|
||||
int update(Inventory inventory);
|
||||
int delete(Long id);
|
||||
List<Inventory> selectLowStock(Long merchantId);
|
||||
}
|
||||
12
src/main/java/com/nlshop/mapper/MerchantMapper.java
Normal file
12
src/main/java/com/nlshop/mapper/MerchantMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Merchant;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface MerchantMapper {
|
||||
int insert(Merchant merchant);
|
||||
Merchant selectById(Long id);
|
||||
Merchant selectByUsername(String username);
|
||||
int update(Merchant merchant);
|
||||
}
|
||||
14
src/main/java/com/nlshop/mapper/MessageMapper.java
Normal file
14
src/main/java/com/nlshop/mapper/MessageMapper.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Message;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface MessageMapper {
|
||||
int insert(Message message);
|
||||
List<Message> selectByUserId(Long userId);
|
||||
List<Message> selectByMerchantId(Long merchantId);
|
||||
Message selectById(Long id);
|
||||
int update(Message message);
|
||||
}
|
||||
12
src/main/java/com/nlshop/mapper/OrderItemMapper.java
Normal file
12
src/main/java/com/nlshop/mapper/OrderItemMapper.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.OrderItem;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OrderItemMapper {
|
||||
int insert(OrderItem item);
|
||||
List<OrderItem> selectByOrderId(Long orderId);
|
||||
OrderItem selectById(Long id);
|
||||
}
|
||||
22
src/main/java/com/nlshop/mapper/OrderMapper.java
Normal file
22
src/main/java/com/nlshop/mapper/OrderMapper.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Order;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OrderMapper {
|
||||
int insert(Order order);
|
||||
Order selectById(Long id);
|
||||
Order selectByOrderNo(String orderNo);
|
||||
List<Order> selectByUserId(@Param("userId") Long userId, @Param("status") String status);
|
||||
List<Order> selectByMerchantId(@Param("merchantId") Long merchantId, @Param("status") String status);
|
||||
int update(Order order);
|
||||
int updateStatus(@Param("id") Long id, @Param("status") String status);
|
||||
int updatePayment(@Param("id") Long id, @Param("paymentStatus") Integer paymentStatus,
|
||||
@Param("paymentMethod") String paymentMethod);
|
||||
List<java.util.Map<String, Object>> selectStickyUsers(@Param("merchantId") Long merchantId,
|
||||
@Param("days") int days,
|
||||
@Param("minOrders") int minOrders);
|
||||
}
|
||||
11
src/main/java/com/nlshop/mapper/OrderTrackMapper.java
Normal file
11
src/main/java/com/nlshop/mapper/OrderTrackMapper.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.OrderTrack;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface OrderTrackMapper {
|
||||
int insert(OrderTrack track);
|
||||
List<OrderTrack> selectByOrderId(Long orderId);
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/ProductCustomMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/ProductCustomMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.ProductCustom;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductCustomMapper {
|
||||
int insert(ProductCustom custom);
|
||||
List<ProductCustom> selectByProductId(Long productId);
|
||||
ProductCustom selectById(Long id);
|
||||
int update(ProductCustom custom);
|
||||
int delete(Long id);
|
||||
int deleteByProductId(Long productId);
|
||||
}
|
||||
17
src/main/java/com/nlshop/mapper/ProductMapper.java
Normal file
17
src/main/java/com/nlshop/mapper/ProductMapper.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Product;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductMapper {
|
||||
int insert(Product product);
|
||||
Product selectById(Long id);
|
||||
List<Product> selectByMerchantId(@Param("merchantId") Long merchantId, @Param("status") Integer status);
|
||||
List<Product> selectAll(@Param("category") String category, @Param("status") Integer status);
|
||||
int update(Product product);
|
||||
int updateStatus(@Param("id") Long id, @Param("status") Integer status);
|
||||
List<Product> selectHotProducts(int limit);
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/ProductSpecMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/ProductSpecMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.ProductSpec;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductSpecMapper {
|
||||
int insert(ProductSpec spec);
|
||||
List<ProductSpec> selectByProductId(Long productId);
|
||||
ProductSpec selectById(Long id);
|
||||
int update(ProductSpec spec);
|
||||
int delete(Long id);
|
||||
int deleteByProductId(Long productId);
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/ProductToppingMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/ProductToppingMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.ProductTopping;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProductToppingMapper {
|
||||
int insert(ProductTopping topping);
|
||||
List<ProductTopping> selectByProductId(Long productId);
|
||||
ProductTopping selectById(Long id);
|
||||
int update(ProductTopping topping);
|
||||
int delete(Long id);
|
||||
int deleteByProductId(Long productId);
|
||||
}
|
||||
17
src/main/java/com/nlshop/mapper/ReviewMapper.java
Normal file
17
src/main/java/com/nlshop/mapper/ReviewMapper.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.Review;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ReviewMapper {
|
||||
int insert(Review review);
|
||||
List<Review> selectByMerchantId(Long merchantId);
|
||||
List<Review> selectByProductId(Long productId);
|
||||
Review selectById(Long id);
|
||||
Review selectByOrderId(Long orderId);
|
||||
List<Review> selectByOrderIdList(@Param("orderIds") List<Long> orderIds);
|
||||
int update(Review review);
|
||||
}
|
||||
15
src/main/java/com/nlshop/mapper/UserAddressMapper.java
Normal file
15
src/main/java/com/nlshop/mapper/UserAddressMapper.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.UserAddress;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserAddressMapper {
|
||||
int insert(UserAddress address);
|
||||
List<UserAddress> selectByUserId(Long userId);
|
||||
UserAddress selectById(Long id);
|
||||
int update(UserAddress address);
|
||||
int delete(Long id);
|
||||
UserAddress selectDefaultByUserId(Long userId);
|
||||
}
|
||||
19
src/main/java/com/nlshop/mapper/UserBehaviorMapper.java
Normal file
19
src/main/java/com/nlshop/mapper/UserBehaviorMapper.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.UserBehavior;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserBehaviorMapper {
|
||||
int insert(UserBehavior behavior);
|
||||
List<UserBehavior> selectByUserId(Long userId);
|
||||
List<UserBehavior> selectByProductId(Long productId);
|
||||
UserBehavior selectByUserAndProduct(@Param("userId") Long userId, @Param("productId") Long productId,
|
||||
@Param("behaviorType") String behaviorType);
|
||||
int update(UserBehavior behavior);
|
||||
List<Long> selectProductIdsByUserId(Long userId);
|
||||
List<Long> selectUserIdsByProductId(Long productId);
|
||||
List<java.util.Map<String, Object>> selectActiveUsers(@Param("merchantId") Long merchantId, @Param("days") int days);
|
||||
}
|
||||
16
src/main/java/com/nlshop/mapper/UserMapper.java
Normal file
16
src/main/java/com/nlshop/mapper/UserMapper.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserMapper {
|
||||
int insert(User user);
|
||||
User selectById(Long id);
|
||||
User selectByUsername(String username);
|
||||
int update(User user);
|
||||
int updatePassword(@Param("id") Long id, @Param("password") String password);
|
||||
List<User> selectAll();
|
||||
}
|
||||
13
src/main/java/com/nlshop/mapper/UserSimilarityMapper.java
Normal file
13
src/main/java/com/nlshop/mapper/UserSimilarityMapper.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.nlshop.mapper;
|
||||
|
||||
import com.nlshop.entity.UserSimilarity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface UserSimilarityMapper {
|
||||
int insertOrUpdate(UserSimilarity similarity);
|
||||
UserSimilarity selectByUserIds(@Param("user1Id") Long user1Id, @Param("user2Id") Long user2Id);
|
||||
List<UserSimilarity> selectSimilarUsers(@Param("userId") Long userId, @Param("limit") int limit);
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
package com.nlshop.service.algorithm;
|
||||
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.UserBehavior;
|
||||
import com.nlshop.entity.UserSimilarity;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.UserBehaviorMapper;
|
||||
import com.nlshop.mapper.UserSimilarityMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class CollaborativeFilteringService {
|
||||
|
||||
@Autowired
|
||||
private UserBehaviorMapper userBehaviorMapper;
|
||||
|
||||
@Autowired
|
||||
private UserSimilarityMapper userSimilarityMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
/**
|
||||
* 计算用户相似度矩阵
|
||||
*/
|
||||
public void calculateUserSimilarity(Long userId) {
|
||||
List<Long> productIds = userBehaviorMapper.selectProductIdsByUserId(userId);
|
||||
if (productIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有用户
|
||||
Set<Long> allUserIds = new HashSet<>();
|
||||
for (Long productId : productIds) {
|
||||
allUserIds.addAll(userBehaviorMapper.selectUserIdsByProductId(productId));
|
||||
}
|
||||
allUserIds.remove(userId);
|
||||
|
||||
// 计算与每个用户的相似度
|
||||
Map<Long, Map<Long, BigDecimal>> userProductScores = buildUserProductMatrix(userId, allUserIds);
|
||||
|
||||
for (Long otherUserId : allUserIds) {
|
||||
BigDecimal similarity = calculateCosineSimilarity(
|
||||
userProductScores.get(userId),
|
||||
userProductScores.get(otherUserId)
|
||||
);
|
||||
|
||||
if (similarity.compareTo(BigDecimal.ZERO) > 0) {
|
||||
UserSimilarity userSimilarity = new UserSimilarity();
|
||||
userSimilarity.setUser1Id(Math.min(userId, otherUserId));
|
||||
userSimilarity.setUser2Id(Math.max(userId, otherUserId));
|
||||
userSimilarity.setSimilarityScore(similarity);
|
||||
userSimilarityMapper.insertOrUpdate(userSimilarity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用户-商品评分矩阵
|
||||
*/
|
||||
private Map<Long, Map<Long, BigDecimal>> buildUserProductMatrix(Long userId, Set<Long> otherUserIds) {
|
||||
Map<Long, Map<Long, BigDecimal>> matrix = new HashMap<>();
|
||||
|
||||
// 当前用户的评分
|
||||
Map<Long, BigDecimal> userScores = new HashMap<>();
|
||||
List<UserBehavior> userBehaviors = userBehaviorMapper.selectByUserId(userId);
|
||||
for (UserBehavior behavior : userBehaviors) {
|
||||
BigDecimal score = calculateBehaviorScore(behavior);
|
||||
userScores.put(behavior.getProductId(), score);
|
||||
}
|
||||
matrix.put(userId, userScores);
|
||||
|
||||
// 其他用户的评分
|
||||
for (Long otherUserId : otherUserIds) {
|
||||
Map<Long, BigDecimal> otherScores = new HashMap<>();
|
||||
List<UserBehavior> otherBehaviors = userBehaviorMapper.selectByUserId(otherUserId);
|
||||
for (UserBehavior behavior : otherBehaviors) {
|
||||
BigDecimal score = calculateBehaviorScore(behavior);
|
||||
otherScores.put(behavior.getProductId(), score);
|
||||
}
|
||||
matrix.put(otherUserId, otherScores);
|
||||
}
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算行为评分(综合购买、评分、浏览)
|
||||
*/
|
||||
private BigDecimal calculateBehaviorScore(UserBehavior behavior) {
|
||||
BigDecimal score = BigDecimal.ZERO;
|
||||
|
||||
if ("PURCHASE".equals(behavior.getBehaviorType())) {
|
||||
score = score.add(new BigDecimal("3.0")); // 购买权重3
|
||||
} else if ("RATING".equals(behavior.getBehaviorType())) {
|
||||
score = score.add(behavior.getScore()); // 评分直接使用
|
||||
} else if ("VIEW".equals(behavior.getBehaviorType())) {
|
||||
score = score.add(new BigDecimal("1.0")); // 浏览权重1
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算余弦相似度
|
||||
*/
|
||||
private BigDecimal calculateCosineSimilarity(Map<Long, BigDecimal> user1Scores, Map<Long, BigDecimal> user2Scores) {
|
||||
Set<Long> commonProducts = new HashSet<>(user1Scores.keySet());
|
||||
commonProducts.retainAll(user2Scores.keySet());
|
||||
|
||||
if (commonProducts.isEmpty()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal dotProduct = BigDecimal.ZERO;
|
||||
BigDecimal norm1 = BigDecimal.ZERO;
|
||||
BigDecimal norm2 = BigDecimal.ZERO;
|
||||
|
||||
for (Long productId : commonProducts) {
|
||||
BigDecimal score1 = user1Scores.get(productId);
|
||||
BigDecimal score2 = user2Scores.get(productId);
|
||||
dotProduct = dotProduct.add(score1.multiply(score2));
|
||||
norm1 = norm1.add(score1.multiply(score1));
|
||||
norm2 = norm2.add(score2.multiply(score2));
|
||||
}
|
||||
|
||||
if (norm1.compareTo(BigDecimal.ZERO) == 0 || norm2.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal denominator = BigDecimal.valueOf(Math.sqrt(norm1.doubleValue() * norm2.doubleValue()));
|
||||
return dotProduct.divide(denominator, 6, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推荐商品列表
|
||||
*/
|
||||
public List<Product> getRecommendations(Long userId, int limit) {
|
||||
// 获取用户已购买的商品
|
||||
List<Long> purchasedProductIds = userBehaviorMapper.selectProductIdsByUserId(userId);
|
||||
Set<Long> purchasedSet = new HashSet<>(purchasedProductIds);
|
||||
|
||||
// 获取相似用户
|
||||
List<UserSimilarity> similarUsers = userSimilarityMapper.selectSimilarUsers(userId, 10);
|
||||
|
||||
// 用于存储已推荐的商品ID,避免重复
|
||||
Set<Long> recommendedProductIds = new HashSet<>();
|
||||
List<Product> recommendations = new ArrayList<>();
|
||||
|
||||
if (similarUsers.isEmpty()) {
|
||||
// 冷启动:返回热门商品(排除已购买的)
|
||||
List<Product> hotProducts = productMapper.selectHotProducts(limit * 2); // 多取一些,避免过滤后不足
|
||||
for (Product product : hotProducts) {
|
||||
if (recommendations.size() >= limit) {
|
||||
break;
|
||||
}
|
||||
if (!purchasedSet.contains(product.getId())
|
||||
&& product.getStatus() == 1
|
||||
&& !recommendedProductIds.contains(product.getId())) {
|
||||
recommendations.add(product);
|
||||
recommendedProductIds.add(product.getId());
|
||||
}
|
||||
}
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// 计算推荐分数
|
||||
Map<Long, BigDecimal> productScores = new HashMap<>();
|
||||
|
||||
for (UserSimilarity similarity : similarUsers) {
|
||||
Long similarUserId = similarity.getUser1Id().equals(userId) ?
|
||||
similarity.getUser2Id() : similarity.getUser1Id();
|
||||
|
||||
List<UserBehavior> behaviors = userBehaviorMapper.selectByUserId(similarUserId);
|
||||
for (UserBehavior behavior : behaviors) {
|
||||
if (!purchasedSet.contains(behavior.getProductId())) {
|
||||
BigDecimal score = calculateBehaviorScore(behavior)
|
||||
.multiply(similarity.getSimilarityScore());
|
||||
productScores.merge(behavior.getProductId(), score, BigDecimal::add);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按分数排序,获取Top N
|
||||
List<Map.Entry<Long, BigDecimal>> sorted = productScores.entrySet().stream()
|
||||
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 添加协同过滤推荐的商品
|
||||
for (Map.Entry<Long, BigDecimal> entry : sorted) {
|
||||
Product product = productMapper.selectById(entry.getKey());
|
||||
if (product != null && product.getStatus() == 1) {
|
||||
recommendations.add(product);
|
||||
recommendedProductIds.add(product.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// 如果推荐数量不足,补充热门商品(排除已购买和已推荐的)
|
||||
if (recommendations.size() < limit) {
|
||||
int needCount = limit - recommendations.size();
|
||||
List<Product> hotProducts = productMapper.selectHotProducts(needCount * 2); // 多取一些,避免过滤后不足
|
||||
for (Product product : hotProducts) {
|
||||
if (recommendations.size() >= limit) {
|
||||
break;
|
||||
}
|
||||
if (!purchasedSet.contains(product.getId())
|
||||
&& product.getStatus() == 1
|
||||
&& !recommendedProductIds.contains(product.getId())) {
|
||||
recommendations.add(product);
|
||||
recommendedProductIds.add(product.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户偏好(购买后)
|
||||
*/
|
||||
public void updateUserPreference(Long userId, Long productId) {
|
||||
if (userId == null || productId == null) {
|
||||
return; // 参数无效,不记录
|
||||
}
|
||||
|
||||
try {
|
||||
// 记录购买行为
|
||||
UserBehavior behavior = userBehaviorMapper.selectByUserAndProduct(userId, productId, "PURCHASE");
|
||||
if (behavior == null) {
|
||||
behavior = new UserBehavior();
|
||||
behavior.setUserId(userId);
|
||||
behavior.setProductId(productId);
|
||||
behavior.setBehaviorType("PURCHASE");
|
||||
behavior.setScore(new BigDecimal("3.0"));
|
||||
int result = userBehaviorMapper.insert(behavior);
|
||||
if (result <= 0) {
|
||||
System.err.println("警告: 用户购买行为记录插入失败 - userId: " + userId + ", productId: " + productId);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新计算相似度
|
||||
calculateUserSimilarity(userId);
|
||||
} catch (Exception e) {
|
||||
// 记录行为失败不应该影响主流程,只打印错误日志
|
||||
System.err.println("记录用户购买行为失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录浏览行为
|
||||
*/
|
||||
public void recordView(Long userId, Long productId) {
|
||||
if (userId == null || productId == null) {
|
||||
return; // 参数无效,不记录
|
||||
}
|
||||
|
||||
try {
|
||||
UserBehavior behavior = userBehaviorMapper.selectByUserAndProduct(userId, productId, "VIEW");
|
||||
if (behavior == null) {
|
||||
behavior = new UserBehavior();
|
||||
behavior.setUserId(userId);
|
||||
behavior.setProductId(productId);
|
||||
behavior.setBehaviorType("VIEW");
|
||||
behavior.setScore(new BigDecimal("1.0"));
|
||||
int result = userBehaviorMapper.insert(behavior);
|
||||
if (result <= 0) {
|
||||
System.err.println("警告: 用户行为记录插入失败 - userId: " + userId + ", productId: " + productId);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 记录行为失败不应该影响主流程,只打印错误日志
|
||||
System.err.println("记录用户浏览行为失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录评分行为
|
||||
*/
|
||||
public void recordRating(Long userId, Long productId, BigDecimal rating) {
|
||||
if (userId == null || productId == null || rating == null) {
|
||||
return; // 参数无效,不记录
|
||||
}
|
||||
|
||||
try {
|
||||
// 查找是否已有该商品的评分记录
|
||||
UserBehavior behavior = userBehaviorMapper.selectByUserAndProduct(userId, productId, "RATING");
|
||||
if (behavior == null) {
|
||||
// 新建评分记录
|
||||
behavior = new UserBehavior();
|
||||
behavior.setUserId(userId);
|
||||
behavior.setProductId(productId);
|
||||
behavior.setBehaviorType("RATING");
|
||||
behavior.setScore(rating);
|
||||
int result = userBehaviorMapper.insert(behavior);
|
||||
if (result <= 0) {
|
||||
System.err.println("警告: 用户评分行为记录插入失败 - userId: " + userId + ", productId: " + productId);
|
||||
}
|
||||
} else {
|
||||
// 更新已有评分记录
|
||||
behavior.setScore(rating);
|
||||
int result = userBehaviorMapper.update(behavior);
|
||||
if (result <= 0) {
|
||||
System.err.println("警告: 用户评分行为记录更新失败 - userId: " + userId + ", productId: " + productId);
|
||||
}
|
||||
}
|
||||
|
||||
// 重新计算相似度
|
||||
calculateUserSimilarity(userId);
|
||||
} catch (Exception e) {
|
||||
// 记录行为失败不应该影响主流程,只打印错误日志
|
||||
System.err.println("记录用户评分行为失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Announcement;
|
||||
import com.nlshop.mapper.AnnouncementMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class AnnouncementService {
|
||||
|
||||
@Autowired
|
||||
private AnnouncementMapper announcementMapper;
|
||||
|
||||
public Announcement createAnnouncement(Long merchantId, Announcement announcement) {
|
||||
announcement.setMerchantId(merchantId);
|
||||
announcement.setStatus(1); // 默认发布
|
||||
announcementMapper.insert(announcement);
|
||||
return announcement;
|
||||
}
|
||||
|
||||
public List<Announcement> getAnnouncements(Long merchantId) {
|
||||
return announcementMapper.selectByMerchantId(merchantId);
|
||||
}
|
||||
|
||||
public Announcement getAnnouncement(Long id) {
|
||||
return announcementMapper.selectById(id);
|
||||
}
|
||||
|
||||
public void updateAnnouncement(Announcement announcement) {
|
||||
announcementMapper.update(announcement);
|
||||
}
|
||||
|
||||
public void deleteAnnouncement(Long id) {
|
||||
announcementMapper.delete(id);
|
||||
}
|
||||
|
||||
public List<Announcement> getPublishedAnnouncements() {
|
||||
return announcementMapper.selectPublished();
|
||||
}
|
||||
}
|
||||
109
src/main/java/com/nlshop/service/merchant/InventoryService.java
Normal file
109
src/main/java/com/nlshop/service/merchant/InventoryService.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Inventory;
|
||||
import com.nlshop.entity.InventoryLog;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.mapper.InventoryLogMapper;
|
||||
import com.nlshop.mapper.InventoryMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class InventoryService {
|
||||
|
||||
@Autowired
|
||||
private InventoryMapper inventoryMapper;
|
||||
|
||||
@Autowired
|
||||
private InventoryLogMapper inventoryLogMapper;
|
||||
|
||||
public List<Inventory> getInventory(Long merchantId) {
|
||||
return inventoryMapper.selectByMerchantId(merchantId);
|
||||
}
|
||||
|
||||
public Inventory getInventoryById(Long inventoryId) {
|
||||
return inventoryMapper.selectById(inventoryId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Inventory addInventory(Long merchantId, Inventory inventory) {
|
||||
// 检查是否已存在
|
||||
Inventory existing = inventoryMapper.selectByMerchantAndMaterial(
|
||||
merchantId, inventory.getMaterialName());
|
||||
|
||||
if (existing != null) {
|
||||
// 更新数量
|
||||
existing.setQuantity(existing.getQuantity().add(inventory.getQuantity()));
|
||||
existing.setLastInTime(LocalDateTime.now());
|
||||
inventoryMapper.update(existing);
|
||||
return existing;
|
||||
} else {
|
||||
// 新增
|
||||
inventory.setMerchantId(merchantId);
|
||||
inventory.setLastInTime(LocalDateTime.now());
|
||||
inventoryMapper.insert(inventory);
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void stockIn(Long inventoryId, BigDecimal quantity, String operator) {
|
||||
Inventory inventory = inventoryMapper.selectById(inventoryId);
|
||||
if (inventory == null) {
|
||||
throw new BusinessException(404, "库存记录不存在");
|
||||
}
|
||||
|
||||
inventory.setQuantity(inventory.getQuantity().add(quantity));
|
||||
inventory.setLastInTime(LocalDateTime.now());
|
||||
inventoryMapper.update(inventory);
|
||||
|
||||
// 记录日志
|
||||
InventoryLog log = new InventoryLog();
|
||||
log.setInventoryId(inventoryId);
|
||||
log.setOperationType("IN");
|
||||
log.setQuantity(quantity);
|
||||
log.setOperator(operator);
|
||||
inventoryLogMapper.insert(log);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void stockOut(Long inventoryId, BigDecimal quantity, String operator) {
|
||||
Inventory inventory = inventoryMapper.selectById(inventoryId);
|
||||
if (inventory == null) {
|
||||
throw new BusinessException(404, "库存记录不存在");
|
||||
}
|
||||
|
||||
if (inventory.getQuantity().compareTo(quantity) < 0) {
|
||||
throw new BusinessException(400, "库存不足");
|
||||
}
|
||||
|
||||
inventory.setQuantity(inventory.getQuantity().subtract(quantity));
|
||||
inventory.setLastOutTime(LocalDateTime.now());
|
||||
inventoryMapper.update(inventory);
|
||||
|
||||
// 记录日志
|
||||
InventoryLog log = new InventoryLog();
|
||||
log.setInventoryId(inventoryId);
|
||||
log.setOperationType("OUT");
|
||||
log.setQuantity(quantity);
|
||||
log.setOperator(operator);
|
||||
inventoryLogMapper.insert(log);
|
||||
}
|
||||
|
||||
public List<Inventory> getLowStockAlerts(Long merchantId) {
|
||||
return inventoryMapper.selectLowStock(merchantId);
|
||||
}
|
||||
|
||||
public void updateInventory(Inventory inventory) {
|
||||
inventoryMapper.update(inventory);
|
||||
}
|
||||
|
||||
public void deleteInventory(Long inventoryId) {
|
||||
inventoryMapper.delete(inventoryId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.dto.request.MerchantRegisterRequest;
|
||||
import com.nlshop.entity.Merchant;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.mapper.MerchantMapper;
|
||||
import com.nlshop.util.PasswordUtil;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class MerchantService {
|
||||
|
||||
@Autowired
|
||||
private MerchantMapper merchantMapper;
|
||||
|
||||
public Merchant register(MerchantRegisterRequest request) {
|
||||
if (merchantMapper.selectByUsername(request.getUsername()) != null) {
|
||||
throw new BusinessException(400, "用户名已存在");
|
||||
}
|
||||
|
||||
Merchant merchant = new Merchant();
|
||||
BeanUtils.copyProperties(request, merchant);
|
||||
merchant.setPassword(PasswordUtil.encrypt(request.getPassword()));
|
||||
merchantMapper.insert(merchant);
|
||||
return merchant;
|
||||
}
|
||||
|
||||
public Merchant login(String username, String password) {
|
||||
Merchant merchant = merchantMapper.selectByUsername(username);
|
||||
if (merchant == null || !PasswordUtil.verify(password, merchant.getPassword())) {
|
||||
throw new BusinessException(401, "用户名或密码错误");
|
||||
}
|
||||
return merchant;
|
||||
}
|
||||
|
||||
public Merchant getProfile(Long merchantId) {
|
||||
return merchantMapper.selectById(merchantId);
|
||||
}
|
||||
|
||||
public Merchant updateProfile(Long merchantId, Merchant merchant) {
|
||||
merchant.setId(merchantId);
|
||||
merchantMapper.update(merchant);
|
||||
return merchantMapper.selectById(merchantId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Message;
|
||||
import com.nlshop.mapper.MessageMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service("merchantMessageService")
|
||||
public class MessageService {
|
||||
|
||||
@Autowired
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
public List<Message> getMessages(Long merchantId) {
|
||||
return messageMapper.selectByMerchantId(merchantId);
|
||||
}
|
||||
|
||||
public Message getMessage(Long id) {
|
||||
return messageMapper.selectById(id);
|
||||
}
|
||||
|
||||
public void replyMessage(Long messageId, String reply) {
|
||||
Message message = messageMapper.selectById(messageId);
|
||||
if (message != null) {
|
||||
message.setReply(reply);
|
||||
message.setStatus(1);
|
||||
message.setReplyTime(LocalDateTime.now());
|
||||
messageMapper.update(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.response.OrderDetailResponse;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.entity.OrderItem;
|
||||
import com.nlshop.entity.OrderTrack;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.mapper.OrderItemMapper;
|
||||
import com.nlshop.mapper.OrderMapper;
|
||||
import com.nlshop.mapper.OrderTrackMapper;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class OrderManageService {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderTrackMapper orderTrackMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
public PageResponse<Order> getOrders(Long merchantId, String status, Integer pageNum, Integer pageSize) {
|
||||
PageHelper.startPage(pageNum, pageSize);
|
||||
List<Order> orders = orderMapper.selectByMerchantId(merchantId, status);
|
||||
PageInfo<Order> pageInfo = new PageInfo<>(orders);
|
||||
return PageResponse.of(orders, pageInfo.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
public OrderDetailResponse getOrderDetail(Long orderId, Long merchantId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
|
||||
List<OrderTrack> tracks = orderTrackMapper.selectByOrderId(orderId);
|
||||
|
||||
// 获取商品图片信息
|
||||
Map<Long, String> productImages = new HashMap<>();
|
||||
for (OrderItem item : items) {
|
||||
Product product = productMapper.selectById(item.getProductId());
|
||||
if (product != null) {
|
||||
productImages.put(item.getProductId(), product.getImage());
|
||||
}
|
||||
}
|
||||
|
||||
return OrderDetailResponse.of(order, items, tracks, productImages);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void acceptOrder(Long orderId, Long merchantId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
if (!"MAKING".equals(order.getOrderStatus())) {
|
||||
throw new BusinessException(400, "订单状态不正确");
|
||||
}
|
||||
|
||||
// 订单状态已经是MAKING,这里可以添加其他业务逻辑
|
||||
addOrderTrack(orderId, "MAKING", "商家已接单,正在制作中");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void rejectOrder(Long orderId, Long merchantId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
if (!"MAKING".equals(order.getOrderStatus())) {
|
||||
throw new BusinessException(400, "订单状态不正确");
|
||||
}
|
||||
|
||||
orderMapper.updateStatus(orderId, "CANCELLED");
|
||||
addOrderTrack(orderId, "CANCELLED", "商家拒单");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateOrderStatus(Long orderId, Long merchantId, String status) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
|
||||
String currentStatus = order.getOrderStatus();
|
||||
String description = "";
|
||||
|
||||
if ("READY".equals(status) && "MAKING".equals(currentStatus)) {
|
||||
description = "制作完成,等待取餐";
|
||||
} else if ("COMPLETED".equals(status) && "READY".equals(currentStatus)) {
|
||||
description = "订单已完成";
|
||||
order.setCompleteTime(LocalDateTime.now());
|
||||
orderMapper.update(order);
|
||||
} else {
|
||||
throw new BusinessException(400, "订单状态转换不正确");
|
||||
}
|
||||
|
||||
orderMapper.updateStatus(orderId, status);
|
||||
addOrderTrack(orderId, status, description);
|
||||
}
|
||||
|
||||
private void addOrderTrack(Long orderId, String status, String description) {
|
||||
OrderTrack track = new OrderTrack();
|
||||
track.setOrderId(orderId);
|
||||
track.setStatus(status);
|
||||
track.setDescription(description);
|
||||
orderTrackMapper.insert(track);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.ProductCustom;
|
||||
import com.nlshop.entity.ProductSpec;
|
||||
import com.nlshop.entity.ProductTopping;
|
||||
import com.nlshop.mapper.ProductCustomMapper;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.ProductSpecMapper;
|
||||
import com.nlshop.mapper.ProductToppingMapper;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class ProductManageService {
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductSpecMapper productSpecMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductToppingMapper productToppingMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductCustomMapper productCustomMapper;
|
||||
|
||||
public PageResponse<Product> getProducts(Long merchantId, Integer pageNum, Integer pageSize) {
|
||||
PageHelper.startPage(pageNum, pageSize);
|
||||
List<Product> products = productMapper.selectByMerchantId(merchantId, null);
|
||||
PageInfo<Product> pageInfo = new PageInfo<>(products);
|
||||
return PageResponse.of(products, pageInfo.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品完整信息(包括规格、配料、定制选项)
|
||||
*/
|
||||
public Map<String, Object> getProduct(Long productId, Long merchantId) {
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw new BusinessException(404, "商品不存在");
|
||||
}
|
||||
|
||||
// 验证商品归属
|
||||
validateProductOwnership(productId, merchantId);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("product", product);
|
||||
result.put("specs", productSpecMapper.selectByProductId(productId));
|
||||
result.put("toppings", productToppingMapper.selectByProductId(productId));
|
||||
result.put("customs", productCustomMapper.selectByProductId(productId));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证商品是否属于指定商家
|
||||
*/
|
||||
public void validateProductOwnership(Long productId, Long merchantId) {
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw new BusinessException(404, "商品不存在");
|
||||
}
|
||||
if (!product.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(403, "无权操作此商品");
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Product createProduct(Long merchantId, Product product) {
|
||||
product.setMerchantId(merchantId);
|
||||
product.setStatus(1); // 默认上架
|
||||
productMapper.insert(product);
|
||||
return product;
|
||||
}
|
||||
|
||||
public Product updateProduct(Long productId, Long merchantId, Product product) {
|
||||
// 验证商品归属
|
||||
validateProductOwnership(productId, merchantId);
|
||||
|
||||
product.setId(productId);
|
||||
productMapper.update(product);
|
||||
return productMapper.selectById(productId);
|
||||
}
|
||||
|
||||
public void offlineProduct(Long productId, Long merchantId) {
|
||||
// 验证商品归属
|
||||
validateProductOwnership(productId, merchantId);
|
||||
productMapper.updateStatus(productId, 0);
|
||||
}
|
||||
|
||||
public void onlineProduct(Long productId, Long merchantId) {
|
||||
// 验证商品归属
|
||||
validateProductOwnership(productId, merchantId);
|
||||
productMapper.updateStatus(productId, 1);
|
||||
}
|
||||
|
||||
public List<ProductSpec> getSpecs(Long productId) {
|
||||
return productSpecMapper.selectByProductId(productId);
|
||||
}
|
||||
|
||||
public ProductSpec addSpec(Long productId, ProductSpec spec) {
|
||||
spec.setProductId(productId);
|
||||
productSpecMapper.insert(spec);
|
||||
return spec;
|
||||
}
|
||||
|
||||
public void updateSpec(ProductSpec spec) {
|
||||
productSpecMapper.update(spec);
|
||||
}
|
||||
|
||||
public void deleteSpec(Long specId) {
|
||||
productSpecMapper.delete(specId);
|
||||
}
|
||||
|
||||
public List<ProductTopping> getToppings(Long productId) {
|
||||
return productToppingMapper.selectByProductId(productId);
|
||||
}
|
||||
|
||||
public ProductTopping addTopping(Long productId, ProductTopping topping) {
|
||||
topping.setProductId(productId);
|
||||
productToppingMapper.insert(topping);
|
||||
return topping;
|
||||
}
|
||||
|
||||
public void updateTopping(ProductTopping topping) {
|
||||
productToppingMapper.update(topping);
|
||||
}
|
||||
|
||||
public void deleteTopping(Long toppingId) {
|
||||
productToppingMapper.delete(toppingId);
|
||||
}
|
||||
|
||||
public List<ProductCustom> getCustoms(Long productId) {
|
||||
return productCustomMapper.selectByProductId(productId);
|
||||
}
|
||||
|
||||
public ProductCustom addCustom(Long productId, ProductCustom custom) {
|
||||
custom.setProductId(productId);
|
||||
productCustomMapper.insert(custom);
|
||||
return custom;
|
||||
}
|
||||
|
||||
public void updateCustom(ProductCustom custom) {
|
||||
productCustomMapper.update(custom);
|
||||
}
|
||||
|
||||
public void deleteCustom(Long customId) {
|
||||
productCustomMapper.delete(customId);
|
||||
}
|
||||
}
|
||||
57
src/main/java/com/nlshop/service/merchant/ReviewService.java
Normal file
57
src/main/java/com/nlshop/service/merchant/ReviewService.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.Review;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.ReviewMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class ReviewService {
|
||||
|
||||
@Autowired
|
||||
private ReviewMapper reviewMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
public List<Review> getReviews(Long merchantId) {
|
||||
return reviewMapper.selectByMerchantId(merchantId);
|
||||
}
|
||||
|
||||
public Review getReview(Long id) {
|
||||
return reviewMapper.selectById(id);
|
||||
}
|
||||
|
||||
public void replyReview(Long merchantId, Long reviewId, String reply) {
|
||||
Review review = reviewMapper.selectById(reviewId);
|
||||
if (review == null) {
|
||||
throw new BusinessException(404, "评价不存在");
|
||||
}
|
||||
|
||||
// 验证评价对应的商品是否属于当前商家
|
||||
Product product = productMapper.selectById(review.getProductId());
|
||||
if (product == null) {
|
||||
throw new BusinessException(404, "商品不存在");
|
||||
}
|
||||
if (!product.getMerchantId().equals(merchantId)) {
|
||||
throw new BusinessException(403, "无权回复该评价");
|
||||
}
|
||||
|
||||
review.setReply(reply);
|
||||
reviewMapper.update(review);
|
||||
}
|
||||
|
||||
public void handleComplaint(Long merchantId, Long reviewId, String reply) {
|
||||
replyReview(merchantId, reviewId, reply);
|
||||
}
|
||||
|
||||
public List<Review> getProductReviews(Long productId) {
|
||||
return reviewMapper.selectByProductId(productId);
|
||||
}
|
||||
}
|
||||
136
src/main/java/com/nlshop/service/merchant/StatisticsService.java
Normal file
136
src/main/java/com/nlshop/service/merchant/StatisticsService.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.nlshop.entity.Order;
|
||||
import com.nlshop.entity.OrderItem;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.mapper.OrderItemMapper;
|
||||
import com.nlshop.mapper.OrderMapper;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.UserBehaviorMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class StatisticsService {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private UserBehaviorMapper userBehaviorMapper;
|
||||
|
||||
public Map<String, Object> getSalesStatistics(Long merchantId, String period) {
|
||||
LocalDateTime startTime = getStartTime(period);
|
||||
List<Order> orders = orderMapper.selectByMerchantId(merchantId, null);
|
||||
|
||||
// 过滤时间范围和已完成订单
|
||||
orders = orders.stream()
|
||||
.filter(order -> order.getOrderStatus() != null &&
|
||||
order.getOrderStatus().equals("COMPLETED") &&
|
||||
order.getCompleteTime() != null &&
|
||||
order.getCompleteTime().isAfter(startTime))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
BigDecimal totalAmount = orders.stream()
|
||||
.map(Order::getTotalAmount)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
BigDecimal avgOrderAmount = orders.size() > 0 ?
|
||||
totalAmount.divide(new BigDecimal(orders.size()), 2, RoundingMode.HALF_UP) :
|
||||
BigDecimal.ZERO;
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalOrders", orders.size());
|
||||
result.put("totalAmount", totalAmount);
|
||||
result.put("avgOrderAmount", avgOrderAmount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getRevenueStatistics(Long merchantId, String period) {
|
||||
Map<String, Object> salesStats = getSalesStatistics(merchantId, period);
|
||||
BigDecimal totalRevenue = (BigDecimal) salesStats.get("totalAmount");
|
||||
|
||||
// 简化处理:假设成本为收入的70%(实际应该从库存等计算)
|
||||
BigDecimal totalCost = totalRevenue.multiply(new BigDecimal("0.7"));
|
||||
BigDecimal netProfit = totalRevenue.subtract(totalCost);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("totalRevenue", totalRevenue);
|
||||
result.put("totalCost", totalCost);
|
||||
result.put("netProfit", netProfit);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getHotProducts(Long merchantId, int limit) {
|
||||
List<Order> orders = orderMapper.selectByMerchantId(merchantId, "COMPLETED");
|
||||
|
||||
// 统计商品销量和销售额
|
||||
Map<Long, Integer> productSales = new HashMap<>();
|
||||
Map<Long, BigDecimal> productAmounts = new HashMap<>();
|
||||
|
||||
for (Order order : orders) {
|
||||
List<OrderItem> items = orderItemMapper.selectByOrderId(order.getId());
|
||||
for (OrderItem item : items) {
|
||||
productSales.merge(item.getProductId(), item.getQuantity(), Integer::sum);
|
||||
BigDecimal itemAmount = item.getSubtotal();
|
||||
productAmounts.merge(item.getProductId(), itemAmount, BigDecimal::add);
|
||||
}
|
||||
}
|
||||
|
||||
// 按销量排序并获取商品信息
|
||||
return productSales.entrySet().stream()
|
||||
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
|
||||
.limit(limit)
|
||||
.map(entry -> {
|
||||
Long productId = entry.getKey();
|
||||
Product product = productMapper.selectById(productId);
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("productId", productId);
|
||||
map.put("productName", product != null ? product.getName() : "未知商品");
|
||||
map.put("salesCount", entry.getValue());
|
||||
map.put("salesAmount", productAmounts.getOrDefault(productId, BigDecimal.ZERO));
|
||||
return map;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getActiveUsers(Long merchantId, int days) {
|
||||
return userBehaviorMapper.selectActiveUsers(merchantId, days);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getStickyUsers(Long merchantId, int days, int minOrders) {
|
||||
return orderMapper.selectStickyUsers(merchantId, days, minOrders);
|
||||
}
|
||||
|
||||
private LocalDateTime getStartTime(String period) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
switch (period) {
|
||||
case "day":
|
||||
return now.minusDays(1);
|
||||
case "week":
|
||||
return now.minusWeeks(1);
|
||||
case "month":
|
||||
return now.minusMonths(1);
|
||||
default:
|
||||
return now.minusDays(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.nlshop.service.merchant;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.User;
|
||||
import com.nlshop.mapper.UserMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class UserManageService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
public PageResponse<User> getUsers(Integer pageNum, Integer pageSize) {
|
||||
PageHelper.startPage(pageNum, pageSize);
|
||||
List<User> users = userMapper.selectAll();
|
||||
PageInfo<User> pageInfo = new PageInfo<>(users);
|
||||
return PageResponse.of(users, pageInfo.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
}
|
||||
127
src/main/java/com/nlshop/service/user/CartService.java
Normal file
127
src/main/java/com/nlshop/service/user/CartService.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.nlshop.dto.request.CartAddRequest;
|
||||
import com.nlshop.entity.CartItem;
|
||||
import com.nlshop.mapper.CartMapper;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class CartService {
|
||||
|
||||
@Autowired
|
||||
private CartMapper cartMapper;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
public CartItem addToCart(Long userId, CartAddRequest request) {
|
||||
// 检查是否已存在相同的商品配置
|
||||
CartItem existing = cartMapper.selectByUserAndProduct(
|
||||
userId, request.getProductId(), request.getSpecId(),
|
||||
request.getCustomSweetness(), request.getCustomIce(), request.getToppings()
|
||||
);
|
||||
|
||||
if (existing != null) {
|
||||
// 更新数量
|
||||
existing.setQuantity(existing.getQuantity() + request.getQuantity());
|
||||
cartMapper.updateQuantity(existing.getId(), existing.getQuantity());
|
||||
return cartMapper.selectById(existing.getId());
|
||||
} else {
|
||||
// 新增
|
||||
CartItem item = new CartItem();
|
||||
item.setUserId(userId);
|
||||
item.setProductId(request.getProductId());
|
||||
item.setSpecId(request.getSpecId());
|
||||
item.setQuantity(request.getQuantity());
|
||||
item.setCustomSweetness(request.getCustomSweetness());
|
||||
item.setCustomIce(request.getCustomIce());
|
||||
item.setToppings(request.getToppings());
|
||||
cartMapper.insert(item);
|
||||
|
||||
// 记录用户行为(加入购物车表示用户对商品有兴趣)
|
||||
// 如果用户已经浏览过该商品,recordView不会重复记录
|
||||
// 使用try-catch确保行为记录失败不影响购物车添加
|
||||
try {
|
||||
cfService.recordView(userId, request.getProductId());
|
||||
} catch (Exception e) {
|
||||
// 记录行为失败不应该影响购物车添加,只打印错误日志
|
||||
System.err.println("记录购物车行为失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return cartMapper.selectById(item.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public List<CartItem> getCart(Long userId) {
|
||||
return cartMapper.selectByUserId(userId);
|
||||
}
|
||||
|
||||
public void updateQuantity(Long userId, Long cartId, Integer quantity) {
|
||||
// 验证购物车项是否属于当前用户
|
||||
CartItem item = cartMapper.selectById(cartId);
|
||||
if (item == null) {
|
||||
throw new RuntimeException("购物车项不存在");
|
||||
}
|
||||
if (!item.getUserId().equals(userId)) {
|
||||
throw new RuntimeException("无权修改该商品");
|
||||
}
|
||||
cartMapper.updateQuantity(cartId, quantity);
|
||||
}
|
||||
|
||||
public void deleteCartItem(Long userId, Long cartId) {
|
||||
// 验证购物车项是否属于当前用户
|
||||
CartItem item = cartMapper.selectById(cartId);
|
||||
if (item == null) {
|
||||
throw new RuntimeException("购物车项不存在");
|
||||
}
|
||||
if (!item.getUserId().equals(userId)) {
|
||||
throw new RuntimeException("无权删除该商品");
|
||||
}
|
||||
cartMapper.delete(cartId);
|
||||
}
|
||||
|
||||
public void deleteBatch(Long userId, List<Long> cartIds) {
|
||||
if (cartIds == null || cartIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 验证所有商品都属于当前用户
|
||||
List<CartItem> items = cartMapper.selectByUserId(userId);
|
||||
for (Long cartId : cartIds) {
|
||||
boolean belongsToUser = items.stream().anyMatch(item -> item.getId().equals(cartId));
|
||||
if (!belongsToUser) {
|
||||
throw new RuntimeException("无权删除该商品");
|
||||
}
|
||||
}
|
||||
// 执行批量删除
|
||||
cartMapper.deleteBatch(cartIds, userId);
|
||||
}
|
||||
|
||||
public void clearCart(Long userId) {
|
||||
cartMapper.deleteByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从cookies同步购物车数据(登录后同步)
|
||||
* 相同商品会自动合并数量
|
||||
*/
|
||||
public void syncCartFromCookies(Long userId, List<CartAddRequest> items) {
|
||||
if (items == null || items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历每个商品,调用addToCart会自动合并相同商品
|
||||
for (CartAddRequest request : items) {
|
||||
try {
|
||||
addToCart(userId, request);
|
||||
} catch (Exception e) {
|
||||
// 单个商品同步失败不影响其他,只记录日志
|
||||
System.err.println("同步购物车商品失败: userId=" + userId +
|
||||
", productId=" + request.getProductId() + ", error=" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/main/java/com/nlshop/service/user/MessageService.java
Normal file
31
src/main/java/com/nlshop/service/user/MessageService.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.nlshop.entity.Message;
|
||||
import com.nlshop.mapper.MessageMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service("userMessageService")
|
||||
public class MessageService {
|
||||
|
||||
@Autowired
|
||||
private MessageMapper messageMapper;
|
||||
|
||||
public Message createMessage(Long userId, Long merchantId, Long orderId, String content) {
|
||||
Message message = new Message();
|
||||
message.setUserId(userId);
|
||||
message.setMerchantId(merchantId);
|
||||
message.setOrderId(orderId);
|
||||
message.setContent(content);
|
||||
message.setStatus(0);
|
||||
messageMapper.insert(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
public List<Message> getMessages(Long userId) {
|
||||
return messageMapper.selectByUserId(userId);
|
||||
}
|
||||
}
|
||||
239
src/main/java/com/nlshop/service/user/OrderService.java
Normal file
239
src/main/java/com/nlshop/service/user/OrderService.java
Normal file
@@ -0,0 +1,239 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.request.OrderCreateRequest;
|
||||
import com.nlshop.dto.request.PaymentRequest;
|
||||
import com.nlshop.dto.response.OrderDetailResponse;
|
||||
import com.nlshop.entity.*;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import com.nlshop.mapper.*;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import com.nlshop.util.OrderNoUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class OrderService {
|
||||
|
||||
@Autowired
|
||||
private OrderMapper orderMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderItemMapper orderItemMapper;
|
||||
|
||||
@Autowired
|
||||
private OrderTrackMapper orderTrackMapper;
|
||||
|
||||
@Autowired
|
||||
private CartMapper cartMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductSpecMapper productSpecMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductToppingMapper productToppingMapper;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
@Transactional
|
||||
public Order createOrder(Long userId, OrderCreateRequest request) {
|
||||
// 获取购物车商品
|
||||
List<CartItem> cartItems = cartMapper.selectByUserId(userId);
|
||||
if (cartItems.isEmpty()) {
|
||||
throw new BusinessException(400, "购物车为空");
|
||||
}
|
||||
|
||||
// 计算总金额
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
Long merchantId = null;
|
||||
|
||||
for (CartItem item : cartItems) {
|
||||
Product product = productMapper.selectById(item.getProductId());
|
||||
if (product == null || product.getStatus() == 0) {
|
||||
throw new BusinessException(400, "商品不存在或已下架: " + product.getName());
|
||||
}
|
||||
|
||||
if (merchantId == null) {
|
||||
merchantId = product.getMerchantId();
|
||||
} else if (!merchantId.equals(product.getMerchantId())) {
|
||||
throw new BusinessException(400, "购物车中包含不同商家的商品");
|
||||
}
|
||||
|
||||
BigDecimal price = product.getBasePrice();
|
||||
if (item.getSpecId() != null) {
|
||||
ProductSpec spec = productSpecMapper.selectById(item.getSpecId());
|
||||
if (spec != null) {
|
||||
price = price.add(spec.getPriceAdjust());
|
||||
}
|
||||
}
|
||||
|
||||
// 计算配料价格
|
||||
if (item.getToppings() != null && !item.getToppings().isEmpty()) {
|
||||
try {
|
||||
List<Long> toppingIds = JSON.parseObject(item.getToppings(), new TypeReference<List<Long>>(){});
|
||||
if (toppingIds != null && !toppingIds.isEmpty()) {
|
||||
for (Long toppingId : toppingIds) {
|
||||
ProductTopping topping = productToppingMapper.selectById(toppingId);
|
||||
if (topping != null && topping.getPrice() != null) {
|
||||
price = price.add(topping.getPrice());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 如果解析失败,忽略配料价格
|
||||
}
|
||||
}
|
||||
|
||||
totalAmount = totalAmount.add(price.multiply(new BigDecimal(item.getQuantity())));
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
Order order = new Order();
|
||||
order.setOrderNo(OrderNoUtil.generate());
|
||||
order.setUserId(userId);
|
||||
order.setMerchantId(merchantId);
|
||||
order.setTotalAmount(totalAmount);
|
||||
order.setOrderStatus("PENDING_PAY");
|
||||
order.setPaymentStatus(0);
|
||||
order.setAddress(request.getAddress());
|
||||
order.setContactPhone(request.getContactPhone());
|
||||
orderMapper.insert(order);
|
||||
|
||||
// 创建订单明细
|
||||
for (CartItem item : cartItems) {
|
||||
Product product = productMapper.selectById(item.getProductId());
|
||||
BigDecimal price = product.getBasePrice();
|
||||
String specName = null;
|
||||
|
||||
if (item.getSpecId() != null) {
|
||||
ProductSpec spec = productSpecMapper.selectById(item.getSpecId());
|
||||
if (spec != null) {
|
||||
price = price.add(spec.getPriceAdjust());
|
||||
specName = spec.getSpecName();
|
||||
}
|
||||
}
|
||||
|
||||
// 计算配料价格
|
||||
if (item.getToppings() != null && !item.getToppings().isEmpty()) {
|
||||
try {
|
||||
List<Long> toppingIds = JSON.parseObject(item.getToppings(), new TypeReference<List<Long>>(){});
|
||||
if (toppingIds != null && !toppingIds.isEmpty()) {
|
||||
for (Long toppingId : toppingIds) {
|
||||
ProductTopping topping = productToppingMapper.selectById(toppingId);
|
||||
if (topping != null && topping.getPrice() != null) {
|
||||
price = price.add(topping.getPrice());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 如果解析失败,忽略配料价格
|
||||
}
|
||||
}
|
||||
|
||||
OrderItem orderItem = new OrderItem();
|
||||
orderItem.setOrderId(order.getId());
|
||||
orderItem.setProductId(item.getProductId());
|
||||
orderItem.setProductName(product.getName());
|
||||
orderItem.setSpecName(specName);
|
||||
orderItem.setQuantity(item.getQuantity());
|
||||
orderItem.setPrice(price);
|
||||
orderItem.setCustomSweetness(item.getCustomSweetness());
|
||||
orderItem.setCustomIce(item.getCustomIce());
|
||||
orderItem.setToppings(item.getToppings());
|
||||
orderItem.setSubtotal(price.multiply(new BigDecimal(item.getQuantity())));
|
||||
orderItemMapper.insert(orderItem);
|
||||
}
|
||||
|
||||
// 记录订单跟踪
|
||||
addOrderTrack(order.getId(), "PENDING_PAY", "订单已创建,等待支付");
|
||||
|
||||
// 清空购物车
|
||||
cartMapper.deleteByUserId(userId);
|
||||
|
||||
return orderMapper.selectById(order.getId());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void payOrder(Long userId, Long orderId, PaymentRequest request) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
if (!order.getUserId().equals(userId)) {
|
||||
throw new BusinessException(403, "无权操作该订单");
|
||||
}
|
||||
if (!"PENDING_PAY".equals(order.getOrderStatus())) {
|
||||
throw new BusinessException(400, "订单状态不正确");
|
||||
}
|
||||
|
||||
// 更新支付信息
|
||||
orderMapper.updatePayment(orderId, 1, request.getPaymentMethod());
|
||||
orderMapper.updateStatus(orderId, "MAKING");
|
||||
|
||||
// 记录订单跟踪
|
||||
addOrderTrack(orderId, "MAKING", "支付成功,商家开始制作");
|
||||
|
||||
// 更新用户偏好(协同过滤)
|
||||
List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
|
||||
for (OrderItem item : items) {
|
||||
cfService.updateUserPreference(order.getUserId(), item.getProductId());
|
||||
}
|
||||
}
|
||||
|
||||
public List<Order> getOrders(Long userId, String status) {
|
||||
return orderMapper.selectByUserId(userId, status);
|
||||
}
|
||||
|
||||
public OrderDetailResponse getOrderDetail(Long orderId, Long userId) {
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null || !order.getUserId().equals(userId)) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
|
||||
List<OrderTrack> tracks = orderTrackMapper.selectByOrderId(orderId);
|
||||
|
||||
// 获取商品图片信息
|
||||
Map<Long, String> productImages = new java.util.HashMap<>();
|
||||
for (OrderItem item : items) {
|
||||
Product product = productMapper.selectById(item.getProductId());
|
||||
if (product != null) {
|
||||
productImages.put(item.getProductId(), product.getImage());
|
||||
}
|
||||
}
|
||||
|
||||
return OrderDetailResponse.of(order, items, tracks, productImages);
|
||||
}
|
||||
|
||||
public List<OrderTrack> getOrderTrack(Long userId, Long orderId) {
|
||||
// 验证订单是否属于当前用户
|
||||
Order order = orderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new BusinessException(404, "订单不存在");
|
||||
}
|
||||
if (!order.getUserId().equals(userId)) {
|
||||
throw new BusinessException(403, "无权查看该订单");
|
||||
}
|
||||
return orderTrackMapper.selectByOrderId(orderId);
|
||||
}
|
||||
|
||||
private void addOrderTrack(Long orderId, String status, String description) {
|
||||
OrderTrack track = new OrderTrack();
|
||||
track.setOrderId(orderId);
|
||||
track.setStatus(status);
|
||||
track.setDescription(description);
|
||||
orderTrackMapper.insert(track);
|
||||
}
|
||||
}
|
||||
77
src/main/java/com/nlshop/service/user/ProductService.java
Normal file
77
src/main/java/com/nlshop/service/user/ProductService.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.nlshop.dto.response.PageResponse;
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.entity.ProductCustom;
|
||||
import com.nlshop.entity.ProductSpec;
|
||||
import com.nlshop.entity.ProductTopping;
|
||||
import com.nlshop.mapper.ProductCustomMapper;
|
||||
import com.nlshop.mapper.ProductMapper;
|
||||
import com.nlshop.mapper.ProductSpecMapper;
|
||||
import com.nlshop.mapper.ProductToppingMapper;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class ProductService {
|
||||
|
||||
@Autowired
|
||||
private ProductMapper productMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductSpecMapper productSpecMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductToppingMapper productToppingMapper;
|
||||
|
||||
@Autowired
|
||||
private ProductCustomMapper productCustomMapper;
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
public PageResponse<Product> getProducts(String category, Integer pageNum, Integer pageSize) {
|
||||
PageHelper.startPage(pageNum, pageSize);
|
||||
List<Product> products = productMapper.selectAll(category, 1); // 只查询上架商品
|
||||
PageInfo<Product> pageInfo = new PageInfo<>(products);
|
||||
return PageResponse.of(products, pageInfo.getTotal(), pageNum, pageSize);
|
||||
}
|
||||
|
||||
public Map<String, Object> getProductDetail(Long productId, Long userId) {
|
||||
Product product = productMapper.selectById(productId);
|
||||
if (product == null || product.getStatus() == 0) {
|
||||
throw new com.nlshop.exception.BusinessException(404, "商品不存在或已下架");
|
||||
}
|
||||
|
||||
// 记录浏览行为
|
||||
if (userId != null) {
|
||||
cfService.recordView(userId, productId);
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("product", product);
|
||||
result.put("specs", productSpecMapper.selectByProductId(productId));
|
||||
result.put("toppings", productToppingMapper.selectByProductId(productId));
|
||||
|
||||
// 获取定制选项
|
||||
List<ProductCustom> customs = productCustomMapper.selectByProductId(productId);
|
||||
Map<String, List<ProductCustom>> customMap = new HashMap<>();
|
||||
for (ProductCustom custom : customs) {
|
||||
customMap.computeIfAbsent(custom.getOptionType(), k -> new java.util.ArrayList<>()).add(custom);
|
||||
}
|
||||
result.put("customs", customMap);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Product> getHotProducts(int limit) {
|
||||
return productMapper.selectHotProducts(limit);
|
||||
}
|
||||
}
|
||||
19
src/main/java/com/nlshop/service/user/RecommendService.java
Normal file
19
src/main/java/com/nlshop/service/user/RecommendService.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.nlshop.entity.Product;
|
||||
import com.nlshop.service.algorithm.CollaborativeFilteringService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class RecommendService {
|
||||
|
||||
@Autowired
|
||||
private CollaborativeFilteringService cfService;
|
||||
|
||||
public List<Product> getRecommendations(Long userId, int limit) {
|
||||
return cfService.getRecommendations(userId, limit);
|
||||
}
|
||||
}
|
||||
113
src/main/java/com/nlshop/service/user/UserService.java
Normal file
113
src/main/java/com/nlshop/service/user/UserService.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package com.nlshop.service.user;
|
||||
|
||||
import com.nlshop.dto.request.UserRegisterRequest;
|
||||
import com.nlshop.entity.User;
|
||||
import com.nlshop.entity.UserAddress;
|
||||
import com.nlshop.exception.BusinessException;
|
||||
import com.nlshop.mapper.UserAddressMapper;
|
||||
import com.nlshop.mapper.UserMapper;
|
||||
import com.nlshop.util.PasswordUtil;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private UserAddressMapper userAddressMapper;
|
||||
|
||||
public User register(UserRegisterRequest request) {
|
||||
// 检查用户名是否已存在
|
||||
if (userMapper.selectByUsername(request.getUsername()) != null) {
|
||||
throw new BusinessException(400, "用户名已存在");
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
BeanUtils.copyProperties(request, user);
|
||||
user.setPassword(PasswordUtil.encrypt(request.getPassword()));
|
||||
userMapper.insert(user);
|
||||
return user;
|
||||
}
|
||||
|
||||
public User login(String username, String password) {
|
||||
User user = userMapper.selectByUsername(username);
|
||||
if (user == null || !PasswordUtil.verify(password, user.getPassword())) {
|
||||
throw new BusinessException(401, "用户名或密码错误");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
public User getProfile(Long userId) {
|
||||
return userMapper.selectById(userId);
|
||||
}
|
||||
|
||||
public User updateProfile(Long userId, User user) {
|
||||
user.setId(userId);
|
||||
userMapper.update(user);
|
||||
return userMapper.selectById(userId);
|
||||
}
|
||||
|
||||
public List<UserAddress> getAddresses(Long userId) {
|
||||
return userAddressMapper.selectByUserId(userId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserAddress addAddress(Long userId, UserAddress address) {
|
||||
address.setUserId(userId);
|
||||
// 如果设置为默认地址,取消其他默认地址
|
||||
if (address.getIsDefault() != null && address.getIsDefault() == 1) {
|
||||
UserAddress defaultAddr = userAddressMapper.selectDefaultByUserId(userId);
|
||||
if (defaultAddr != null) {
|
||||
defaultAddr.setIsDefault(0);
|
||||
userAddressMapper.update(defaultAddr);
|
||||
}
|
||||
}
|
||||
userAddressMapper.insert(address);
|
||||
return address;
|
||||
}
|
||||
|
||||
public UserAddress updateAddress(Long userId, Long addressId, UserAddress address) {
|
||||
// 验证地址是否属于当前用户
|
||||
UserAddress existing = userAddressMapper.selectById(addressId);
|
||||
if (existing == null) {
|
||||
throw new BusinessException(404, "地址不存在");
|
||||
}
|
||||
if (!existing.getUserId().equals(userId)) {
|
||||
throw new BusinessException(403, "无权修改该地址");
|
||||
}
|
||||
|
||||
address.setId(addressId);
|
||||
address.setUserId(userId);
|
||||
|
||||
// 如果设置为默认地址,取消其他默认地址
|
||||
if (address.getIsDefault() != null && address.getIsDefault() == 1) {
|
||||
UserAddress defaultAddr = userAddressMapper.selectDefaultByUserId(userId);
|
||||
if (defaultAddr != null && !defaultAddr.getId().equals(addressId)) {
|
||||
defaultAddr.setIsDefault(0);
|
||||
userAddressMapper.update(defaultAddr);
|
||||
}
|
||||
}
|
||||
|
||||
userAddressMapper.update(address);
|
||||
return userAddressMapper.selectById(addressId);
|
||||
}
|
||||
|
||||
public void deleteAddress(Long userId, Long addressId) {
|
||||
// 验证地址是否属于当前用户
|
||||
UserAddress existing = userAddressMapper.selectById(addressId);
|
||||
if (existing == null) {
|
||||
throw new BusinessException(404, "地址不存在");
|
||||
}
|
||||
if (!existing.getUserId().equals(userId)) {
|
||||
throw new BusinessException(403, "无权删除该地址");
|
||||
}
|
||||
userAddressMapper.delete(addressId);
|
||||
}
|
||||
}
|
||||
70
src/main/java/com/nlshop/util/FileUploadUtil.java
Normal file
70
src/main/java/com/nlshop/util/FileUploadUtil.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package com.nlshop.util;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class FileUploadUtil {
|
||||
|
||||
@Value("${file.upload-path}")
|
||||
private String uploadPath;
|
||||
|
||||
public String uploadAvatar(MultipartFile file) throws IOException {
|
||||
return uploadFile(file, "avatar");
|
||||
}
|
||||
|
||||
public String uploadProductImage(MultipartFile file) throws IOException {
|
||||
return uploadFile(file, "product");
|
||||
}
|
||||
|
||||
private String uploadFile(MultipartFile file, String subDir) throws IOException {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建目录
|
||||
String dir = uploadPath + File.separator + subDir;
|
||||
Path path = Paths.get(dir);
|
||||
if (!Files.exists(path)) {
|
||||
Files.createDirectories(path);
|
||||
}
|
||||
|
||||
// 生成文件名
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
String extension = "";
|
||||
if (originalFilename != null && originalFilename.contains(".")) {
|
||||
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||
}
|
||||
String filename = UUID.randomUUID().toString() + extension;
|
||||
|
||||
// 保存文件
|
||||
Path filePath = Paths.get(dir, filename);
|
||||
Files.write(filePath, file.getBytes());
|
||||
|
||||
// 返回相对路径
|
||||
return "/upload/" + subDir + "/" + filename;
|
||||
}
|
||||
|
||||
public boolean deleteFile(String filePath) {
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 移除开头的 /upload/
|
||||
String relativePath = filePath.replace("/upload/", "");
|
||||
Path path = Paths.get(uploadPath, relativePath);
|
||||
return Files.deleteIfExists(path);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/main/java/com/nlshop/util/OrderNoUtil.java
Normal file
16
src/main/java/com/nlshop/util/OrderNoUtil.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.nlshop.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class OrderNoUtil {
|
||||
private static final AtomicInteger counter = new AtomicInteger(0);
|
||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
|
||||
public static String generate() {
|
||||
String timestamp = LocalDateTime.now().format(formatter);
|
||||
int seq = counter.incrementAndGet() % 10000;
|
||||
return "ORD" + timestamp + String.format("%04d", seq);
|
||||
}
|
||||
}
|
||||
25
src/main/java/com/nlshop/util/PasswordUtil.java
Normal file
25
src/main/java/com/nlshop/util/PasswordUtil.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.nlshop.util;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class PasswordUtil {
|
||||
|
||||
public static String encrypt(String password) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] bytes = md.digest(password.getBytes());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("密码加密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean verify(String password, String encryptedPassword) {
|
||||
return encrypt(password).equals(encryptedPassword);
|
||||
}
|
||||
}
|
||||
5
src/main/resources/application-dev.yml
Normal file
5
src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/nl_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: root
|
||||
47
src/main/resources/application.yml
Normal file
47
src/main/resources/application.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
server:
|
||||
port: 14001
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: nl-shop
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://127.0.0.1:3306/z_wb_nc_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: root
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
password: redis_pBjaRs
|
||||
timeout: 3000ms
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 10MB
|
||||
max-request-size: 10MB
|
||||
thymeleaf:
|
||||
prefix: classpath:/templates/
|
||||
suffix: .html
|
||||
mode: HTML
|
||||
encoding: UTF-8
|
||||
cache: false
|
||||
|
||||
mybatis:
|
||||
mapper-locations: classpath:mapper/*.xml
|
||||
type-aliases-package: com.nlshop.entity
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
pagehelper:
|
||||
helper-dialect: mysql
|
||||
reasonable: true
|
||||
support-methods-arguments: true
|
||||
|
||||
file:
|
||||
upload-path: ${user.dir}/src/main/resources/static/upload
|
||||
248
src/main/resources/db/schema.sql
Normal file
248
src/main/resources/db/schema.sql
Normal file
@@ -0,0 +1,248 @@
|
||||
-- 奶茶店管理系统数据库表结构
|
||||
|
||||
-- 用户表
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`username` VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名',
|
||||
`password` VARCHAR(255) NOT NULL COMMENT '密码(加密)',
|
||||
`name` VARCHAR(50) COMMENT '姓名',
|
||||
`gender` TINYINT COMMENT '性别:0-女,1-男',
|
||||
`phone` VARCHAR(20) COMMENT '手机号',
|
||||
`email` VARCHAR(100) COMMENT '邮箱',
|
||||
`avatar` VARCHAR(255) COMMENT '头像路径',
|
||||
`address` VARCHAR(255) COMMENT '默认地址',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
|
||||
|
||||
-- 用户地址表
|
||||
CREATE TABLE IF NOT EXISTS `user_address` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`address` VARCHAR(255) NOT NULL COMMENT '详细地址',
|
||||
`contact_name` VARCHAR(50) COMMENT '联系人姓名',
|
||||
`contact_phone` VARCHAR(20) COMMENT '联系人电话',
|
||||
`is_default` TINYINT DEFAULT 0 COMMENT '是否默认:0-否,1-是',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户地址表';
|
||||
|
||||
-- 商家表
|
||||
CREATE TABLE IF NOT EXISTS `merchant` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`username` VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名',
|
||||
`password` VARCHAR(255) NOT NULL COMMENT '密码(加密)',
|
||||
`store_name` VARCHAR(100) NOT NULL COMMENT '门店名称',
|
||||
`address` VARCHAR(255) COMMENT '门店地址',
|
||||
`phone` VARCHAR(20) COMMENT '联系电话',
|
||||
`email` VARCHAR(100) COMMENT '邮箱',
|
||||
`manager_name` VARCHAR(50) COMMENT '负责人姓名',
|
||||
`avatar` VARCHAR(255) COMMENT '头像路径',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商家表';
|
||||
|
||||
-- 商品表
|
||||
CREATE TABLE IF NOT EXISTS `product` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`name` VARCHAR(100) NOT NULL COMMENT '商品名称',
|
||||
`category` VARCHAR(50) COMMENT '分类',
|
||||
`description` TEXT COMMENT '商品描述',
|
||||
`base_price` DECIMAL(10,2) NOT NULL COMMENT '基础价格',
|
||||
`image` VARCHAR(255) COMMENT '商品图片',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态:0-下架,1-上架',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
|
||||
|
||||
-- 商品规格表
|
||||
CREATE TABLE IF NOT EXISTS `product_spec` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`spec_name` VARCHAR(50) NOT NULL COMMENT '规格名称(大杯/中杯/小杯)',
|
||||
`price_adjust` DECIMAL(10,2) DEFAULT 0 COMMENT '价格调整(相对于基础价格)',
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品规格表';
|
||||
|
||||
-- 配料表
|
||||
CREATE TABLE IF NOT EXISTS `product_topping` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`topping_name` VARCHAR(50) NOT NULL COMMENT '配料名称',
|
||||
`price` DECIMAL(10,2) DEFAULT 0 COMMENT '配料价格',
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配料表';
|
||||
|
||||
-- 商品定制选项表
|
||||
CREATE TABLE IF NOT EXISTS `product_custom` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`option_type` VARCHAR(20) NOT NULL COMMENT '选项类型:sweetness-甜度,ice-冰度',
|
||||
`option_value` VARCHAR(50) NOT NULL COMMENT '选项值',
|
||||
`price_adjust` DECIMAL(10,2) DEFAULT 0 COMMENT '价格调整',
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品定制选项表';
|
||||
|
||||
-- 购物车表
|
||||
CREATE TABLE IF NOT EXISTS `cart` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`spec_id` BIGINT COMMENT '规格ID',
|
||||
`quantity` INT DEFAULT 1 COMMENT '数量',
|
||||
`custom_sweetness` VARCHAR(20) COMMENT '甜度定制',
|
||||
`custom_ice` VARCHAR(20) COMMENT '冰度定制',
|
||||
`toppings` TEXT COMMENT '配料JSON',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车表';
|
||||
|
||||
-- 订单表
|
||||
CREATE TABLE IF NOT EXISTS `order` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`order_no` VARCHAR(50) UNIQUE NOT NULL COMMENT '订单号',
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`total_amount` DECIMAL(10,2) NOT NULL COMMENT '订单总金额',
|
||||
`payment_method` VARCHAR(20) COMMENT '支付方式:wechat/alipay',
|
||||
`payment_status` TINYINT DEFAULT 0 COMMENT '支付状态:0-未支付,1-已支付',
|
||||
`order_status` VARCHAR(20) DEFAULT 'PENDING_PAY' COMMENT '订单状态:PENDING_PAY-待支付,MAKING-制作中,READY-待取餐,COMPLETED-已完成,CANCELLED-已取消',
|
||||
`address` VARCHAR(255) NOT NULL COMMENT '收货地址',
|
||||
`contact_phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`pay_time` DATETIME COMMENT '支付时间',
|
||||
`complete_time` DATETIME COMMENT '完成时间',
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
|
||||
|
||||
-- 订单明细表
|
||||
CREATE TABLE IF NOT EXISTS `order_item` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`order_id` BIGINT NOT NULL COMMENT '订单ID',
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`product_name` VARCHAR(100) NOT NULL COMMENT '商品名称',
|
||||
`spec_name` VARCHAR(50) COMMENT '规格名称',
|
||||
`quantity` INT NOT NULL COMMENT '数量',
|
||||
`price` DECIMAL(10,2) NOT NULL COMMENT '单价',
|
||||
`custom_sweetness` VARCHAR(20) COMMENT '甜度',
|
||||
`custom_ice` VARCHAR(20) COMMENT '冰度',
|
||||
`toppings` TEXT COMMENT '配料JSON',
|
||||
`subtotal` DECIMAL(10,2) NOT NULL COMMENT '小计',
|
||||
FOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';
|
||||
|
||||
-- 订单跟踪表
|
||||
CREATE TABLE IF NOT EXISTS `order_track` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`order_id` BIGINT NOT NULL COMMENT '订单ID',
|
||||
`status` VARCHAR(20) NOT NULL COMMENT '状态',
|
||||
`description` VARCHAR(255) COMMENT '描述',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`order_id`) REFERENCES `order`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单跟踪表';
|
||||
|
||||
-- 公告表
|
||||
CREATE TABLE IF NOT EXISTS `announcement` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`title` VARCHAR(200) NOT NULL COMMENT '标题',
|
||||
`content` TEXT COMMENT '内容',
|
||||
`type` VARCHAR(20) DEFAULT 'NOTICE' COMMENT '类型:ACTIVITY-活动,NOTICE-通知',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态:0-下架,1-发布',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公告表';
|
||||
|
||||
-- 留言反馈表
|
||||
CREATE TABLE IF NOT EXISTS `message` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`order_id` BIGINT COMMENT '订单ID',
|
||||
`content` TEXT NOT NULL COMMENT '留言内容',
|
||||
`reply` TEXT COMMENT '回复内容',
|
||||
`status` TINYINT DEFAULT 0 COMMENT '状态:0-未回复,1-已回复',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`reply_time` DATETIME COMMENT '回复时间',
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='留言反馈表';
|
||||
|
||||
-- 评价表
|
||||
CREATE TABLE IF NOT EXISTS `review` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`order_id` BIGINT NOT NULL COMMENT '订单ID',
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`rating` TINYINT NOT NULL COMMENT '评分:1-5',
|
||||
`content` TEXT COMMENT '评价内容',
|
||||
`reply` TEXT COMMENT '商家回复',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`),
|
||||
FOREIGN KEY (`order_id`) REFERENCES `order`(`id`),
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评价表';
|
||||
|
||||
-- 库存表
|
||||
CREATE TABLE IF NOT EXISTS `inventory` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`merchant_id` BIGINT NOT NULL COMMENT '商家ID',
|
||||
`material_name` VARCHAR(100) NOT NULL COMMENT '原料名称',
|
||||
`quantity` DECIMAL(10,2) NOT NULL COMMENT '数量',
|
||||
`unit` VARCHAR(20) COMMENT '单位',
|
||||
`min_threshold` DECIMAL(10,2) DEFAULT 0 COMMENT '最低库存阈值',
|
||||
`last_in_time` DATETIME COMMENT '最后入库时间',
|
||||
`last_out_time` DATETIME COMMENT '最后出库时间',
|
||||
FOREIGN KEY (`merchant_id`) REFERENCES `merchant`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存表';
|
||||
|
||||
-- 库存操作日志表
|
||||
CREATE TABLE IF NOT EXISTS `inventory_log` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`inventory_id` BIGINT NOT NULL COMMENT '库存ID',
|
||||
`operation_type` VARCHAR(20) NOT NULL COMMENT '操作类型:IN-入库,OUT-出库',
|
||||
`quantity` DECIMAL(10,2) NOT NULL COMMENT '操作数量',
|
||||
`operator` VARCHAR(50) COMMENT '操作人',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`inventory_id`) REFERENCES `inventory`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存操作日志表';
|
||||
|
||||
-- 用户行为表(用于协同过滤)
|
||||
CREATE TABLE IF NOT EXISTS `user_behavior` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL COMMENT '用户ID',
|
||||
`product_id` BIGINT NOT NULL COMMENT '商品ID',
|
||||
`behavior_type` VARCHAR(20) NOT NULL COMMENT '行为类型:VIEW-浏览,PURCHASE-购买,RATING-评分',
|
||||
`score` DECIMAL(5,2) DEFAULT 0 COMMENT '评分(0-5)',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`product_id`) REFERENCES `product`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户行为表';
|
||||
|
||||
-- 用户相似度缓存表(可选)
|
||||
CREATE TABLE IF NOT EXISTS `user_similarity` (
|
||||
`user1_id` BIGINT NOT NULL COMMENT '用户1ID',
|
||||
`user2_id` BIGINT NOT NULL COMMENT '用户2ID',
|
||||
`similarity_score` DECIMAL(10,6) NOT NULL COMMENT '相似度分数',
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`user1_id`, `user2_id`),
|
||||
FOREIGN KEY (`user1_id`) REFERENCES `user`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`user2_id`) REFERENCES `user`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户相似度缓存表';
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_user_username ON `user`(`username`);
|
||||
CREATE INDEX idx_merchant_username ON `merchant`(`username`);
|
||||
CREATE INDEX idx_product_merchant ON `product`(`merchant_id`);
|
||||
CREATE INDEX idx_product_status ON `product`(`status`);
|
||||
CREATE INDEX idx_cart_user ON `cart`(`user_id`);
|
||||
CREATE INDEX idx_order_user ON `order`(`user_id`);
|
||||
CREATE INDEX idx_order_merchant ON `order`(`merchant_id`);
|
||||
CREATE INDEX idx_order_status ON `order`(`order_status`);
|
||||
CREATE INDEX idx_order_no ON `order`(`order_no`);
|
||||
CREATE INDEX idx_user_behavior_user ON `user_behavior`(`user_id`);
|
||||
CREATE INDEX idx_user_behavior_product ON `user_behavior`(`product_id`);
|
||||
45
src/main/resources/mapper/AnnouncementMapper.xml
Normal file
45
src/main/resources/mapper/AnnouncementMapper.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nlshop.mapper.AnnouncementMapper">
|
||||
<resultMap id="BaseResultMap" type="com.nlshop.entity.Announcement">
|
||||
<id column="id" property="id"/>
|
||||
<result column="merchant_id" property="merchantId"/>
|
||||
<result column="title" property="title"/>
|
||||
<result column="content" property="content"/>
|
||||
<result column="type" property="type"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="com.nlshop.entity.Announcement" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO announcement (merchant_id, title, content, type, status)
|
||||
VALUES (#{merchantId}, #{title}, #{content}, #{type}, #{status})
|
||||
</insert>
|
||||
|
||||
<select id="selectByMerchantId" resultMap="BaseResultMap">
|
||||
SELECT * FROM announcement WHERE merchant_id = #{merchantId} ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectPublished" resultMap="BaseResultMap">
|
||||
SELECT * FROM announcement WHERE status = 1 ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultMap="BaseResultMap">
|
||||
SELECT * FROM announcement WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<update id="update" parameterType="com.nlshop.entity.Announcement">
|
||||
UPDATE announcement
|
||||
<set>
|
||||
<if test="title != null">title = #{title},</if>
|
||||
<if test="content != null">content = #{content},</if>
|
||||
<if test="type != null">type = #{type},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
</set>
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="delete">
|
||||
DELETE FROM announcement WHERE id = #{id}
|
||||
</delete>
|
||||
</mapper>
|
||||
95
src/main/resources/mapper/CartMapper.xml
Normal file
95
src/main/resources/mapper/CartMapper.xml
Normal file
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nlshop.mapper.CartMapper">
|
||||
<resultMap id="BaseResultMap" type="com.nlshop.entity.CartItem">
|
||||
<id column="id" property="id"/>
|
||||
<result column="user_id" property="userId"/>
|
||||
<result column="product_id" property="productId"/>
|
||||
<result column="spec_id" property="specId"/>
|
||||
<result column="quantity" property="quantity"/>
|
||||
<result column="custom_sweetness" property="customSweetness"/>
|
||||
<result column="custom_ice" property="customIce"/>
|
||||
<result column="toppings" property="toppings"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<association property="product" javaType="com.nlshop.entity.Product">
|
||||
<id column="p_id" property="id"/>
|
||||
<result column="p_name" property="name"/>
|
||||
<result column="p_image" property="image"/>
|
||||
<result column="p_base_price" property="basePrice"/>
|
||||
<result column="p_merchant_id" property="merchantId"/>
|
||||
</association>
|
||||
<association property="spec" javaType="com.nlshop.entity.ProductSpec">
|
||||
<id column="s_id" property="id"/>
|
||||
<result column="s_spec_name" property="specName"/>
|
||||
<result column="s_price_adjust" property="priceAdjust"/>
|
||||
</association>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="com.nlshop.entity.CartItem" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO cart (user_id, product_id, spec_id, quantity, custom_sweetness, custom_ice, toppings)
|
||||
VALUES (#{userId}, #{productId}, #{specId}, #{quantity}, #{customSweetness}, #{customIce}, #{toppings})
|
||||
</insert>
|
||||
|
||||
<select id="selectByUserId" resultMap="BaseResultMap">
|
||||
SELECT c.*,
|
||||
p.id as p_id, p.name as p_name, p.image as p_image, p.base_price as p_base_price, p.merchant_id as p_merchant_id,
|
||||
s.id as s_id, s.spec_name as s_spec_name, s.price_adjust as s_price_adjust
|
||||
FROM cart c
|
||||
LEFT JOIN product p ON c.product_id = p.id
|
||||
LEFT JOIN product_spec s ON c.spec_id = s.id
|
||||
WHERE c.user_id = #{userId}
|
||||
ORDER BY c.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultMap="BaseResultMap">
|
||||
SELECT c.*,
|
||||
p.id as p_id, p.name as p_name, p.image as p_image, p.base_price as p_base_price, p.merchant_id as p_merchant_id,
|
||||
s.id as s_id, s.spec_name as s_spec_name, s.price_adjust as s_price_adjust
|
||||
FROM cart c
|
||||
LEFT JOIN product p ON c.product_id = p.id
|
||||
LEFT JOIN product_spec s ON c.spec_id = s.id
|
||||
WHERE c.id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectByUserAndProduct" resultMap="BaseResultMap">
|
||||
SELECT * FROM cart
|
||||
WHERE user_id = #{userId} AND product_id = #{productId}
|
||||
AND (spec_id = #{specId} OR (#{specId} IS NULL AND spec_id IS NULL))
|
||||
AND (custom_sweetness = #{customSweetness} OR (#{customSweetness} IS NULL AND custom_sweetness IS NULL))
|
||||
AND (custom_ice = #{customIce} OR (#{customIce} IS NULL AND custom_ice IS NULL))
|
||||
AND (toppings = #{toppings} OR (#{toppings} IS NULL AND toppings IS NULL))
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<update id="update" parameterType="com.nlshop.entity.CartItem">
|
||||
UPDATE cart
|
||||
<set>
|
||||
<if test="quantity != null">quantity = #{quantity},</if>
|
||||
<if test="customSweetness != null">custom_sweetness = #{customSweetness},</if>
|
||||
<if test="customIce != null">custom_ice = #{customIce},</if>
|
||||
<if test="toppings != null">toppings = #{toppings},</if>
|
||||
</set>
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="updateQuantity">
|
||||
UPDATE cart SET quantity = #{quantity} WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="delete">
|
||||
DELETE FROM cart WHERE id = #{id}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByUserId">
|
||||
DELETE FROM cart WHERE user_id = #{userId}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteBatch">
|
||||
DELETE FROM cart
|
||||
WHERE id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
AND user_id = #{userId}
|
||||
</delete>
|
||||
</mapper>
|
||||
21
src/main/resources/mapper/InventoryLogMapper.xml
Normal file
21
src/main/resources/mapper/InventoryLogMapper.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nlshop.mapper.InventoryLogMapper">
|
||||
<resultMap id="BaseResultMap" type="com.nlshop.entity.InventoryLog">
|
||||
<id column="id" property="id"/>
|
||||
<result column="inventory_id" property="inventoryId"/>
|
||||
<result column="operation_type" property="operationType"/>
|
||||
<result column="quantity" property="quantity"/>
|
||||
<result column="operator" property="operator"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="com.nlshop.entity.InventoryLog" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO inventory_log (inventory_id, operation_type, quantity, operator)
|
||||
VALUES (#{inventoryId}, #{operationType}, #{quantity}, #{operator})
|
||||
</insert>
|
||||
|
||||
<select id="selectByInventoryId" resultMap="BaseResultMap">
|
||||
SELECT * FROM inventory_log WHERE inventory_id = #{inventoryId} ORDER BY create_time DESC
|
||||
</select>
|
||||
</mapper>
|
||||
55
src/main/resources/mapper/InventoryMapper.xml
Normal file
55
src/main/resources/mapper/InventoryMapper.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nlshop.mapper.InventoryMapper">
|
||||
<resultMap id="BaseResultMap" type="com.nlshop.entity.Inventory">
|
||||
<id column="id" property="id"/>
|
||||
<result column="merchant_id" property="merchantId"/>
|
||||
<result column="material_name" property="materialName"/>
|
||||
<result column="quantity" property="quantity"/>
|
||||
<result column="unit" property="unit"/>
|
||||
<result column="min_threshold" property="minThreshold"/>
|
||||
<result column="last_in_time" property="lastInTime"/>
|
||||
<result column="last_out_time" property="lastOutTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="com.nlshop.entity.Inventory" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO inventory (merchant_id, material_name, quantity, unit, min_threshold)
|
||||
VALUES (#{merchantId}, #{materialName}, #{quantity}, #{unit}, #{minThreshold})
|
||||
</insert>
|
||||
|
||||
<select id="selectByMerchantId" resultMap="BaseResultMap">
|
||||
SELECT * FROM inventory WHERE merchant_id = #{merchantId} ORDER BY material_name
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultMap="BaseResultMap">
|
||||
SELECT * FROM inventory WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectByMerchantAndMaterial" resultMap="BaseResultMap">
|
||||
SELECT * FROM inventory
|
||||
WHERE merchant_id = #{merchantId} AND material_name = #{materialName}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="selectLowStock" resultMap="BaseResultMap">
|
||||
SELECT * FROM inventory
|
||||
WHERE merchant_id = #{merchantId} AND quantity <= min_threshold
|
||||
ORDER BY quantity ASC
|
||||
</select>
|
||||
|
||||
<update id="update" parameterType="com.nlshop.entity.Inventory">
|
||||
UPDATE inventory
|
||||
<set>
|
||||
<if test="quantity != null">quantity = #{quantity},</if>
|
||||
<if test="unit != null">unit = #{unit},</if>
|
||||
<if test="minThreshold != null">min_threshold = #{minThreshold},</if>
|
||||
<if test="lastInTime != null">last_in_time = #{lastInTime},</if>
|
||||
<if test="lastOutTime != null">last_out_time = #{lastOutTime},</if>
|
||||
</set>
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="delete">
|
||||
DELETE FROM inventory WHERE id = #{id}
|
||||
</delete>
|
||||
</mapper>
|
||||
43
src/main/resources/mapper/MerchantMapper.xml
Normal file
43
src/main/resources/mapper/MerchantMapper.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nlshop.mapper.MerchantMapper">
|
||||
<resultMap id="BaseResultMap" type="com.nlshop.entity.Merchant">
|
||||
<id column="id" property="id"/>
|
||||
<result column="username" property="username"/>
|
||||
<result column="password" property="password"/>
|
||||
<result column="store_name" property="storeName"/>
|
||||
<result column="address" property="address"/>
|
||||
<result column="phone" property="phone"/>
|
||||
<result column="email" property="email"/>
|
||||
<result column="manager_name" property="managerName"/>
|
||||
<result column="avatar" property="avatar"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="com.nlshop.entity.Merchant" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO merchant (username, password, store_name, address, phone, email, manager_name, avatar)
|
||||
VALUES (#{username}, #{password}, #{storeName}, #{address}, #{phone}, #{email}, #{managerName}, #{avatar})
|
||||
</insert>
|
||||
|
||||
<select id="selectById" resultMap="BaseResultMap">
|
||||
SELECT * FROM merchant WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectByUsername" resultMap="BaseResultMap">
|
||||
SELECT * FROM merchant WHERE username = #{username}
|
||||
</select>
|
||||
|
||||
<update id="update" parameterType="com.nlshop.entity.Merchant">
|
||||
UPDATE merchant
|
||||
<set>
|
||||
<if test="storeName != null">store_name = #{storeName},</if>
|
||||
<if test="address != null">address = #{address},</if>
|
||||
<if test="phone != null">phone = #{phone},</if>
|
||||
<if test="email != null">email = #{email},</if>
|
||||
<if test="managerName != null">manager_name = #{managerName},</if>
|
||||
<if test="avatar != null">avatar = #{avatar},</if>
|
||||
</set>
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
</mapper>
|
||||
42
src/main/resources/mapper/MessageMapper.xml
Normal file
42
src/main/resources/mapper/MessageMapper.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nlshop.mapper.MessageMapper">
|
||||
<resultMap id="BaseResultMap" type="com.nlshop.entity.Message">
|
||||
<id column="id" property="id"/>
|
||||
<result column="user_id" property="userId"/>
|
||||
<result column="merchant_id" property="merchantId"/>
|
||||
<result column="order_id" property="orderId"/>
|
||||
<result column="content" property="content"/>
|
||||
<result column="reply" property="reply"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="reply_time" property="replyTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="com.nlshop.entity.Message" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO message (user_id, merchant_id, order_id, content, status)
|
||||
VALUES (#{userId}, #{merchantId}, #{orderId}, #{content}, #{status})
|
||||
</insert>
|
||||
|
||||
<select id="selectByUserId" resultMap="BaseResultMap">
|
||||
SELECT * FROM message WHERE user_id = #{userId} ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectByMerchantId" resultMap="BaseResultMap">
|
||||
SELECT * FROM message WHERE merchant_id = #{merchantId} ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultMap="BaseResultMap">
|
||||
SELECT * FROM message WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
<update id="update" parameterType="com.nlshop.entity.Message">
|
||||
UPDATE message
|
||||
<set>
|
||||
<if test="reply != null">reply = #{reply},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="replyTime != null">reply_time = #{replyTime},</if>
|
||||
</set>
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
</mapper>
|
||||
32
src/main/resources/mapper/OrderItemMapper.xml
Normal file
32
src/main/resources/mapper/OrderItemMapper.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nlshop.mapper.OrderItemMapper">
|
||||
<resultMap id="BaseResultMap" type="com.nlshop.entity.OrderItem">
|
||||
<id column="id" property="id"/>
|
||||
<result column="order_id" property="orderId"/>
|
||||
<result column="product_id" property="productId"/>
|
||||
<result column="product_name" property="productName"/>
|
||||
<result column="spec_name" property="specName"/>
|
||||
<result column="quantity" property="quantity"/>
|
||||
<result column="price" property="price"/>
|
||||
<result column="custom_sweetness" property="customSweetness"/>
|
||||
<result column="custom_ice" property="customIce"/>
|
||||
<result column="toppings" property="toppings"/>
|
||||
<result column="subtotal" property="subtotal"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="com.nlshop.entity.OrderItem" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO order_item (order_id, product_id, product_name, spec_name, quantity, price,
|
||||
custom_sweetness, custom_ice, toppings, subtotal)
|
||||
VALUES (#{orderId}, #{productId}, #{productName}, #{specName}, #{quantity}, #{price},
|
||||
#{customSweetness}, #{customIce}, #{toppings}, #{subtotal})
|
||||
</insert>
|
||||
|
||||
<select id="selectByOrderId" resultMap="BaseResultMap">
|
||||
SELECT * FROM order_item WHERE order_id = #{orderId}
|
||||
</select>
|
||||
|
||||
<select id="selectById" resultMap="BaseResultMap">
|
||||
SELECT * FROM order_item WHERE id = #{id}
|
||||
</select>
|
||||
</mapper>
|
||||
107
src/main/resources/mapper/OrderMapper.xml
Normal file
107
src/main/resources/mapper/OrderMapper.xml
Normal file
@@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nlshop.mapper.OrderMapper">
|
||||
<resultMap id="BaseResultMap" type="com.nlshop.entity.Order">
|
||||
<id column="id" property="id"/>
|
||||
<result column="order_no" property="orderNo"/>
|
||||
<result column="user_id" property="userId"/>
|
||||
<result column="merchant_id" property="merchantId"/>
|
||||
<result column="total_amount" property="totalAmount"/>
|
||||
<result column="payment_method" property="paymentMethod"/>
|
||||
<result column="payment_status" property="paymentStatus"/>
|
||||
<result column="order_status" property="orderStatus"/>
|
||||
<result column="address" property="address"/>
|
||||
<result column="contact_phone" property="contactPhone"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="pay_time" property="payTime"/>
|
||||
<result column="complete_time" property="completeTime"/>
|
||||
<association property="merchant" javaType="com.nlshop.entity.Merchant">
|
||||
<id column="m_id" property="id"/>
|
||||
<result column="m_store_name" property="storeName"/>
|
||||
</association>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="com.nlshop.entity.Order" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO `order` (order_no, user_id, merchant_id, total_amount, payment_method,
|
||||
payment_status, order_status, address, contact_phone, pay_time, complete_time)
|
||||
VALUES (#{orderNo}, #{userId}, #{merchantId}, #{totalAmount}, #{paymentMethod},
|
||||
#{paymentStatus}, #{orderStatus}, #{address}, #{contactPhone}, #{payTime}, #{completeTime})
|
||||
</insert>
|
||||
|
||||
<select id="selectById" resultMap="BaseResultMap">
|
||||
SELECT o.*, m.id as m_id, m.store_name as m_store_name
|
||||
FROM `order` o
|
||||
LEFT JOIN merchant m ON o.merchant_id = m.id
|
||||
WHERE o.id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectByOrderNo" resultMap="BaseResultMap">
|
||||
SELECT * FROM `order` WHERE order_no = #{orderNo}
|
||||
</select>
|
||||
|
||||
<select id="selectByUserId" resultMap="BaseResultMap">
|
||||
SELECT o.*, m.id as m_id, m.store_name as m_store_name
|
||||
FROM `order` o
|
||||
LEFT JOIN merchant m ON o.merchant_id = m.id
|
||||
WHERE o.user_id = #{userId}
|
||||
<if test="status != null and status != ''">
|
||||
AND o.order_status = #{status}
|
||||
</if>
|
||||
ORDER BY o.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectByMerchantId" resultMap="BaseResultMap">
|
||||
SELECT o.*, m.id as m_id, m.store_name as m_store_name
|
||||
FROM `order` o
|
||||
LEFT JOIN merchant m ON o.merchant_id = m.id
|
||||
WHERE o.merchant_id = #{merchantId}
|
||||
<if test="status != null and status != ''">
|
||||
AND o.order_status = #{status}
|
||||
</if>
|
||||
ORDER BY o.create_time DESC
|
||||
</select>
|
||||
|
||||
<update id="update" parameterType="com.nlshop.entity.Order">
|
||||
UPDATE `order`
|
||||
<set>
|
||||
<if test="totalAmount != null">total_amount = #{totalAmount},</if>
|
||||
<if test="paymentMethod != null">payment_method = #{paymentMethod},</if>
|
||||
<if test="paymentStatus != null">payment_status = #{paymentStatus},</if>
|
||||
<if test="orderStatus != null">order_status = #{orderStatus},</if>
|
||||
<if test="address != null">address = #{address},</if>
|
||||
<if test="contactPhone != null">contact_phone = #{contactPhone},</if>
|
||||
<if test="payTime != null">pay_time = #{payTime},</if>
|
||||
<if test="completeTime != null">complete_time = #{completeTime},</if>
|
||||
</set>
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="updateStatus">
|
||||
UPDATE `order` SET order_status = #{status} WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<update id="updatePayment">
|
||||
UPDATE `order`
|
||||
SET payment_status = #{paymentStatus},
|
||||
payment_method = #{paymentMethod},
|
||||
pay_time = NOW()
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="selectStickyUsers" resultType="java.util.HashMap">
|
||||
SELECT
|
||||
u.id as userId,
|
||||
u.username,
|
||||
u.name,
|
||||
COUNT(o.id) as orderCount,
|
||||
SUM(o.total_amount) as totalAmount
|
||||
FROM `order` o
|
||||
INNER JOIN user u ON o.user_id = u.id
|
||||
WHERE o.merchant_id = #{merchantId}
|
||||
AND o.order_status = 'COMPLETED'
|
||||
AND o.create_time >= DATE_SUB(NOW(), INTERVAL #{days} DAY)
|
||||
GROUP BY u.id, u.username, u.name
|
||||
HAVING COUNT(o.id) >= #{minOrders}
|
||||
ORDER BY orderCount DESC, totalAmount DESC
|
||||
</select>
|
||||
</mapper>
|
||||
20
src/main/resources/mapper/OrderTrackMapper.xml
Normal file
20
src/main/resources/mapper/OrderTrackMapper.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nlshop.mapper.OrderTrackMapper">
|
||||
<resultMap id="BaseResultMap" type="com.nlshop.entity.OrderTrack">
|
||||
<id column="id" property="id"/>
|
||||
<result column="order_id" property="orderId"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="description" property="description"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insert" parameterType="com.nlshop.entity.OrderTrack" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO order_track (order_id, status, description)
|
||||
VALUES (#{orderId}, #{status}, #{description})
|
||||
</insert>
|
||||
|
||||
<select id="selectByOrderId" resultMap="BaseResultMap">
|
||||
SELECT * FROM order_track WHERE order_id = #{orderId} ORDER BY create_time ASC
|
||||
</select>
|
||||
</mapper>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user