初始化v1

This commit is contained in:
2026-01-11 18:14:42 +08:00
commit 641b03e8e2
297 changed files with 28656 additions and 0 deletions

View 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);
}
}

View 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();
}
}

View 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/");
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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); // 返回成功,避免影响登录流程
}
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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());
}
}
}

View 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格式
}

View 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;
}

View File

@@ -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;
}

View 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;
}

View 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
}

View File

@@ -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;
}

View 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;
}
}

View 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;
}

View File

@@ -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;
}
}

View 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;
}
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}
}

View File

@@ -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);
}
}

View 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;
}
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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();
}

View 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);
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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);
}
}

View 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);
}
}
}

View File

@@ -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);
}
}

View 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());
}
}
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}
}

View 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);
}
}

View 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);
}
}

View 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

View 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

View 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`);

View 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>

View 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>

View 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>

View 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 &lt;= 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>

View 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>

View 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>

View 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>

View 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>

View 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