Преглед изворни кода

feat:初步完成通过测试

yangyi пре 4 месеци
родитељ
комит
8def57101b
100 измењених фајлова са 3825 додато и 49 уклоњено
  1. 50 10
      pom.xml
  2. 11 0
      src/main/java/space/anyi/constant/OrderStatus.java
  3. 0 22
      src/main/java/space/anyi/controller/HelloController.java
  4. 127 0
      src/main/java/space/anyi/controller/admin/AdminActivityController.java
  5. 225 0
      src/main/java/space/anyi/controller/admin/AdminCakeController.java
  6. 131 0
      src/main/java/space/anyi/controller/admin/AdminLoginController.java
  7. 76 0
      src/main/java/space/anyi/controller/admin/AdminOrderController.java
  8. 60 0
      src/main/java/space/anyi/controller/admin/AdminReviewController.java
  9. 95 0
      src/main/java/space/anyi/controller/admin/AdminSettingsController.java
  10. 101 0
      src/main/java/space/anyi/controller/admin/AdminStatisticsController.java
  11. 68 0
      src/main/java/space/anyi/controller/admin/AdminUserController.java
  12. 31 0
      src/main/java/space/anyi/controller/front/ActivityController.java
  13. 143 0
      src/main/java/space/anyi/controller/front/CakeController.java
  14. 113 0
      src/main/java/space/anyi/controller/front/CartController.java
  15. 37 0
      src/main/java/space/anyi/controller/front/CouponController.java
  16. 44 0
      src/main/java/space/anyi/controller/front/FavoriteController.java
  17. 32 0
      src/main/java/space/anyi/controller/front/IndexController.java
  18. 40 0
      src/main/java/space/anyi/controller/front/MessageController.java
  19. 96 0
      src/main/java/space/anyi/controller/front/OrderController.java
  20. 39 0
      src/main/java/space/anyi/controller/front/ReviewController.java
  21. 182 0
      src/main/java/space/anyi/controller/front/UserController.java
  22. 45 0
      src/main/java/space/anyi/entity/Activity.java
  23. 45 0
      src/main/java/space/anyi/entity/Address.java
  24. 33 0
      src/main/java/space/anyi/entity/Admin.java
  25. 30 0
      src/main/java/space/anyi/entity/Announcement.java
  26. 76 0
      src/main/java/space/anyi/entity/Cake.java
  27. 30 0
      src/main/java/space/anyi/entity/CakeCategory.java
  28. 29 0
      src/main/java/space/anyi/entity/CakeImage.java
  29. 30 0
      src/main/java/space/anyi/entity/CakeSpec.java
  30. 27 0
      src/main/java/space/anyi/entity/Cart.java
  31. 36 0
      src/main/java/space/anyi/entity/CartItem.java
  32. 49 0
      src/main/java/space/anyi/entity/Coupon.java
  33. 33 0
      src/main/java/space/anyi/entity/CustomOption.java
  34. 30 0
      src/main/java/space/anyi/entity/DeliveryConfig.java
  35. 27 0
      src/main/java/space/anyi/entity/Favorite.java
  36. 42 0
      src/main/java/space/anyi/entity/Message.java
  37. 42 0
      src/main/java/space/anyi/entity/OrderItem.java
  38. 36 0
      src/main/java/space/anyi/entity/OrderTracking.java
  39. 82 0
      src/main/java/space/anyi/entity/Orders.java
  40. 42 0
      src/main/java/space/anyi/entity/Review.java
  41. 42 9
      src/main/java/space/anyi/entity/User.java
  42. 33 0
      src/main/java/space/anyi/entity/UserCoupon.java
  43. 29 0
      src/main/java/space/anyi/entity/dto/AddressDTO.java
  44. 17 0
      src/main/java/space/anyi/entity/dto/AnnouncementDTO.java
  45. 19 0
      src/main/java/space/anyi/entity/dto/CakeSalesVO.java
  46. 61 0
      src/main/java/space/anyi/entity/dto/CakeSaveDTO.java
  47. 31 0
      src/main/java/space/anyi/entity/dto/CakeSearchCriteria.java
  48. 22 0
      src/main/java/space/anyi/entity/dto/CakeSpecDTO.java
  49. 14 0
      src/main/java/space/anyi/entity/dto/CategoryDTO.java
  50. 25 0
      src/main/java/space/anyi/entity/dto/CustomOptionDTO.java
  51. 22 0
      src/main/java/space/anyi/entity/dto/DeliveryConfigDTO.java
  52. 17 0
      src/main/java/space/anyi/entity/dto/OrderSearchCriteria.java
  53. 20 0
      src/main/java/space/anyi/entity/dto/UserRegisterDTO.java
  54. 20 0
      src/main/java/space/anyi/entity/dto/UserUpdateDTO.java
  55. 37 0
      src/main/java/space/anyi/entity/vo/AddressVO.java
  56. 20 0
      src/main/java/space/anyi/entity/vo/CakeImageVO.java
  57. 22 0
      src/main/java/space/anyi/entity/vo/CakeSpecVO.java
  58. 81 0
      src/main/java/space/anyi/entity/vo/CakeVO.java
  59. 41 0
      src/main/java/space/anyi/entity/vo/CartItemVO.java
  60. 29 0
      src/main/java/space/anyi/entity/vo/FavoriteVO.java
  61. 40 0
      src/main/java/space/anyi/entity/vo/OrderItemVO.java
  62. 28 0
      src/main/java/space/anyi/entity/vo/OrderTrackingVO.java
  63. 87 0
      src/main/java/space/anyi/entity/vo/OrderVO.java
  64. 32 0
      src/main/java/space/anyi/entity/vo/PageResultVO.java
  65. 40 0
      src/main/java/space/anyi/entity/vo/ReviewVO.java
  66. 34 0
      src/main/java/space/anyi/entity/vo/UserVO.java
  67. 21 0
      src/main/java/space/anyi/interceptor/AdminLoginInterceptor.java
  68. 21 0
      src/main/java/space/anyi/interceptor/LoginInterceptor.java
  69. 13 0
      src/main/java/space/anyi/mapper/ActivityMapper.java
  70. 15 0
      src/main/java/space/anyi/mapper/AddressMapper.java
  71. 15 0
      src/main/java/space/anyi/mapper/AdminMapper.java
  72. 13 0
      src/main/java/space/anyi/mapper/AnnouncementMapper.java
  73. 12 0
      src/main/java/space/anyi/mapper/CakeCategoryMapper.java
  74. 14 0
      src/main/java/space/anyi/mapper/CakeImageMapper.java
  75. 22 0
      src/main/java/space/anyi/mapper/CakeMapper.java
  76. 14 0
      src/main/java/space/anyi/mapper/CakeSpecMapper.java
  77. 17 0
      src/main/java/space/anyi/mapper/CartItemMapper.java
  78. 13 0
      src/main/java/space/anyi/mapper/CartMapper.java
  79. 13 0
      src/main/java/space/anyi/mapper/CouponMapper.java
  80. 16 0
      src/main/java/space/anyi/mapper/CustomOptionMapper.java
  81. 13 0
      src/main/java/space/anyi/mapper/DeliveryConfigMapper.java
  82. 17 0
      src/main/java/space/anyi/mapper/FavoriteMapper.java
  83. 16 0
      src/main/java/space/anyi/mapper/MessageMapper.java
  84. 15 0
      src/main/java/space/anyi/mapper/OrderItemMapper.java
  85. 13 0
      src/main/java/space/anyi/mapper/OrderTrackingMapper.java
  86. 19 0
      src/main/java/space/anyi/mapper/OrdersMapper.java
  87. 17 0
      src/main/java/space/anyi/mapper/ReviewMapper.java
  88. 16 0
      src/main/java/space/anyi/mapper/UserCouponMapper.java
  89. 10 8
      src/main/java/space/anyi/mapper/UserMapper.java
  90. 13 0
      src/main/java/space/anyi/service/ActivityService.java
  91. 13 0
      src/main/java/space/anyi/service/AddressService.java
  92. 8 0
      src/main/java/space/anyi/service/AdminService.java
  93. 12 0
      src/main/java/space/anyi/service/AnnouncementService.java
  94. 20 0
      src/main/java/space/anyi/service/CakeService.java
  95. 12 0
      src/main/java/space/anyi/service/CakeSpecService.java
  96. 16 0
      src/main/java/space/anyi/service/CartService.java
  97. 12 0
      src/main/java/space/anyi/service/CategoryService.java
  98. 11 0
      src/main/java/space/anyi/service/CouponService.java
  99. 13 0
      src/main/java/space/anyi/service/CustomOptionService.java
  100. 12 0
      src/main/java/space/anyi/service/DeliveryService.java

+ 50 - 10
pom.xml

@@ -8,7 +8,7 @@
     <artifactId>SSM_template</artifactId>
     <version>1.0-SNAPSHOT</version>
     <packaging>war</packaging>
-    <description>SSM框架的模板</description>
+    <description>Cake Sales Management System</description>
 
     <properties>
         <maven.compiler.source>8</maven.compiler.source>
@@ -21,7 +21,9 @@
         <javax.servlet.jsp.version>2.3.3</javax.servlet.jsp.version>
         <!-- JSTL标签库版本 -->
         <jstl.version>1.2.2</jstl.version>
-        <thymeleaf.version>3.0.12.RELEASE</thymeleaf.version>
+        <commons-fileupload.version>1.4</commons-fileupload.version>
+        <commons-io.version>2.8.0</commons-io.version>
+        <jackson.version>2.15.2</jackson.version>
         <mybatis.version>3.5.6</mybatis.version>
         <mybatis-spring.version>2.0.6</mybatis-spring.version>
         <mysql.version>8.0.25</mysql.version>
@@ -66,18 +68,24 @@
             <version>1.9.7</version>
         </dependency>
 
-        <!-- Thymeleaf 核心库 -->
+        <!-- JSTL -->
         <dependency>
-            <groupId>org.thymeleaf</groupId>
-            <artifactId>thymeleaf</artifactId>
-            <version>${thymeleaf.version}</version>
+            <groupId>javax.servlet.jsp.jstl</groupId>
+            <artifactId>javax.servlet.jsp.jstl-api</artifactId>
+            <version>1.2.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.web</groupId>
+            <artifactId>javax.servlet.jsp.jstl</artifactId>
+            <version>1.2.4</version>
         </dependency>
 
-        <!-- Thymeleaf 与 Spring 5 集成包(Java 8 环境使用此版本) -->
+        <!-- JSP API -->
         <dependency>
-            <groupId>org.thymeleaf</groupId>
-            <artifactId>thymeleaf-spring5</artifactId>
-            <version>${thymeleaf.version}</version>
+            <groupId>javax.servlet.jsp</groupId>
+            <artifactId>javax.servlet.jsp-api</artifactId>
+            <version>${javax.servlet.jsp.version}</version>
+            <scope>provided</scope>
         </dependency>
 
         <!-- ========== MyBatis 依赖 ========== -->
@@ -112,6 +120,18 @@
             <scope>provided</scope>
         </dependency>
 
+        <!-- ========== 文件上传 ========== -->
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+            <version>${commons-fileupload.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>${commons-io.version}</version>
+        </dependency>
+
         <!-- ========== 日志 ========== -->
         <dependency>
             <groupId>ch.qos.logback</groupId>
@@ -119,6 +139,13 @@
             <version>1.2.11</version>
         </dependency>
 
+        <!-- ========== JSON 处理 ========== -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.version}</version>
+        </dependency>
+
         <!-- ========== 测试 ========== -->
         <dependency>
             <groupId>org.junit.jupiter</groupId>
@@ -132,6 +159,19 @@
             <version>${spring.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.12.4</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- PageHelper 分页 -->
+        <dependency>
+            <groupId>com.github.pagehelper</groupId>
+            <artifactId>pagehelper</artifactId>
+            <version>5.3.3</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 11 - 0
src/main/java/space/anyi/constant/OrderStatus.java

@@ -0,0 +1,11 @@
+package space.anyi.constant;
+
+public class OrderStatus {
+    public static final String PENDING_PAY = "待付款";
+    public static final String PENDING_PROCESS = "待制作";
+    public static final String PROCESSING = "制作中";
+    public static final String DELIVERING = "配送中";
+    public static final String COMPLETED = "已完成";
+    public static final String CANCELLED = "已取消";
+    public static final String REJECTED = "已拒绝";
+}

+ 0 - 22
src/main/java/space/anyi/controller/HelloController.java

@@ -1,22 +0,0 @@
-package space.anyi.controller;
-
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.servlet.ModelAndView;
-
-/**
- * @fileName: HelloController
- * @projectName: SSM_template
- * @package: space.anyi.controller
- * @author: 杨逸
- * @date:2026/4/23 23:04
- * @description: TODO
- */
-@Controller
-public class HelloController {
-    @GetMapping("/")
-    public ModelAndView hello(ModelAndView modelAndView){
-        modelAndView.setViewName("index");
-        return modelAndView;
-    }
-}

+ 127 - 0
src/main/java/space/anyi/controller/admin/AdminActivityController.java

@@ -0,0 +1,127 @@
+package space.anyi.controller.admin;
+
+import space.anyi.entity.Activity;
+import space.anyi.service.ActivityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+import java.io.File;
+import java.util.Date;
+import java.util.UUID;
+
+@Controller
+@RequestMapping("/admin")
+public class AdminActivityController {
+
+    @Value("${upload.path}")
+    private String uploadPath;
+
+    @Autowired
+    private ActivityService activityService;
+
+    @GetMapping("/activity")
+    public ModelAndView activityList() {
+        ModelAndView mv = new ModelAndView("admin/activity");
+        mv.addObject("activities", activityService.findAll());
+        return mv;
+    }
+
+    @GetMapping("/activity/add")
+    public ModelAndView addPage() {
+        return new ModelAndView("admin/activityEdit");
+    }
+
+    @GetMapping("/activity/edit/{id}")
+    public ModelAndView editPage(@PathVariable Integer id) {
+        ModelAndView mv = new ModelAndView("admin/activityEdit");
+        mv.addObject("activity", activityService.findById(id));
+        return mv;
+    }
+
+    @PostMapping("/activity/save")
+    public String save(Activity activity,
+                       @RequestParam(required = false) MultipartFile imageFile,
+                       @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") Date startTime,
+                       @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") Date endTime,
+                       RedirectAttributes attr) {
+        if (imageFile != null && !imageFile.isEmpty()) {
+            String path = saveUploadFile(imageFile);
+            if (path != null) {
+                activity.setImageUrl(path);
+            }
+        }
+        if (activity.getTitle() == null || activity.getTitle().trim().isEmpty()) {
+            attr.addFlashAttribute("error", "活动标题不能为空");
+            return "redirect:/admin/activity";
+        }
+
+        activity.setStartTime(startTime);
+        activity.setEndTime(endTime);
+
+        if (activity.getId() != null) {
+            Activity existing = activityService.findById(activity.getId());
+            if (activity.getImageUrl() == null || activity.getImageUrl().isEmpty()) {
+                activity.setImageUrl(existing.getImageUrl());
+            }
+            if (activity.getStatus() == null) {
+                activity.setStatus(existing.getStatus());
+            }
+            activityService.update(activity);
+            attr.addFlashAttribute("success", "更新成功");
+        } else {
+            if (activity.getStatus() == null) {
+                activity.setStatus("启用");
+            }
+            activity.setCreateTime(new Date());
+            activityService.add(activity);
+            attr.addFlashAttribute("success", "添加成功");
+        }
+        return "redirect:/admin/activity";
+    }
+
+    @PostMapping("/activity/delete/{id}")
+    public String delete(@PathVariable Integer id, RedirectAttributes attr) {
+        activityService.delete(id);
+        attr.addFlashAttribute("success", "删除成功");
+        return "redirect:/admin/activity";
+    }
+
+    @PostMapping("/activity/toggleStatus")
+    @ResponseBody
+    public String toggleStatus(@RequestParam Integer id) {
+        Activity activity = activityService.findById(id);
+        if (activity != null) {
+            activity.setStatus("启用".equals(activity.getStatus()) ? "停用" : "启用");
+            activityService.update(activity);
+        }
+        return "ok";
+    }
+
+    private String saveUploadFile(MultipartFile file) {
+        try {
+            String dirPath = new File(uploadPath).getAbsolutePath();
+            File dir = new File(dirPath);
+            if (!dir.exists()) {
+                dir.mkdirs();
+            }
+            String originalFilename = file.getOriginalFilename();
+            String ext = "";
+            if (originalFilename != null && originalFilename.contains(".")) {
+                ext = originalFilename.substring(originalFilename.lastIndexOf("."));
+            }
+            String newFilename = UUID.randomUUID().toString().replace("-", "") + ext;
+            File dest = new File(dir, newFilename);
+            file.transferTo(dest);
+            return "/upload/" + newFilename;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 225 - 0
src/main/java/space/anyi/controller/admin/AdminCakeController.java

@@ -0,0 +1,225 @@
+package space.anyi.controller.admin;
+
+import space.anyi.entity.*;
+import space.anyi.entity.dto.*;
+import space.anyi.service.*;
+import space.anyi.util.Converter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import java.io.File;
+import java.util.List;
+import java.util.UUID;
+
+@Controller
+@RequestMapping("/admin")
+public class AdminCakeController {
+
+    @Value("${upload.path}")
+    private String uploadPath;
+
+    @Autowired private CategoryService categoryService;
+    @Autowired private CakeService cakeService;
+    @Autowired private CakeSpecService cakeSpecService;
+    @Autowired private CustomOptionService customOptionService;
+
+    // === Category Management ===
+    @GetMapping("/category")
+    public ModelAndView category() {
+        ModelAndView mv = new ModelAndView("admin/category");
+        mv.addObject("categories", categoryService.findAll());
+        return mv;
+    }
+
+    @PostMapping("/category")
+    public String addCategory(CategoryDTO dto, RedirectAttributes attr) {
+        categoryService.add(Converter.toCategory(dto));
+        attr.addFlashAttribute("success", "添加成功");
+        return "redirect:/admin/category";
+    }
+
+    @PostMapping("/category/update")
+    public String updateCategory(CategoryDTO dto, RedirectAttributes attr) {
+        categoryService.update(Converter.toCategory(dto));
+        attr.addFlashAttribute("success", "更新成功");
+        return "redirect:/admin/category";
+    }
+
+    @PostMapping("/category/delete/{id}")
+    public String deleteCategory(@PathVariable Integer id, RedirectAttributes attr) {
+        categoryService.delete(id);
+        attr.addFlashAttribute("success", "删除成功");
+        return "redirect:/admin/category";
+    }
+
+    // === Cake Management ===
+    @GetMapping("/cake")
+    public ModelAndView cakeList(@RequestParam(required = false) String status,
+                                 @RequestParam(defaultValue = "1") Integer page) {
+        ModelAndView mv = new ModelAndView("admin/cake");
+        CakeSearchCriteria criteria = new CakeSearchCriteria();
+        criteria.setStatus(status);
+        List<Cake> cakeList = cakeService.search(criteria, page, 10);
+        int totalPages = 1;
+        if (cakeList instanceof com.github.pagehelper.Page) {
+            com.github.pagehelper.Page<Cake> p = (com.github.pagehelper.Page<Cake>) cakeList;
+            totalPages = p.getPages();
+        }
+        mv.addObject("cakes", Converter.toCakeVOList(cakeList));
+        mv.addObject("categories", categoryService.findAll());
+        mv.addObject("status", status);
+        mv.addObject("page", page);
+        mv.addObject("totalPages", totalPages);
+        return mv;
+    }
+
+    @GetMapping("/cake/add")
+    public ModelAndView addCakePage() {
+        ModelAndView mv = new ModelAndView("admin/cakeEdit");
+        mv.addObject("categories", categoryService.findAll());
+        return mv;
+    }
+
+    @GetMapping("/cake/edit/{id}")
+    public ModelAndView editCakePage(@PathVariable Integer id) {
+        ModelAndView mv = new ModelAndView("admin/cakeEdit");
+        mv.addObject("cake", Converter.toCakeVO(cakeService.findById(id)));
+        mv.addObject("categories", categoryService.findAll());
+        return mv;
+    }
+
+    @PostMapping("/cake/save")
+    public String saveCake(CakeSaveDTO dto,
+                           @RequestParam(required = false) MultipartFile mainImageFile,
+                           RedirectAttributes attr) {
+        if (mainImageFile != null && !mainImageFile.isEmpty()) {
+            String path = saveUploadFile(mainImageFile);
+            if (path != null) {
+                dto.setMainImage(path);
+            }
+        }
+        if (dto.getName() == null || dto.getName().trim().isEmpty()) {
+            attr.addFlashAttribute("error", "蛋糕名称不能为空");
+            return "redirect:/admin/cake";
+        }
+        if (dto.getBasePrice() == null) {
+            attr.addFlashAttribute("error", "基础价格不能为空");
+            return "redirect:/admin/cake";
+        }
+        Cake cake = Converter.toCake(dto);
+        if (cake.getId() != null) {
+            cakeService.update(cake);
+            attr.addFlashAttribute("success", "更新成功");
+        } else {
+            cakeService.add(cake);
+            attr.addFlashAttribute("success", "添加成功");
+        }
+        return "redirect:/admin/cake";
+    }
+
+    private String saveUploadFile(MultipartFile file) {
+        try {
+            String dirPath = new File(uploadPath).getAbsolutePath();
+            File dir = new File(dirPath);
+            if (!dir.exists()) {
+                dir.mkdirs();
+            }
+            String originalFilename = file.getOriginalFilename();
+            String ext = "";
+            if (originalFilename != null && originalFilename.contains(".")) {
+                ext = originalFilename.substring(originalFilename.lastIndexOf("."));
+            }
+            String newFilename = UUID.randomUUID().toString().replace("-", "") + ext;
+            File dest = new File(dir, newFilename);
+            file.transferTo(dest);
+            return "/upload/" + newFilename;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @PostMapping("/cake/delete/{id}")
+    public String deleteCake(@PathVariable Integer id, RedirectAttributes attr) {
+        cakeService.delete(id);
+        attr.addFlashAttribute("success", "删除成功");
+        return "redirect:/admin/cake";
+    }
+
+    @PostMapping("/cake/toggleStatus")
+    @ResponseBody
+    public String toggleStatus(@RequestParam Integer id) {
+        cakeService.toggleStatus(id);
+        return "ok";
+    }
+
+    // === Spec Management ===
+    @GetMapping("/spec")
+    public String specRedirect() {
+        return "redirect:/admin/cake";
+    }
+
+    @GetMapping("/spec/{cakeId}")
+    public ModelAndView spec(@PathVariable Integer cakeId) {
+        ModelAndView mv = new ModelAndView("admin/spec");
+        mv.addObject("cakeId", cakeId);
+        mv.addObject("specs", Converter.toCakeSpecVOList(cakeSpecService.findByCakeId(cakeId)));
+        return mv;
+    }
+
+    @PostMapping("/spec/save")
+    public String saveSpec(CakeSpecDTO dto, RedirectAttributes attr) {
+        CakeSpec spec = Converter.toCakeSpec(dto);
+        if (spec.getId() != null) {
+            cakeSpecService.update(spec);
+        } else {
+            cakeSpecService.add(spec);
+        }
+        attr.addFlashAttribute("success", "保存成功");
+        return "redirect:/admin/spec/" + spec.getCakeId();
+    }
+
+    @PostMapping("/spec/delete/{id}")
+    public String deleteSpec(@PathVariable Integer id, @RequestParam Integer cakeId, RedirectAttributes attr) {
+        cakeSpecService.delete(id);
+        attr.addFlashAttribute("success", "删除成功");
+        return "redirect:/admin/spec/" + cakeId;
+    }
+
+    // === Custom Option Management ===
+    @GetMapping("/customOption")
+    public String customOptionRedirect() {
+        return "redirect:/admin/cake";
+    }
+
+    @GetMapping("/custom-option/{cakeId}")
+    public ModelAndView customOption(@PathVariable Integer cakeId) {
+        ModelAndView mv = new ModelAndView("admin/customOption");
+        mv.addObject("cakeId", cakeId);
+        mv.addObject("customOptions", customOptionService.findByCakeId(cakeId));
+        return mv;
+    }
+
+    @PostMapping("/custom-option/save")
+    public String saveCustomOption(CustomOptionDTO dto, RedirectAttributes attr) {
+        CustomOption option = Converter.toCustomOption(dto);
+        if (option.getId() != null) {
+            customOptionService.update(option);
+        } else {
+            customOptionService.add(option);
+        }
+        attr.addFlashAttribute("success", "保存成功");
+        return "redirect:/admin/custom-option/" + option.getCakeId();
+    }
+
+    @PostMapping("/custom-option/delete/{id}")
+    public String deleteCustomOption(@PathVariable Integer id, @RequestParam Integer cakeId, RedirectAttributes attr) {
+        customOptionService.delete(id);
+        attr.addFlashAttribute("success", "删除成功");
+        return "redirect:/admin/custom-option/" + cakeId;
+    }
+}

+ 131 - 0
src/main/java/space/anyi/controller/admin/AdminLoginController.java

@@ -0,0 +1,131 @@
+package space.anyi.controller.admin;
+
+import space.anyi.entity.Admin;
+import space.anyi.entity.Cake;
+import space.anyi.entity.Orders;
+import space.anyi.entity.User;
+import space.anyi.entity.dto.CakeSalesVO;
+import space.anyi.service.AdminService;
+import space.anyi.service.CakeService;
+import space.anyi.service.OrderService;
+import space.anyi.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Controller
+@RequestMapping("/admin")
+public class AdminLoginController {
+
+    @Autowired
+    private AdminService adminService;
+
+    @Autowired
+    private OrderService orderService;
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private CakeService cakeService;
+
+    @GetMapping("/login")
+    public String loginPage() {
+        return "admin/login";
+    }
+
+    @PostMapping("/login")
+    public String login(@RequestParam String username, @RequestParam String password,
+                        HttpSession session, RedirectAttributes attr) {
+        Admin admin = adminService.login(username, password);
+        if (admin != null) {
+            session.setAttribute("admin", admin);
+            return "redirect:/admin/";
+        }
+        attr.addFlashAttribute("error", "用户名或密码错误");
+        return "redirect:/admin/login";
+    }
+
+    @GetMapping("/logout")
+    public String logout(HttpSession session) {
+        session.removeAttribute("admin");
+        return "redirect:/admin/login";
+    }
+
+    @GetMapping({"", "/", "/index"})
+    public String index(Model model) {
+        Date now = new Date();
+        Calendar cal = Calendar.getInstance();
+        cal.add(Calendar.DAY_OF_MONTH, -1);
+        Date yesterday = cal.getTime();
+
+        List<Orders> allOrders = orderService.findAll(null, null, null);
+
+        long todayOrderCount = allOrders.stream().filter(o -> isSameDay(o.getCreateTime(), now)).count();
+        long yesterdayOrderCount = allOrders.stream().filter(o -> isSameDay(o.getCreateTime(), yesterday)).count();
+
+        BigDecimal todayRevenue = allOrders.stream()
+                .filter(o -> isSameDay(o.getCreateTime(), now))
+                .map(Orders::getPayAmount)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        BigDecimal monthRevenue = allOrders.stream()
+                .filter(o -> isSameMonth(o.getCreateTime(), now))
+                .map(Orders::getPayAmount)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        List<User> allUsers = userService.findAll();
+        long userCount = allUsers.size();
+        long todayNewUserCount = allUsers.stream().filter(u -> isSameDay(u.getCreateTime(), now)).count();
+
+        List<Cake> allCakes = cakeService.search(null, null, null);
+        long cakeCount = allCakes.size();
+        long onlineCakeCount = allCakes.stream().filter(c -> "上架".equals(c.getStatus())).count();
+
+        List<Orders> recentOrders = allOrders.stream().limit(10).collect(Collectors.toList());
+
+        List<CakeSalesVO> hotCakes = cakeService.findHotCakes();
+
+        model.addAttribute("todayOrderCount", todayOrderCount);
+        model.addAttribute("yesterdayOrderCount", yesterdayOrderCount);
+        model.addAttribute("todayRevenue", todayRevenue);
+        model.addAttribute("monthRevenue", monthRevenue);
+        model.addAttribute("userCount", userCount);
+        model.addAttribute("todayNewUserCount", todayNewUserCount);
+        model.addAttribute("cakeCount", cakeCount);
+        model.addAttribute("onlineCakeCount", onlineCakeCount);
+        model.addAttribute("recentOrders", recentOrders);
+        model.addAttribute("hotCakes", hotCakes);
+
+        return "admin/index";
+    }
+
+    private boolean isSameDay(Date d1, Date d2) {
+        if (d1 == null || d2 == null) return false;
+        Calendar c1 = Calendar.getInstance();
+        c1.setTime(d1);
+        Calendar c2 = Calendar.getInstance();
+        c2.setTime(d2);
+        return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
+                && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
+    }
+
+    private boolean isSameMonth(Date d1, Date d2) {
+        if (d1 == null || d2 == null) return false;
+        Calendar c1 = Calendar.getInstance();
+        c1.setTime(d1);
+        Calendar c2 = Calendar.getInstance();
+        c2.setTime(d2);
+        return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
+                && c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH);
+    }
+}

+ 76 - 0
src/main/java/space/anyi/controller/admin/AdminOrderController.java

@@ -0,0 +1,76 @@
+package space.anyi.controller.admin;
+
+import space.anyi.entity.*;
+import space.anyi.service.*;
+import space.anyi.util.Converter;
+import space.anyi.entity.vo.OrderVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+import space.anyi.entity.dto.OrderSearchCriteria;
+import java.util.List;
+
+@Controller
+@RequestMapping("/admin")
+public class AdminOrderController {
+
+    @Autowired private OrderService orderService;
+    @Autowired private OrderItemService orderItemService;
+    @Autowired private OrderTrackingService orderTrackingService;
+    @Autowired private UserService userService;
+
+    @GetMapping("/order")
+    public ModelAndView list(@RequestParam(required = false) String status,
+                             @RequestParam(defaultValue = "1") Integer page) {
+        ModelAndView mv = new ModelAndView("admin/order");
+        OrderSearchCriteria criteria = new OrderSearchCriteria();
+        criteria.setStatus(status);
+        List<OrderVO> orderVOs = Converter.toOrderVOList(orderService.findAll(criteria, page, 10));
+        for (OrderVO vo : orderVOs) {
+            if (vo.getUserId() != null) {
+                User user = userService.findById(vo.getUserId());
+                if (user != null) {
+                    vo.setUsername(user.getUsername());
+                }
+            }
+            List<OrderItem> items = orderItemService.findByOrderId(vo.getId());
+            vo.setItemCount(items != null ? items.size() : 0);
+        }
+        mv.addObject("orders", orderVOs);
+        mv.addObject("status", status);
+        mv.addObject("page", page);
+        return mv;
+    }
+
+    @GetMapping("/order/{id}")
+    public ModelAndView detail(@PathVariable Integer id) {
+        ModelAndView mv = new ModelAndView("admin/orderDetail");
+        Orders order = orderService.findById(id);
+        OrderVO orderVO = Converter.toOrderVO(order);
+        if (order.getUserId() != null) {
+            User user = userService.findById(order.getUserId());
+            if (user != null) {
+                orderVO.setUsername(user.getUsername());
+            }
+        }
+        mv.addObject("order", orderVO);
+        mv.addObject("items", Converter.toOrderItemVOList(orderItemService.findByOrderId(order.getId())));
+        mv.addObject("tracking", Converter.toOrderTrackingVOList(orderTrackingService.findByOrderId(order.getId())));
+        return mv;
+    }
+
+    @PostMapping("/order/updateStatus")
+    public String updateStatus(@RequestParam Integer id, @RequestParam String status,
+                               @RequestParam(required = false) String remark,
+                               HttpSession session, RedirectAttributes attr) {
+        Admin admin = (Admin) session.getAttribute("admin");
+        String operator = admin != null ? admin.getUsername() : "系统";
+        orderService.updateStatus(id, status, operator, remark);
+        attr.addFlashAttribute("success", "状态已更新");
+        return "redirect:/admin/order/" + id;
+    }
+}

+ 60 - 0
src/main/java/space/anyi/controller/admin/AdminReviewController.java

@@ -0,0 +1,60 @@
+package space.anyi.controller.admin;
+
+import space.anyi.entity.Cake;
+import space.anyi.entity.Review;
+import space.anyi.service.CakeService;
+import space.anyi.service.ReviewService;
+import space.anyi.util.Converter;
+import space.anyi.entity.vo.ReviewVO;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+@Controller
+@RequestMapping("/admin")
+public class AdminReviewController {
+
+    @Autowired private ReviewService reviewService;
+    @Autowired private CakeService cakeService;
+
+    @GetMapping("/review")
+    public ModelAndView list(@RequestParam(defaultValue = "1") Integer page) {
+        ModelAndView mv = new ModelAndView("admin/review");
+        List<Review> reviewList = reviewService.findAll(page, 10);
+        int totalPages = 1;
+        if (reviewList instanceof com.github.pagehelper.Page) {
+            com.github.pagehelper.Page<Review> p = (com.github.pagehelper.Page<Review>) reviewList;
+            totalPages = p.getPages();
+        }
+        List<ReviewVO> reviewVOs = Converter.toReviewVOList(reviewList);
+        for (ReviewVO vo : reviewVOs) {
+            if (vo.getCakeId() != null) {
+                Cake cake = cakeService.findById(vo.getCakeId());
+                if (cake != null) {
+                    vo.setCakeName(cake.getName());
+                }
+            }
+        }
+        mv.addObject("reviews", reviewVOs);
+        mv.addObject("page", page);
+        mv.addObject("totalPages", totalPages);
+        return mv;
+    }
+
+    @PostMapping("/review/toggleStatus")
+    public String toggleStatus(@RequestParam Integer id, RedirectAttributes attr) {
+        reviewService.toggleStatus(id);
+        attr.addFlashAttribute("success", "状态已更改");
+        return "redirect:/admin/review";
+    }
+
+    @PostMapping("/review/reply")
+    public String reply(@RequestParam Integer id, @RequestParam String replyContent, RedirectAttributes attr) {
+        reviewService.reply(id, replyContent);
+        attr.addFlashAttribute("success", "回复成功");
+        return "redirect:/admin/review";
+    }
+}

+ 95 - 0
src/main/java/space/anyi/controller/admin/AdminSettingsController.java

@@ -0,0 +1,95 @@
+package space.anyi.controller.admin;
+
+import space.anyi.entity.*;
+import space.anyi.entity.dto.*;
+import space.anyi.service.*;
+import space.anyi.util.Converter;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+
+@Controller
+@RequestMapping("/admin")
+public class AdminSettingsController {
+
+    @Autowired private DeliveryService deliveryService;
+    @Autowired private AnnouncementService announcementService;
+    @Autowired private AdminService adminService;
+    @Autowired private MessageService messageService;
+
+    @GetMapping("/settings")
+    public ModelAndView settings() throws Exception {
+        ModelAndView mv = new ModelAndView("admin/settings");
+        ObjectMapper mapper = new ObjectMapper();
+        mv.addObject("deliveryConfigs", deliveryService.findAll());
+        mv.addObject("announcements", announcementService.findAll());
+        mv.addObject("messages", messageService.findAll());
+        mv.addObject("deliveryConfigsJson", mapper.writeValueAsString(deliveryService.findAll()));
+        mv.addObject("announcementsJson", mapper.writeValueAsString(announcementService.findAll()));
+        return mv;
+    }
+
+    @PostMapping("/settings/delivery/save")
+    public String saveDelivery(DeliveryConfigDTO dto, RedirectAttributes attr) {
+        DeliveryConfig config = Converter.toDeliveryConfig(dto);
+        if (config.getId() != null) {
+            deliveryService.update(config);
+        } else {
+            deliveryService.save(config);
+        }
+        attr.addFlashAttribute("success", "配送设置已保存");
+        return "redirect:/admin/settings";
+    }
+
+    @PostMapping("/settings/delivery/delete/{id}")
+    public String deleteDelivery(@PathVariable Integer id, RedirectAttributes attr) {
+        deliveryService.delete(id);
+        attr.addFlashAttribute("success", "配送区域已删除");
+        return "redirect:/admin/settings";
+    }
+
+    @PostMapping("/settings/announcement/save")
+    public String saveAnnouncement(AnnouncementDTO dto, RedirectAttributes attr) {
+        Announcement announcement = Converter.toAnnouncement(dto);
+        if (announcement.getId() != null) {
+            announcementService.update(announcement);
+        } else {
+            announcementService.add(announcement);
+        }
+        attr.addFlashAttribute("success", "公告已保存");
+        return "redirect:/admin/settings";
+    }
+
+    @PostMapping("/settings/announcement/delete/{id}")
+    public String deleteAnnouncement(@PathVariable Integer id, RedirectAttributes attr) {
+        announcementService.delete(id);
+        attr.addFlashAttribute("success", "公告已删除");
+        return "redirect:/admin/settings";
+    }
+
+    @PostMapping("/settings/password")
+    public String updatePassword(@RequestParam String oldPassword, @RequestParam String newPassword,
+                                 HttpSession session, RedirectAttributes attr) {
+        Admin admin = (Admin) session.getAttribute("admin");
+        Admin loggedIn = adminService.login(admin.getUsername(), oldPassword);
+        if (loggedIn != null) {
+            adminService.updatePassword(admin.getId(), newPassword);
+            attr.addFlashAttribute("success", "密码修改成功");
+        } else {
+            attr.addFlashAttribute("error", "原密码错误");
+        }
+        return "redirect:/admin/settings";
+    }
+
+    @PostMapping("/settings/message/reply")
+    public String replyMessage(@RequestParam Integer id, @RequestParam String replyContent, RedirectAttributes attr) {
+        messageService.reply(id, replyContent);
+        attr.addFlashAttribute("success", "回复成功");
+        return "redirect:/admin/settings";
+    }
+}

+ 101 - 0
src/main/java/space/anyi/controller/admin/AdminStatisticsController.java

@@ -0,0 +1,101 @@
+package space.anyi.controller.admin;
+
+import space.anyi.entity.Orders;
+import space.anyi.entity.dto.CakeSalesVO;
+import space.anyi.entity.dto.OrderSearchCriteria;
+import space.anyi.mapper.OrdersMapper;
+import space.anyi.service.CakeService;
+import space.anyi.service.OrderService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Controller
+@RequestMapping("/admin")
+public class AdminStatisticsController {
+
+    @Autowired
+    private OrderService orderService;
+
+    @Autowired
+    private OrdersMapper ordersMapper;
+
+    @Autowired
+    private CakeService cakeService;
+
+    @GetMapping("/statistics")
+    public ModelAndView statistics() {
+        ModelAndView mv = new ModelAndView("admin/statistics");
+
+        Date now = new Date();
+        List<Orders> allOrders = orderService.findAll(null, null, null);
+
+        long todayOrderCount = allOrders.stream().filter(o -> isSameDay(o.getCreateTime(), now)).count();
+        long monthOrderCount = allOrders.stream().filter(o -> isSameMonth(o.getCreateTime(), now)).count();
+
+        BigDecimal todayRevenue = allOrders.stream()
+                .filter(o -> isSameDay(o.getCreateTime(), now))
+                .map(Orders::getPayAmount)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        BigDecimal monthRevenue = allOrders.stream()
+                .filter(o -> isSameMonth(o.getCreateTime(), now))
+                .map(Orders::getPayAmount)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        mv.addObject("todayOrderCount", todayOrderCount);
+        mv.addObject("monthOrderCount", monthOrderCount);
+        mv.addObject("todayRevenue", todayRevenue);
+        mv.addObject("monthRevenue", monthRevenue);
+
+        OrderSearchCriteria criteria = new OrderSearchCriteria();
+        criteria.setStatus("已完成");
+        List<Map<String, Object>> dailyAmount = ordersMapper.sumAmountByDate(criteria);
+        mv.addObject("revenueData", dailyAmount);
+
+        Map<String, Long> statusCounts = allOrders.stream()
+                .filter(o -> o.getStatus() != null)
+                .collect(Collectors.groupingBy(Orders::getStatus, Collectors.counting()));
+        List<Map<String, Object>> orderStatusData = new ArrayList<>();
+        for (Map.Entry<String, Long> entry : statusCounts.entrySet()) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("label", entry.getKey());
+            item.put("count", entry.getValue());
+            orderStatusData.add(item);
+        }
+        mv.addObject("orderStatusData", orderStatusData);
+
+        List<CakeSalesVO> hotCakes = cakeService.findHotCakes();
+        mv.addObject("hotCakes", hotCakes);
+
+        return mv;
+    }
+
+    private boolean isSameDay(Date d1, Date d2) {
+        if (d1 == null || d2 == null) return false;
+        Calendar c1 = Calendar.getInstance();
+        c1.setTime(d1);
+        Calendar c2 = Calendar.getInstance();
+        c2.setTime(d2);
+        return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
+                && c1.get(Calendar.DAY_OF_YEAR) == c2.get(Calendar.DAY_OF_YEAR);
+    }
+
+    private boolean isSameMonth(Date d1, Date d2) {
+        if (d1 == null || d2 == null) return false;
+        Calendar c1 = Calendar.getInstance();
+        c1.setTime(d1);
+        Calendar c2 = Calendar.getInstance();
+        c2.setTime(d2);
+        return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR)
+                && c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH);
+    }
+}

+ 68 - 0
src/main/java/space/anyi/controller/admin/AdminUserController.java

@@ -0,0 +1,68 @@
+package space.anyi.controller.admin;
+
+import space.anyi.entity.User;
+import space.anyi.service.UserService;
+import space.anyi.util.Converter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+import java.util.Date;
+import java.util.List;
+
+@Controller
+@RequestMapping("/admin")
+public class AdminUserController {
+
+    @Autowired private UserService userService;
+
+    @GetMapping("/user")
+    public ModelAndView list(@RequestParam(defaultValue = "1") Integer page) {
+        ModelAndView mv = new ModelAndView("admin/user");
+        List<User> userList = userService.findAll(page, 10);
+        int totalPages = 1;
+        if (userList instanceof com.github.pagehelper.Page) {
+            com.github.pagehelper.Page<User> p = (com.github.pagehelper.Page<User>) userList;
+            totalPages = p.getPages();
+        }
+        mv.addObject("users", Converter.toUserVOList(userList));
+        mv.addObject("page", page);
+        mv.addObject("totalPages", totalPages);
+        return mv;
+    }
+
+    @PostMapping("/user/toggleStatus")
+    public String toggleStatus(@RequestParam Integer id, RedirectAttributes attr) {
+        userService.toggleStatus(id);
+        attr.addFlashAttribute("success", "状态已更改");
+        return "redirect:/admin/user";
+    }
+
+    @GetMapping("/user/edit/{id}")
+    public ModelAndView editPage(@PathVariable Integer id) {
+        ModelAndView mv = new ModelAndView("admin/userEdit");
+        mv.addObject("user", Converter.toUserVO(userService.findById(id)));
+        return mv;
+    }
+
+    @PostMapping("/user/edit")
+    public String edit(User user, RedirectAttributes attr) {
+        User existing = userService.findById(user.getId());
+        if (existing != null) {
+            user.setUsername(existing.getUsername());
+            user.setAvatar(existing.getAvatar());
+        }
+        user.setUpdateTime(new Date());
+        userService.update(user);
+        attr.addFlashAttribute("success", "用户信息已更新");
+        return "redirect:/admin/user";
+    }
+
+    @PostMapping("/user/delete/{id}")
+    public String delete(@PathVariable Integer id, RedirectAttributes attr) {
+        userService.deleteById(id);
+        attr.addFlashAttribute("success", "用户已删除");
+        return "redirect:/admin/user";
+    }
+}

+ 31 - 0
src/main/java/space/anyi/controller/front/ActivityController.java

@@ -0,0 +1,31 @@
+package space.anyi.controller.front;
+
+import space.anyi.service.ActivityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+@Controller
+@RequestMapping("/activity")
+public class ActivityController {
+
+    @Autowired
+    private ActivityService activityService;
+
+    @GetMapping({"", "/list"})
+    public ModelAndView list() {
+        ModelAndView mv = new ModelAndView("activity/list");
+        mv.addObject("activities", activityService.findActive());
+        return mv;
+    }
+
+    @GetMapping("/{id}")
+    public ModelAndView detail(@PathVariable Integer id) {
+        ModelAndView mv = new ModelAndView("activity/detail");
+        mv.addObject("activity", activityService.findById(id));
+        return mv;
+    }
+}

+ 143 - 0
src/main/java/space/anyi/controller/front/CakeController.java

@@ -0,0 +1,143 @@
+package space.anyi.controller.front;
+
+import space.anyi.entity.CustomOption;
+import space.anyi.entity.User;
+import space.anyi.entity.dto.CakeSearchCriteria;
+import space.anyi.service.*;
+import space.anyi.util.Converter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpSession;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Controller
+@RequestMapping("/cake")
+public class CakeController {
+
+    @Autowired
+    private CakeService cakeService;
+    @Autowired
+    private CategoryService categoryService;
+    @Autowired
+    private CakeSpecService cakeSpecService;
+    @Autowired
+    private CustomOptionService customOptionService;
+    @Autowired
+    private ReviewService reviewService;
+    @Autowired
+    private FavoriteService favoriteService;
+
+    private static final java.util.Set<String> ALLOWED_SORT = new java.util.HashSet<>(
+            java.util.Arrays.asList("base_price ASC", "base_price DESC", "sales_count DESC", "create_time DESC", "name ASC"));
+
+    @GetMapping("/list")
+    public ModelAndView list(@RequestParam(required = false) Integer categoryId,
+                             @RequestParam(required = false) String taste,
+                             @RequestParam(required = false) String style,
+                             @RequestParam(required = false) String keyword,
+                             @RequestParam(required = false) BigDecimal minPrice,
+                             @RequestParam(required = false) BigDecimal maxPrice,
+                             @RequestParam(required = false) String sortBy,
+                             @RequestParam(defaultValue = "1") Integer page) {
+        ModelAndView mv = new ModelAndView("cake/list");
+        if (sortBy != null && !ALLOWED_SORT.contains(sortBy)) {
+            sortBy = null;
+        }
+        CakeSearchCriteria criteria = new CakeSearchCriteria();
+        criteria.setCategoryId(categoryId);
+        criteria.setTaste(taste);
+        criteria.setStyle(style);
+        criteria.setKeyword(keyword);
+        criteria.setMinPrice(minPrice);
+        criteria.setMaxPrice(maxPrice);
+        criteria.setSortBy(sortBy);
+        List<space.anyi.entity.Cake> cakeList = cakeService.search(criteria, page, 12);
+        int totalPages = 1;
+        long total = 0;
+        if (cakeList instanceof com.github.pagehelper.Page) {
+            com.github.pagehelper.Page<space.anyi.entity.Cake> p = (com.github.pagehelper.Page<space.anyi.entity.Cake>) cakeList;
+            totalPages = p.getPages();
+            total = p.getTotal();
+        }
+        mv.addObject("cakes", Converter.toCakeVOList(cakeList));
+        mv.addObject("categories", categoryService.findAll());
+        mv.addObject("categoryId", categoryId);
+        mv.addObject("taste", taste);
+        mv.addObject("style", style);
+        mv.addObject("keyword", keyword);
+        mv.addObject("page", page);
+        mv.addObject("totalPages", totalPages);
+        mv.addObject("total", total);
+        return mv;
+    }
+
+    @GetMapping("/{id}")
+    public ModelAndView detail(@PathVariable Integer id, HttpSession session) {
+        ModelAndView mv = new ModelAndView("cake/detail");
+        mv.addObject("cake", Converter.toCakeVO(cakeService.findById(id)));
+        mv.addObject("specs", Converter.toCakeSpecVOList(cakeSpecService.findByCakeId(id)));
+        mv.addObject("reviews", Converter.toReviewVOList(reviewService.findByCakeId(id)));
+        User user = (User) session.getAttribute("user");
+        if (user != null) {
+            mv.addObject("isFavorited", favoriteService.isFavorited(user.getId(), id));
+        }
+        return mv;
+    }
+
+    @GetMapping("/custom")
+    public ModelAndView custom(@RequestParam(required = false) Integer cakeId) {
+        if (cakeId == null) {
+            return new ModelAndView("redirect:/cake/list");
+        }
+        ModelAndView mv = new ModelAndView("cake/custom");
+        mv.addObject("cake", Converter.toCakeVO(cakeService.findById(cakeId)));
+        mv.addObject("options", customOptionService.findByCakeId(cakeId));
+        mv.addObject("allOptions", customOptionService.findAll());
+        return mv;
+    }
+
+    @PostMapping("/custom/preview")
+    @ResponseBody
+    public Map<String, Object> preview(@RequestParam(required = false) Integer cakeId,
+                                       @RequestParam(required = false) Integer specId,
+                                       @RequestParam(required = false) String optionIds) {
+        Map<String, Object> result = new HashMap<>();
+        BigDecimal totalAddPrice = BigDecimal.ZERO;
+
+        if (specId != null) {
+            space.anyi.entity.CakeSpec spec = cakeSpecService.findById(specId);
+            if (spec != null) {
+                totalAddPrice = totalAddPrice.add(spec.getAddPrice());
+            }
+        }
+
+        if (optionIds != null && !optionIds.isEmpty()) {
+            for (String idStr : optionIds.split(",")) {
+                CustomOption option = customOptionService.findById(Integer.parseInt(idStr.trim()));
+                if (option != null && option.getAddPrice() != null) {
+                    totalAddPrice = totalAddPrice.add(option.getAddPrice());
+                }
+            }
+        }
+
+        BigDecimal basePrice = BigDecimal.ZERO;
+        if (cakeId != null) {
+            space.anyi.entity.Cake cake = cakeService.findById(cakeId);
+            if (cake != null) {
+                basePrice = cake.getBasePrice();
+            }
+        }
+
+        result.put("basePrice", basePrice);
+        result.put("addPrice", totalAddPrice);
+        result.put("totalPrice", basePrice.add(totalAddPrice));
+        result.put("success", true);
+        return result;
+    }
+}

+ 113 - 0
src/main/java/space/anyi/controller/front/CartController.java

@@ -0,0 +1,113 @@
+package space.anyi.controller.front;
+
+import space.anyi.entity.User;
+import space.anyi.service.CartService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+import java.io.File;
+import java.util.UUID;
+
+@Controller
+@RequestMapping("/cart")
+public class CartController {
+
+    @Value("${upload.path}")
+    private String uploadPath;
+
+    @Autowired
+    private CartService cartService;
+
+    private String saveUploadFile(MultipartFile file) {
+        try {
+            String dirPath = new File(uploadPath).getAbsolutePath();
+            File dir = new File(dirPath);
+            if (!dir.exists()) {
+                dir.mkdirs();
+            }
+            String originalFilename = file.getOriginalFilename();
+            String ext = "";
+            if (originalFilename != null && originalFilename.contains(".")) {
+                ext = originalFilename.substring(originalFilename.lastIndexOf("."));
+            }
+            String newFilename = UUID.randomUUID().toString().replace("-", "") + ext;
+            File dest = new File(dir, newFilename);
+            file.transferTo(dest);
+            return "/upload/" + newFilename;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    @GetMapping
+    public ModelAndView cart(HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        ModelAndView mv = new ModelAndView("cart/cart");
+        mv.addObject("cartItems", cartService.getCartItemVOs(user.getId()));
+        return mv;
+    }
+
+    @GetMapping("/add")
+    public String addPage() {
+        return "redirect:/cart";
+    }
+
+    @PostMapping("/add")
+    public String addItem(@RequestParam(required = false) Integer cakeId,
+                          @RequestParam(required = false) Integer specId,
+                          @RequestParam(defaultValue = "1") Integer quantity,
+                          @RequestParam(required = false) String customJson,
+                          @RequestParam(required = false) MultipartFile image,
+                          HttpSession session,
+                          RedirectAttributes attr) {
+        if (cakeId == null) {
+            attr.addFlashAttribute("error", "请先选择蛋糕");
+            return "redirect:/cake/list";
+        }
+        if (image != null && !image.isEmpty()) {
+            String imagePath = saveUploadFile(image);
+            if (imagePath != null && customJson != null && customJson.endsWith("}")) {
+                customJson = customJson.substring(0, customJson.length() - 1)
+                        + ",\"image\":\"" + imagePath + "\"}";
+            }
+        }
+        User user = (User) session.getAttribute("user");
+        cartService.addItem(user.getId(), cakeId, specId, quantity, customJson);
+        session.setAttribute("cartCount", cartService.getCartItems(user.getId()).size());
+        attr.addFlashAttribute("success", "已加入购物车");
+        return "redirect:/cart";
+    }
+
+    @PostMapping("/update")
+    public String updateQuantity(@RequestParam Integer itemId,
+                                 @RequestParam Integer quantity,
+                                 HttpSession session) {
+        cartService.updateQuantity(itemId, quantity);
+        User user = (User) session.getAttribute("user");
+        session.setAttribute("cartCount", cartService.getCartItems(user.getId()).size());
+        return "redirect:/cart";
+    }
+
+    @PostMapping("/remove")
+    public String removeItem(@RequestParam Integer itemId,
+                             HttpSession session) {
+        cartService.removeItem(itemId);
+        User user = (User) session.getAttribute("user");
+        session.setAttribute("cartCount", cartService.getCartItems(user.getId()).size());
+        return "redirect:/cart";
+    }
+
+    @PostMapping("/clear")
+    public String clearCart(HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        cartService.clearCart(user.getId());
+        session.setAttribute("cartCount", 0);
+        return "redirect:/cart";
+    }
+}

+ 37 - 0
src/main/java/space/anyi/controller/front/CouponController.java

@@ -0,0 +1,37 @@
+package space.anyi.controller.front;
+
+import space.anyi.entity.User;
+import space.anyi.service.CouponService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+
+@Controller
+@RequestMapping("/coupon")
+public class CouponController {
+
+    @Autowired
+    private CouponService couponService;
+
+    @GetMapping
+    public ModelAndView list(HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        ModelAndView mv = new ModelAndView("coupon/list");
+        mv.addObject("coupons", couponService.findByUser(user.getId()));
+        mv.addObject("availableCoupons", couponService.findAvailable());
+        return mv;
+    }
+
+    @PostMapping("/claim")
+    public String claim(@RequestParam Integer couponId, HttpSession session,
+                        RedirectAttributes attr) {
+        User user = (User) session.getAttribute("user");
+        couponService.claim(user.getId(), couponId);
+        attr.addFlashAttribute("success", "优惠券领取成功");
+        return "redirect:/coupon";
+    }
+}

+ 44 - 0
src/main/java/space/anyi/controller/front/FavoriteController.java

@@ -0,0 +1,44 @@
+package space.anyi.controller.front;
+
+import space.anyi.entity.User;
+import space.anyi.service.FavoriteService;
+import space.anyi.util.Converter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+
+@Controller
+@RequestMapping("/favorite")
+public class FavoriteController {
+
+    @Autowired
+    private FavoriteService favoriteService;
+
+    @GetMapping
+    public ModelAndView list(HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        ModelAndView mv = new ModelAndView("favorite/list");
+        mv.addObject("favorites", favoriteService.getFavoriteVOs(user.getId()));
+        return mv;
+    }
+
+    @PostMapping("/add")
+    public String add(@RequestParam Integer cakeId, HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        favoriteService.add(user.getId(), cakeId);
+        return "redirect:/favorite";
+    }
+
+    @PostMapping("/remove")
+    public String remove(@RequestParam Integer cakeId, HttpSession session,
+                         RedirectAttributes attr) {
+        User user = (User) session.getAttribute("user");
+        favoriteService.remove(user.getId(), cakeId);
+        attr.addFlashAttribute("success", "已取消收藏");
+        return "redirect:/favorite";
+    }
+}

+ 32 - 0
src/main/java/space/anyi/controller/front/IndexController.java

@@ -0,0 +1,32 @@
+package space.anyi.controller.front;
+
+import space.anyi.service.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+@Controller
+public class IndexController {
+
+    @Autowired
+    private CakeService cakeService;
+    @Autowired
+    private CategoryService categoryService;
+    @Autowired
+    private AnnouncementService announcementService;
+    @Autowired
+    private DeliveryService deliveryService;
+
+    @GetMapping("/")
+    public ModelAndView index() {
+        ModelAndView mv = new ModelAndView("index");
+        mv.addObject("categories", categoryService.findAll());
+        mv.addObject("hotCakes", cakeService.findHot());
+        mv.addObject("newCakes", cakeService.findNew());
+        mv.addObject("discountCakes", cakeService.findDiscount());
+        mv.addObject("announcements", announcementService.findActive());
+        mv.addObject("deliveryConfigs", deliveryService.findActive());
+        return mv;
+    }
+}

+ 40 - 0
src/main/java/space/anyi/controller/front/MessageController.java

@@ -0,0 +1,40 @@
+package space.anyi.controller.front;
+
+import space.anyi.entity.Message;
+import space.anyi.entity.User;
+import space.anyi.service.MessageService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+
+@Controller
+@RequestMapping("/message")
+public class MessageController {
+
+    @Autowired
+    private MessageService messageService;
+
+    @GetMapping
+    public ModelAndView list(HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        ModelAndView mv = new ModelAndView("message/list");
+        mv.addObject("messages", messageService.findByUser(user.getId()));
+        return mv;
+    }
+
+    @PostMapping("/add")
+    public String add(@RequestParam String content, HttpSession session,
+                      RedirectAttributes attr) {
+        User user = (User) session.getAttribute("user");
+        Message message = new Message();
+        message.setUserId(user.getId());
+        message.setContent(content);
+        messageService.add(message);
+        attr.addFlashAttribute("success", "留言提交成功");
+        return "redirect:/message";
+    }
+}

+ 96 - 0
src/main/java/space/anyi/controller/front/OrderController.java

@@ -0,0 +1,96 @@
+package space.anyi.controller.front;
+
+import space.anyi.entity.User;
+import space.anyi.service.*;
+import space.anyi.util.Converter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+
+@Controller
+@RequestMapping("/order")
+public class OrderController {
+
+    @Autowired
+    private OrderService orderService;
+    @Autowired
+    private CartService cartService;
+    @Autowired
+    private AddressService addressService;
+    @Autowired
+    private CouponService couponService;
+    @Autowired
+    private OrderItemService orderItemService;
+    @Autowired
+    private OrderTrackingService orderTrackingService;
+
+    @GetMapping("/checkout")
+    public ModelAndView checkout(HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        ModelAndView mv = new ModelAndView("order/checkout");
+        mv.addObject("cartItems", cartService.getCartItemVOs(user.getId()));
+        mv.addObject("addresses", Converter.toAddressVOList(addressService.findByUser(user.getId())));
+        mv.addObject("coupons", couponService.findByUser(user.getId()));
+        return mv;
+    }
+
+    @PostMapping("/submit")
+    public String submit(@RequestParam Integer[] cartItemIds,
+                         @RequestParam(required = false) Integer addressId,
+                         @RequestParam(required = false) String deliveryDate,
+                         @RequestParam(required = false) String deliveryTime,
+                         @RequestParam(required = false) String remark,
+                         @RequestParam(required = false) Integer couponId,
+                         HttpSession session,
+                         RedirectAttributes attr) {
+        if (addressId == null) {
+            attr.addFlashAttribute("error", "请选择收货地址");
+            return "redirect:/order/checkout";
+        }
+        User user = (User) session.getAttribute("user");
+        String orderNo = orderService.submitOrder(user.getId(), cartItemIds, addressId, deliveryDate, deliveryTime, remark, couponId);
+        session.setAttribute("cartCount", cartService.getCartItems(user.getId()).size());
+        attr.addFlashAttribute("success", "订单提交成功");
+        return "redirect:/order/detail/" + orderNo;
+    }
+
+    @GetMapping("/list")
+    public ModelAndView list(@RequestParam(defaultValue = "1") Integer page,
+                             HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        ModelAndView mv = new ModelAndView("order/list");
+        mv.addObject("orders", Converter.toOrderVOList(orderService.findByUser(user.getId(), page, 10)));
+        mv.addObject("page", page);
+        return mv;
+    }
+
+    @GetMapping("/detail/{orderNo}")
+    public ModelAndView detail(@PathVariable String orderNo) {
+        ModelAndView mv = new ModelAndView("order/detail");
+        space.anyi.entity.Orders order = orderService.findByOrderNo(orderNo);
+        if (order != null) {
+            mv.addObject("order", Converter.toOrderVO(order));
+            mv.addObject("orderItems", Converter.toOrderItemVOList(orderItemService.findByOrderId(order.getId())));
+            mv.addObject("tracking", Converter.toOrderTrackingVOList(orderTrackingService.findByOrderId(order.getId())));
+        }
+        return mv;
+    }
+
+    @PostMapping("/pay")
+    public String pay(@RequestParam String orderNo, RedirectAttributes attr) {
+        orderService.pay(orderNo);
+        attr.addFlashAttribute("success", "支付成功");
+        return "redirect:/order/detail/" + orderNo;
+    }
+
+    @PostMapping("/cancel")
+    public String cancel(@RequestParam String orderNo, RedirectAttributes attr) {
+        orderService.cancel(orderNo);
+        attr.addFlashAttribute("success", "订单已取消");
+        return "redirect:/order/list";
+    }
+}

+ 39 - 0
src/main/java/space/anyi/controller/front/ReviewController.java

@@ -0,0 +1,39 @@
+package space.anyi.controller.front;
+
+import space.anyi.entity.Review;
+import space.anyi.entity.User;
+import space.anyi.service.ReviewService;
+import space.anyi.util.Converter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+
+@Controller
+@RequestMapping("/review")
+public class ReviewController {
+
+    @Autowired
+    private ReviewService reviewService;
+
+    @PostMapping("/add")
+    public String add(@RequestParam Integer cakeId,
+                      @RequestParam String content,
+                      @RequestParam Integer rating,
+                      HttpSession session,
+                      RedirectAttributes attr) {
+        User user = (User) session.getAttribute("user");
+        Review review = new Review();
+        review.setCakeId(cakeId);
+        review.setUserId(user.getId());
+        review.setContent(content);
+        review.setRating(rating);
+        reviewService.add(review);
+        attr.addFlashAttribute("success", "评价提交成功");
+        return "redirect:/cake/" + cakeId;
+    }
+}

+ 182 - 0
src/main/java/space/anyi/controller/front/UserController.java

@@ -0,0 +1,182 @@
+package space.anyi.controller.front;
+
+import space.anyi.entity.Address;
+import space.anyi.entity.User;
+import space.anyi.entity.dto.AddressDTO;
+import space.anyi.entity.dto.UserRegisterDTO;
+import space.anyi.entity.dto.UserUpdateDTO;
+import space.anyi.service.*;
+import space.anyi.util.Converter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import javax.servlet.http.HttpSession;
+import java.io.File;
+import java.util.UUID;
+
+@Controller
+@RequestMapping("/user")
+public class UserController {
+
+    @Value("${upload.path}")
+    private String uploadPath;
+
+    @Autowired
+    private UserService userService;
+    @Autowired
+    private AddressService addressService;
+    @Autowired
+    private FavoriteService favoriteService;
+    @Autowired
+    private OrderService orderService;
+
+    @GetMapping("/login")
+    public ModelAndView loginPage() {
+        return new ModelAndView("user/login");
+    }
+
+    @PostMapping("/login")
+    public String login(@RequestParam String username,
+                        @RequestParam String password,
+                        HttpSession session,
+                        RedirectAttributes attr) {
+        User user = userService.login(username, password);
+        if (user == null) {
+            attr.addFlashAttribute("error", "用户名或密码错误");
+            return "redirect:/user/login";
+        }
+        user.setPassword(null);
+        session.setAttribute("user", user);
+        return "redirect:/";
+    }
+
+    @GetMapping("/register")
+    public ModelAndView registerPage() {
+        return new ModelAndView("user/register");
+    }
+
+    @PostMapping("/register")
+    public String register(UserRegisterDTO dto, RedirectAttributes attr) {
+        if (dto.getUsername() == null || dto.getUsername().trim().isEmpty()) {
+            attr.addFlashAttribute("error", "用户名不能为空");
+            return "redirect:/user/register";
+        }
+        if (dto.getPassword() == null || dto.getPassword().length() < 6) {
+            attr.addFlashAttribute("error", "密码不能少于6位");
+            return "redirect:/user/register";
+        }
+        userService.register(Converter.toUser(dto));
+        attr.addFlashAttribute("success", "注册成功,请登录");
+        return "redirect:/user/login";
+    }
+
+    @GetMapping("/logout")
+    public String logout(HttpSession session) {
+        session.invalidate();
+        return "redirect:/";
+    }
+
+    @GetMapping("/profile")
+    public ModelAndView profile(HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        ModelAndView mv = new ModelAndView("user/profile");
+        mv.addObject("user", Converter.toUserVO(userService.findById(user.getId())));
+        return mv;
+    }
+
+    @PostMapping("/profile/update")
+    public String updateProfile(UserUpdateDTO dto,
+                                @RequestParam(required = false) MultipartFile avatarFile,
+                                HttpSession session,
+                                RedirectAttributes attr) {
+        if (avatarFile != null && !avatarFile.isEmpty()) {
+            String path = saveUploadFile(avatarFile);
+            if (path != null) {
+                dto.setAvatar(path);
+            }
+        }
+        userService.update(Converter.toUser(dto));
+        User updated = userService.findById(dto.getId());
+        updated.setPassword(null);
+        session.setAttribute("user", updated);
+        attr.addFlashAttribute("success", "个人信息更新成功");
+        return "redirect:/user/profile";
+    }
+
+    @GetMapping("/address")
+    public ModelAndView addressList(HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        ModelAndView mv = new ModelAndView("user/address");
+        mv.addObject("addresses", Converter.toAddressVOList(addressService.findByUser(user.getId())));
+        return mv;
+    }
+
+    @GetMapping("/address/add")
+    public ModelAndView addAddressPage() {
+        return new ModelAndView("user/address_add");
+    }
+
+    @PostMapping("/address/add")
+    public String addAddress(AddressDTO dto, HttpSession session, RedirectAttributes attr) {
+        User user = (User) session.getAttribute("user");
+        Address address = Converter.toAddress(dto);
+        address.setUserId(user.getId());
+        addressService.add(address);
+        attr.addFlashAttribute("success", "地址添加成功");
+        return "redirect:/user/address";
+    }
+
+    @GetMapping("/address/edit/{id}")
+    public ModelAndView editAddressPage(@PathVariable Integer id) {
+        ModelAndView mv = new ModelAndView("user/address_edit");
+        mv.addObject("address", Converter.toAddressVO(addressService.findById(id)));
+        return mv;
+    }
+
+    @PostMapping("/address/update")
+    public String updateAddress(AddressDTO dto, RedirectAttributes attr) {
+        addressService.update(Converter.toAddress(dto));
+        attr.addFlashAttribute("success", "地址更新成功");
+        return "redirect:/user/address";
+    }
+
+    @PostMapping("/address/delete/{id}")
+    public String deleteAddress(@PathVariable Integer id, RedirectAttributes attr) {
+        addressService.delete(id);
+        attr.addFlashAttribute("success", "地址已删除");
+        return "redirect:/user/address";
+    }
+
+    @PostMapping("/address/default/{id}")
+    public String setDefaultAddress(@PathVariable Integer id, HttpSession session) {
+        User user = (User) session.getAttribute("user");
+        addressService.setDefault(id, user.getId());
+        return "redirect:/user/address";
+    }
+
+    private String saveUploadFile(MultipartFile file) {
+        try {
+            String dirPath = new File(uploadPath).getAbsolutePath();
+            File dir = new File(dirPath);
+            if (!dir.exists()) {
+                dir.mkdirs();
+            }
+            String originalFilename = file.getOriginalFilename();
+            String ext = "";
+            if (originalFilename != null && originalFilename.contains(".")) {
+                ext = originalFilename.substring(originalFilename.lastIndexOf("."));
+            }
+            String newFilename = UUID.randomUUID().toString().replace("-", "") + ext;
+            File dest = new File(dir, newFilename);
+            file.transferTo(dest);
+            return "/upload/" + newFilename;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 45 - 0
src/main/java/space/anyi/entity/Activity.java

@@ -0,0 +1,45 @@
+package space.anyi.entity;
+
+import org.springframework.format.annotation.DateTimeFormat;
+import java.io.Serializable;
+import java.util.Date;
+
+public class Activity implements Serializable {
+    private Integer id;
+    private String title;
+    private String type;
+    private String content;
+    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
+    private Date startTime;
+    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
+    private Date endTime;
+    private String imageUrl;
+    private String status;
+    private Date createTime;
+
+    public Activity() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getTitle() { return title; }
+    public void setTitle(String title) { this.title = title; }
+    public String getType() { return type; }
+    public void setType(String type) { this.type = type; }
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+    public Date getStartTime() { return startTime; }
+    public void setStartTime(Date startTime) { this.startTime = startTime; }
+    public Date getEndTime() { return endTime; }
+    public void setEndTime(Date endTime) { this.endTime = endTime; }
+    public String getImageUrl() { return imageUrl; }
+    public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "Activity{id=" + id + ", title='" + title + "'}";
+    }
+}

+ 45 - 0
src/main/java/space/anyi/entity/Address.java

@@ -0,0 +1,45 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Address implements Serializable {
+    private Integer id;
+    private Integer userId;
+    private String receiverName;
+    private String receiverPhone;
+    private String province;
+    private String city;
+    private String district;
+    private String detailAddress;
+    private Integer isDefault;
+    private Date createTime;
+
+    public Address() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public String getReceiverName() { return receiverName; }
+    public void setReceiverName(String receiverName) { this.receiverName = receiverName; }
+    public String getReceiverPhone() { return receiverPhone; }
+    public void setReceiverPhone(String receiverPhone) { this.receiverPhone = receiverPhone; }
+    public String getProvince() { return province; }
+    public void setProvince(String province) { this.province = province; }
+    public String getCity() { return city; }
+    public void setCity(String city) { this.city = city; }
+    public String getDistrict() { return district; }
+    public void setDistrict(String district) { this.district = district; }
+    public String getDetailAddress() { return detailAddress; }
+    public void setDetailAddress(String detailAddress) { this.detailAddress = detailAddress; }
+    public Integer getIsDefault() { return isDefault; }
+    public void setIsDefault(Integer isDefault) { this.isDefault = isDefault; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "Address{id=" + id + ", receiverName='" + receiverName + "'}";
+    }
+}

+ 33 - 0
src/main/java/space/anyi/entity/Admin.java

@@ -0,0 +1,33 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Admin implements Serializable {
+    private Integer id;
+    private String username;
+    private String password;
+    private String nickname;
+    private String role;
+    private Date createTime;
+
+    public Admin() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getUsername() { return username; }
+    public void setUsername(String username) { this.username = username; }
+    public String getPassword() { return password; }
+    public void setPassword(String password) { this.password = password; }
+    public String getNickname() { return nickname; }
+    public void setNickname(String nickname) { this.nickname = nickname; }
+    public String getRole() { return role; }
+    public void setRole(String role) { this.role = role; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "Admin{id=" + id + ", username='" + username + "'}";
+    }
+}

+ 30 - 0
src/main/java/space/anyi/entity/Announcement.java

@@ -0,0 +1,30 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Announcement implements Serializable {
+    private Integer id;
+    private String title;
+    private String content;
+    private Integer isActive;
+    private Date createTime;
+
+    public Announcement() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getTitle() { return title; }
+    public void setTitle(String title) { this.title = title; }
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+    public Integer getIsActive() { return isActive; }
+    public void setIsActive(Integer isActive) { this.isActive = isActive; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "Announcement{id=" + id + ", title='" + title + "'}";
+    }
+}

+ 76 - 0
src/main/java/space/anyi/entity/Cake.java

@@ -0,0 +1,76 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class Cake implements Serializable {
+    private Integer id;
+    private Integer categoryId;
+    private String name;
+    private String description;
+    private BigDecimal basePrice;
+    private String mainImage;
+    private String taste;
+    private String layers;
+    private String filling;
+    private String style;
+    private Integer isCustomizable;
+    private Integer isNew;
+    private Integer isHot;
+    private Integer isDiscount;
+    private BigDecimal discountPrice;
+    private Integer stock;
+    private String status;
+    private Integer salesCount;
+    private Date createTime;
+    private Date updateTime;
+
+    public Cake() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCategoryId() { return categoryId; }
+    public void setCategoryId(Integer categoryId) { this.categoryId = categoryId; }
+    public String getName() { return name; }
+    public void setName(String name) { this.name = name; }
+    public String getDescription() { return description; }
+    public void setDescription(String description) { this.description = description; }
+    public BigDecimal getBasePrice() { return basePrice; }
+    public void setBasePrice(BigDecimal basePrice) { this.basePrice = basePrice; }
+    public String getMainImage() { return mainImage; }
+    public void setMainImage(String mainImage) { this.mainImage = mainImage; }
+    public String getTaste() { return taste; }
+    public void setTaste(String taste) { this.taste = taste; }
+    public String getLayers() { return layers; }
+    public void setLayers(String layers) { this.layers = layers; }
+    public String getFilling() { return filling; }
+    public void setFilling(String filling) { this.filling = filling; }
+    public String getStyle() { return style; }
+    public void setStyle(String style) { this.style = style; }
+    public Integer getIsCustomizable() { return isCustomizable; }
+    public void setIsCustomizable(Integer isCustomizable) { this.isCustomizable = isCustomizable; }
+    public Integer getIsNew() { return isNew; }
+    public void setIsNew(Integer isNew) { this.isNew = isNew; }
+    public Integer getIsHot() { return isHot; }
+    public void setIsHot(Integer isHot) { this.isHot = isHot; }
+    public Integer getIsDiscount() { return isDiscount; }
+    public void setIsDiscount(Integer isDiscount) { this.isDiscount = isDiscount; }
+    public BigDecimal getDiscountPrice() { return discountPrice; }
+    public void setDiscountPrice(BigDecimal discountPrice) { this.discountPrice = discountPrice; }
+    public Integer getStock() { return stock; }
+    public void setStock(Integer stock) { this.stock = stock; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public Integer getSalesCount() { return salesCount; }
+    public void setSalesCount(Integer salesCount) { this.salesCount = salesCount; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+    public Date getUpdateTime() { return updateTime; }
+    public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
+
+    @Override
+    public String toString() {
+        return "Cake{id=" + id + ", name='" + name + "', basePrice=" + basePrice + "}";
+    }
+}

+ 30 - 0
src/main/java/space/anyi/entity/CakeCategory.java

@@ -0,0 +1,30 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class CakeCategory implements Serializable {
+    private Integer id;
+    private String name;
+    private Integer sortOrder;
+    private Date createTime;
+    private Date updateTime;
+
+    public CakeCategory() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getName() { return name; }
+    public void setName(String name) { this.name = name; }
+    public Integer getSortOrder() { return sortOrder; }
+    public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+    public Date getUpdateTime() { return updateTime; }
+    public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
+
+    @Override
+    public String toString() {
+        return "CakeCategory{id=" + id + ", name='" + name + "'}";
+    }
+}

+ 29 - 0
src/main/java/space/anyi/entity/CakeImage.java

@@ -0,0 +1,29 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+
+public class CakeImage implements Serializable {
+    private Integer id;
+    private Integer cakeId;
+    private String imageUrl;
+    private Integer isMain;
+    private Integer sortOrder;
+
+    public CakeImage() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getImageUrl() { return imageUrl; }
+    public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
+    public Integer getIsMain() { return isMain; }
+    public void setIsMain(Integer isMain) { this.isMain = isMain; }
+    public Integer getSortOrder() { return sortOrder; }
+    public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
+
+    @Override
+    public String toString() {
+        return "CakeImage{id=" + id + ", cakeId=" + cakeId + "}";
+    }
+}

+ 30 - 0
src/main/java/space/anyi/entity/CakeSpec.java

@@ -0,0 +1,30 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public class CakeSpec implements Serializable {
+    private Integer id;
+    private Integer cakeId;
+    private String specName;
+    private BigDecimal addPrice;
+    private Integer sortOrder;
+
+    public CakeSpec() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getSpecName() { return specName; }
+    public void setSpecName(String specName) { this.specName = specName; }
+    public BigDecimal getAddPrice() { return addPrice; }
+    public void setAddPrice(BigDecimal addPrice) { this.addPrice = addPrice; }
+    public Integer getSortOrder() { return sortOrder; }
+    public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
+
+    @Override
+    public String toString() {
+        return "CakeSpec{id=" + id + ", specName='" + specName + "'}";
+    }
+}

+ 27 - 0
src/main/java/space/anyi/entity/Cart.java

@@ -0,0 +1,27 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Cart implements Serializable {
+    private Integer id;
+    private Integer userId;
+    private Date createTime;
+    private Date updateTime;
+
+    public Cart() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+    public Date getUpdateTime() { return updateTime; }
+    public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
+
+    @Override
+    public String toString() {
+        return "Cart{id=" + id + ", userId=" + userId + "}";
+    }
+}

+ 36 - 0
src/main/java/space/anyi/entity/CartItem.java

@@ -0,0 +1,36 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class CartItem implements Serializable {
+    private Integer id;
+    private Integer cartId;
+    private Integer cakeId;
+    private Integer specId;
+    private Integer quantity;
+    private String customJson;
+    private Date createTime;
+
+    public CartItem() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCartId() { return cartId; }
+    public void setCartId(Integer cartId) { this.cartId = cartId; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public Integer getSpecId() { return specId; }
+    public void setSpecId(Integer specId) { this.specId = specId; }
+    public Integer getQuantity() { return quantity; }
+    public void setQuantity(Integer quantity) { this.quantity = quantity; }
+    public String getCustomJson() { return customJson; }
+    public void setCustomJson(String customJson) { this.customJson = customJson; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "CartItem{id=" + id + ", cakeId=" + cakeId + "}";
+    }
+}

+ 49 - 0
src/main/java/space/anyi/entity/Coupon.java

@@ -0,0 +1,49 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class Coupon implements Serializable {
+    private Integer id;
+    private String name;
+    private String type;
+    private BigDecimal conditionAmount;
+    private BigDecimal discountAmount;
+    private BigDecimal discountRate;
+    private Integer totalCount;
+    private Integer remainCount;
+    private Date startTime;
+    private Date endTime;
+    private Date createTime;
+
+    public Coupon() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getName() { return name; }
+    public void setName(String name) { this.name = name; }
+    public String getType() { return type; }
+    public void setType(String type) { this.type = type; }
+    public BigDecimal getConditionAmount() { return conditionAmount; }
+    public void setConditionAmount(BigDecimal conditionAmount) { this.conditionAmount = conditionAmount; }
+    public BigDecimal getDiscountAmount() { return discountAmount; }
+    public void setDiscountAmount(BigDecimal discountAmount) { this.discountAmount = discountAmount; }
+    public BigDecimal getDiscountRate() { return discountRate; }
+    public void setDiscountRate(BigDecimal discountRate) { this.discountRate = discountRate; }
+    public Integer getTotalCount() { return totalCount; }
+    public void setTotalCount(Integer totalCount) { this.totalCount = totalCount; }
+    public Integer getRemainCount() { return remainCount; }
+    public void setRemainCount(Integer remainCount) { this.remainCount = remainCount; }
+    public Date getStartTime() { return startTime; }
+    public void setStartTime(Date startTime) { this.startTime = startTime; }
+    public Date getEndTime() { return endTime; }
+    public void setEndTime(Date endTime) { this.endTime = endTime; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "Coupon{id=" + id + ", name='" + name + "'}";
+    }
+}

+ 33 - 0
src/main/java/space/anyi/entity/CustomOption.java

@@ -0,0 +1,33 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public class CustomOption implements Serializable {
+    private Integer id;
+    private Integer cakeId;
+    private String optionType;
+    private String optionName;
+    private BigDecimal addPrice;
+    private Integer sortOrder;
+
+    public CustomOption() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getOptionType() { return optionType; }
+    public void setOptionType(String optionType) { this.optionType = optionType; }
+    public String getOptionName() { return optionName; }
+    public void setOptionName(String optionName) { this.optionName = optionName; }
+    public BigDecimal getAddPrice() { return addPrice; }
+    public void setAddPrice(BigDecimal addPrice) { this.addPrice = addPrice; }
+    public Integer getSortOrder() { return sortOrder; }
+    public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
+
+    @Override
+    public String toString() {
+        return "CustomOption{id=" + id + ", optionName='" + optionName + "'}";
+    }
+}

+ 30 - 0
src/main/java/space/anyi/entity/DeliveryConfig.java

@@ -0,0 +1,30 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public class DeliveryConfig implements Serializable {
+    private Integer id;
+    private String region;
+    private BigDecimal fee;
+    private BigDecimal freeAmount;
+    private Integer isActive;
+
+    public DeliveryConfig() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getRegion() { return region; }
+    public void setRegion(String region) { this.region = region; }
+    public BigDecimal getFee() { return fee; }
+    public void setFee(BigDecimal fee) { this.fee = fee; }
+    public BigDecimal getFreeAmount() { return freeAmount; }
+    public void setFreeAmount(BigDecimal freeAmount) { this.freeAmount = freeAmount; }
+    public Integer getIsActive() { return isActive; }
+    public void setIsActive(Integer isActive) { this.isActive = isActive; }
+
+    @Override
+    public String toString() {
+        return "DeliveryConfig{id=" + id + ", region='" + region + "'}";
+    }
+}

+ 27 - 0
src/main/java/space/anyi/entity/Favorite.java

@@ -0,0 +1,27 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Favorite implements Serializable {
+    private Integer id;
+    private Integer userId;
+    private Integer cakeId;
+    private Date createTime;
+
+    public Favorite() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "Favorite{id=" + id + ", userId=" + userId + ", cakeId=" + cakeId + "}";
+    }
+}

+ 42 - 0
src/main/java/space/anyi/entity/Message.java

@@ -0,0 +1,42 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Message implements Serializable {
+    private Integer id;
+    private Integer userId;
+    private String title;
+    private String content;
+    private String nickname;
+    private String replyContent;
+    private Date replyTime;
+    private String status;
+    private Date createTime;
+
+    public Message() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public String getNickname() { return nickname; }
+    public void setNickname(String nickname) { this.nickname = nickname; }
+    public String getTitle() { return title; }
+    public void setTitle(String title) { this.title = title; }
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+    public String getReplyContent() { return replyContent; }
+    public void setReplyContent(String replyContent) { this.replyContent = replyContent; }
+    public Date getReplyTime() { return replyTime; }
+    public void setReplyTime(Date replyTime) { this.replyTime = replyTime; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "Message{id=" + id + ", title='" + title + "'}";
+    }
+}

+ 42 - 0
src/main/java/space/anyi/entity/OrderItem.java

@@ -0,0 +1,42 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public class OrderItem implements Serializable {
+    private Integer id;
+    private Integer orderId;
+    private Integer cakeId;
+    private String cakeName;
+    private String specName;
+    private BigDecimal price;
+    private Integer quantity;
+    private String customJson;
+    private String imageUrl;
+
+    public OrderItem() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getOrderId() { return orderId; }
+    public void setOrderId(Integer orderId) { this.orderId = orderId; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getCakeName() { return cakeName; }
+    public void setCakeName(String cakeName) { this.cakeName = cakeName; }
+    public String getSpecName() { return specName; }
+    public void setSpecName(String specName) { this.specName = specName; }
+    public BigDecimal getPrice() { return price; }
+    public void setPrice(BigDecimal price) { this.price = price; }
+    public Integer getQuantity() { return quantity; }
+    public void setQuantity(Integer quantity) { this.quantity = quantity; }
+    public String getCustomJson() { return customJson; }
+    public void setCustomJson(String customJson) { this.customJson = customJson; }
+    public String getImageUrl() { return imageUrl; }
+    public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
+
+    @Override
+    public String toString() {
+        return "OrderItem{id=" + id + ", cakeName='" + cakeName + "'}";
+    }
+}

+ 36 - 0
src/main/java/space/anyi/entity/OrderTracking.java

@@ -0,0 +1,36 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class OrderTracking implements Serializable {
+    private Integer id;
+    private Integer orderId;
+    private String fromStatus;
+    private String toStatus;
+    private String operator;
+    private String remark;
+    private Date createTime;
+
+    public OrderTracking() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getOrderId() { return orderId; }
+    public void setOrderId(Integer orderId) { this.orderId = orderId; }
+    public String getFromStatus() { return fromStatus; }
+    public void setFromStatus(String fromStatus) { this.fromStatus = fromStatus; }
+    public String getToStatus() { return toStatus; }
+    public void setToStatus(String toStatus) { this.toStatus = toStatus; }
+    public String getOperator() { return operator; }
+    public void setOperator(String operator) { this.operator = operator; }
+    public String getRemark() { return remark; }
+    public void setRemark(String remark) { this.remark = remark; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "OrderTracking{id=" + id + ", orderId=" + orderId + "}";
+    }
+}

+ 82 - 0
src/main/java/space/anyi/entity/Orders.java

@@ -0,0 +1,82 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class Orders implements Serializable {
+    private Integer id;
+    private String orderNo;
+    private Integer userId;
+    private BigDecimal totalAmount;
+    private BigDecimal discountAmount;
+    private BigDecimal payAmount;
+    private String status;
+    private String receiverName;
+    private String receiverPhone;
+    private String province;
+    private String city;
+    private String district;
+    private String detailAddress;
+    private Date deliveryDate;
+    private String deliveryTime;
+    private String remark;
+    private String refuseReason;
+    private String paymentMethod;
+    private Date paymentTime;
+    private BigDecimal deliveryFee;
+    private Date createTime;
+    private Date updateTime;
+
+    public Orders() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getOrderNo() { return orderNo; }
+    public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public BigDecimal getTotalAmount() { return totalAmount; }
+    public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; }
+    public BigDecimal getDiscountAmount() { return discountAmount; }
+    public void setDiscountAmount(BigDecimal discountAmount) { this.discountAmount = discountAmount; }
+    public BigDecimal getPayAmount() { return payAmount; }
+    public void setPayAmount(BigDecimal payAmount) { this.payAmount = payAmount; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public String getReceiverName() { return receiverName; }
+    public void setReceiverName(String receiverName) { this.receiverName = receiverName; }
+    public String getReceiverPhone() { return receiverPhone; }
+    public void setReceiverPhone(String receiverPhone) { this.receiverPhone = receiverPhone; }
+    public String getProvince() { return province; }
+    public void setProvince(String province) { this.province = province; }
+    public String getCity() { return city; }
+    public void setCity(String city) { this.city = city; }
+    public String getDistrict() { return district; }
+    public void setDistrict(String district) { this.district = district; }
+    public String getDetailAddress() { return detailAddress; }
+    public void setDetailAddress(String detailAddress) { this.detailAddress = detailAddress; }
+    public Date getDeliveryDate() { return deliveryDate; }
+    public void setDeliveryDate(Date deliveryDate) { this.deliveryDate = deliveryDate; }
+    public String getDeliveryTime() { return deliveryTime; }
+    public void setDeliveryTime(String deliveryTime) { this.deliveryTime = deliveryTime; }
+    public String getRemark() { return remark; }
+    public void setRemark(String remark) { this.remark = remark; }
+    public String getRefuseReason() { return refuseReason; }
+    public void setRefuseReason(String refuseReason) { this.refuseReason = refuseReason; }
+    public String getPaymentMethod() { return paymentMethod; }
+    public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
+    public Date getPaymentTime() { return paymentTime; }
+    public void setPaymentTime(Date paymentTime) { this.paymentTime = paymentTime; }
+    public BigDecimal getDeliveryFee() { return deliveryFee; }
+    public void setDeliveryFee(BigDecimal deliveryFee) { this.deliveryFee = deliveryFee; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+    public Date getUpdateTime() { return updateTime; }
+    public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
+
+    @Override
+    public String toString() {
+        return "Orders{id=" + id + ", orderNo='" + orderNo + "', status='" + status + "'}";
+    }
+}

+ 42 - 0
src/main/java/space/anyi/entity/Review.java

@@ -0,0 +1,42 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class Review implements Serializable {
+    private Integer id;
+    private Integer orderId;
+    private Integer cakeId;
+    private Integer userId;
+    private Integer rating;
+    private String content;
+    private String replyContent;
+    private String status;
+    private Date createTime;
+
+    public Review() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getOrderId() { return orderId; }
+    public void setOrderId(Integer orderId) { this.orderId = orderId; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public Integer getRating() { return rating; }
+    public void setRating(Integer rating) { this.rating = rating; }
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+    public String getReplyContent() { return replyContent; }
+    public void setReplyContent(String replyContent) { this.replyContent = replyContent; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "Review{id=" + id + ", rating=" + rating + "}";
+    }
+}

+ 42 - 9
src/main/java/space/anyi/entity/User.java

@@ -1,12 +1,45 @@
 package space.anyi.entity;
 
-/**
- * @fileName: User
- * @projectName: SSM_template
- * @package: space.anyi.entity
- * @author: 杨逸
- * @date:2026/4/24 11:17
- * @description: TODO
- */
-public class User {
+import java.io.Serializable;
+import java.util.Date;
+
+public class User implements Serializable {
+    private Integer id;
+    private String username;
+    private String password;
+    private String nickname;
+    private String phone;
+    private String email;
+    private String avatar;
+    private String status;
+    private Date createTime;
+    private Date updateTime;
+
+    public User() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getUsername() { return username; }
+    public void setUsername(String username) { this.username = username; }
+    public String getPassword() { return password; }
+    public void setPassword(String password) { this.password = password; }
+    public String getNickname() { return nickname; }
+    public void setNickname(String nickname) { this.nickname = nickname; }
+    public String getPhone() { return phone; }
+    public void setPhone(String phone) { this.phone = phone; }
+    public String getEmail() { return email; }
+    public void setEmail(String email) { this.email = email; }
+    public String getAvatar() { return avatar; }
+    public void setAvatar(String avatar) { this.avatar = avatar; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+    public Date getUpdateTime() { return updateTime; }
+    public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
+
+    @Override
+    public String toString() {
+        return "User{id=" + id + ", username='" + username + "'}";
+    }
 }

+ 33 - 0
src/main/java/space/anyi/entity/UserCoupon.java

@@ -0,0 +1,33 @@
+package space.anyi.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class UserCoupon implements Serializable {
+    private Integer id;
+    private Integer userId;
+    private Integer couponId;
+    private Integer used;
+    private Date usedTime;
+    private Date createTime;
+
+    public UserCoupon() {}
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public Integer getCouponId() { return couponId; }
+    public void setCouponId(Integer couponId) { this.couponId = couponId; }
+    public Integer getUsed() { return used; }
+    public void setUsed(Integer used) { this.used = used; }
+    public Date getUsedTime() { return usedTime; }
+    public void setUsedTime(Date usedTime) { this.usedTime = usedTime; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+
+    @Override
+    public String toString() {
+        return "UserCoupon{id=" + id + ", userId=" + userId + ", couponId=" + couponId + "}";
+    }
+}

+ 29 - 0
src/main/java/space/anyi/entity/dto/AddressDTO.java

@@ -0,0 +1,29 @@
+package space.anyi.entity.dto;
+
+public class AddressDTO {
+    private Integer id;
+    private String receiverName;
+    private String receiverPhone;
+    private String province;
+    private String city;
+    private String district;
+    private String detailAddress;
+    private Integer isDefault;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getReceiverName() { return receiverName; }
+    public void setReceiverName(String receiverName) { this.receiverName = receiverName; }
+    public String getReceiverPhone() { return receiverPhone; }
+    public void setReceiverPhone(String receiverPhone) { this.receiverPhone = receiverPhone; }
+    public String getProvince() { return province; }
+    public void setProvince(String province) { this.province = province; }
+    public String getCity() { return city; }
+    public void setCity(String city) { this.city = city; }
+    public String getDistrict() { return district; }
+    public void setDistrict(String district) { this.district = district; }
+    public String getDetailAddress() { return detailAddress; }
+    public void setDetailAddress(String detailAddress) { this.detailAddress = detailAddress; }
+    public Integer getIsDefault() { return isDefault; }
+    public void setIsDefault(Integer isDefault) { this.isDefault = isDefault; }
+}

+ 17 - 0
src/main/java/space/anyi/entity/dto/AnnouncementDTO.java

@@ -0,0 +1,17 @@
+package space.anyi.entity.dto;
+
+public class AnnouncementDTO {
+    private Integer id;
+    private String title;
+    private String content;
+    private Integer isActive;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getTitle() { return title; }
+    public void setTitle(String title) { this.title = title; }
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+    public Integer getIsActive() { return isActive; }
+    public void setIsActive(Integer isActive) { this.isActive = isActive; }
+}

+ 19 - 0
src/main/java/space/anyi/entity/dto/CakeSalesVO.java

@@ -0,0 +1,19 @@
+package space.anyi.entity.dto;
+
+import java.math.BigDecimal;
+
+public class CakeSalesVO {
+    private String name;
+    private Integer salesCount;
+    private BigDecimal revenue;
+    private String categoryName;
+
+    public String getName() { return name; }
+    public void setName(String name) { this.name = name; }
+    public Integer getSalesCount() { return salesCount; }
+    public void setSalesCount(Integer salesCount) { this.salesCount = salesCount; }
+    public BigDecimal getRevenue() { return revenue; }
+    public void setRevenue(BigDecimal revenue) { this.revenue = revenue; }
+    public String getCategoryName() { return categoryName; }
+    public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
+}

+ 61 - 0
src/main/java/space/anyi/entity/dto/CakeSaveDTO.java

@@ -0,0 +1,61 @@
+package space.anyi.entity.dto;
+
+import java.math.BigDecimal;
+
+public class CakeSaveDTO {
+    private Integer id;
+    private Integer categoryId;
+    private String name;
+    private String description;
+    private BigDecimal basePrice;
+    private String mainImage;
+    private String taste;
+    private String layers;
+    private String filling;
+    private String style;
+    private Integer isCustomizable;
+    private Integer isNew;
+    private Integer isHot;
+    private Integer isDiscount;
+    private BigDecimal discountPrice;
+    private Integer stock;
+    private String status;
+    private Integer salesCount;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCategoryId() { return categoryId; }
+    public void setCategoryId(Integer categoryId) { this.categoryId = categoryId; }
+    public String getName() { return name; }
+    public void setName(String name) { this.name = name; }
+    public String getDescription() { return description; }
+    public void setDescription(String description) { this.description = description; }
+    public BigDecimal getBasePrice() { return basePrice; }
+    public void setBasePrice(BigDecimal basePrice) { this.basePrice = basePrice; }
+    public String getMainImage() { return mainImage; }
+    public void setMainImage(String mainImage) { this.mainImage = mainImage; }
+    public String getTaste() { return taste; }
+    public void setTaste(String taste) { this.taste = taste; }
+    public String getLayers() { return layers; }
+    public void setLayers(String layers) { this.layers = layers; }
+    public String getFilling() { return filling; }
+    public void setFilling(String filling) { this.filling = filling; }
+    public String getStyle() { return style; }
+    public void setStyle(String style) { this.style = style; }
+    public Integer getIsCustomizable() { return isCustomizable; }
+    public void setIsCustomizable(Integer isCustomizable) { this.isCustomizable = isCustomizable; }
+    public Integer getIsNew() { return isNew; }
+    public void setIsNew(Integer isNew) { this.isNew = isNew; }
+    public Integer getIsHot() { return isHot; }
+    public void setIsHot(Integer isHot) { this.isHot = isHot; }
+    public Integer getIsDiscount() { return isDiscount; }
+    public void setIsDiscount(Integer isDiscount) { this.isDiscount = isDiscount; }
+    public BigDecimal getDiscountPrice() { return discountPrice; }
+    public void setDiscountPrice(BigDecimal discountPrice) { this.discountPrice = discountPrice; }
+    public Integer getStock() { return stock; }
+    public void setStock(Integer stock) { this.stock = stock; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public Integer getSalesCount() { return salesCount; }
+    public void setSalesCount(Integer salesCount) { this.salesCount = salesCount; }
+}

+ 31 - 0
src/main/java/space/anyi/entity/dto/CakeSearchCriteria.java

@@ -0,0 +1,31 @@
+package space.anyi.entity.dto;
+
+import java.math.BigDecimal;
+
+public class CakeSearchCriteria {
+    private Integer categoryId;
+    private String taste;
+    private String style;
+    private String keyword;
+    private BigDecimal minPrice;
+    private BigDecimal maxPrice;
+    private String sortBy;
+    private String status;
+
+    public Integer getCategoryId() { return categoryId; }
+    public void setCategoryId(Integer categoryId) { this.categoryId = categoryId; }
+    public String getTaste() { return taste; }
+    public void setTaste(String taste) { this.taste = taste; }
+    public String getStyle() { return style; }
+    public void setStyle(String style) { this.style = style; }
+    public String getKeyword() { return keyword; }
+    public void setKeyword(String keyword) { this.keyword = keyword; }
+    public BigDecimal getMinPrice() { return minPrice; }
+    public void setMinPrice(BigDecimal minPrice) { this.minPrice = minPrice; }
+    public BigDecimal getMaxPrice() { return maxPrice; }
+    public void setMaxPrice(BigDecimal maxPrice) { this.maxPrice = maxPrice; }
+    public String getSortBy() { return sortBy; }
+    public void setSortBy(String sortBy) { this.sortBy = sortBy; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+}

+ 22 - 0
src/main/java/space/anyi/entity/dto/CakeSpecDTO.java

@@ -0,0 +1,22 @@
+package space.anyi.entity.dto;
+
+import java.math.BigDecimal;
+
+public class CakeSpecDTO {
+    private Integer id;
+    private Integer cakeId;
+    private String specName;
+    private BigDecimal addPrice;
+    private Integer sortOrder;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getSpecName() { return specName; }
+    public void setSpecName(String specName) { this.specName = specName; }
+    public BigDecimal getAddPrice() { return addPrice; }
+    public void setAddPrice(BigDecimal addPrice) { this.addPrice = addPrice; }
+    public Integer getSortOrder() { return sortOrder; }
+    public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
+}

+ 14 - 0
src/main/java/space/anyi/entity/dto/CategoryDTO.java

@@ -0,0 +1,14 @@
+package space.anyi.entity.dto;
+
+public class CategoryDTO {
+    private Integer id;
+    private String name;
+    private Integer sortOrder;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getName() { return name; }
+    public void setName(String name) { this.name = name; }
+    public Integer getSortOrder() { return sortOrder; }
+    public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
+}

+ 25 - 0
src/main/java/space/anyi/entity/dto/CustomOptionDTO.java

@@ -0,0 +1,25 @@
+package space.anyi.entity.dto;
+
+import java.math.BigDecimal;
+
+public class CustomOptionDTO {
+    private Integer id;
+    private Integer cakeId;
+    private String optionType;
+    private String optionName;
+    private BigDecimal addPrice;
+    private Integer sortOrder;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getOptionType() { return optionType; }
+    public void setOptionType(String optionType) { this.optionType = optionType; }
+    public String getOptionName() { return optionName; }
+    public void setOptionName(String optionName) { this.optionName = optionName; }
+    public BigDecimal getAddPrice() { return addPrice; }
+    public void setAddPrice(BigDecimal addPrice) { this.addPrice = addPrice; }
+    public Integer getSortOrder() { return sortOrder; }
+    public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
+}

+ 22 - 0
src/main/java/space/anyi/entity/dto/DeliveryConfigDTO.java

@@ -0,0 +1,22 @@
+package space.anyi.entity.dto;
+
+import java.math.BigDecimal;
+
+public class DeliveryConfigDTO {
+    private Integer id;
+    private String region;
+    private BigDecimal fee;
+    private BigDecimal freeAmount;
+    private Integer isActive;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getRegion() { return region; }
+    public void setRegion(String region) { this.region = region; }
+    public BigDecimal getFee() { return fee; }
+    public void setFee(BigDecimal fee) { this.fee = fee; }
+    public BigDecimal getFreeAmount() { return freeAmount; }
+    public void setFreeAmount(BigDecimal freeAmount) { this.freeAmount = freeAmount; }
+    public Integer getIsActive() { return isActive; }
+    public void setIsActive(Integer isActive) { this.isActive = isActive; }
+}

+ 17 - 0
src/main/java/space/anyi/entity/dto/OrderSearchCriteria.java

@@ -0,0 +1,17 @@
+package space.anyi.entity.dto;
+
+public class OrderSearchCriteria {
+    private Integer userId;
+    private String status;
+    private String startDate;
+    private String endDate;
+
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public String getStartDate() { return startDate; }
+    public void setStartDate(String startDate) { this.startDate = startDate; }
+    public String getEndDate() { return endDate; }
+    public void setEndDate(String endDate) { this.endDate = endDate; }
+}

+ 20 - 0
src/main/java/space/anyi/entity/dto/UserRegisterDTO.java

@@ -0,0 +1,20 @@
+package space.anyi.entity.dto;
+
+public class UserRegisterDTO {
+    private String username;
+    private String password;
+    private String nickname;
+    private String phone;
+    private String email;
+
+    public String getUsername() { return username; }
+    public void setUsername(String username) { this.username = username; }
+    public String getPassword() { return password; }
+    public void setPassword(String password) { this.password = password; }
+    public String getNickname() { return nickname; }
+    public void setNickname(String nickname) { this.nickname = nickname; }
+    public String getPhone() { return phone; }
+    public void setPhone(String phone) { this.phone = phone; }
+    public String getEmail() { return email; }
+    public void setEmail(String email) { this.email = email; }
+}

+ 20 - 0
src/main/java/space/anyi/entity/dto/UserUpdateDTO.java

@@ -0,0 +1,20 @@
+package space.anyi.entity.dto;
+
+public class UserUpdateDTO {
+    private Integer id;
+    private String nickname;
+    private String phone;
+    private String email;
+    private String avatar;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getNickname() { return nickname; }
+    public void setNickname(String nickname) { this.nickname = nickname; }
+    public String getPhone() { return phone; }
+    public void setPhone(String phone) { this.phone = phone; }
+    public String getEmail() { return email; }
+    public void setEmail(String email) { this.email = email; }
+    public String getAvatar() { return avatar; }
+    public void setAvatar(String avatar) { this.avatar = avatar; }
+}

+ 37 - 0
src/main/java/space/anyi/entity/vo/AddressVO.java

@@ -0,0 +1,37 @@
+package space.anyi.entity.vo;
+
+import java.util.Date;
+
+public class AddressVO {
+    private Integer id;
+    private Integer userId;
+    private String receiverName;
+    private String receiverPhone;
+    private String province;
+    private String city;
+    private String district;
+    private String detailAddress;
+    private Integer isDefault;
+    private Date createTime;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public String getReceiverName() { return receiverName; }
+    public void setReceiverName(String receiverName) { this.receiverName = receiverName; }
+    public String getReceiverPhone() { return receiverPhone; }
+    public void setReceiverPhone(String receiverPhone) { this.receiverPhone = receiverPhone; }
+    public String getProvince() { return province; }
+    public void setProvince(String province) { this.province = province; }
+    public String getCity() { return city; }
+    public void setCity(String city) { this.city = city; }
+    public String getDistrict() { return district; }
+    public void setDistrict(String district) { this.district = district; }
+    public String getDetailAddress() { return detailAddress; }
+    public void setDetailAddress(String detailAddress) { this.detailAddress = detailAddress; }
+    public Integer getIsDefault() { return isDefault; }
+    public void setIsDefault(Integer isDefault) { this.isDefault = isDefault; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+}

+ 20 - 0
src/main/java/space/anyi/entity/vo/CakeImageVO.java

@@ -0,0 +1,20 @@
+package space.anyi.entity.vo;
+
+public class CakeImageVO {
+    private Integer id;
+    private Integer cakeId;
+    private String imageUrl;
+    private Integer isMain;
+    private Integer sortOrder;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getImageUrl() { return imageUrl; }
+    public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
+    public Integer getIsMain() { return isMain; }
+    public void setIsMain(Integer isMain) { this.isMain = isMain; }
+    public Integer getSortOrder() { return sortOrder; }
+    public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
+}

+ 22 - 0
src/main/java/space/anyi/entity/vo/CakeSpecVO.java

@@ -0,0 +1,22 @@
+package space.anyi.entity.vo;
+
+import java.math.BigDecimal;
+
+public class CakeSpecVO {
+    private Integer id;
+    private Integer cakeId;
+    private String specName;
+    private BigDecimal addPrice;
+    private Integer sortOrder;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getSpecName() { return specName; }
+    public void setSpecName(String specName) { this.specName = specName; }
+    public BigDecimal getAddPrice() { return addPrice; }
+    public void setAddPrice(BigDecimal addPrice) { this.addPrice = addPrice; }
+    public Integer getSortOrder() { return sortOrder; }
+    public void setSortOrder(Integer sortOrder) { this.sortOrder = sortOrder; }
+}

+ 81 - 0
src/main/java/space/anyi/entity/vo/CakeVO.java

@@ -0,0 +1,81 @@
+package space.anyi.entity.vo;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+public class CakeVO {
+    private Integer id;
+    private Integer categoryId;
+    private String categoryName;
+    private String name;
+    private String description;
+    private BigDecimal basePrice;
+    private String mainImage;
+    private String taste;
+    private String layers;
+    private String filling;
+    private String style;
+    private Integer isCustomizable;
+    private Integer isNew;
+    private Integer isHot;
+    private Integer isDiscount;
+    private BigDecimal discountPrice;
+    private Integer stock;
+    private String status;
+    private Integer salesCount;
+    private Date createTime;
+    private Date updateTime;
+    private List<CakeImageVO> images;
+    private List<CakeSpecVO> specs;
+    private List<ReviewVO> reviews;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCategoryId() { return categoryId; }
+    public void setCategoryId(Integer categoryId) { this.categoryId = categoryId; }
+    public String getCategoryName() { return categoryName; }
+    public void setCategoryName(String categoryName) { this.categoryName = categoryName; }
+    public String getName() { return name; }
+    public void setName(String name) { this.name = name; }
+    public String getDescription() { return description; }
+    public void setDescription(String description) { this.description = description; }
+    public BigDecimal getBasePrice() { return basePrice; }
+    public void setBasePrice(BigDecimal basePrice) { this.basePrice = basePrice; }
+    public String getMainImage() { return mainImage; }
+    public void setMainImage(String mainImage) { this.mainImage = mainImage; }
+    public String getTaste() { return taste; }
+    public void setTaste(String taste) { this.taste = taste; }
+    public String getLayers() { return layers; }
+    public void setLayers(String layers) { this.layers = layers; }
+    public String getFilling() { return filling; }
+    public void setFilling(String filling) { this.filling = filling; }
+    public String getStyle() { return style; }
+    public void setStyle(String style) { this.style = style; }
+    public Integer getIsCustomizable() { return isCustomizable; }
+    public void setIsCustomizable(Integer isCustomizable) { this.isCustomizable = isCustomizable; }
+    public Integer getIsNew() { return isNew; }
+    public void setIsNew(Integer isNew) { this.isNew = isNew; }
+    public Integer getIsHot() { return isHot; }
+    public void setIsHot(Integer isHot) { this.isHot = isHot; }
+    public Integer getIsDiscount() { return isDiscount; }
+    public void setIsDiscount(Integer isDiscount) { this.isDiscount = isDiscount; }
+    public BigDecimal getDiscountPrice() { return discountPrice; }
+    public void setDiscountPrice(BigDecimal discountPrice) { this.discountPrice = discountPrice; }
+    public Integer getStock() { return stock; }
+    public void setStock(Integer stock) { this.stock = stock; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public Integer getSalesCount() { return salesCount; }
+    public void setSalesCount(Integer salesCount) { this.salesCount = salesCount; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+    public Date getUpdateTime() { return updateTime; }
+    public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
+    public List<CakeImageVO> getImages() { return images; }
+    public void setImages(List<CakeImageVO> images) { this.images = images; }
+    public List<CakeSpecVO> getSpecs() { return specs; }
+    public void setSpecs(List<CakeSpecVO> specs) { this.specs = specs; }
+    public List<ReviewVO> getReviews() { return reviews; }
+    public void setReviews(List<ReviewVO> reviews) { this.reviews = reviews; }
+}

+ 41 - 0
src/main/java/space/anyi/entity/vo/CartItemVO.java

@@ -0,0 +1,41 @@
+package space.anyi.entity.vo;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class CartItemVO {
+    private Integer id;
+    private Integer cartId;
+    private Integer cakeId;
+    private String cakeName;
+    private String mainImage;
+    private Integer specId;
+    private String specName;
+    private BigDecimal price;
+    private Integer quantity;
+    private String customJson;
+    private Date createTime;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getCartId() { return cartId; }
+    public void setCartId(Integer cartId) { this.cartId = cartId; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getCakeName() { return cakeName; }
+    public void setCakeName(String cakeName) { this.cakeName = cakeName; }
+    public String getMainImage() { return mainImage; }
+    public void setMainImage(String mainImage) { this.mainImage = mainImage; }
+    public Integer getSpecId() { return specId; }
+    public void setSpecId(Integer specId) { this.specId = specId; }
+    public String getSpecName() { return specName; }
+    public void setSpecName(String specName) { this.specName = specName; }
+    public BigDecimal getPrice() { return price; }
+    public void setPrice(BigDecimal price) { this.price = price; }
+    public Integer getQuantity() { return quantity; }
+    public void setQuantity(Integer quantity) { this.quantity = quantity; }
+    public String getCustomJson() { return customJson; }
+    public void setCustomJson(String customJson) { this.customJson = customJson; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+}

+ 29 - 0
src/main/java/space/anyi/entity/vo/FavoriteVO.java

@@ -0,0 +1,29 @@
+package space.anyi.entity.vo;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class FavoriteVO {
+    private Integer id;
+    private Integer userId;
+    private Integer cakeId;
+    private String cakeName;
+    private String mainImage;
+    private BigDecimal cakePrice;
+    private Date createTime;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getCakeName() { return cakeName; }
+    public void setCakeName(String cakeName) { this.cakeName = cakeName; }
+    public String getMainImage() { return mainImage; }
+    public void setMainImage(String mainImage) { this.mainImage = mainImage; }
+    public BigDecimal getCakePrice() { return cakePrice; }
+    public void setCakePrice(BigDecimal cakePrice) { this.cakePrice = cakePrice; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+}

+ 40 - 0
src/main/java/space/anyi/entity/vo/OrderItemVO.java

@@ -0,0 +1,40 @@
+package space.anyi.entity.vo;
+
+import java.math.BigDecimal;
+
+public class OrderItemVO {
+    private Integer id;
+    private Integer orderId;
+    private Integer cakeId;
+    private String cakeName;
+    private String specName;
+    private BigDecimal price;
+    private Integer quantity;
+    private String customJson;
+    private String imageUrl;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getOrderId() { return orderId; }
+    public void setOrderId(Integer orderId) { this.orderId = orderId; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public String getCakeName() { return cakeName; }
+    public void setCakeName(String cakeName) { this.cakeName = cakeName; }
+    public String getSpecName() { return specName; }
+    public void setSpecName(String specName) { this.specName = specName; }
+    public BigDecimal getPrice() { return price; }
+    public void setPrice(BigDecimal price) { this.price = price; }
+    public Integer getQuantity() { return quantity; }
+    public void setQuantity(Integer quantity) { this.quantity = quantity; }
+    public String getCustomJson() { return customJson; }
+    public void setCustomJson(String customJson) { this.customJson = customJson; }
+    public String getImageUrl() { return imageUrl; }
+    public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
+    public BigDecimal getTotalPrice() {
+        if (price != null && quantity != null) {
+            return price.multiply(BigDecimal.valueOf(quantity));
+        }
+        return BigDecimal.ZERO;
+    }
+}

+ 28 - 0
src/main/java/space/anyi/entity/vo/OrderTrackingVO.java

@@ -0,0 +1,28 @@
+package space.anyi.entity.vo;
+
+import java.util.Date;
+
+public class OrderTrackingVO {
+    private Integer id;
+    private Integer orderId;
+    private String fromStatus;
+    private String toStatus;
+    private String operator;
+    private String remark;
+    private Date createTime;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getOrderId() { return orderId; }
+    public void setOrderId(Integer orderId) { this.orderId = orderId; }
+    public String getFromStatus() { return fromStatus; }
+    public void setFromStatus(String fromStatus) { this.fromStatus = fromStatus; }
+    public String getToStatus() { return toStatus; }
+    public void setToStatus(String toStatus) { this.toStatus = toStatus; }
+    public String getOperator() { return operator; }
+    public void setOperator(String operator) { this.operator = operator; }
+    public String getRemark() { return remark; }
+    public void setRemark(String remark) { this.remark = remark; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+}

+ 87 - 0
src/main/java/space/anyi/entity/vo/OrderVO.java

@@ -0,0 +1,87 @@
+package space.anyi.entity.vo;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+public class OrderVO {
+    private Integer id;
+    private String orderNo;
+    private Integer userId;
+    private String username;
+    private Integer itemCount;
+    private BigDecimal totalAmount;
+    private BigDecimal discountAmount;
+    private BigDecimal payAmount;
+    private String status;
+    private String receiverName;
+    private String receiverPhone;
+    private String province;
+    private String city;
+    private String district;
+    private String detailAddress;
+    private Date deliveryDate;
+    private String deliveryTime;
+    private String remark;
+    private String refuseReason;
+    private String paymentMethod;
+    private Date paymentTime;
+    private BigDecimal deliveryFee;
+    private Date createTime;
+    private Date updateTime;
+    private List<OrderItemVO> orderItems;
+    private List<OrderTrackingVO> tracking;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getOrderNo() { return orderNo; }
+    public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public String getUsername() { return username; }
+    public void setUsername(String username) { this.username = username; }
+    public Integer getItemCount() { return itemCount; }
+    public void setItemCount(Integer itemCount) { this.itemCount = itemCount; }
+    public BigDecimal getTotalAmount() { return totalAmount; }
+    public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; }
+    public BigDecimal getDiscountAmount() { return discountAmount; }
+    public void setDiscountAmount(BigDecimal discountAmount) { this.discountAmount = discountAmount; }
+    public BigDecimal getPayAmount() { return payAmount; }
+    public void setPayAmount(BigDecimal payAmount) { this.payAmount = payAmount; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public String getReceiverName() { return receiverName; }
+    public void setReceiverName(String receiverName) { this.receiverName = receiverName; }
+    public String getReceiverPhone() { return receiverPhone; }
+    public void setReceiverPhone(String receiverPhone) { this.receiverPhone = receiverPhone; }
+    public String getProvince() { return province; }
+    public void setProvince(String province) { this.province = province; }
+    public String getCity() { return city; }
+    public void setCity(String city) { this.city = city; }
+    public String getDistrict() { return district; }
+    public void setDistrict(String district) { this.district = district; }
+    public String getDetailAddress() { return detailAddress; }
+    public void setDetailAddress(String detailAddress) { this.detailAddress = detailAddress; }
+    public Date getDeliveryDate() { return deliveryDate; }
+    public void setDeliveryDate(Date deliveryDate) { this.deliveryDate = deliveryDate; }
+    public String getDeliveryTime() { return deliveryTime; }
+    public void setDeliveryTime(String deliveryTime) { this.deliveryTime = deliveryTime; }
+    public String getRemark() { return remark; }
+    public void setRemark(String remark) { this.remark = remark; }
+    public String getRefuseReason() { return refuseReason; }
+    public void setRefuseReason(String refuseReason) { this.refuseReason = refuseReason; }
+    public String getPaymentMethod() { return paymentMethod; }
+    public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
+    public Date getPaymentTime() { return paymentTime; }
+    public void setPaymentTime(Date paymentTime) { this.paymentTime = paymentTime; }
+    public BigDecimal getDeliveryFee() { return deliveryFee; }
+    public void setDeliveryFee(BigDecimal deliveryFee) { this.deliveryFee = deliveryFee; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+    public Date getUpdateTime() { return updateTime; }
+    public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
+    public List<OrderItemVO> getOrderItems() { return orderItems; }
+    public void setOrderItems(List<OrderItemVO> orderItems) { this.orderItems = orderItems; }
+    public List<OrderTrackingVO> getTracking() { return tracking; }
+    public void setTracking(List<OrderTrackingVO> tracking) { this.tracking = tracking; }
+}

+ 32 - 0
src/main/java/space/anyi/entity/vo/PageResultVO.java

@@ -0,0 +1,32 @@
+package space.anyi.entity.vo;
+
+import java.util.List;
+
+public class PageResultVO<T> {
+    private List<T> list;
+    private int pageNum;
+    private int pageSize;
+    private long total;
+    private int pages;
+
+    public PageResultVO() {}
+
+    public PageResultVO(List<T> list, int pageNum, int pageSize, long total) {
+        this.list = list;
+        this.pageNum = pageNum;
+        this.pageSize = pageSize;
+        this.total = total;
+        this.pages = (int) (total / pageSize + (total % pageSize == 0 ? 0 : 1));
+    }
+
+    public List<T> getList() { return list; }
+    public void setList(List<T> list) { this.list = list; }
+    public int getPageNum() { return pageNum; }
+    public void setPageNum(int pageNum) { this.pageNum = pageNum; }
+    public int getPageSize() { return pageSize; }
+    public void setPageSize(int pageSize) { this.pageSize = pageSize; }
+    public long getTotal() { return total; }
+    public void setTotal(long total) { this.total = total; }
+    public int getPages() { return pages; }
+    public void setPages(int pages) { this.pages = pages; }
+}

+ 40 - 0
src/main/java/space/anyi/entity/vo/ReviewVO.java

@@ -0,0 +1,40 @@
+package space.anyi.entity.vo;
+
+import java.util.Date;
+
+public class ReviewVO {
+    private Integer id;
+    private Integer orderId;
+    private Integer cakeId;
+    private Integer userId;
+    private String username;
+    private Integer rating;
+    private String content;
+    private String replyContent;
+    private String cakeName;
+    private String status;
+    private Date createTime;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public Integer getOrderId() { return orderId; }
+    public void setOrderId(Integer orderId) { this.orderId = orderId; }
+    public Integer getCakeId() { return cakeId; }
+    public void setCakeId(Integer cakeId) { this.cakeId = cakeId; }
+    public Integer getUserId() { return userId; }
+    public void setUserId(Integer userId) { this.userId = userId; }
+    public String getUsername() { return username; }
+    public void setUsername(String username) { this.username = username; }
+    public Integer getRating() { return rating; }
+    public void setRating(Integer rating) { this.rating = rating; }
+    public String getContent() { return content; }
+    public void setContent(String content) { this.content = content; }
+    public String getReplyContent() { return replyContent; }
+    public void setReplyContent(String replyContent) { this.replyContent = replyContent; }
+    public String getCakeName() { return cakeName; }
+    public void setCakeName(String cakeName) { this.cakeName = cakeName; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+}

+ 34 - 0
src/main/java/space/anyi/entity/vo/UserVO.java

@@ -0,0 +1,34 @@
+package space.anyi.entity.vo;
+
+import java.util.Date;
+
+public class UserVO {
+    private Integer id;
+    private String username;
+    private String nickname;
+    private String phone;
+    private String email;
+    private String avatar;
+    private String status;
+    private Date createTime;
+    private Date updateTime;
+
+    public Integer getId() { return id; }
+    public void setId(Integer id) { this.id = id; }
+    public String getUsername() { return username; }
+    public void setUsername(String username) { this.username = username; }
+    public String getNickname() { return nickname; }
+    public void setNickname(String nickname) { this.nickname = nickname; }
+    public String getPhone() { return phone; }
+    public void setPhone(String phone) { this.phone = phone; }
+    public String getEmail() { return email; }
+    public void setEmail(String email) { this.email = email; }
+    public String getAvatar() { return avatar; }
+    public void setAvatar(String avatar) { this.avatar = avatar; }
+    public String getStatus() { return status; }
+    public void setStatus(String status) { this.status = status; }
+    public Date getCreateTime() { return createTime; }
+    public void setCreateTime(Date createTime) { this.createTime = createTime; }
+    public Date getUpdateTime() { return updateTime; }
+    public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
+}

+ 21 - 0
src/main/java/space/anyi/interceptor/AdminLoginInterceptor.java

@@ -0,0 +1,21 @@
+package space.anyi.interceptor;
+
+import space.anyi.entity.Admin;
+import org.springframework.web.servlet.HandlerInterceptor;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+public class AdminLoginInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        HttpSession session = request.getSession();
+        Admin admin = (Admin) session.getAttribute("admin");
+        if (admin == null) {
+            response.sendRedirect(request.getContextPath() + "/admin/login");
+            return false;
+        }
+        return true;
+    }
+}

+ 21 - 0
src/main/java/space/anyi/interceptor/LoginInterceptor.java

@@ -0,0 +1,21 @@
+package space.anyi.interceptor;
+
+import space.anyi.entity.User;
+import org.springframework.web.servlet.HandlerInterceptor;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+public class LoginInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        HttpSession session = request.getSession();
+        User user = (User) session.getAttribute("user");
+        if (user == null) {
+            response.sendRedirect(request.getContextPath() + "/user/login");
+            return false;
+        }
+        return true;
+    }
+}

+ 13 - 0
src/main/java/space/anyi/mapper/ActivityMapper.java

@@ -0,0 +1,13 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Activity;
+import java.util.List;
+
+public interface ActivityMapper {
+    void insert(Activity activity);
+    void update(Activity activity);
+    void deleteById(Integer id);
+    Activity selectById(Integer id);
+    List<Activity> selectAll();
+    List<Activity> selectActive();
+}

+ 15 - 0
src/main/java/space/anyi/mapper/AddressMapper.java

@@ -0,0 +1,15 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Address;
+import java.util.List;
+
+public interface AddressMapper {
+    void insert(Address address);
+    void update(Address address);
+    void deleteById(Integer id);
+    Address selectById(Integer id);
+    List<Address> selectAll();
+    List<Address> selectByUserId(Integer userId);
+    void clearDefault(Integer userId);
+    void setDefault(Integer id);
+}

+ 15 - 0
src/main/java/space/anyi/mapper/AdminMapper.java

@@ -0,0 +1,15 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Admin;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface AdminMapper {
+    void insert(Admin admin);
+    void update(Admin admin);
+    void deleteById(Integer id);
+    Admin selectById(Integer id);
+    List<Admin> selectAll();
+    Admin selectByUsername(String username);
+    void updatePassword(@Param("id") Integer id, @Param("password") String password);
+}

+ 13 - 0
src/main/java/space/anyi/mapper/AnnouncementMapper.java

@@ -0,0 +1,13 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Announcement;
+import java.util.List;
+
+public interface AnnouncementMapper {
+    void insert(Announcement announcement);
+    void update(Announcement announcement);
+    void deleteById(Integer id);
+    Announcement selectById(Integer id);
+    List<Announcement> selectAll();
+    List<Announcement> selectActive();
+}

+ 12 - 0
src/main/java/space/anyi/mapper/CakeCategoryMapper.java

@@ -0,0 +1,12 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.CakeCategory;
+import java.util.List;
+
+public interface CakeCategoryMapper {
+    void insert(CakeCategory cakeCategory);
+    void update(CakeCategory cakeCategory);
+    void deleteById(Integer id);
+    CakeCategory selectById(Integer id);
+    List<CakeCategory> selectAll();
+}

+ 14 - 0
src/main/java/space/anyi/mapper/CakeImageMapper.java

@@ -0,0 +1,14 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.CakeImage;
+import java.util.List;
+
+public interface CakeImageMapper {
+    void insert(CakeImage cakeImage);
+    void update(CakeImage cakeImage);
+    void deleteById(Integer id);
+    CakeImage selectById(Integer id);
+    List<CakeImage> selectAll();
+    List<CakeImage> selectByCakeId(Integer cakeId);
+    CakeImage selectMainImage(Integer cakeId);
+}

+ 22 - 0
src/main/java/space/anyi/mapper/CakeMapper.java

@@ -0,0 +1,22 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Cake;
+import space.anyi.entity.dto.CakeSearchCriteria;
+import space.anyi.entity.dto.CakeSalesVO;
+import java.util.List;
+
+public interface CakeMapper {
+    void insert(Cake cake);
+    void update(Cake cake);
+    void deleteById(Integer id);
+    Cake selectById(Integer id);
+    List<Cake> selectAll();
+    List<Cake> search(CakeSearchCriteria criteria);
+    List<Cake> selectHot();
+    List<Cake> selectNew();
+    List<Cake> selectDiscount();
+    void toggleStatus(Integer id);
+    List<Cake> selectByStatus(String status);
+    List<Cake> findByCategory(Integer categoryId);
+    List<CakeSalesVO> selectHotCakes();
+}

+ 14 - 0
src/main/java/space/anyi/mapper/CakeSpecMapper.java

@@ -0,0 +1,14 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.CakeSpec;
+import java.util.List;
+
+public interface CakeSpecMapper {
+    void insert(CakeSpec cakeSpec);
+    void update(CakeSpec cakeSpec);
+    void deleteById(Integer id);
+    CakeSpec selectById(Integer id);
+    List<CakeSpec> selectAll();
+    List<CakeSpec> selectByCakeId(Integer cakeId);
+    void deleteByCakeId(Integer cakeId);
+}

+ 17 - 0
src/main/java/space/anyi/mapper/CartItemMapper.java

@@ -0,0 +1,17 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.CartItem;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface CartItemMapper {
+    void insert(CartItem cartItem);
+    void update(CartItem cartItem);
+    void deleteById(Integer id);
+    CartItem selectById(Integer id);
+    List<CartItem> selectAll();
+    List<CartItem> selectByCartId(Integer cartId);
+    CartItem findByCartIdAndCakeIdAndSpecId(@Param("cartId") Integer cartId, @Param("cakeId") Integer cakeId, @Param("specId") Integer specId);
+    void deleteByCartId(Integer cartId);
+    void deleteBatch(Integer[] ids);
+}

+ 13 - 0
src/main/java/space/anyi/mapper/CartMapper.java

@@ -0,0 +1,13 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Cart;
+import java.util.List;
+
+public interface CartMapper {
+    void insert(Cart cart);
+    void update(Cart cart);
+    void deleteById(Integer id);
+    Cart selectById(Integer id);
+    List<Cart> selectAll();
+    Cart selectByUserId(Integer userId);
+}

+ 13 - 0
src/main/java/space/anyi/mapper/CouponMapper.java

@@ -0,0 +1,13 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Coupon;
+import java.util.List;
+
+public interface CouponMapper {
+    void insert(Coupon coupon);
+    void update(Coupon coupon);
+    void deleteById(Integer id);
+    Coupon selectById(Integer id);
+    List<Coupon> selectAll();
+    List<Coupon> selectAvailable();
+}

+ 16 - 0
src/main/java/space/anyi/mapper/CustomOptionMapper.java

@@ -0,0 +1,16 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.CustomOption;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface CustomOptionMapper {
+    void insert(CustomOption customOption);
+    void update(CustomOption customOption);
+    void deleteById(Integer id);
+    CustomOption selectById(Integer id);
+    List<CustomOption> selectAll();
+    List<CustomOption> selectByCakeId(Integer cakeId);
+    List<CustomOption> selectByType(@Param("cakeId") Integer cakeId, @Param("optionType") String optionType);
+    void deleteByCakeId(Integer cakeId);
+}

+ 13 - 0
src/main/java/space/anyi/mapper/DeliveryConfigMapper.java

@@ -0,0 +1,13 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.DeliveryConfig;
+import java.util.List;
+
+public interface DeliveryConfigMapper {
+    void insert(DeliveryConfig deliveryConfig);
+    void update(DeliveryConfig deliveryConfig);
+    void deleteById(Integer id);
+    DeliveryConfig selectById(Integer id);
+    List<DeliveryConfig> selectAll();
+    List<DeliveryConfig> selectActive();
+}

+ 17 - 0
src/main/java/space/anyi/mapper/FavoriteMapper.java

@@ -0,0 +1,17 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Favorite;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface FavoriteMapper {
+    void insert(Favorite favorite);
+    void update(Favorite favorite);
+    void deleteById(Integer id);
+    Favorite selectById(Integer id);
+    List<Favorite> selectAll();
+    List<Favorite> selectByUserId(Integer userId);
+    Favorite selectByUserAndCake(@Param("userId") Integer userId, @Param("cakeId") Integer cakeId);
+    int deleteByUserAndCake(@Param("userId") Integer userId, @Param("cakeId") Integer cakeId);
+    int countByCakeId(Integer cakeId);
+}

+ 16 - 0
src/main/java/space/anyi/mapper/MessageMapper.java

@@ -0,0 +1,16 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Message;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface MessageMapper {
+    void insert(Message message);
+    void update(Message message);
+    void deleteById(Integer id);
+    Message selectById(Integer id);
+    List<Message> selectAll();
+    List<Message> selectByUserId(Integer userId);
+    List<Message> selectUnreplied();
+    void reply(@Param("id") Integer id, @Param("replyContent") String replyContent);
+}

+ 15 - 0
src/main/java/space/anyi/mapper/OrderItemMapper.java

@@ -0,0 +1,15 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.OrderItem;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface OrderItemMapper {
+    void insert(OrderItem orderItem);
+    void update(OrderItem orderItem);
+    void deleteById(Integer id);
+    OrderItem selectById(Integer id);
+    List<OrderItem> selectAll();
+    List<OrderItem> selectByOrderId(Integer orderId);
+    void insertBatch(@Param("list") List<OrderItem> list);
+}

+ 13 - 0
src/main/java/space/anyi/mapper/OrderTrackingMapper.java

@@ -0,0 +1,13 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.OrderTracking;
+import java.util.List;
+
+public interface OrderTrackingMapper {
+    void insert(OrderTracking orderTracking);
+    void update(OrderTracking orderTracking);
+    void deleteById(Integer id);
+    OrderTracking selectById(Integer id);
+    List<OrderTracking> selectAll();
+    List<OrderTracking> selectByOrderId(Integer orderId);
+}

+ 19 - 0
src/main/java/space/anyi/mapper/OrdersMapper.java

@@ -0,0 +1,19 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Orders;
+import space.anyi.entity.dto.OrderSearchCriteria;
+import java.util.List;
+import java.util.Map;
+
+public interface OrdersMapper {
+    void insert(Orders orders);
+    void update(Orders orders);
+    void deleteById(Integer id);
+    Orders selectById(Integer id);
+    List<Orders> selectAll();
+    Orders selectByOrderNo(String orderNo);
+    List<Orders> selectByUserId(Integer userId);
+    List<Orders> selectByStatus(OrderSearchCriteria criteria);
+    List<Map<String, Object>> countByDate(OrderSearchCriteria criteria);
+    List<Map<String, Object>> sumAmountByDate(OrderSearchCriteria criteria);
+}

+ 17 - 0
src/main/java/space/anyi/mapper/ReviewMapper.java

@@ -0,0 +1,17 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.Review;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface ReviewMapper {
+    void insert(Review review);
+    void update(Review review);
+    void deleteById(Integer id);
+    Review selectById(Integer id);
+    List<Review> selectAll();
+    List<Review> selectByCakeId(Integer cakeId);
+    List<Review> selectByOrderId(Integer orderId);
+    void toggleStatus(Integer id);
+    void reply(@Param("id") Integer id, @Param("replyContent") String replyContent);
+}

+ 16 - 0
src/main/java/space/anyi/mapper/UserCouponMapper.java

@@ -0,0 +1,16 @@
+package space.anyi.mapper;
+
+import space.anyi.entity.UserCoupon;
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface UserCouponMapper {
+    void insert(UserCoupon userCoupon);
+    void update(UserCoupon userCoupon);
+    void deleteById(Integer id);
+    UserCoupon selectById(Integer id);
+    List<UserCoupon> selectAll();
+    List<UserCoupon> selectByUserId(Integer userId);
+    UserCoupon selectByUserAndCoupon(@Param("userId") Integer userId, @Param("couponId") Integer couponId);
+    void markUsed(Integer id);
+}

+ 10 - 8
src/main/java/space/anyi/mapper/UserMapper.java

@@ -1,12 +1,14 @@
 package space.anyi.mapper;
 
-/**
- * @fileName: UserMapper
- * @projectName: SSM_template
- * @package: space.anyi.mapper
- * @author: 杨逸
- * @date:2026/4/24 11:17
- * @description:
- */
+import space.anyi.entity.User;
+import java.util.List;
+
 public interface UserMapper {
+    void insert(User user);
+    void update(User user);
+    void deleteById(Integer id);
+    User selectById(Integer id);
+    List<User> selectAll();
+    User selectByUsername(String username);
+    void toggleStatus(Integer id);
 }

+ 13 - 0
src/main/java/space/anyi/service/ActivityService.java

@@ -0,0 +1,13 @@
+package space.anyi.service;
+
+import space.anyi.entity.Activity;
+import java.util.List;
+
+public interface ActivityService {
+    List<Activity> findAll();
+    Activity findById(Integer id);
+    List<Activity> findActive();
+    void add(Activity activity);
+    void update(Activity activity);
+    void delete(Integer id);
+}

+ 13 - 0
src/main/java/space/anyi/service/AddressService.java

@@ -0,0 +1,13 @@
+package space.anyi.service;
+
+import space.anyi.entity.Address;
+import java.util.List;
+
+public interface AddressService {
+    List<Address> findByUser(Integer userId);
+    Address findById(Integer id);
+    void add(Address address);
+    void update(Address address);
+    void delete(Integer id);
+    void setDefault(Integer id, Integer userId);
+}

+ 8 - 0
src/main/java/space/anyi/service/AdminService.java

@@ -0,0 +1,8 @@
+package space.anyi.service;
+
+import space.anyi.entity.Admin;
+
+public interface AdminService {
+    Admin login(String username, String password);
+    void updatePassword(Integer id, String newPassword);
+}

+ 12 - 0
src/main/java/space/anyi/service/AnnouncementService.java

@@ -0,0 +1,12 @@
+package space.anyi.service;
+
+import space.anyi.entity.Announcement;
+import java.util.List;
+
+public interface AnnouncementService {
+    List<Announcement> findActive();
+    List<Announcement> findAll();
+    void add(Announcement announcement);
+    void update(Announcement announcement);
+    void delete(Integer id);
+}

+ 20 - 0
src/main/java/space/anyi/service/CakeService.java

@@ -0,0 +1,20 @@
+package space.anyi.service;
+
+import space.anyi.entity.Cake;
+import space.anyi.entity.dto.CakeSearchCriteria;
+import space.anyi.entity.dto.CakeSalesVO;
+import java.util.List;
+
+public interface CakeService {
+    Cake findById(Integer id);
+    List<Cake> search(CakeSearchCriteria criteria, Integer page, Integer size);
+    List<Cake> findHot();
+    List<Cake> findNew();
+    List<Cake> findDiscount();
+    List<Cake> findByCategory(Integer categoryId);
+    void add(Cake cake);
+    void update(Cake cake);
+    void delete(Integer id);
+    void toggleStatus(Integer id);
+    List<CakeSalesVO> findHotCakes();
+}

+ 12 - 0
src/main/java/space/anyi/service/CakeSpecService.java

@@ -0,0 +1,12 @@
+package space.anyi.service;
+
+import space.anyi.entity.CakeSpec;
+import java.util.List;
+
+public interface CakeSpecService {
+    List<CakeSpec> findByCakeId(Integer cakeId);
+    CakeSpec findById(Integer id);
+    void add(CakeSpec cakeSpec);
+    void update(CakeSpec cakeSpec);
+    void delete(Integer id);
+}

+ 16 - 0
src/main/java/space/anyi/service/CartService.java

@@ -0,0 +1,16 @@
+package space.anyi.service;
+
+import space.anyi.entity.Cart;
+import space.anyi.entity.CartItem;
+import space.anyi.entity.vo.CartItemVO;
+import java.util.List;
+
+public interface CartService {
+    Cart getCart(Integer userId);
+    List<CartItem> getCartItems(Integer userId);
+    List<CartItemVO> getCartItemVOs(Integer userId);
+    void addItem(Integer userId, Integer cakeId, Integer specId, Integer quantity, String customJson);
+    void updateQuantity(Integer itemId, Integer quantity);
+    void removeItem(Integer itemId);
+    void clearCart(Integer userId);
+}

+ 12 - 0
src/main/java/space/anyi/service/CategoryService.java

@@ -0,0 +1,12 @@
+package space.anyi.service;
+
+import space.anyi.entity.CakeCategory;
+import java.util.List;
+
+public interface CategoryService {
+    List<CakeCategory> findAll();
+    CakeCategory findById(Integer id);
+    void add(CakeCategory category);
+    void update(CakeCategory category);
+    void delete(Integer id);
+}

+ 11 - 0
src/main/java/space/anyi/service/CouponService.java

@@ -0,0 +1,11 @@
+package space.anyi.service;
+
+import space.anyi.entity.Coupon;
+import space.anyi.entity.UserCoupon;
+import java.util.List;
+
+public interface CouponService {
+    List<Coupon> findAvailable();
+    List<UserCoupon> findByUser(Integer userId);
+    void claim(Integer userId, Integer couponId);
+}

+ 13 - 0
src/main/java/space/anyi/service/CustomOptionService.java

@@ -0,0 +1,13 @@
+package space.anyi.service;
+
+import space.anyi.entity.CustomOption;
+import java.util.List;
+
+public interface CustomOptionService {
+    List<CustomOption> findByCakeId(Integer cakeId);
+    List<CustomOption> findAll();
+    CustomOption findById(Integer id);
+    void add(CustomOption option);
+    void update(CustomOption option);
+    void delete(Integer id);
+}

+ 12 - 0
src/main/java/space/anyi/service/DeliveryService.java

@@ -0,0 +1,12 @@
+package space.anyi.service;
+
+import space.anyi.entity.DeliveryConfig;
+import java.util.List;
+
+public interface DeliveryService {
+    List<DeliveryConfig> findAll();
+    List<DeliveryConfig> findActive();
+    void save(DeliveryConfig config);
+    void update(DeliveryConfig config);
+    void delete(Integer id);
+}

Неке датотеке нису приказане због велике количине промена